addressbook.php 15 KB

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