addressbook.php 30 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955
  1. <?php
  2. /**
  3. * functions/addressbook.php - Functions and classes for the addressbook system
  4. *
  5. * Functions require SM_PATH and support of forms.php functions
  6. *
  7. * @copyright &copy; 1999-2007 The SquirrelMail Project Team
  8. * @license http://opensource.org/licenses/gpl-license.php GNU Public License
  9. * @version $Id$
  10. * @package squirrelmail
  11. * @subpackage addressbook
  12. */
  13. /**
  14. * Create and initialize an addressbook object.
  15. * @param boolean $showerr display any address book init errors. html page header
  16. * must be created before calling addressbook_init() with $showerr enabled.
  17. * @param boolean $onlylocal enable only local address book backends. Should
  18. * be used when code does not need access to remote backends. Backends
  19. * that provide read only address books with limited listing options can be
  20. * tagged as remote.
  21. * @return object address book object.
  22. */
  23. function addressbook_init($showerr = true, $onlylocal = false) {
  24. global $data_dir, $username, $ldap_server, $address_book_global_filename;
  25. global $addrbook_dsn, $addrbook_table;
  26. global $abook_global_file, $abook_global_file_writeable, $abook_global_file_listing;
  27. global $addrbook_global_dsn, $addrbook_global_table, $addrbook_global_writeable, $addrbook_global_listing;
  28. global $abook_file_line_length;
  29. /* Create a new addressbook object */
  30. $abook = new AddressBook;
  31. /* Create empty error message */
  32. $abook_init_error='';
  33. /*
  34. Always add a local backend. We use *either* file-based *or* a
  35. database addressbook. If $addrbook_dsn is set, the database
  36. backend is used. If not, addressbooks are stores in files.
  37. */
  38. if (isset($addrbook_dsn) && !empty($addrbook_dsn)) {
  39. /* Database */
  40. if (!isset($addrbook_table) || empty($addrbook_table)) {
  41. $addrbook_table = 'address';
  42. }
  43. $r = $abook->add_backend('database', Array('dsn' => $addrbook_dsn,
  44. 'owner' => $username,
  45. 'table' => $addrbook_table));
  46. if (!$r && $showerr) {
  47. $abook_init_error.=_("Error initializing address book database.") . "\n" . $abook->error;
  48. }
  49. } else {
  50. /* File */
  51. $filename = getHashedFile($username, $data_dir, "$username.abook");
  52. $r = $abook->add_backend('local_file', Array('filename' => $filename,
  53. 'line_length' => $abook_file_line_length,
  54. 'create' => true));
  55. if(!$r && $showerr) {
  56. // no need to use $abook->error, because message explains error.
  57. $abook_init_error.=sprintf( _("Error opening file %s"), $filename );
  58. }
  59. }
  60. /* Global file based addressbook */
  61. if (isset($abook_global_file) &&
  62. isset($abook_global_file_writeable) &&
  63. isset($abook_global_file_listing) &&
  64. trim($abook_global_file)!=''){
  65. // Detect place of address book
  66. if (! preg_match("/[\/\\\]/",$abook_global_file)) {
  67. /* no path chars, address book stored in data directory
  68. * make sure that there is a slash between data directory
  69. * and address book file name
  70. */
  71. $abook_global_filename=$data_dir
  72. . ((substr($data_dir, -1) != '/') ? '/' : '')
  73. . $abook_global_file;
  74. } elseif (preg_match("/^\/|\w:/",$abook_global_file)) {
  75. // full path is set in options (starts with slash or x:)
  76. $abook_global_filename=$abook_global_file;
  77. } else {
  78. $abook_global_filename=SM_PATH . $abook_global_file;
  79. }
  80. $r = $abook->add_backend('local_file',array('filename'=>$abook_global_filename,
  81. 'name' => _("Global address book"),
  82. 'detect_writeable' => false,
  83. 'line_length' => $abook_file_line_length,
  84. 'writeable'=> $abook_global_file_writeable,
  85. 'listing' => $abook_global_file_listing));
  86. /* global abook init error is not fatal. add error message and continue */
  87. if (!$r && $showerr) {
  88. if ($abook_init_error!='') $abook_init_error.="\n";
  89. $abook_init_error.=_("Error initializing global address book.") . "\n" . $abook->error;
  90. }
  91. }
  92. /* Load global addressbook from SQL if configured */
  93. if (isset($addrbook_global_dsn) && !empty($addrbook_global_dsn)) {
  94. /* Database configured */
  95. if (!isset($addrbook_global_table) || empty($addrbook_global_table)) {
  96. $addrbook_global_table = 'global_abook';
  97. }
  98. $r = $abook->add_backend('database',
  99. Array('dsn' => $addrbook_global_dsn,
  100. 'owner' => 'global',
  101. 'name' => _("Global address book"),
  102. 'writeable' => $addrbook_global_writeable,
  103. 'listing' => $addrbook_global_listing,
  104. 'table' => $addrbook_global_table));
  105. /* global abook init error is not fatal. add error message and continue */
  106. if (!$r && $showerr) {
  107. if ($abook_init_error!='') $abook_init_error.="\n";
  108. $abook_init_error.=_("Error initializing global address book.") . "\n" . $abook->error;
  109. }
  110. }
  111. /*
  112. * hook allows to include different address book backends.
  113. * plugins should extract $abook and $r from arguments
  114. * and use same add_backend commands as above functions.
  115. * Since 1.5.2 hook sends third ($onlylocal) argument to address book
  116. * plugins in order to allow detection of local address book init.
  117. * @since 1.5.1 and 1.4.5
  118. * Since 1.5.2, the plugin arguments are passed inside an array
  119. * and by reference, so plugins hooking in here need to accept arguments
  120. * in an array and change those values as needed instead of returning
  121. * the changed values.
  122. */
  123. do_hook('abook_init', $temp=array(&$abook, &$r, &$onlylocal));
  124. if (!$r && $showerr) {
  125. if ($abook_init_error!='') $abook_init_error.="\n";
  126. $abook_init_error.=_("Error initializing other address books.") . "\n" . $abook->error;
  127. }
  128. /* Load configured LDAP servers (if PHP has LDAP support) */
  129. if (isset($ldap_server) && is_array($ldap_server)) {
  130. reset($ldap_server);
  131. while (list($undef,$param) = each($ldap_server)) {
  132. if (!is_array($param))
  133. continue;
  134. /* if onlylocal is true, we only add writeable ldap servers */
  135. if ($onlylocal && (!isset($param['writeable']) || $param['writeable'] != true))
  136. continue;
  137. $r = $abook->add_backend('ldap_server', $param);
  138. if (!$r && $showerr) {
  139. if ($abook_init_error!='') $abook_init_error.="\n";
  140. $abook_init_error.=sprintf(_("Error initializing LDAP server %s:"), $param['host'])."\n";
  141. $abook_init_error.= $abook->error;
  142. }
  143. }
  144. } // end of ldap server init
  145. /**
  146. * display address book init errors.
  147. */
  148. if ($abook_init_error!='' && $showerr) {
  149. error_box(nl2br(htmlspecialchars($abook_init_error)));
  150. }
  151. /* Return the initialized object */
  152. return $abook;
  153. }
  154. /**
  155. * Display the "new address" form
  156. *
  157. * Form is not closed and you must add closing form tag.
  158. * @since 1.5.1
  159. * @param string $form_url form action url
  160. * @param string $name form name
  161. * @param string $title form title
  162. * @param string $button form button name
  163. * @param array $defdata values of form fields
  164. */
  165. function abook_create_form($form_url,$name,$title,$button,$defdata=array()) {
  166. global $oTemplate;
  167. echo addForm($form_url, 'post', 'f_add');
  168. if ($button == _("Update address")) {
  169. $edit = true;
  170. $backends = NULL;
  171. } else {
  172. $edit = false;
  173. $backends = getWritableBackends();
  174. }
  175. $fields = array (
  176. 'nickname' => 'NickName',
  177. 'firstname' => 'FirstName',
  178. 'lastname' => 'LastName',
  179. 'email' => 'Email',
  180. 'label' => 'Info',
  181. );
  182. $values = array();
  183. foreach ($fields as $sqm=>$template) {
  184. $values[$template] = isset($defdata[$sqm]) ? $defdata[$sqm] : '';
  185. }
  186. $oTemplate->assign('writable_backends', $backends);
  187. $oTemplate->assign('values', $values);
  188. $oTemplate->assign('edit', $edit);
  189. $oTemplate->display('addrbook_addedit.tpl');
  190. }
  191. /**
  192. * Had to move this function outside of the Addressbook Class
  193. * PHP 4.0.4 Seemed to be having problems with inline functions.
  194. * Note: this can return now since we don't support 4.0.4 anymore.
  195. */
  196. function addressbook_cmp($a,$b) {
  197. if($a['backend'] > $b['backend']) {
  198. return 1;
  199. } else if($a['backend'] < $b['backend']) {
  200. return -1;
  201. }
  202. return (strtolower($a['name']) > strtolower($b['name'])) ? 1 : -1;
  203. }
  204. /**
  205. * Retrieve a list of writable backends
  206. * @since 1.5.2
  207. */
  208. function getWritableBackends () {
  209. global $abook;
  210. $write = array();
  211. $backends = $abook->get_backend_list();
  212. while (list($undef,$v) = each($backends)) {
  213. if ($v->writeable) {
  214. $write[$v->bnum]=$v->sname;
  215. }
  216. }
  217. return $write;
  218. }
  219. /**
  220. * Sort array by the key "name"
  221. */
  222. function alistcmp($a,$b) {
  223. $abook_sort_order=get_abook_sort();
  224. switch ($abook_sort_order) {
  225. case 0:
  226. case 1:
  227. $abook_sort='nickname';
  228. break;
  229. case 4:
  230. case 5:
  231. $abook_sort='email';
  232. break;
  233. case 6:
  234. case 7:
  235. $abook_sort='label';
  236. break;
  237. case 2:
  238. case 3:
  239. case 8:
  240. default:
  241. $abook_sort='name';
  242. }
  243. if ($a['backend'] > $b['backend']) {
  244. return 1;
  245. } else {
  246. if ($a['backend'] < $b['backend']) {
  247. return -1;
  248. }
  249. }
  250. if( (($abook_sort_order+2) % 2) == 1) {
  251. return (strtolower($a[$abook_sort]) < strtolower($b[$abook_sort])) ? 1 : -1;
  252. } else {
  253. return (strtolower($a[$abook_sort]) > strtolower($b[$abook_sort])) ? 1 : -1;
  254. }
  255. }
  256. /**
  257. * Address book sorting options
  258. *
  259. * returns address book sorting order
  260. * @return integer book sorting options order
  261. */
  262. function get_abook_sort() {
  263. global $data_dir, $username;
  264. /* get sorting order */
  265. if(sqgetGlobalVar('abook_sort_order', $temp, SQ_GET)) {
  266. $abook_sort_order = (int) $temp;
  267. if ($abook_sort_order < 0 or $abook_sort_order > 8)
  268. $abook_sort_order=8;
  269. setPref($data_dir, $username, 'abook_sort_order', $abook_sort_order);
  270. } else {
  271. /* get previous sorting options. default to unsorted */
  272. $abook_sort_order = getPref($data_dir, $username, 'abook_sort_order', 8);
  273. }
  274. return $abook_sort_order;
  275. }
  276. /**
  277. * This function shows the address book sort button.
  278. *
  279. * @param integer $abook_sort_order current sort value
  280. * @param string $alt_tag alt tag value (string visible to text only browsers)
  281. * @param integer $Down sort value when list is sorted ascending
  282. * @param integer $Up sort value when list is sorted descending
  283. * @return string html code with sorting images and urls
  284. */
  285. function show_abook_sort_button($abook_sort_order, $alt_tag, $Down, $Up ) {
  286. global $form_url, $icon_theme_path;
  287. /* Figure out which image we want to use. */
  288. if ($abook_sort_order != $Up && $abook_sort_order != $Down) {
  289. $img = 'sort_none.png';
  290. $text_icon = '&#9723;'; // U+25FB WHITE MEDIUM SQUARE
  291. $which = $Up;
  292. } elseif ($abook_sort_order == $Up) {
  293. $img = 'up_pointer.png';
  294. $text_icon = '&#8679;'; // U+21E7 UPWARDS WHITE ARROW
  295. $which = $Down;
  296. } else {
  297. $img = 'down_pointer.png';
  298. $text_icon = '&#8681;'; // U+21E9 DOWNWARDS WHITE ARROW
  299. $which = 8;
  300. }
  301. /* Now that we have everything figured out, show the actual button. */
  302. return '&nbsp;<a href="' . $form_url .'?abook_sort_order=' . $which .
  303. '" style="text-decoration:none" title="'.$alt_tag.'">' .
  304. getIcon($icon_theme_path, $img, $text_icon, $alt_tag) .
  305. '</a>';
  306. }
  307. /**
  308. * This is the main address book class that connect all the
  309. * backends and provide services to the functions above.
  310. * @package squirrelmail
  311. * @subpackage addressbook
  312. */
  313. class AddressBook {
  314. /**
  315. * Enabled address book backends
  316. * @var array
  317. */
  318. var $backends = array();
  319. /**
  320. * Number of enabled backends
  321. * @var integer
  322. */
  323. var $numbackends = 0;
  324. /**
  325. * Error messages
  326. * @var string
  327. */
  328. var $error = '';
  329. /**
  330. * id of backend with personal address book
  331. * @var integer
  332. */
  333. var $localbackend = 0;
  334. /**
  335. * Name of backend with personal address book
  336. * @var string
  337. */
  338. var $localbackendname = '';
  339. /**
  340. * Controls use of 'extra' field
  341. *
  342. * Extra field can be used to add link to form, which allows
  343. * to modify all fields supported by backend. This is the only field
  344. * that is not sanitized with htmlspecialchars. Backends MUST make
  345. * sure that field data is sanitized and displayed correctly inside
  346. * table cell. Use of html formating in other address book fields is
  347. * not allowed. Backends that don't return 'extra' row in address book
  348. * data should not modify this object property.
  349. * @var boolean
  350. * @since 1.5.1
  351. */
  352. var $add_extra_field = false;
  353. /**
  354. * Constructor function.
  355. */
  356. function AddressBook() {
  357. $this->localbackendname = _("Personal address book");
  358. }
  359. /**
  360. * Return an array of backends of a given type,
  361. * or all backends if no type is specified.
  362. * @param string $type backend type
  363. * @return array list of backends
  364. */
  365. function get_backend_list($type = '') {
  366. $ret = array();
  367. for ($i = 1 ; $i <= $this->numbackends ; $i++) {
  368. if (empty($type) || $type == $this->backends[$i]->btype) {
  369. $ret[] = &$this->backends[$i];
  370. }
  371. }
  372. return $ret;
  373. }
  374. /* ========================== Public ======================== */
  375. /**
  376. * Add a new backend.
  377. *
  378. * @param string $backend backend name (without the abook_ prefix)
  379. * @param mixed optional variable that is passed to the backend constructor.
  380. * See each of the backend classes for valid parameters
  381. * @return integer number of backends
  382. */
  383. function add_backend($backend, $param = '') {
  384. static $backend_classes;
  385. if (!isset($backend_classes)) {
  386. $backend_classes = array();
  387. }
  388. if (!isset($backend_classes[$backend])) {
  389. /**
  390. * Support backend provided by plugins. Plugin function must
  391. * return an associative array with as key the backend name ($backend)
  392. * and as value the file including the path containing the backend class.
  393. * i.e.: $aBackend = array('backend_template' => SM_PATH . 'plugins/abook_backend_template/functions.php')
  394. *
  395. * NB: Because the backend files are included from within this function they DO NOT have access to
  396. * vars in the global scope. This function is the global scope for the included backend !!!
  397. */
  398. global $null;
  399. $aBackend = do_hook('abook_add_class', $null);
  400. if (isset($aBackend) && is_array($aBackend) && isset($aBackend[$backend])) {
  401. require_once($aBackend[$backend]);
  402. } else {
  403. require_once(SM_PATH . 'functions/abook_'.$backend.'.php');
  404. }
  405. $backend_classes[$backend] = true;
  406. }
  407. $backend_name = 'abook_' . $backend;
  408. $newback = new $backend_name($param);
  409. //eval('$newback = new ' . $backend_name . '($param);');
  410. if(!empty($newback->error)) {
  411. $this->error = $newback->error;
  412. return false;
  413. }
  414. $this->numbackends++;
  415. $newback->bnum = $this->numbackends;
  416. $this->backends[$this->numbackends] = $newback;
  417. /* Store ID of first local backend added */
  418. if ($this->localbackend == 0 && $newback->btype == 'local') {
  419. $this->localbackend = $this->numbackends;
  420. $this->localbackendname = $newback->sname;
  421. }
  422. return $this->numbackends;
  423. }
  424. /**
  425. * create string with name and email address
  426. *
  427. * This function takes a $row array as returned by the addressbook
  428. * search and returns an e-mail address with the full name or
  429. * nickname optionally prepended.
  430. * @param array $row address book entry
  431. * @return string email address with real name prepended
  432. */
  433. function full_address($row) {
  434. global $addrsrch_fullname, $data_dir, $username;
  435. $prefix = getPref($data_dir, $username, 'addrsrch_fullname');
  436. if (($prefix != "" || (isset($addrsrch_fullname) &&
  437. $prefix == $addrsrch_fullname)) && $prefix != 'noprefix') {
  438. $name = ($prefix == 'nickname' ? $row['nickname'] : $row['name']);
  439. return $name . ' <' . trim($row['email']) . '>';
  440. } else {
  441. return trim($row['email']);
  442. }
  443. }
  444. /**
  445. * Search for entries in address books
  446. *
  447. * Return a list of addresses matching expression in
  448. * all backends of a given type.
  449. * @param string $expression search expression
  450. * @param integer $bnum backend number. default to search in all backends
  451. * @return array search results
  452. */
  453. function search($expression, $bnum = -1) {
  454. $ret = array();
  455. $this->error = '';
  456. /* Search all backends */
  457. if ($bnum == -1) {
  458. $sel = $this->get_backend_list('');
  459. $failed = 0;
  460. for ($i = 0 ; $i < sizeof($sel) ; $i++) {
  461. $backend = &$sel[$i];
  462. $backend->error = '';
  463. $res = $backend->search($expression);
  464. if (is_array($res)) {
  465. $ret = array_merge($ret, $res);
  466. } else {
  467. $this->error .= "\n" . $backend->error;
  468. $failed++;
  469. }
  470. }
  471. /* Only fail if all backends failed */
  472. if( $failed >= sizeof( $sel ) ) {
  473. $ret = FALSE;
  474. }
  475. } elseif (! isset($this->backends[$bnum])) {
  476. /* make sure that backend exists */
  477. $this->error = _("Unknown address book backend");
  478. $ret = false;
  479. } else {
  480. /* Search only one backend */
  481. $ret = $this->backends[$bnum]->search($expression);
  482. if (!is_array($ret)) {
  483. $this->error .= "\n" . $this->backends[$bnum]->error;
  484. $ret = FALSE;
  485. }
  486. }
  487. return( $ret );
  488. }
  489. /**
  490. * Sorted search
  491. * @param string $expression search expression
  492. * @param integer $bnum backend number. default to search in all backends
  493. * @return array search results
  494. */
  495. function s_search($expression, $bnum = -1) {
  496. $ret = $this->search($expression, $bnum);
  497. if ( is_array( $ret ) ) {
  498. usort($ret, 'addressbook_cmp');
  499. }
  500. return $ret;
  501. }
  502. /**
  503. * Lookup an address by alias.
  504. * Only possible in local backends.
  505. * @param string $alias
  506. * @param integer backend number
  507. * @return array lookup results. False, if not found.
  508. */
  509. function lookup($alias, $bnum = -1) {
  510. $ret = array();
  511. if ($bnum > -1) {
  512. if (!isset($this->backends[$bnum])) {
  513. $this->error = _("Unknown address book backend");
  514. return false;
  515. }
  516. $res = $this->backends[$bnum]->lookup($alias);
  517. if (is_array($res)) {
  518. return $res;
  519. } else {
  520. $this->error = $this->backends[$bnum]->error;
  521. return false;
  522. }
  523. }
  524. $sel = $this->get_backend_list('local');
  525. for ($i = 0 ; $i < sizeof($sel) ; $i++) {
  526. $backend = &$sel[$i];
  527. $backend->error = '';
  528. $res = $backend->lookup($alias);
  529. if (is_array($res)) {
  530. if(!empty($res))
  531. return $res;
  532. } else {
  533. $this->error = $backend->error;
  534. return false;
  535. }
  536. }
  537. return $ret;
  538. }
  539. /**
  540. * Return all addresses
  541. * @param integer $bnum backend number
  542. * @return mixed array with search results or boolean false on error.
  543. */
  544. function list_addr($bnum = -1) {
  545. $ret = array();
  546. if ($bnum == -1) {
  547. $sel = $this->get_backend_list('');
  548. } elseif (! isset($this->backends[$bnum])) {
  549. /* make sure that backend exists */
  550. $this->error = _("Unknown address book backend");
  551. $ret = false;
  552. } else {
  553. $sel = array(0 => &$this->backends[$bnum]);
  554. }
  555. for ($i = 0 ; $i < sizeof($sel) ; $i++) {
  556. $backend = &$sel[$i];
  557. $backend->error = '';
  558. $res = $backend->list_addr();
  559. if (is_array($res)) {
  560. $ret = array_merge($ret, $res);
  561. } else {
  562. $this->error = $backend->error;
  563. return false;
  564. }
  565. }
  566. return $ret;
  567. }
  568. /**
  569. * Create a new address
  570. * @param array $userdata added address record
  571. * @param integer $bnum backend number
  572. * @return integer the backend number that the/ address was added
  573. * to, or false if it failed.
  574. */
  575. function add($userdata, $bnum) {
  576. /* Validate data */
  577. if (!is_array($userdata)) {
  578. $this->error = _("Invalid input data");
  579. return false;
  580. }
  581. if (empty($userdata['firstname']) && empty($userdata['lastname'])) {
  582. $this->error = _("Name is missing");
  583. return false;
  584. }
  585. if (empty($userdata['email'])) {
  586. $this->error = _("E-mail address is missing");
  587. return false;
  588. }
  589. if (empty($userdata['nickname'])) {
  590. $userdata['nickname'] = $userdata['email'];
  591. }
  592. /* Blocks use of space, :, |, #, " and ! in nickname */
  593. if (eregi('[ \\:\\|\\#\\"\\!]', $userdata['nickname'])) {
  594. $this->error = _("Nickname contains illegal characters");
  595. return false;
  596. }
  597. /* make sure that backend exists */
  598. if (! isset($this->backends[$bnum])) {
  599. $this->error = _("Unknown address book backend");
  600. return false;
  601. }
  602. /* Check that specified backend accept new entries */
  603. if (!$this->backends[$bnum]->writeable) {
  604. $this->error = _("Address book is read-only");
  605. return false;
  606. }
  607. /* Add address to backend */
  608. $res = $this->backends[$bnum]->add($userdata);
  609. if ($res) {
  610. return $bnum;
  611. } else {
  612. $this->error = $this->backends[$bnum]->error;
  613. return false;
  614. }
  615. return false; // Not reached
  616. } /* end of add() */
  617. /**
  618. * Remove the entries from address book
  619. * @param mixed $alias entries that have to be removed. Can be string with nickname or array with list of nicknames
  620. * @param integer $bnum backend number
  621. * @return bool true if removed successfully. false if there s an error. $this->error contains error message
  622. */
  623. function remove($alias, $bnum) {
  624. /* Check input */
  625. if (empty($alias)) {
  626. return true;
  627. }
  628. /* Convert string to single element array */
  629. if (!is_array($alias)) {
  630. $alias = array(0 => $alias);
  631. }
  632. /* make sure that backend exists */
  633. if (! isset($this->backends[$bnum])) {
  634. $this->error = _("Unknown address book backend");
  635. return false;
  636. }
  637. /* Check that specified backend is writable */
  638. if (!$this->backends[$bnum]->writeable) {
  639. $this->error = _("Address book is read-only");
  640. return false;
  641. }
  642. /* Remove user from backend */
  643. $res = $this->backends[$bnum]->remove($alias);
  644. if ($res) {
  645. return $bnum;
  646. } else {
  647. $this->error = $this->backends[$bnum]->error;
  648. return false;
  649. }
  650. return FALSE; /* Not reached */
  651. } /* end of remove() */
  652. /**
  653. * Modify entry in address book
  654. * @param string $alias nickname
  655. * @param array $userdata newdata
  656. * @param integer $bnum backend number
  657. */
  658. function modify($alias, $userdata, $bnum) {
  659. /* Check input */
  660. if (empty($alias) || !is_string($alias)) {
  661. return true;
  662. }
  663. /* Validate data */
  664. if(!is_array($userdata)) {
  665. $this->error = _("Invalid input data");
  666. return false;
  667. }
  668. if (empty($userdata['firstname']) && empty($userdata['lastname'])) {
  669. $this->error = _("Name is missing");
  670. return false;
  671. }
  672. if (empty($userdata['email'])) {
  673. $this->error = _("E-mail address is missing");
  674. return false;
  675. }
  676. if (eregi('[\\: \\|\\#"\\!]', $userdata['nickname'])) {
  677. $this->error = _("Nickname contains illegal characters");
  678. return false;
  679. }
  680. if (empty($userdata['nickname'])) {
  681. $userdata['nickname'] = $userdata['email'];
  682. }
  683. /* make sure that backend exists */
  684. if (! isset($this->backends[$bnum])) {
  685. $this->error = _("Unknown address book backend");
  686. return false;
  687. }
  688. /* Check that specified backend is writable */
  689. if (!$this->backends[$bnum]->writeable) {
  690. $this->error = _("Address book is read-only");;
  691. return false;
  692. }
  693. /* Modify user in backend */
  694. $res = $this->backends[$bnum]->modify($alias, $userdata);
  695. if ($res) {
  696. return $bnum;
  697. } else {
  698. $this->error = $this->backends[$bnum]->error;
  699. return false;
  700. }
  701. return FALSE; /* Not reached */
  702. } /* end of modify() */
  703. } /* End of class Addressbook */
  704. /**
  705. * Generic backend that all other backends extend
  706. * @package squirrelmail
  707. * @subpackage addressbook
  708. */
  709. class addressbook_backend {
  710. /* Variables that all backends must provide. */
  711. /**
  712. * Backend type
  713. *
  714. * Can be 'local' or 'remote'
  715. * @var string backend type
  716. */
  717. var $btype = 'dummy';
  718. /**
  719. * Internal backend name
  720. * @var string
  721. */
  722. var $bname = 'dummy';
  723. /**
  724. * Displayed backend name
  725. * @var string
  726. */
  727. var $sname = 'Dummy backend';
  728. /*
  729. * Variables common for all backends, but that
  730. * should not be changed by the backends.
  731. */
  732. /**
  733. * Backend number
  734. * @var integer
  735. */
  736. var $bnum = -1;
  737. /**
  738. * Error messages
  739. * @var string
  740. */
  741. var $error = '';
  742. /**
  743. * Writeable flag
  744. * @var bool
  745. */
  746. var $writeable = false;
  747. /**
  748. * Set error message
  749. * @param string $string error message
  750. * @return bool
  751. */
  752. function set_error($string) {
  753. $this->error = '[' . $this->sname . '] ' . $string;
  754. return false;
  755. }
  756. /* ========================== Public ======================== */
  757. /**
  758. * Search for entries in backend
  759. *
  760. * Working backend should support use of wildcards. * symbol
  761. * should match one or more symbols. ? symbol should match any
  762. * single symbol.
  763. * @param string $expression
  764. * @return bool
  765. */
  766. function search($expression) {
  767. $this->set_error('search is not implemented');
  768. return false;
  769. }
  770. /**
  771. * Find entry in backend by alias
  772. * @param string $alias name used for id
  773. * @return bool
  774. */
  775. function lookup($alias) {
  776. $this->set_error('lookup is not implemented');
  777. return false;
  778. }
  779. /**
  780. * List all entries in backend
  781. *
  782. * Working backend should provide this function or at least
  783. * dummy function that returns empty array.
  784. * @return bool
  785. */
  786. function list_addr() {
  787. $this->set_error('list_addr is not implemented');
  788. return false;
  789. }
  790. /**
  791. * Add entry to backend
  792. * @param array userdata
  793. * @return bool
  794. */
  795. function add($userdata) {
  796. $this->set_error('add is not implemented');
  797. return false;
  798. }
  799. /**
  800. * Remove entry from backend
  801. * @param string $alias name used for id
  802. * @return bool
  803. */
  804. function remove($alias) {
  805. $this->set_error('delete is not implemented');
  806. return false;
  807. }
  808. /**
  809. * Modify entry in backend
  810. * @param string $alias name used for id
  811. * @param array $newuserdata new data
  812. * @return bool
  813. */
  814. function modify($alias, $newuserdata) {
  815. $this->set_error('modify is not implemented');
  816. return false;
  817. }
  818. /**
  819. * Creates full name from given name and surname
  820. *
  821. * Handles name order differences. Function always runs in SquirrelMail gettext domain.
  822. * Plugins don't have to switch domains before calling this function.
  823. * @param string $firstname given name
  824. * @param string $lastname surname
  825. * @return string full name
  826. * @since 1.5.2
  827. */
  828. function fullname($firstname,$lastname) {
  829. /**
  830. * i18n: allows to control fullname layout in address book listing
  831. * first %s is for first name, second %s is for last name.
  832. * Translate it to '%2$s %1$s', if surname must be displayed first in your language.
  833. * Please note that variables can be set to empty string and extra formating
  834. * (for example '%2$s, %1$s' as in 'Smith, John') might break. Use it only for
  835. * setting name and surname order. scripts will remove all prepended and appended
  836. * whitespace.
  837. */
  838. return trim(sprintf(dgettext('squirrelmail',"%s %s"),$firstname,$lastname));
  839. }
  840. }