addressbook.php 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546
  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;
  173. if ((isset($addrsrch_fullname)) && ($addrsrch_fullname)) {
  174. $name = ($addrsrch_fullname === 'nickname') ? $row['nickname']
  175. : $row['name'];
  176. return $name . ' <' . trim($row['email']) . '>';
  177. } else {
  178. return trim($row['email']);
  179. }
  180. }
  181. /*
  182. Return a list of addresses matching expression in
  183. all backends of a given type.
  184. */
  185. function search($expression, $bnum = -1) {
  186. $ret = array();
  187. $this->error = '';
  188. /* Search all backends */
  189. if ($bnum == -1) {
  190. $sel = $this->get_backend_list('');
  191. $failed = 0;
  192. for ($i = 0 ; $i < sizeof($sel) ; $i++) {
  193. $backend = &$sel[$i];
  194. $backend->error = '';
  195. $res = $backend->search($expression);
  196. if (is_array($res)) {
  197. $ret = array_merge($ret, $res);
  198. } else {
  199. $this->error .= "<br>\n" . $backend->error;
  200. $failed++;
  201. }
  202. }
  203. /* Only fail if all backends failed */
  204. if( $failed >= sizeof( $sel ) ) {
  205. $ret = FALSE;
  206. }
  207. } else {
  208. /* Search only one backend */
  209. $ret = $this->backends[$bnum]->search($expression);
  210. if (!is_array($ret)) {
  211. $this->error .= "<br>\n" . $this->backends[$bnum]->error;
  212. $ret = FALSE;
  213. }
  214. }
  215. return( $ret );
  216. }
  217. /* Return a sorted search */
  218. function s_search($expression, $bnum = -1) {
  219. $ret = $this->search($expression, $bnum);
  220. if ( is_array( $ret ) ) {
  221. usort($ret, 'addressbook_cmp');
  222. }
  223. return $ret;
  224. }
  225. /*
  226. * Lookup an address by alias. Only possible in
  227. * local backends.
  228. */
  229. function lookup($alias, $bnum = -1) {
  230. $ret = array();
  231. if ($bnum > -1) {
  232. $res = $this->backends[$bnum]->lookup($alias);
  233. if (is_array($res)) {
  234. return $res;
  235. } else {
  236. $this->error = $backend->error;
  237. return false;
  238. }
  239. }
  240. $sel = $this->get_backend_list('local');
  241. for ($i = 0 ; $i < sizeof($sel) ; $i++) {
  242. $backend = &$sel[$i];
  243. $backend->error = '';
  244. $res = $backend->lookup($alias);
  245. if (is_array($res)) {
  246. if(!empty($res))
  247. return $res;
  248. } else {
  249. $this->error = $backend->error;
  250. return false;
  251. }
  252. }
  253. return $ret;
  254. }
  255. /* Return all addresses */
  256. function list_addr($bnum = -1) {
  257. $ret = array();
  258. if ($bnum == -1) {
  259. $sel = $this->get_backend_list('local');
  260. } else {
  261. $sel = array(0 => &$this->backends[$bnum]);
  262. }
  263. for ($i = 0 ; $i < sizeof($sel) ; $i++) {
  264. $backend = &$sel[$i];
  265. $backend->error = '';
  266. $res = $backend->list_addr();
  267. if (is_array($res)) {
  268. $ret = array_merge($ret, $res);
  269. } else {
  270. $this->error = $backend->error;
  271. return false;
  272. }
  273. }
  274. return $ret;
  275. }
  276. /*
  277. * Create a new address from $userdata, in backend $bnum.
  278. * Return the backend number that the/ address was added
  279. * to, or false if it failed.
  280. */
  281. function add($userdata, $bnum) {
  282. /* Validate data */
  283. if (!is_array($userdata)) {
  284. $this->error = _("Invalid input data");
  285. return false;
  286. }
  287. if (empty($userdata['firstname']) && empty($userdata['lastname'])) {
  288. $this->error = _("Name is missing");
  289. return false;
  290. }
  291. if (empty($userdata['email'])) {
  292. $this->error = _("E-mail address is missing");
  293. return false;
  294. }
  295. if (empty($userdata['nickname'])) {
  296. $userdata['nickname'] = $userdata['email'];
  297. }
  298. if (eregi('[ \\:\\|\\#\\"\\!]', $userdata['nickname'])) {
  299. $this->error = _("Nickname contains illegal characters");
  300. return false;
  301. }
  302. /* Check that specified backend accept new entries */
  303. if (!$this->backends[$bnum]->writeable) {
  304. $this->error = _("Addressbook is read-only");
  305. return false;
  306. }
  307. /* Add address to backend */
  308. $res = $this->backends[$bnum]->add($userdata);
  309. if ($res) {
  310. return $bnum;
  311. } else {
  312. $this->error = $this->backends[$bnum]->error;
  313. return false;
  314. }
  315. return false; // Not reached
  316. } /* end of add() */
  317. /*
  318. * Remove the user identified by $alias from backend $bnum
  319. * If $alias is an array, all users in the array are removed.
  320. */
  321. function remove($alias, $bnum) {
  322. /* Check input */
  323. if (empty($alias)) {
  324. return true;
  325. }
  326. /* Convert string to single element array */
  327. if (!is_array($alias)) {
  328. $alias = array(0 => $alias);
  329. }
  330. /* Check that specified backend is writable */
  331. if (!$this->backends[$bnum]->writeable) {
  332. $this->error = _("Addressbook is read-only");
  333. return false;
  334. }
  335. /* Remove user from backend */
  336. $res = $this->backends[$bnum]->remove($alias);
  337. if ($res) {
  338. return $bnum;
  339. } else {
  340. $this->error = $this->backends[$bnum]->error;
  341. return false;
  342. }
  343. return FALSE; /* Not reached */
  344. } /* end of remove() */
  345. /*
  346. * Remove the user identified by $alias from backend $bnum
  347. * If $alias is an array, all users in the array are removed.
  348. */
  349. function modify($alias, $userdata, $bnum) {
  350. /* Check input */
  351. if (empty($alias) || !is_string($alias)) {
  352. return true;
  353. }
  354. /* Validate data */
  355. if(!is_array($userdata)) {
  356. $this->error = _("Invalid input data");
  357. return false;
  358. }
  359. if (empty($userdata['firstname']) && empty($userdata['lastname'])) {
  360. $this->error = _("Name is missing");
  361. return false;
  362. }
  363. if (empty($userdata['email'])) {
  364. $this->error = _("E-mail address is missing");
  365. return false;
  366. }
  367. if (eregi('[\\: \\|\\#"\\!]', $userdata['nickname'])) {
  368. $this->error = _("Nickname contains illegal characters");
  369. return false;
  370. }
  371. if (empty($userdata['nickname'])) {
  372. $userdata['nickname'] = $userdata['email'];
  373. }
  374. /* Check that specified backend is writable */
  375. if (!$this->backends[$bnum]->writeable) {
  376. $this->error = _("Addressbook is read-only");;
  377. return false;
  378. }
  379. /* Modify user in backend */
  380. $res = $this->backends[$bnum]->modify($alias, $userdata);
  381. if ($res) {
  382. return $bnum;
  383. } else {
  384. $this->error = $this->backends[$bnum]->error;
  385. return false;
  386. }
  387. return FALSE; /* Not reached */
  388. } /* end of modify() */
  389. } /* End of class Addressbook */
  390. /*
  391. * Generic backend that all other backends extend
  392. */
  393. class addressbook_backend {
  394. /* Variables that all backends must provide. */
  395. var $btype = 'dummy';
  396. var $bname = 'dummy';
  397. var $sname = 'Dummy backend';
  398. /*
  399. * Variables common for all backends, but that
  400. * should not be changed by the backends.
  401. */
  402. var $bnum = -1;
  403. var $error = '';
  404. var $writeable = false;
  405. function set_error($string) {
  406. $this->error = '[' . $this->sname . '] ' . $string;
  407. return false;
  408. }
  409. /* ========================== Public ======================== */
  410. function search($expression) {
  411. $this->set_error('search not implemented');
  412. return false;
  413. }
  414. function lookup($alias) {
  415. $this->set_error('lookup not implemented');
  416. return false;
  417. }
  418. function list_addr() {
  419. $this->set_error('list_addr not implemented');
  420. return false;
  421. }
  422. function add($userdata) {
  423. $this->set_error('add not implemented');
  424. return false;
  425. }
  426. function remove($alias) {
  427. $this->set_error('delete not implemented');
  428. return false;
  429. }
  430. function modify($alias, $newuserdata) {
  431. $this->set_error('modify not implemented');
  432. return false;
  433. }
  434. }
  435. /* Sort array by the key "name" */
  436. function alistcmp($a,$b) {
  437. if ($a['backend'] > $b['backend']) {
  438. return 1;
  439. } else {
  440. if ($a['backend'] < $b['backend']) {
  441. return -1;
  442. }
  443. }
  444. return (strtolower($a['name']) > strtolower($b['name'])) ? 1 : -1;
  445. }
  446. ?>