addressbook.php 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615
  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=get_abook_sort();
  457. if ($a['backend'] > $b['backend']) {
  458. return 1;
  459. } else {
  460. if ($a['backend'] < $b['backend']) {
  461. return -1;
  462. }
  463. }
  464. return (strtolower($a[$abook_sort]) > strtolower($b[$abook_sort])) ? 1 : -1;
  465. }
  466. /**
  467. * Address book sorting order
  468. *
  469. * returns name of field that should be used for sorting.
  470. * @return string name of address book field
  471. */
  472. function get_abook_sort() {
  473. global $data_dir, $username;
  474. /* get sorting order */
  475. if(sqgetGlobalVar('abook_sort', $abook_sort,SQ_GET)) {
  476. sqgetGlobalVar('abook_sort', $abook_sort,SQ_GET);
  477. setPref($data_dir, $username, 'abook_sort', $abook_sort);
  478. } else {
  479. $abook_sort = getPref($data_dir, $username, 'abook_sort', 'name');
  480. }
  481. if ($abook_sort != "nickname" && $abook_sort != "email" && $abook_sort != "label")
  482. $abook_sort = "name";
  483. return $abook_sort;
  484. }
  485. /*
  486. PHP 5 requires that the class be made first, which seems rather
  487. logical, and should have been the way it was generated the first time.
  488. */
  489. require_once(SM_PATH . 'functions/abook_local_file.php');
  490. require_once(SM_PATH . 'functions/abook_ldap_server.php');
  491. /* Use this if you wanna have a global address book */
  492. if (isset($address_book_global_filename)) {
  493. include_once(SM_PATH . 'functions/abook_global_file.php');
  494. }
  495. /* Only load database backend if database is configured */
  496. if((isset($addrbook_dsn) && !empty($addrbook_dsn)) ||
  497. (isset($addrbook_global_dsn) && !empty($addrbook_global_dsn)) ) {
  498. include_once(SM_PATH . 'functions/abook_database.php');
  499. }
  500. /*
  501. * hook allows adding different address book classes.
  502. * class must follow address book class coding standards.
  503. *
  504. * see addressbook_backend class and functions/abook_*.php files.
  505. */
  506. do_hook('abook_add_class');
  507. ?>