addressbook.php 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672
  1. <?php
  2. /**
  3. * addressbook.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. * Functions and classes for the addressbook system.
  9. *
  10. * @version $Id$
  11. * @package squirrelmail
  12. * @subpackage addressbook
  13. */
  14. /**
  15. This is the path to the global site-wide addressbook.
  16. It looks and feels just like a user's .abook file
  17. If this is in the data directory, use "$data_dir/global.abook"
  18. If not, specify the path as though it was accessed from the
  19. src/ directory ("../global.abook" -> in main directory)
  20. If you don't want a global site-wide addressbook, comment these
  21. two lines out. (They are disabled by default.)
  22. The global addressbook is unmodifiable by anyone. You must actually
  23. use a shell script or whatnot to modify the contents.
  24. global $data_dir, $address_book_global_filename;
  25. $address_book_global_filename = "$data_dir/global.abook";
  26. */
  27. global $addrbook_dsn, $addrbook_global_dsn;
  28. /**
  29. Create and initialize an addressbook object.
  30. Returns the created object
  31. */
  32. function addressbook_init($showerr = true, $onlylocal = false) {
  33. global $data_dir, $username, $ldap_server, $address_book_global_filename;
  34. global $addrbook_dsn, $addrbook_table;
  35. global $addrbook_global_dsn, $addrbook_global_table, $addrbook_global_writeable, $addrbook_global_listing;
  36. /* Create a new addressbook object */
  37. $abook = new AddressBook;
  38. /*
  39. Always add a local backend. We use *either* file-based *or* a
  40. database addressbook. If $addrbook_dsn is set, the database
  41. backend is used. If not, addressbooks are stores in files.
  42. */
  43. if (isset($addrbook_dsn) && !empty($addrbook_dsn)) {
  44. /* Database */
  45. if (!isset($addrbook_table) || empty($addrbook_table)) {
  46. $addrbook_table = 'address';
  47. }
  48. $r = $abook->add_backend('database', Array('dsn' => $addrbook_dsn,
  49. 'owner' => $username,
  50. 'table' => $addrbook_table));
  51. if (!$r && $showerr) {
  52. echo _("Error initializing addressbook database.");
  53. exit;
  54. }
  55. } else {
  56. /* File */
  57. $filename = getHashedFile($username, $data_dir, "$username.abook");
  58. $r = $abook->add_backend('local_file', Array('filename' => $filename,
  59. 'create' => true));
  60. if(!$r && $showerr) {
  61. printf( _("Error opening file %s"), $filename );
  62. exit;
  63. }
  64. }
  65. /* This would be for the global addressbook */
  66. if (isset($address_book_global_filename)) {
  67. $r = $abook->add_backend('global_file');
  68. if (!$r && $showerr) {
  69. echo _("Error initializing global addressbook.");
  70. exit;
  71. }
  72. }
  73. /* Load global addressbook from SQL if configured */
  74. if (isset($addrbook_global_dsn) && !empty($addrbook_global_dsn)) {
  75. /* Database configured */
  76. if (!isset($addrbook_global_table) || empty($addrbook_global_table)) {
  77. $addrbook_global_table = 'global_abook';
  78. }
  79. $r = $abook->add_backend('database',
  80. Array('dsn' => $addrbook_global_dsn,
  81. 'owner' => 'global',
  82. 'name' => _("Global address book"),
  83. 'writeable' => $addrbook_global_writeable,
  84. 'listing' => $addrbook_global_listing,
  85. 'table' => $addrbook_global_table));
  86. }
  87. /*
  88. * hook allows to include different address book backends.
  89. * plugins should extract $abook and $r from arguments
  90. * and use same add_backend commands as above functions.
  91. */
  92. $hookReturn = do_hook('abook_init', $abook, $r);
  93. $abook = $hookReturn[1];
  94. $r = $hookReturn[2];
  95. if ($onlylocal) {
  96. return $abook;
  97. }
  98. /* Load configured LDAP servers (if PHP has LDAP support) */
  99. if (isset($ldap_server) && is_array($ldap_server) && function_exists('ldap_connect')) {
  100. reset($ldap_server);
  101. while (list($undef,$param) = each($ldap_server)) {
  102. if (is_array($param)) {
  103. $r = $abook->add_backend('ldap_server', $param);
  104. if (!$r && $showerr) {
  105. printf( '&nbsp;' . _("Error initializing LDAP server %s:") .
  106. "<BR>\n", $param['host']);
  107. echo '&nbsp;' . $abook->error;
  108. exit;
  109. }
  110. }
  111. }
  112. }
  113. /* Return the initialized object */
  114. return $abook;
  115. }
  116. /*
  117. * Had to move this function outside of the Addressbook Class
  118. * PHP 4.0.4 Seemed to be having problems with inline functions.
  119. * Note: this can return now since we don't support 4.0.4 anymore.
  120. */
  121. function addressbook_cmp($a,$b) {
  122. if($a['backend'] > $b['backend']) {
  123. return 1;
  124. } else if($a['backend'] < $b['backend']) {
  125. return -1;
  126. }
  127. return (strtolower($a['name']) > strtolower($b['name'])) ? 1 : -1;
  128. }
  129. /**
  130. * This is the main address book class that connect all the
  131. * backends and provide services to the functions above.
  132. * @package squirrelmail
  133. */
  134. class AddressBook {
  135. var $backends = array();
  136. var $numbackends = 0;
  137. var $error = '';
  138. var $localbackend = 0;
  139. var $localbackendname = '';
  140. // Constructor function.
  141. function AddressBook() {
  142. $localbackendname = _("Personal address book");
  143. }
  144. /*
  145. * Return an array of backends of a given type,
  146. * or all backends if no type is specified.
  147. */
  148. function get_backend_list($type = '') {
  149. $ret = array();
  150. for ($i = 1 ; $i <= $this->numbackends ; $i++) {
  151. if (empty($type) || $type == $this->backends[$i]->btype) {
  152. $ret[] = &$this->backends[$i];
  153. }
  154. }
  155. return $ret;
  156. }
  157. /*
  158. ========================== Public ========================
  159. Add a new backend. $backend is the name of a backend
  160. (without the abook_ prefix), and $param is an optional
  161. mixed variable that is passed to the backend constructor.
  162. See each of the backend classes for valid parameters.
  163. */
  164. function add_backend($backend, $param = '') {
  165. $backend_name = 'abook_' . $backend;
  166. eval('$newback = new ' . $backend_name . '($param);');
  167. if(!empty($newback->error)) {
  168. $this->error = $newback->error;
  169. return false;
  170. }
  171. $this->numbackends++;
  172. $newback->bnum = $this->numbackends;
  173. $this->backends[$this->numbackends] = $newback;
  174. /* Store ID of first local backend added */
  175. if ($this->localbackend == 0 && $newback->btype == 'local') {
  176. $this->localbackend = $this->numbackends;
  177. $this->localbackendname = $newback->sname;
  178. }
  179. return $this->numbackends;
  180. }
  181. /*
  182. * This function takes a $row array as returned by the addressbook
  183. * search and returns an e-mail address with the full name or
  184. * nickname optionally prepended.
  185. */
  186. function full_address($row) {
  187. global $addrsrch_fullname, $data_dir, $username;
  188. $prefix = getPref($data_dir, $username, 'addrsrch_fullname');
  189. if (($prefix != "" || (isset($addrsrch_fullname) &&
  190. $prefix == $addrsrch_fullname)) && $prefix != 'noprefix') {
  191. $name = ($prefix == 'nickname' ? $row['nickname'] : $row['name']);
  192. return $name . ' <' . trim($row['email']) . '>';
  193. } else {
  194. return trim($row['email']);
  195. }
  196. }
  197. /*
  198. Return a list of addresses matching expression in
  199. all backends of a given type.
  200. */
  201. function search($expression, $bnum = -1) {
  202. $ret = array();
  203. $this->error = '';
  204. /* Search all backends */
  205. if ($bnum == -1) {
  206. $sel = $this->get_backend_list('');
  207. $failed = 0;
  208. for ($i = 0 ; $i < sizeof($sel) ; $i++) {
  209. $backend = &$sel[$i];
  210. $backend->error = '';
  211. $res = $backend->search($expression);
  212. if (is_array($res)) {
  213. $ret = array_merge($ret, $res);
  214. } else {
  215. $this->error .= "<br>\n" . $backend->error;
  216. $failed++;
  217. }
  218. }
  219. /* Only fail if all backends failed */
  220. if( $failed >= sizeof( $sel ) ) {
  221. $ret = FALSE;
  222. }
  223. } else {
  224. /* Search only one backend */
  225. $ret = $this->backends[$bnum]->search($expression);
  226. if (!is_array($ret)) {
  227. $this->error .= "<br>\n" . $this->backends[$bnum]->error;
  228. $ret = FALSE;
  229. }
  230. }
  231. return( $ret );
  232. }
  233. /* Return a sorted search */
  234. function s_search($expression, $bnum = -1) {
  235. $ret = $this->search($expression, $bnum);
  236. if ( is_array( $ret ) ) {
  237. usort($ret, 'addressbook_cmp');
  238. }
  239. return $ret;
  240. }
  241. /*
  242. * Lookup an address by alias. Only possible in
  243. * local backends.
  244. */
  245. function lookup($alias, $bnum = -1) {
  246. $ret = array();
  247. if ($bnum > -1) {
  248. $res = $this->backends[$bnum]->lookup($alias);
  249. if (is_array($res)) {
  250. return $res;
  251. } else {
  252. $this->error = $backend->error;
  253. return false;
  254. }
  255. }
  256. $sel = $this->get_backend_list('local');
  257. for ($i = 0 ; $i < sizeof($sel) ; $i++) {
  258. $backend = &$sel[$i];
  259. $backend->error = '';
  260. $res = $backend->lookup($alias);
  261. if (is_array($res)) {
  262. if(!empty($res))
  263. return $res;
  264. } else {
  265. $this->error = $backend->error;
  266. return false;
  267. }
  268. }
  269. return $ret;
  270. }
  271. /* Return all addresses */
  272. function list_addr($bnum = -1) {
  273. $ret = array();
  274. if ($bnum == -1) {
  275. $sel = $this->get_backend_list('local');
  276. } else {
  277. $sel = array(0 => &$this->backends[$bnum]);
  278. }
  279. for ($i = 0 ; $i < sizeof($sel) ; $i++) {
  280. $backend = &$sel[$i];
  281. $backend->error = '';
  282. $res = $backend->list_addr();
  283. if (is_array($res)) {
  284. $ret = array_merge($ret, $res);
  285. } else {
  286. $this->error = $backend->error;
  287. return false;
  288. }
  289. }
  290. return $ret;
  291. }
  292. /*
  293. * Create a new address from $userdata, in backend $bnum.
  294. * Return the backend number that the/ address was added
  295. * to, or false if it failed.
  296. */
  297. function add($userdata, $bnum) {
  298. /* Validate data */
  299. if (!is_array($userdata)) {
  300. $this->error = _("Invalid input data");
  301. return false;
  302. }
  303. if (empty($userdata['firstname']) && empty($userdata['lastname'])) {
  304. $this->error = _("Name is missing");
  305. return false;
  306. }
  307. if (empty($userdata['email'])) {
  308. $this->error = _("E-mail address is missing");
  309. return false;
  310. }
  311. if (empty($userdata['nickname'])) {
  312. $userdata['nickname'] = $userdata['email'];
  313. }
  314. if (eregi('[ \\:\\|\\#\\"\\!]', $userdata['nickname'])) {
  315. $this->error = _("Nickname contains illegal characters");
  316. return false;
  317. }
  318. /* Check that specified backend accept new entries */
  319. if (!$this->backends[$bnum]->writeable) {
  320. $this->error = _("Addressbook is read-only");
  321. return false;
  322. }
  323. /* Add address to backend */
  324. $res = $this->backends[$bnum]->add($userdata);
  325. if ($res) {
  326. return $bnum;
  327. } else {
  328. $this->error = $this->backends[$bnum]->error;
  329. return false;
  330. }
  331. return false; // Not reached
  332. } /* end of add() */
  333. /*
  334. * Remove the user identified by $alias from backend $bnum
  335. * If $alias is an array, all users in the array are removed.
  336. */
  337. function remove($alias, $bnum) {
  338. /* Check input */
  339. if (empty($alias)) {
  340. return true;
  341. }
  342. /* Convert string to single element array */
  343. if (!is_array($alias)) {
  344. $alias = array(0 => $alias);
  345. }
  346. /* Check that specified backend is writeable */
  347. if (!$this->backends[$bnum]->writeable) {
  348. $this->error = _("Addressbook is read-only");
  349. return false;
  350. }
  351. /* Remove user from backend */
  352. $res = $this->backends[$bnum]->remove($alias);
  353. if ($res) {
  354. return $bnum;
  355. } else {
  356. $this->error = $this->backends[$bnum]->error;
  357. return false;
  358. }
  359. return FALSE; /* Not reached */
  360. } /* end of remove() */
  361. /*
  362. * Remove the user identified by $alias from backend $bnum
  363. * If $alias is an array, all users in the array are removed.
  364. */
  365. function modify($alias, $userdata, $bnum) {
  366. /* Check input */
  367. if (empty($alias) || !is_string($alias)) {
  368. return true;
  369. }
  370. /* Validate data */
  371. if(!is_array($userdata)) {
  372. $this->error = _("Invalid input data");
  373. return false;
  374. }
  375. if (empty($userdata['firstname']) && empty($userdata['lastname'])) {
  376. $this->error = _("Name is missing");
  377. return false;
  378. }
  379. if (empty($userdata['email'])) {
  380. $this->error = _("E-mail address is missing");
  381. return false;
  382. }
  383. if (eregi('[\\: \\|\\#"\\!]', $userdata['nickname'])) {
  384. $this->error = _("Nickname contains illegal characters");
  385. return false;
  386. }
  387. if (empty($userdata['nickname'])) {
  388. $userdata['nickname'] = $userdata['email'];
  389. }
  390. /* Check that specified backend is writeable */
  391. if (!$this->backends[$bnum]->writeable) {
  392. $this->error = _("Addressbook is read-only");;
  393. return false;
  394. }
  395. /* Modify user in backend */
  396. $res = $this->backends[$bnum]->modify($alias, $userdata);
  397. if ($res) {
  398. return $bnum;
  399. } else {
  400. $this->error = $this->backends[$bnum]->error;
  401. return false;
  402. }
  403. return FALSE; /* Not reached */
  404. } /* end of modify() */
  405. } /* End of class Addressbook */
  406. /**
  407. * Generic backend that all other backends extend
  408. * @package squirrelmail
  409. */
  410. class addressbook_backend {
  411. /* Variables that all backends must provide. */
  412. var $btype = 'dummy';
  413. var $bname = 'dummy';
  414. var $sname = 'Dummy backend';
  415. /*
  416. * Variables common for all backends, but that
  417. * should not be changed by the backends.
  418. */
  419. var $bnum = -1;
  420. var $error = '';
  421. var $writeable = false;
  422. function set_error($string) {
  423. $this->error = '[' . $this->sname . '] ' . $string;
  424. return false;
  425. }
  426. /* ========================== Public ======================== */
  427. function search($expression) {
  428. $this->set_error('search not implemented');
  429. return false;
  430. }
  431. function lookup($alias) {
  432. $this->set_error('lookup not implemented');
  433. return false;
  434. }
  435. function list_addr() {
  436. $this->set_error('list_addr not implemented');
  437. return false;
  438. }
  439. function add($userdata) {
  440. $this->set_error('add not implemented');
  441. return false;
  442. }
  443. function remove($alias) {
  444. $this->set_error('delete not implemented');
  445. return false;
  446. }
  447. function modify($alias, $newuserdata) {
  448. $this->set_error('modify not implemented');
  449. return false;
  450. }
  451. }
  452. /**
  453. * Sort array by the key "name"
  454. */
  455. function alistcmp($a,$b) {
  456. $abook_sort_order=get_abook_sort();
  457. switch ($abook_sort_order) {
  458. case 0:
  459. case 1:
  460. $abook_sort='nickname';
  461. break;
  462. case 4:
  463. case 5:
  464. $abook_sort='email';
  465. break;
  466. case 6:
  467. case 7:
  468. $abook_sort='label';
  469. break;
  470. case 2:
  471. case 3:
  472. case 8:
  473. default:
  474. $abook_sort='name';
  475. }
  476. if ($a['backend'] > $b['backend']) {
  477. return 1;
  478. } else {
  479. if ($a['backend'] < $b['backend']) {
  480. return -1;
  481. }
  482. }
  483. if( (($abook_sort_order+2) % 2) == 1) {
  484. return (strtolower($a[$abook_sort]) < strtolower($b[$abook_sort])) ? 1 : -1;
  485. } else {
  486. return (strtolower($a[$abook_sort]) > strtolower($b[$abook_sort])) ? 1 : -1;
  487. }
  488. }
  489. /**
  490. * Address book sorting options
  491. *
  492. * returns address book sorting order
  493. * @return integer book sorting options order
  494. */
  495. function get_abook_sort() {
  496. global $data_dir, $username;
  497. /* get sorting order */
  498. if(sqgetGlobalVar('abook_sort_order', $temp, SQ_GET)) {
  499. $abook_sort_order = (int) $temp;
  500. if ($abook_sort_order < 0 or $abook_sort_order > 8)
  501. $abook_sort_order=8;
  502. setPref($data_dir, $username, 'abook_sort_order', $abook_sort_order);
  503. } else {
  504. /* get previous sorting options. default to unsorted */
  505. $abook_sort_order = getPref($data_dir, $username, 'abook_sort_order', 8);
  506. }
  507. return $abook_sort_order;
  508. }
  509. /**
  510. * This function shows the address book sort button.
  511. *
  512. * @param integer $abook_sort_order current sort value
  513. * @param string $alt_tag alt tag value (string visible to text only browsers)
  514. * @param integer $Down sort value when list is sorted ascending
  515. * @param integer $Up sort value when list is sorted descending
  516. * @return string html code with sorting images and urls
  517. */
  518. function show_abook_sort_button($abook_sort_order, $alt_tag, $Down, $Up ) {
  519. global $form_url;
  520. /* Figure out which image we want to use. */
  521. if ($abook_sort_order != $Up && $abook_sort_order != $Down) {
  522. $img = 'sort_none.png';
  523. $which = $Up;
  524. } elseif ($abook_sort_order == $Up) {
  525. $img = 'up_pointer.png';
  526. $which = $Down;
  527. } else {
  528. $img = 'down_pointer.png';
  529. $which = 8;
  530. }
  531. /* Now that we have everything figured out, show the actual button. */
  532. return ' <a href="' . $form_url .'?abook_sort_order=' . $which
  533. . '"><img src="../images/' . $img
  534. . '" border="0" width="12" height="10" alt="' . $alt_tag . '" title="'
  535. . _("Click here to change the sorting of the address list") .'"></a>';
  536. }
  537. /*
  538. PHP 5 requires that the class be made first, which seems rather
  539. logical, and should have been the way it was generated the first time.
  540. */
  541. require_once(SM_PATH . 'functions/abook_local_file.php');
  542. require_once(SM_PATH . 'functions/abook_ldap_server.php');
  543. /* Use this if you wanna have a global address book */
  544. if (isset($address_book_global_filename)) {
  545. include_once(SM_PATH . 'functions/abook_global_file.php');
  546. }
  547. /* Only load database backend if database is configured */
  548. if((isset($addrbook_dsn) && !empty($addrbook_dsn)) ||
  549. (isset($addrbook_global_dsn) && !empty($addrbook_global_dsn)) ) {
  550. include_once(SM_PATH . 'functions/abook_database.php');
  551. }
  552. /*
  553. * hook allows adding different address book classes.
  554. * class must follow address book class coding standards.
  555. *
  556. * see addressbook_backend class and functions/abook_*.php files.
  557. */
  558. do_hook('abook_add_class');
  559. ?>