addressbook.php 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548
  1. <?php
  2. /**
  3. * addressbook.php
  4. *
  5. * Copyright (c) 1999-2002 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. * $Id$
  11. */
  12. /*
  13. This is the path to the global site-wide addressbook.
  14. It looks and feels just like a user's .abook file
  15. If this is in the data directory, use "$data_dir/global.abook"
  16. If not, specify the path as though it was accessed from the
  17. src/ directory ("../global.abook" -> in main directory)
  18. If you don't want a global site-wide addressbook, comment these
  19. two lines out. (They are disabled by default.)
  20. The global addressbook is unmodifiable by anyone. You must actually
  21. use a shell script or whatnot to modify the contents.
  22. global $data_dir;
  23. $address_book_global_filename = "$data_dir/global.abook";
  24. Include backends here.
  25. */
  26. require_once('../functions/abook_local_file.php');
  27. require_once('../functions/abook_ldap_server.php');
  28. global $addrbook_dsn;
  29. /* Use this if you wanna have a global address book */
  30. if (isset($address_book_global_filename)) {
  31. include_once('../functions/abook_global_file.php');
  32. }
  33. /* Only load database backend if database is configured */
  34. if(isset($addrbook_dsn) && !empty($addrbook_dsn)) {
  35. include_once('../functions/abook_database.php');
  36. }
  37. /*
  38. Create and initialize an addressbook object.
  39. Returns the created object
  40. */
  41. function addressbook_init($showerr = true, $onlylocal = false) {
  42. global $data_dir, $username, $ldap_server, $address_book_global_filename;
  43. global $addrbook_dsn, $addrbook_table;
  44. /* Create a new addressbook object */
  45. $abook = new AddressBook;
  46. /*
  47. Always add a local backend. We use *either* file-based *or* a
  48. database addressbook. If $addrbook_dsn is set, the database
  49. backend is used. If not, addressbooks are stores in files.
  50. */
  51. if (isset($addrbook_dsn) && !empty($addrbook_dsn)) {
  52. /* Database */
  53. if (!isset($addrbook_table) || empty($addrbook_table)) {
  54. $addrbook_table = 'address';
  55. }
  56. $r = $abook->add_backend('database', Array('dsn' => $addrbook_dsn,
  57. 'owner' => $username,
  58. 'table' => $addrbook_table));
  59. if (!$r && $showerr) {
  60. echo _("Error initializing addressbook database.");
  61. exit;
  62. }
  63. } else {
  64. /* File */
  65. $filename = getHashedFile($username, $data_dir, "$username.abook");
  66. $r = $abook->add_backend('local_file', Array('filename' => $filename,
  67. 'create' => true));
  68. if(!$r && $showerr) {
  69. printf( _("Error opening file %s"), $filename );
  70. exit;
  71. }
  72. }
  73. /* This would be for the global addressbook */
  74. if (isset($address_book_global_filename)) {
  75. $r = $abook->add_backend('global_file');
  76. if (!$r && $showerr) {
  77. echo _("Error initializing global addressbook.");
  78. exit;
  79. }
  80. }
  81. if ($onlylocal) {
  82. return $abook;
  83. }
  84. /* Load configured LDAP servers (if PHP has LDAP support) */
  85. if (isset($ldap_server) && is_array($ldap_server) && function_exists('ldap_connect')) {
  86. reset($ldap_server);
  87. while (list($undef,$param) = each($ldap_server)) {
  88. if (is_array($param)) {
  89. $r = $abook->add_backend('ldap_server', $param);
  90. if (!$r && $showerr) {
  91. printf( '&nbsp;' . _("Error initializing LDAP server %s:") .
  92. "<BR>\n", $param['host']);
  93. echo '&nbsp;' . $abook->error;
  94. exit;
  95. }
  96. }
  97. }
  98. }
  99. /* Return the initialized object */
  100. return $abook;
  101. }
  102. /*
  103. * Had to move this function outside of the Addressbook Class
  104. * PHP 4.0.4 Seemed to be having problems with inline functions.
  105. */
  106. function addressbook_cmp($a,$b) {
  107. if($a['backend'] > $b['backend']) {
  108. return 1;
  109. } else if($a['backend'] < $b['backend']) {
  110. return -1;
  111. }
  112. return (strtolower($a['name']) > strtolower($b['name'])) ? 1 : -1;
  113. }
  114. /*
  115. * This is the main address book class that connect all the
  116. * backends and provide services to the functions above.
  117. *
  118. */
  119. class AddressBook {
  120. var $backends = array();
  121. var $numbackends = 0;
  122. var $error = '';
  123. var $localbackend = 0;
  124. var $localbackendname = '';
  125. // Constructor function.
  126. function AddressBook() {
  127. $localbackendname = _("Personal address book");
  128. }
  129. /*
  130. * Return an array of backends of a given type,
  131. * or all backends if no type is specified.
  132. */
  133. function get_backend_list($type = '') {
  134. $ret = array();
  135. for ($i = 1 ; $i <= $this->numbackends ; $i++) {
  136. if (empty($type) || $type == $this->backends[$i]->btype) {
  137. $ret[] = &$this->backends[$i];
  138. }
  139. }
  140. return $ret;
  141. }
  142. /*
  143. ========================== Public ========================
  144. Add a new backend. $backend is the name of a backend
  145. (without the abook_ prefix), and $param is an optional
  146. mixed variable that is passed to the backend constructor.
  147. See each of the backend classes for valid parameters.
  148. */
  149. function add_backend($backend, $param = '') {
  150. $backend_name = 'abook_' . $backend;
  151. eval('$newback = new ' . $backend_name . '($param);');
  152. if(!empty($newback->error)) {
  153. $this->error = $newback->error;
  154. return false;
  155. }
  156. $this->numbackends++;
  157. $newback->bnum = $this->numbackends;
  158. $this->backends[$this->numbackends] = $newback;
  159. /* Store ID of first local backend added */
  160. if ($this->localbackend == 0 && $newback->btype == 'local') {
  161. $this->localbackend = $this->numbackends;
  162. $this->localbackendname = $newback->sname;
  163. }
  164. return $this->numbackends;
  165. }
  166. /*
  167. * This function takes a $row array as returned by the addressbook
  168. * search and returns an e-mail address with the full name or
  169. * nickname optionally prepended.
  170. */
  171. function full_address($row) {
  172. global $addrsrch_fullname, $datadir, $user;
  173. if (($prefix = getPref($datadir, $user, 'addrsrch_fullname') or
  174. isset($addrsrch_fullname) and $prefix = $addrsrch_fullname)
  175. and $prefix !== 'noprefix') {
  176. $name = ($prefix === 'nickname') ? $row['nickname']
  177. : $row['name'];
  178. return $name . ' <' . trim($row['email']) . '>';
  179. } else {
  180. return trim($row['email']);
  181. }
  182. }
  183. /*
  184. Return a list of addresses matching expression in
  185. all backends of a given type.
  186. */
  187. function search($expression, $bnum = -1) {
  188. $ret = array();
  189. $this->error = '';
  190. /* Search all backends */
  191. if ($bnum == -1) {
  192. $sel = $this->get_backend_list('');
  193. $failed = 0;
  194. for ($i = 0 ; $i < sizeof($sel) ; $i++) {
  195. $backend = &$sel[$i];
  196. $backend->error = '';
  197. $res = $backend->search($expression);
  198. if (is_array($res)) {
  199. $ret = array_merge($ret, $res);
  200. } else {
  201. $this->error .= "<br>\n" . $backend->error;
  202. $failed++;
  203. }
  204. }
  205. /* Only fail if all backends failed */
  206. if( $failed >= sizeof( $sel ) ) {
  207. $ret = FALSE;
  208. }
  209. } else {
  210. /* Search only one backend */
  211. $ret = $this->backends[$bnum]->search($expression);
  212. if (!is_array($ret)) {
  213. $this->error .= "<br>\n" . $this->backends[$bnum]->error;
  214. $ret = FALSE;
  215. }
  216. }
  217. return( $ret );
  218. }
  219. /* Return a sorted search */
  220. function s_search($expression, $bnum = -1) {
  221. $ret = $this->search($expression, $bnum);
  222. if ( is_array( $ret ) ) {
  223. usort($ret, 'addressbook_cmp');
  224. }
  225. return $ret;
  226. }
  227. /*
  228. * Lookup an address by alias. Only possible in
  229. * local backends.
  230. */
  231. function lookup($alias, $bnum = -1) {
  232. $ret = array();
  233. if ($bnum > -1) {
  234. $res = $this->backends[$bnum]->lookup($alias);
  235. if (is_array($res)) {
  236. return $res;
  237. } else {
  238. $this->error = $backend->error;
  239. return false;
  240. }
  241. }
  242. $sel = $this->get_backend_list('local');
  243. for ($i = 0 ; $i < sizeof($sel) ; $i++) {
  244. $backend = &$sel[$i];
  245. $backend->error = '';
  246. $res = $backend->lookup($alias);
  247. if (is_array($res)) {
  248. if(!empty($res))
  249. return $res;
  250. } else {
  251. $this->error = $backend->error;
  252. return false;
  253. }
  254. }
  255. return $ret;
  256. }
  257. /* Return all addresses */
  258. function list_addr($bnum = -1) {
  259. $ret = array();
  260. if ($bnum == -1) {
  261. $sel = $this->get_backend_list('local');
  262. } else {
  263. $sel = array(0 => &$this->backends[$bnum]);
  264. }
  265. for ($i = 0 ; $i < sizeof($sel) ; $i++) {
  266. $backend = &$sel[$i];
  267. $backend->error = '';
  268. $res = $backend->list_addr();
  269. if (is_array($res)) {
  270. $ret = array_merge($ret, $res);
  271. } else {
  272. $this->error = $backend->error;
  273. return false;
  274. }
  275. }
  276. return $ret;
  277. }
  278. /*
  279. * Create a new address from $userdata, in backend $bnum.
  280. * Return the backend number that the/ address was added
  281. * to, or false if it failed.
  282. */
  283. function add($userdata, $bnum) {
  284. /* Validate data */
  285. if (!is_array($userdata)) {
  286. $this->error = _("Invalid input data");
  287. return false;
  288. }
  289. if (empty($userdata['firstname']) && empty($userdata['lastname'])) {
  290. $this->error = _("Name is missing");
  291. return false;
  292. }
  293. if (empty($userdata['email'])) {
  294. $this->error = _("E-mail address is missing");
  295. return false;
  296. }
  297. if (empty($userdata['nickname'])) {
  298. $userdata['nickname'] = $userdata['email'];
  299. }
  300. if (eregi('[ \\:\\|\\#\\"\\!]', $userdata['nickname'])) {
  301. $this->error = _("Nickname contains illegal characters");
  302. return false;
  303. }
  304. /* Check that specified backend accept new entries */
  305. if (!$this->backends[$bnum]->writeable) {
  306. $this->error = _("Addressbook is read-only");
  307. return false;
  308. }
  309. /* Add address to backend */
  310. $res = $this->backends[$bnum]->add($userdata);
  311. if ($res) {
  312. return $bnum;
  313. } else {
  314. $this->error = $this->backends[$bnum]->error;
  315. return false;
  316. }
  317. return false; // Not reached
  318. } /* end of add() */
  319. /*
  320. * Remove the user identified by $alias from backend $bnum
  321. * If $alias is an array, all users in the array are removed.
  322. */
  323. function remove($alias, $bnum) {
  324. /* Check input */
  325. if (empty($alias)) {
  326. return true;
  327. }
  328. /* Convert string to single element array */
  329. if (!is_array($alias)) {
  330. $alias = array(0 => $alias);
  331. }
  332. /* Check that specified backend is writable */
  333. if (!$this->backends[$bnum]->writeable) {
  334. $this->error = _("Addressbook is read-only");
  335. return false;
  336. }
  337. /* Remove user from backend */
  338. $res = $this->backends[$bnum]->remove($alias);
  339. if ($res) {
  340. return $bnum;
  341. } else {
  342. $this->error = $this->backends[$bnum]->error;
  343. return false;
  344. }
  345. return FALSE; /* Not reached */
  346. } /* end of remove() */
  347. /*
  348. * Remove the user identified by $alias from backend $bnum
  349. * If $alias is an array, all users in the array are removed.
  350. */
  351. function modify($alias, $userdata, $bnum) {
  352. /* Check input */
  353. if (empty($alias) || !is_string($alias)) {
  354. return true;
  355. }
  356. /* Validate data */
  357. if(!is_array($userdata)) {
  358. $this->error = _("Invalid input data");
  359. return false;
  360. }
  361. if (empty($userdata['firstname']) && empty($userdata['lastname'])) {
  362. $this->error = _("Name is missing");
  363. return false;
  364. }
  365. if (empty($userdata['email'])) {
  366. $this->error = _("E-mail address is missing");
  367. return false;
  368. }
  369. if (eregi('[\\: \\|\\#"\\!]', $userdata['nickname'])) {
  370. $this->error = _("Nickname contains illegal characters");
  371. return false;
  372. }
  373. if (empty($userdata['nickname'])) {
  374. $userdata['nickname'] = $userdata['email'];
  375. }
  376. /* Check that specified backend is writable */
  377. if (!$this->backends[$bnum]->writeable) {
  378. $this->error = _("Addressbook is read-only");;
  379. return false;
  380. }
  381. /* Modify user in backend */
  382. $res = $this->backends[$bnum]->modify($alias, $userdata);
  383. if ($res) {
  384. return $bnum;
  385. } else {
  386. $this->error = $this->backends[$bnum]->error;
  387. return false;
  388. }
  389. return FALSE; /* Not reached */
  390. } /* end of modify() */
  391. } /* End of class Addressbook */
  392. /*
  393. * Generic backend that all other backends extend
  394. */
  395. class addressbook_backend {
  396. /* Variables that all backends must provide. */
  397. var $btype = 'dummy';
  398. var $bname = 'dummy';
  399. var $sname = 'Dummy backend';
  400. /*
  401. * Variables common for all backends, but that
  402. * should not be changed by the backends.
  403. */
  404. var $bnum = -1;
  405. var $error = '';
  406. var $writeable = false;
  407. function set_error($string) {
  408. $this->error = '[' . $this->sname . '] ' . $string;
  409. return false;
  410. }
  411. /* ========================== Public ======================== */
  412. function search($expression) {
  413. $this->set_error('search not implemented');
  414. return false;
  415. }
  416. function lookup($alias) {
  417. $this->set_error('lookup not implemented');
  418. return false;
  419. }
  420. function list_addr() {
  421. $this->set_error('list_addr not implemented');
  422. return false;
  423. }
  424. function add($userdata) {
  425. $this->set_error('add not implemented');
  426. return false;
  427. }
  428. function remove($alias) {
  429. $this->set_error('delete not implemented');
  430. return false;
  431. }
  432. function modify($alias, $newuserdata) {
  433. $this->set_error('modify not implemented');
  434. return false;
  435. }
  436. }
  437. /* Sort array by the key "name" */
  438. function alistcmp($a,$b) {
  439. if ($a['backend'] > $b['backend']) {
  440. return 1;
  441. } else {
  442. if ($a['backend'] < $b['backend']) {
  443. return -1;
  444. }
  445. }
  446. return (strtolower($a['name']) > strtolower($b['name'])) ? 1 : -1;
  447. }
  448. ?>