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