imap_mailbox.php 37 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009
  1. <?php
  2. /**
  3. * imap_mailbox.php
  4. *
  5. * Copyright (c) 1999-2003 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. * $Id$
  11. */
  12. require_once(SM_PATH . 'functions/imap_utf7_local.php');
  13. global $boxesnew;
  14. /*
  15. FIXME. This class should be extracted and placed in a separate file that
  16. can be included before we start the session. That makes caching of the tree
  17. possible. On a refresh mailboxes from left_main.php the only function that
  18. should be called is the sqimap_get_status_mbx_tree. In case of subscribe
  19. / rename / delete / new we have to create methods for adding/changing the
  20. mailbox in the mbx_tree without the need for a refresh.
  21. */
  22. class mailboxes {
  23. var $mailboxname_full = '', $mailboxname_sub= '', $is_noselect = false, $is_noinferiors = false,
  24. $is_special = false, $is_root = false, $is_inbox = false, $is_sent = false,
  25. $is_trash = false, $is_draft = false, $mbxs = array(),
  26. $unseen = false, $total = false;
  27. function addMbx($mbx, $delimiter, $start, $specialfirst) {
  28. $ary = explode($delimiter, $mbx->mailboxname_full);
  29. $mbx_parent =& $this;
  30. for ($i = $start, $c = count($ary)-1; $i < $c; $i++) {
  31. $mbx_childs =& $mbx_parent->mbxs;
  32. $found = false;
  33. if ($mbx_childs) {
  34. foreach ($mbx_childs as $key => $parent) {
  35. if ($parent->mailboxname_sub == $ary[$i]) {
  36. $mbx_parent =& $mbx_parent->mbxs[$key];
  37. $found = true;
  38. break;
  39. }
  40. }
  41. }
  42. if (!$found) {
  43. $no_select_mbx = new mailboxes();
  44. if (isset($mbx_parent->mailboxname_full) && $mbx_parent->mailboxname_full != '') {
  45. $no_select_mbx->mailboxname_full = $mbx_parent->mailboxname_full.$delimiter.$ary[$i];
  46. } else {
  47. $no_select_mbx->mailboxname_full = $ary[$i];
  48. }
  49. $no_select_mbx->mailboxname_sub = $ary[$i];
  50. $no_select_mbx->is_noselect = true;
  51. $mbx_parent->mbxs[] = $no_select_mbx;
  52. $i--;
  53. }
  54. }
  55. $mbx_parent->mbxs[] = $mbx;
  56. if ($mbx->is_special && $specialfirst) {
  57. usort($mbx_parent->mbxs, 'sortSpecialMbx');
  58. }
  59. }
  60. }
  61. function sortSpecialMbx($a, $b) {
  62. if ($a->is_inbox) {
  63. $acmp = '0'. $a->mailboxname_full;
  64. } else if ($a->is_special) {
  65. $acmp = '1'. $a->mailboxname_full;
  66. } else {
  67. $acmp = '2' . $a->mailboxname_full;
  68. }
  69. if ($b->is_inbox) {
  70. $bcmp = '0'. $b->mailboxname_full;
  71. }else if ($b->is_special) {
  72. $bcmp = '1' . $b->mailboxname_full;
  73. } else {
  74. $bcmp = '2' . $b->mailboxname_full;
  75. }
  76. if ($acmp == $bcmp) return 0;
  77. return ($acmp > $bcmp) ? 1: -1;
  78. }
  79. function compact_mailboxes_response($ary)
  80. {
  81. /*
  82. * Workaround for mailboxes returned as literal
  83. * FIXME : Doesn't work if the mailbox name is multiple lines
  84. * (larger then fgets buffer)
  85. */
  86. for ($i = 0, $iCnt=count($ary); $i < $iCnt; $i++) {
  87. if (isset($ary[$i + 1]) && substr($ary[$i], -3) == "}\r\n") {
  88. if (ereg("^(\\* [A-Z]+.*)\\{[0-9]+\\}([ \n\r\t]*)$",
  89. $ary[$i], $regs)) {
  90. $ary[$i] = $regs[1] . '"' . addslashes(trim($ary[$i+1])) . '"' . $regs[2];
  91. array_splice($ary, $i+1, 2);
  92. }
  93. }
  94. }
  95. /* remove duplicates and ensure array is contiguous */
  96. return array_values(array_unique($ary));
  97. }
  98. /*
  99. function find_mailbox_name ($mailbox) {
  100. if (preg_match('/\*.+\"([^\r\n\"]*)\"[\s\r\n]*$/', $mailbox, $regs))
  101. return $regs[1];
  102. if (ereg(" *\"([^\r\n\"]*)\"[ \r\n]*$", $mailbox, $regs))
  103. return $regs[1];
  104. ereg(" *([^ \r\n\"]*)[ \r\n]*$",$mailbox,$regs);
  105. return $regs[1];
  106. }
  107. */
  108. // Extract the mailbox name from an untagged LIST (7.2.2) or LSUB (7.2.3) answer
  109. // * (LIST|LSUB) (<Flags list>) (NIL|"<separator atom>") <mailbox name string>\r\n
  110. // mailbox name in quoted string MUST be unquoted and stripslashed (sm API)
  111. function find_mailbox_name($line)
  112. {
  113. if (preg_match('/^\* (?:LIST|LSUB) \([^\)]*\) (?:NIL|\"[^\"]*\") ([^\r\n]*)[\r\n]*$/i', $line, $regs)) {
  114. if (substr($regs[1], 0, 1) == '"')
  115. return stripslashes(substr($regs[1], 1, -1));
  116. return $regs[1];
  117. }
  118. return '';
  119. }
  120. function check_is_noselect ($lsub_line) {
  121. return preg_match("/^\* (LSUB|LIST) \([^\)]*\\\\Noselect[^\)]*\)/i", $lsub_line);
  122. }
  123. function check_is_noinferiors ($lsub_line) {
  124. return preg_match("/^\* (LSUB|LIST) \([^\)]*\\\\Noinferiors[^\)]*\)/i", $lsub_line);
  125. }
  126. /**
  127. * If $haystack is a full mailbox name, and $needle is the mailbox
  128. * separator character, returns the second last part of the full
  129. * mailbox name (i.e. the mailbox's parent mailbox)
  130. */
  131. function readMailboxParent($haystack, $needle) {
  132. if ($needle == '') {
  133. $ret = '';
  134. } else {
  135. $parts = explode($needle, $haystack);
  136. $elem = array_pop($parts);
  137. while ($elem == '' && count($parts)) {
  138. $elem = array_pop($parts);
  139. }
  140. $ret = join($needle, $parts);
  141. }
  142. return( $ret );
  143. }
  144. /**
  145. * Check if $subbox is below the specified $parentbox
  146. */
  147. function isBoxBelow( $subbox, $parentbox ) {
  148. global $delimiter;
  149. /*
  150. * Eliminate the obvious mismatch, where the
  151. * subfolder path is shorter than that of the potential parent
  152. */
  153. if ( strlen($subbox) < strlen($parentbox) ) {
  154. return false;
  155. }
  156. /* check for delimiter */
  157. if (!substr($parentbox,-1) == $delimiter) {
  158. $parentbox.=$delimiter;
  159. }
  160. if (substr($subbox,0,strlen($parentbox)) == $parentbox) {
  161. return true;
  162. } else {
  163. return false;
  164. }
  165. }
  166. /* Defines special mailboxes */
  167. function isSpecialMailbox( $box ) {
  168. $ret = ( (strtolower($box) == 'inbox') ||
  169. isTrashMailbox($box) || isSentMailbox($box) || isDraftMailbox($box) );
  170. if ( !$ret ) {
  171. $ret = do_hook_function( 'special_mailbox', $box );
  172. }
  173. return $ret;
  174. }
  175. function isTrashMailbox ($box) {
  176. global $trash_folder, $move_to_trash;
  177. return $move_to_trash && $trash_folder &&
  178. ( $box == $trash_folder || isBoxBelow($box, $trash_folder) );
  179. }
  180. function isSentMailbox($box) {
  181. global $sent_folder, $move_to_sent;
  182. return $move_to_sent && $sent_folder &&
  183. ( $box == $sent_folder || isBoxBelow($box, $sent_folder) );
  184. }
  185. function isDraftMailbox($box) {
  186. global $draft_folder, $save_as_draft;
  187. return $save_as_draft &&
  188. ( $box == $draft_folder || isBoxBelow($box, $draft_folder) );
  189. }
  190. /* Expunges a mailbox */
  191. function sqimap_mailbox_expunge ($imap_stream, $mailbox, $handle_errors = true, $id='') {
  192. global $uid_support;
  193. if ($id) {
  194. if (is_array($id)) {
  195. $id = sqimap_message_list_squisher($id);
  196. }
  197. $id = ' '.$id;
  198. $uid = $uid_support;
  199. } else {
  200. $uid = false;
  201. }
  202. $read = sqimap_run_command($imap_stream, 'EXPUNGE'.$id, $handle_errors,
  203. $response, $message, $uid);
  204. $cnt = 0;
  205. if (is_array($read)) {
  206. foreach ($read as $r) {
  207. if (preg_match('/^\*\s[0-9]+\sEXPUNGE/AUi',$r,$regs)) {
  208. $cnt++;
  209. }
  210. }
  211. }
  212. return $cnt;
  213. }
  214. /* Checks whether or not the specified mailbox exists */
  215. function sqimap_mailbox_exists ($imap_stream, $mailbox) {
  216. if (!isset($mailbox) || empty($mailbox)) {
  217. return false;
  218. }
  219. $mbx = sqimap_run_command($imap_stream, 'LIST "" ' . sqimap_encode_mailbox_name($mailbox),
  220. true, $response, $message);
  221. return isset($mbx[0]);
  222. }
  223. /* Selects a mailbox */
  224. function sqimap_mailbox_select ($imap_stream, $mailbox) {
  225. global $auto_expunge;
  226. if ($mailbox == 'None') {
  227. return;
  228. }
  229. $read = sqimap_run_command($imap_stream, 'SELECT ' . sqimap_encode_mailbox_name($mailbox),
  230. true, $response, $message);
  231. $result = array();
  232. for ($i = 0, $cnt = count($read); $i < $cnt; $i++) {
  233. if (preg_match('/^\*\s+OK\s\[(\w+)\s(\w+)\]/',$read[$i], $regs)) {
  234. $result[strtoupper($regs[1])] = $regs[2];
  235. } else if (preg_match('/^\*\s([0-9]+)\s(\w+)/',$read[$i], $regs)) {
  236. $result[strtoupper($regs[2])] = $regs[1];
  237. } else {
  238. if (preg_match("/PERMANENTFLAGS(.*)/i",$read[$i], $regs)) {
  239. $regs[1]=trim(preg_replace ( array ("/\(/","/\)/","/\]/") ,'', $regs[1])) ;
  240. $result['PERMANENTFLAGS'] = $regs[1];
  241. } else if (preg_match("/FLAGS(.*)/i",$read[$i], $regs)) {
  242. $regs[1]=trim(preg_replace ( array ("/\(/","/\)/") ,'', $regs[1])) ;
  243. $result['FLAGS'] = $regs[1];
  244. }
  245. }
  246. }
  247. if (preg_match('/^\[(.+)\]/',$message, $regs)) {
  248. $result['RIGHTS']=$regs[1];
  249. }
  250. if ($auto_expunge) {
  251. $tmp = sqimap_run_command($imap_stream, 'EXPUNGE', false, $a, $b);
  252. }
  253. return $result;
  254. }
  255. /* Creates a folder */
  256. function sqimap_mailbox_create ($imap_stream, $mailbox, $type) {
  257. global $delimiter;
  258. if (strtolower($type) == 'noselect') {
  259. $mailbox .= $delimiter;
  260. }
  261. $read_ary = sqimap_run_command($imap_stream, 'CREATE ' . sqimap_encode_mailbox_name($mailbox),
  262. true, $response, $message);
  263. sqimap_subscribe ($imap_stream, $mailbox);
  264. }
  265. /* Subscribes to an existing folder */
  266. function sqimap_subscribe ($imap_stream, $mailbox) {
  267. $read_ary = sqimap_run_command($imap_stream, 'SUBSCRIBE ' . sqimap_encode_mailbox_name($mailbox),
  268. true, $response, $message);
  269. }
  270. /* Unsubscribes to an existing folder */
  271. function sqimap_unsubscribe ($imap_stream, $mailbox) {
  272. $read_ary = sqimap_run_command($imap_stream, 'UNSUBSCRIBE ' . sqimap_encode_mailbox_name($mailbox),
  273. true, $response, $message);
  274. }
  275. /* Deletes the given folder */
  276. function sqimap_mailbox_delete ($imap_stream, $mailbox) {
  277. global $data_dir, $username;
  278. $read_ary = sqimap_run_command($imap_stream, 'DELETE ' . sqimap_encode_mailbox_name($mailbox),
  279. true, $response, $message);
  280. sqimap_unsubscribe ($imap_stream, $mailbox);
  281. do_hook_function('rename_or_delete_folder', $args = array($mailbox, 'delete', ''));
  282. removePref($data_dir, $username, "thread_$mailbox");
  283. }
  284. /* Determines if the user is subscribed to the folder or not */
  285. function sqimap_mailbox_is_subscribed($imap_stream, $folder) {
  286. $boxesall = sqimap_mailbox_list ($imap_stream);
  287. foreach ($boxesall as $ref) {
  288. if ($ref['unformatted'] == $folder) {
  289. return true;
  290. }
  291. }
  292. return false;
  293. }
  294. /* Renames a mailbox */
  295. function sqimap_mailbox_rename( $imap_stream, $old_name, $new_name ) {
  296. if ( $old_name != $new_name ) {
  297. global $delimiter, $imap_server_type, $data_dir, $username;
  298. if ( substr( $old_name, -1 ) == $delimiter ) {
  299. $old_name = substr( $old_name, 0, strlen( $old_name ) - 1 );
  300. $new_name = substr( $new_name, 0, strlen( $new_name ) - 1 );
  301. $postfix = $delimiter;
  302. } else {
  303. $postfix = '';
  304. }
  305. $boxesall = sqimap_mailbox_list($imap_stream);
  306. $cmd = 'RENAME ' . sqimap_encode_mailbox_name($old_name) . ' ' . sqimap_encode_mailbox_name($new_name);
  307. $data = sqimap_run_command($imap_stream, $cmd, true, $response, $message);
  308. sqimap_unsubscribe($imap_stream, $old_name.$postfix);
  309. $oldpref = getPref($data_dir, $username, 'thread_'.$old_name.$postfix);
  310. removePref($data_dir, $username, 'thread_'.$old_name.$postfix);
  311. sqimap_subscribe($imap_stream, $new_name.$postfix);
  312. setPref($data_dir, $username, 'thread_'.$new_name.$postfix, $oldpref);
  313. do_hook_function('rename_or_delete_folder',$args = array($old_name, 'rename', $new_name));
  314. $l = strlen( $old_name ) + 1;
  315. $p = 'unformatted';
  316. foreach ($boxesall as $box) {
  317. if (substr($box[$p], 0, $l) == $old_name . $delimiter) {
  318. $new_sub = $new_name . $delimiter . substr($box[$p], $l);
  319. if ($imap_server_type == 'cyrus') {
  320. $cmd = 'RENAME "' . $box[$p] . '" "' . $new_sub . '"';
  321. $data = sqimap_run_command($imap_stream, $cmd, true,
  322. $response, $message);
  323. }
  324. sqimap_unsubscribe($imap_stream, $box[$p]);
  325. $oldpref = getPref($data_dir, $username, 'thread_'.$box[$p]);
  326. removePref($data_dir, $username, 'thread_'.$box[$p]);
  327. sqimap_subscribe($imap_stream, $new_sub);
  328. setPref($data_dir, $username, 'thread_'.$new_sub, $oldpref);
  329. do_hook_function('rename_or_delete_folder',
  330. $args = array($box[$p], 'rename', $new_sub));
  331. }
  332. }
  333. }
  334. }
  335. /*
  336. * Formats a mailbox into parts for the $boxesall array
  337. *
  338. * The parts are:
  339. *
  340. * raw - Raw LIST/LSUB response from the IMAP server
  341. * formatted - nicely formatted folder name
  342. * unformatted - unformatted, but with delimiter at end removed
  343. * unformatted-dm - folder name as it appears in raw response
  344. * unformatted-disp - unformatted without $folder_prefix
  345. */
  346. function sqimap_mailbox_parse ($line, $line_lsub) {
  347. global $folder_prefix, $delimiter;
  348. /* Process each folder line */
  349. for ($g = 0, $cnt = count($line); $g < $cnt; ++$g) {
  350. /* Store the raw IMAP reply */
  351. if (isset($line[$g])) {
  352. $boxesall[$g]['raw'] = $line[$g];
  353. } else {
  354. $boxesall[$g]['raw'] = '';
  355. }
  356. /* Count number of delimiters ($delimiter) in folder name */
  357. $mailbox = /*trim(*/$line_lsub[$g]/*)*/;
  358. $dm_count = substr_count($mailbox, $delimiter);
  359. if (substr($mailbox, -1) == $delimiter) {
  360. /* If name ends in delimiter, decrement count by one */
  361. $dm_count--;
  362. }
  363. /* Format folder name, but only if it's a INBOX.* or has a parent. */
  364. $boxesallbyname[$mailbox] = $g;
  365. $parentfolder = readMailboxParent($mailbox, $delimiter);
  366. if ( (strtolower(substr($mailbox, 0, 5)) == "inbox") ||
  367. (substr($mailbox, 0, strlen($folder_prefix)) == $folder_prefix) ||
  368. (isset($boxesallbyname[$parentfolder]) &&
  369. (strlen($parentfolder) > 0) ) ) {
  370. $indent = $dm_count - (substr_count($folder_prefix, $delimiter));
  371. if ($indent > 0) {
  372. $boxesall[$g]['formatted'] = str_repeat('&nbsp;&nbsp;', $indent);
  373. } else {
  374. $boxesall[$g]['formatted'] = '';
  375. }
  376. $boxesall[$g]['formatted'] .= imap_utf7_decode_local(readShortMailboxName($mailbox, $delimiter));
  377. } else {
  378. $boxesall[$g]['formatted'] = imap_utf7_decode_local($mailbox);
  379. }
  380. $boxesall[$g]['unformatted-dm'] = $mailbox;
  381. if (substr($mailbox, -1) == $delimiter) {
  382. $mailbox = substr($mailbox, 0, strlen($mailbox) - 1);
  383. }
  384. $boxesall[$g]['unformatted'] = $mailbox;
  385. if (substr($mailbox,0,strlen($folder_prefix))==$folder_prefix) {
  386. $mailbox = substr($mailbox, strlen($folder_prefix));
  387. }
  388. $boxesall[$g]['unformatted-disp'] = $mailbox;
  389. $boxesall[$g]['id'] = $g;
  390. $boxesall[$g]['flags'] = array();
  391. if (isset($line[$g])) {
  392. ereg("\(([^)]*)\)",$line[$g],$regs);
  393. // FIXME Flags do contain the \ character. \NoSelect \NoInferiors
  394. // and $MDNSent <= last one doesn't have the \
  395. // It's better to follow RFC3501 instead of using our own naming.
  396. $flags = trim(strtolower(str_replace('\\', '',$regs[1])));
  397. if ($flags) {
  398. $boxesall[$g]['flags'] = explode(' ', $flags);
  399. }
  400. }
  401. }
  402. return $boxesall;
  403. }
  404. /*
  405. * Sorting function used to sort mailbox names.
  406. * + Original patch from dave_michmerhuizen@yahoo.com
  407. * + Allows case insensitivity when sorting folders
  408. * + Takes care of the delimiter being sorted to the end, causing
  409. * subfolders to be listed in below folders that are prefixed
  410. * with their parent folders name.
  411. *
  412. * For example: INBOX.foo, INBOX.foobar, and INBOX.foo.bar
  413. * Without special sort function: foobar between foo and foo.bar
  414. * With special sort function: foobar AFTER foo and foo.bar :)
  415. */
  416. function user_strcasecmp($a, $b) {
  417. return strnatcasecmp($a, $b);
  418. }
  419. /*
  420. * Returns list of options (to be echoed into select statement
  421. * based on available mailboxes and separators
  422. * Caller should surround options with <SELECT..> </SELECT> and
  423. * any formatting.
  424. * $imap_stream - $imapConnection to query for mailboxes
  425. * $show_selected - array containing list of mailboxes to pre-select (0 if none)
  426. * $folder_skip - array of folders to keep out of option list (compared in lower)
  427. * $boxes - list of already fetched boxes (for places like folder panel, where
  428. * you know these options will be shown 3 times in a row.. (most often unset).
  429. * $flag - flag to check for in mailbox flags, used to filter out mailboxes.
  430. * 'noselect' by default to remove unselectable mailboxes.
  431. * 'noinferiors' used to filter out folders that can not contain subfolders.
  432. * NULL to avoid flag check entirely.
  433. * NOTE: noselect and noiferiors are used internally. The IMAP representation is
  434. * \NoSelect and \NoInferiors
  435. * $use_long_format - override folder display preference and always show full folder name.
  436. */
  437. function sqimap_mailbox_option_list($imap_stream, $show_selected = 0, $folder_skip = 0, $boxes = 0,
  438. $flag = 'noselect', $use_long_format = false ) {
  439. global $username, $data_dir;
  440. $mbox_options = '';
  441. if ( $use_long_format ) {
  442. $shorten_box_names = 0;
  443. } else {
  444. $shorten_box_names = getPref($data_dir, $username, 'mailbox_select_style', SMPREF_OFF);
  445. }
  446. if ($boxes == 0) {
  447. $boxes = sqimap_mailbox_list($imap_stream);
  448. }
  449. foreach ($boxes as $boxes_part) {
  450. if ($flag == NULL || !in_array($flag, $boxes_part['flags'])) {
  451. $box = $boxes_part['unformatted'];
  452. if ($folder_skip != 0 && in_array($box, $folder_skip) ) {
  453. continue;
  454. }
  455. $lowerbox = strtolower($box);
  456. // mailboxes are casesensitive => inbox.sent != inbox.Sent
  457. // nevermind, to many dependencies this should be fixed!
  458. if (strtolower($box) == 'inbox') { // inbox is special and not casesensitive
  459. $box2 = _("INBOX");
  460. } else {
  461. switch ($shorten_box_names)
  462. {
  463. case 2: /* delimited, style = 2 */
  464. $box2 = str_replace('&nbsp;&nbsp;', '.&nbsp;', $boxes_part['formatted']);
  465. break;
  466. case 1: /* indent, style = 1 */
  467. $box2 = $boxes_part['formatted'];
  468. break;
  469. default: /* default, long names, style = 0 */
  470. $box2 = str_replace(' ', '&nbsp;', htmlspecialchars(imap_utf7_decode_local($boxes_part['unformatted-disp'])));
  471. break;
  472. }
  473. }
  474. if ($show_selected != 0 && in_array($lowerbox, $show_selected) ) {
  475. $mbox_options .= '<OPTION VALUE="' . htmlspecialchars($box) .'" SELECTED>'.$box2.'</OPTION>' . "\n";
  476. } else {
  477. $mbox_options .= '<OPTION VALUE="' . htmlspecialchars($box) .'">'.$box2.'</OPTION>' . "\n";
  478. }
  479. }
  480. }
  481. return $mbox_options;
  482. }
  483. /*
  484. * Returns sorted mailbox lists in several different ways.
  485. * See comment on sqimap_mailbox_parse() for info about the returned array.
  486. */
  487. function sqimap_mailbox_list($imap_stream) {
  488. global $default_folder_prefix;
  489. if (!isset($boxesnew)) {
  490. global $data_dir, $username, $list_special_folders_first,
  491. $folder_prefix, $trash_folder, $sent_folder, $draft_folder,
  492. $move_to_trash, $move_to_sent, $save_as_draft,
  493. $delimiter, $noselect_fix_enable;
  494. $inbox_in_list = false;
  495. $inbox_subscribed = false;
  496. require_once(SM_PATH . 'include/load_prefs.php');
  497. if ($noselect_fix_enable) {
  498. $lsub_args = "LSUB \"$folder_prefix\" \"*%\"";
  499. } else {
  500. $lsub_args = "LSUB \"$folder_prefix\" \"*\"";
  501. }
  502. /* LSUB array */
  503. $lsub_ary = sqimap_run_command ($imap_stream, $lsub_args,
  504. true, $response, $message);
  505. $lsub_ary = compact_mailboxes_response($lsub_ary);
  506. $sorted_lsub_ary = array();
  507. for ($i = 0, $cnt = count($lsub_ary);$i < $cnt; $i++) {
  508. $temp_mailbox_name = find_mailbox_name($lsub_ary[$i]);
  509. $sorted_lsub_ary[] = $temp_mailbox_name;
  510. if (!$inbox_subscribed && strtoupper($temp_mailbox_name) == 'INBOX') {
  511. $inbox_subscribed = true;
  512. }
  513. }
  514. /* natural sort mailboxes */
  515. if (isset($sorted_lsub_ary)) {
  516. usort($sorted_lsub_ary, 'user_strcasecmp');
  517. }
  518. /*
  519. * The LSUB response doesn't provide us information about \Noselect
  520. * mail boxes. The LIST response does, that's why we need to do a LIST
  521. * call to retrieve the flags for the mailbox
  522. * Note: according RFC2060 an imap server may provide \NoSelect flags in the LSUB response.
  523. * in other words, we cannot rely on it.
  524. */
  525. $sorted_list_ary = array();
  526. for ($i=0; $i < count($sorted_lsub_ary); $i++) {
  527. if (substr($sorted_lsub_ary[$i], -1) == $delimiter) {
  528. $mbx = substr($sorted_lsub_ary[$i], 0, strlen($sorted_lsub_ary[$i])-1);
  529. }
  530. else {
  531. $mbx = $sorted_lsub_ary[$i];
  532. }
  533. $mbx = stripslashes($mbx);
  534. $read = sqimap_run_command ($imap_stream, 'LIST "" ' . sqimap_encode_mailbox_name($mbx),
  535. true, $response, $message);
  536. $read = compact_mailboxes_response($read);
  537. if (isset($read[0])) {
  538. $sorted_list_ary[$i] = $read[0];
  539. } else {
  540. $sorted_list_ary[$i] = '';
  541. }
  542. }
  543. /*
  544. * Just in case they're not subscribed to their inbox,
  545. * we'll get it for them anyway
  546. */
  547. if (!$inbox_subscribed) {
  548. $inbox_ary = sqimap_run_command ($imap_stream, 'LIST "" INBOX',
  549. true, $response, $message);
  550. $sorted_list_ary[] = implode('', compact_mailboxes_response($inbox_ary));
  551. $sorted_lsub_ary[] = find_mailbox_name($inbox_ary[0]);
  552. }
  553. $boxesall = sqimap_mailbox_parse ($sorted_list_ary, $sorted_lsub_ary);
  554. /* Now, lets sort for special folders */
  555. $boxesnew = $used = array();
  556. /* Find INBOX */
  557. $cnt = count($boxesall);
  558. $used = array_pad($used,$cnt,false);
  559. for($k = 0; $k < $cnt; ++$k) {
  560. if (strtolower($boxesall[$k]['unformatted']) == 'inbox') {
  561. $boxesnew[] = $boxesall[$k];
  562. $used[$k] = true;
  563. break;
  564. }
  565. }
  566. /* List special folders and their subfolders, if requested. */
  567. if ($list_special_folders_first) {
  568. for($k = 0; $k < $cnt; ++$k) {
  569. if (!$used[$k] && isSpecialMailbox($boxesall[$k]['unformatted'])) {
  570. $boxesnew[] = $boxesall[$k];
  571. $used[$k] = true;
  572. }
  573. }
  574. }
  575. /* Rest of the folders */
  576. for($k = 0; $k < $cnt; $k++) {
  577. if (!$used[$k]) {
  578. $boxesnew[] = $boxesall[$k];
  579. }
  580. }
  581. }
  582. return $boxesnew;
  583. }
  584. /*
  585. * Returns a list of all folders, subscribed or not
  586. */
  587. function sqimap_mailbox_list_all($imap_stream) {
  588. global $list_special_folders_first, $folder_prefix, $delimiter;
  589. $read_ary = sqimap_run_command($imap_stream,"LIST \"$folder_prefix\" *",true,$response, $message,false);
  590. $read_ary = compact_mailboxes_response($read_ary);
  591. $g = 0;
  592. $phase = 'inbox';
  593. $fld_pre_length = strlen($folder_prefix);
  594. for ($i = 0, $cnt = count($read_ary); $i < $cnt; $i++) {
  595. /* Store the raw IMAP reply */
  596. $boxes[$g]['raw'] = $read_ary[$i];
  597. /* Count number of delimiters ($delimiter) in folder name */
  598. $mailbox = find_mailbox_name($read_ary[$i]);
  599. $dm_count = substr_count($mailbox, $delimiter);
  600. if (substr($mailbox, -1) == $delimiter) {
  601. /* If name ends in delimiter - decrement count by one */
  602. $dm_count--;
  603. }
  604. /* Format folder name, but only if it's a INBOX.* or has a parent. */
  605. $boxesallbyname[$mailbox] = $g;
  606. $parentfolder = readMailboxParent($mailbox, $delimiter);
  607. if((eregi('^inbox'.quotemeta($delimiter), $mailbox)) ||
  608. (ereg('^'.$folder_prefix, $mailbox)) ||
  609. ( isset($boxesallbyname[$parentfolder]) && (strlen($parentfolder) > 0) ) ) {
  610. if ($dm_count) {
  611. $boxes[$g]['formatted'] = str_repeat('&nbsp;&nbsp;', $dm_count);
  612. } else {
  613. $boxes[$g]['formatted'] = '';
  614. }
  615. $boxes[$g]['formatted'] .= imap_utf7_decode_local(readShortMailboxName($mailbox, $delimiter));
  616. } else {
  617. $boxes[$g]['formatted'] = imap_utf7_decode_local($mailbox);
  618. }
  619. $boxes[$g]['unformatted-dm'] = $mailbox;
  620. if (substr($mailbox, -1) == $delimiter) {
  621. $mailbox = substr($mailbox, 0, strlen($mailbox) - 1);
  622. }
  623. $boxes[$g]['unformatted'] = $mailbox;
  624. $boxes[$g]['unformatted-disp'] = substr($mailbox,$fld_pre_length);
  625. $boxes[$g]['id'] = $g;
  626. /* Now lets get the flags for this mailbox */
  627. $read_mlbx = $read_ary[$i];
  628. $flags = substr($read_mlbx, strpos($read_mlbx, '(')+1);
  629. $flags = substr($flags, 0, strpos($flags, ')'));
  630. $flags = str_replace('\\', '', $flags);
  631. $flags = trim(strtolower($flags));
  632. if ($flags) {
  633. $boxes[$g]['flags'] = explode(' ', $flags);
  634. } else {
  635. $boxes[$g]['flags'] = array();
  636. }
  637. $g++;
  638. }
  639. if(is_array($boxes)) {
  640. sort ($boxes);
  641. }
  642. return $boxes;
  643. }
  644. function sqimap_mailbox_tree($imap_stream) {
  645. global $boxesnew, $default_folder_prefix, $unseen_notify, $unseen_type;
  646. if (!isset($boxesnew)) {
  647. global $data_dir, $username, $list_special_folders_first,
  648. $folder_prefix, $delimiter, $trash_folder, $move_to_trash,
  649. $imap_server_type;
  650. $inbox_in_list = false;
  651. $inbox_subscribed = false;
  652. $noselect = false;
  653. $noinferiors = false;
  654. require_once(SM_PATH . 'include/load_prefs.php');
  655. /* LSUB array */
  656. $lsub_ary = sqimap_run_command ($imap_stream, "LSUB \"$folder_prefix\" \"*\"",
  657. true, $response, $message);
  658. $lsub_ary = compact_mailboxes_response($lsub_ary);
  659. /* Check to see if we have an INBOX */
  660. $has_inbox = false;
  661. for ($i = 0, $cnt = count($lsub_ary); $i < $cnt; $i++) {
  662. if (preg_match("/^\*\s+LSUB\s+(.*)\"?INBOX\"?[^(\/\.)].*$/i",$lsub_ary[$i])) {
  663. $lsub_ary[$i] = strtoupper($lsub_ary[$i]);
  664. $has_inbox = true;
  665. break;
  666. }
  667. }
  668. if ($has_inbox == false) {
  669. $lsub_ary[] = '* LSUB () NIL INBOX';
  670. }
  671. /*
  672. * Section about removing the last element was removed
  673. * We don't return "* OK" anymore from sqimap_read_data
  674. */
  675. $sorted_lsub_ary = array();
  676. $cnt = count($lsub_ary);
  677. for ($i = 0; $i < $cnt; $i++) {
  678. $mbx = find_mailbox_name($lsub_ary[$i]);
  679. // only do the noselect test if !uw, is checked later. FIX ME see conf.pl setting
  680. if ($imap_server_type != "uw") {
  681. $noselect = check_is_noselect($lsub_ary[$i]);
  682. $noinferiors = check_is_noinferiors($lsub_ary[$i]);
  683. }
  684. if (substr($mbx, -1) == $delimiter) {
  685. $mbx = substr($mbx, 0, strlen($mbx) - 1);
  686. }
  687. $sorted_lsub_ary[] = array ('mbx' => $mbx, 'noselect' => $noselect, 'noinferiors' => $noinferiors);
  688. }
  689. // FIX ME this requires a config setting inside conf.pl instead of checking on server type
  690. if ($imap_server_type == "uw") {
  691. $aQuery = array();
  692. $aTag = array();
  693. // prepare an array with queries
  694. foreach ($sorted_lsub_ary as $aMbx) {
  695. $mbx = stripslashes($aMbx['mbx']);
  696. sqimap_prepare_pipelined_query('LIST "" ' . sqimap_encode_mailbox_name($mbx), $tag, $aQuery, false);
  697. $aTag[$tag] = $mbx;
  698. }
  699. $sorted_lsub_ary = array();
  700. // execute all the queries at once
  701. $aResponse = sqimap_run_pipelined_command ($imap_stream, $aQuery, false, $aServerResponse, $aServerMessage);
  702. foreach($aTag as $tag => $mbx) {
  703. if ($aServerResponse[$tag] == 'OK') {
  704. $sResponse = implode('', $aResponse[$tag]);
  705. $noselect = check_is_noselect($sResponse);
  706. $noinferiors = check_is_noinferiors($sResponse);
  707. $sorted_lsub_ary[] = array ('mbx' => $mbx, 'noselect' => $noselect, 'noinferiors' => $noinferiors);
  708. }
  709. }
  710. $cnt = count($sorted_lsub_ary);
  711. }
  712. $sorted_lsub_ary = array_values($sorted_lsub_ary);
  713. array_multisort($sorted_lsub_ary, SORT_ASC, SORT_REGULAR);
  714. $boxesnew = sqimap_fill_mailbox_tree($sorted_lsub_ary,false,$imap_stream);
  715. return $boxesnew;
  716. }
  717. }
  718. function sqimap_fill_mailbox_tree($mbx_ary, $mbxs=false,$imap_stream) {
  719. global $data_dir, $username, $list_special_folders_first,
  720. $folder_prefix, $trash_folder, $sent_folder, $draft_folder,
  721. $move_to_trash, $move_to_sent, $save_as_draft,
  722. $delimiter, $imap_server_type;
  723. $special_folders = array ('INBOX', $sent_folder, $draft_folder, $trash_folder);
  724. /* create virtual root node */
  725. $mailboxes= new mailboxes();
  726. $mailboxes->is_root = true;
  727. $trail_del = false;
  728. $start = 0;
  729. if (isset($folder_prefix) && ($folder_prefix != '')) {
  730. $start = substr_count($folder_prefix,$delimiter);
  731. if (strrpos($folder_prefix, $delimiter) == (strlen($folder_prefix)-1)) {
  732. $trail_del = true;
  733. $mailboxes->mailboxname_full = substr($folder_prefix,0, (strlen($folder_prefix)-1));
  734. } else {
  735. $mailboxes->mailboxname_full = $folder_prefix;
  736. $start++;
  737. }
  738. $mailboxes->mailboxname_sub = $mailboxes->mailboxname_full;
  739. } else {
  740. $start = 0;
  741. }
  742. $cnt = count($mbx_ary);
  743. for ($i=0; $i < $cnt; $i++) {
  744. if ($mbx_ary[$i]['mbx'] !='' ) {
  745. $mbx = new mailboxes();
  746. $mailbox = $mbx_ary[$i]['mbx'];
  747. /*
  748. sent subfolders messes up using existing code as subfolders
  749. were marked, but the parents were ordered somewhere else in
  750. the list, despite having "special folders at top" option set.
  751. Need a better method than this.
  752. */
  753. if ($mailbox == 'INBOX') {
  754. $mbx->is_special = true;
  755. } elseif (stristr($trash_folder , $mailbox)) {
  756. $mbx->is_special = true;
  757. } elseif (stristr($sent_folder , $mailbox)) {
  758. $mbx->is_special = true;
  759. } elseif (stristr($draft_folder , $mailbox)) {
  760. $mbx->is_special = true;
  761. }
  762. switch ($mailbox) {
  763. case 'INBOX':
  764. $mbx->is_inbox = true;
  765. $mbx->is_special = true;
  766. $mbx_ary[$i]['noselect'] = false;
  767. break;
  768. case $trash_folder:
  769. $mbx->is_trash = true;
  770. $mbx->is_special = true;
  771. break;
  772. case $sent_folder:
  773. $mbx->is_sent = true;
  774. $mbx->is_special = true;
  775. break;
  776. case $draft_folder:
  777. $mbx->is_draft = true;
  778. $mbx->is_special = true;
  779. break;
  780. }
  781. if (isset($mbx_ary[$i]['unseen'])) {
  782. $mbx->unseen = $mbx_ary[$i]['unseen'];
  783. }
  784. if (isset($mbx_ary[$i]['nummessages'])) {
  785. $mbx->total = $mbx_ary[$i]['nummessages'];
  786. }
  787. $mbx->is_noselect = $mbx_ary[$i]['noselect'];
  788. $mbx->is_noinferiors = $mbx_ary[$i]['noinferiors'];
  789. $r_del_pos = strrpos($mbx_ary[$i]['mbx'], $delimiter);
  790. if ($r_del_pos) {
  791. $mbx->mailboxname_sub = substr($mbx_ary[$i]['mbx'],$r_del_pos+1);
  792. } else { /* mailbox is root folder */
  793. $mbx->mailboxname_sub = $mbx_ary[$i]['mbx'];
  794. }
  795. $mbx->mailboxname_full = $mbx_ary[$i]['mbx'];
  796. $mailboxes->addMbx($mbx, $delimiter, $start, $list_special_folders_first);
  797. }
  798. }
  799. sqimap_utf7_decode_mbx_tree($mailboxes);
  800. sqimap_get_status_mbx_tree($imap_stream,$mailboxes);
  801. return $mailboxes;
  802. }
  803. function sqimap_utf7_decode_mbx_tree(&$mbx_tree) {
  804. if (strtoupper($mbx_tree->mailboxname_sub) == 'INBOX')
  805. $mbx_tree->mailboxname_sub = _("INBOX");
  806. else
  807. $mbx_tree->mailboxname_sub = imap_utf7_decode_local($mbx_tree->mailboxname_sub);
  808. if ($mbx_tree->mbxs) {
  809. $iCnt = count($mbx_tree->mbxs);
  810. for ($i=0;$i<$iCnt;++$i) {
  811. $mbxs_tree->mbxs[$i] = sqimap_utf7_decode_mbx_tree($mbx_tree->mbxs[$i]);
  812. }
  813. }
  814. }
  815. function sqimap_tree_to_ref_array(&$mbx_tree,&$aMbxs) {
  816. if ($mbx_tree)
  817. $aMbxs[] =& $mbx_tree;
  818. if ($mbx_tree->mbxs) {
  819. $iCnt = count($mbx_tree->mbxs);
  820. for ($i=0;$i<$iCnt;++$i) {
  821. sqimap_tree_to_ref_array($mbx_tree->mbxs[$i],$aMbxs);
  822. }
  823. }
  824. }
  825. /* Define preferences for folder settings. */
  826. /* FIXME, we should load constants.php
  827. unseen_notify
  828. define('SMPREF_UNSEEN_NONE', 1);
  829. define('SMPREF_UNSEEN_INBOX', 2);
  830. define('SMPREF_UNSEEN_ALL', 3);
  831. define('SMPREF_UNSEEN_SPECIAL', 4); // Only special folders
  832. define('SMPREF_UNSEEN_NORMAL', 5); // Only normal folders
  833. unseen_type
  834. define('SMPREF_UNSEEN_ONLY', 1);
  835. define('SMPREF_UNSEEN_TOTAL', 2);
  836. */
  837. function sqimap_get_status_mbx_tree($imap_stream,&$mbx_tree) {
  838. global $unseen_notify, $unseen_type, $trash_folder,$move_to_trash;
  839. $aMbxs = $aQuery = $aTag = array();
  840. sqimap_tree_to_ref_array($mbx_tree,$aMbxs);
  841. // remove the root node
  842. array_shift($aMbxs);
  843. if($unseen_notify == 3) {
  844. $cnt = count($aMbxs);
  845. for($i=0;$i<$cnt;++$i) {
  846. $oMbx =& $aMbxs[$i];
  847. if (!$oMbx->is_noselect) {
  848. $mbx = $oMbx->mailboxname_full;
  849. if ($unseen_type == 2 ||
  850. ($move_to_trash && $oMbx->mailboxname_full == $trash_folder)) {
  851. $query = 'STATUS ' . sqimap_encode_mailbox_name($mbx) . ' (MESSAGES UNSEEN)';
  852. } else {
  853. $query = 'STATUS ' . sqimap_encode_mailbox_name($mbx) . ' (UNSEEN)';
  854. }
  855. sqimap_prepare_pipelined_query($query,$tag,$aQuery,false);
  856. } else {
  857. $oMbx->unseen = $oMbx->total = false;
  858. $tag = false;
  859. }
  860. $oMbx->tag = $tag;
  861. $aMbxs[$i] =& $oMbx;
  862. }
  863. // execute all the queries at once
  864. $aResponse = sqimap_run_pipelined_command ($imap_stream, $aQuery, false, $aServerResponse, $aServerMessage);
  865. $cnt = count($aMbxs);
  866. for($i=0;$i<$cnt;++$i) {
  867. $oMbx =& $aMbxs[$i];
  868. $tag = $oMbx->tag;
  869. if ($tag && $aServerResponse[$tag] == 'OK') {
  870. $sResponse = implode('', $aResponse[$tag]);
  871. if (preg_match('/UNSEEN\s+([0-9]+)/i', $sResponse, $regs)) {
  872. $oMbx->unseen = $regs[1];
  873. }
  874. if (preg_match('/MESSAGES\s+([0-9]+)/i', $sResponse, $regs)) {
  875. $oMbx->total = $regs[1];
  876. }
  877. }
  878. unset($oMbx->tag);
  879. }
  880. } else if ($unseen_notify == 2) { // INBOX only
  881. $cnt = count($aMbxs);
  882. for($i=0;$i<$cnt;++$i) {
  883. $oMbx =& $aMbxs[$i];
  884. if (strtoupper($oMbx->mailboxname_full) == 'INBOX' ||
  885. ($move_to_trash && $oMbx->mailboxname_full == $trash_folder)) {
  886. if ($unseen_type == 2 ||
  887. ($oMbx->mailboxname_full == $trash_folder && $move_to_trash)) {
  888. $aStatus = sqimap_status_messages($imap_stream,$oMbx->mailboxname_full);
  889. $oMbx->unseen = $aStatus['UNSEEN'];
  890. $oMbx->total = $aStatus['MESSAGES'];
  891. } else {
  892. $oMbx->unseen = sqimap_unseen_messages($imap_stream,$oMbx->mailboxname_full);
  893. }
  894. $aMbxs[$i] =& $oMbx;
  895. if (!$move_to_trash && $trash_folder) {
  896. break;
  897. } else {
  898. // trash comes after INBOX
  899. if ($oMbx->mailboxname_full == $trash_folder) {
  900. break;
  901. }
  902. }
  903. }
  904. }
  905. }
  906. }
  907. ?>