addressbook.php 30 KB

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