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