addressbook.php 31 KB

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