- Improve recovery when EHLO not supported on legacy SMTP servers
[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 $text_icon = '&#9723;'; // U+25FB WHITE MEDIUM SQUARE
386 $which = $Up;
387 } elseif ($abook_sort_order == $Up) {
388 $img = 'up_pointer.png';
389 $text_icon = '&#8679;'; // U+21E7 UPWARDS WHITE ARROW
390 $which = $Down;
391 } else {
392 $img = 'down_pointer.png';
393 $text_icon = '&#8681;'; // U+21E9 DOWNWARDS WHITE ARROW
394 $which = 8;
395 }
396
397 /* Now that we have everything figured out, show the actual button. */
398 return '&nbsp;<a href="' . $form_url .'?abook_sort_order=' . $which .
399 '" style="text-decoration:none" title="'.$alt_tag.'">' .
400 getIcon($icon_theme_path, $img, $text_icon, $alt_tag) .
401 '</a>';
402 }
403
404
405 /**
406 * This is the main address book class that connect all the
407 * backends and provide services to the functions above.
408 * @package squirrelmail
409 * @subpackage addressbook
410 */
411 class AddressBook {
412
413 /*
414 Cleaning errors from html with htmlspecialchars:
415 Errors from the backend are cleaned up in this class because we not always
416 have control over it when error output is generated in the backend.
417 If this appears to be wrong place then clean it up at the source (the backend)
418 */
419
420 /**
421 * Enabled address book backends
422 * @var array
423 */
424 var $backends = array();
425 /**
426 * Number of enabled backends
427 * @var integer
428 */
429 var $numbackends = 0;
430 /**
431 * Error messages
432 * @var string
433 */
434 var $error = '';
435 /**
436 * id of backend with personal address book
437 * @var integer
438 */
439 var $localbackend = 0;
440 /**
441 * Name of backend with personal address book
442 * @var string
443 */
444 var $localbackendname = '';
445 /**
446 * Controls use of 'extra' field
447 *
448 * Extra field can be used to add link to form, which allows
449 * to modify all fields supported by backend. This is the only field
450 * that is not sanitized with htmlspecialchars. Backends MUST make
451 * sure that field data is sanitized and displayed correctly inside
452 * table cell. Use of html formating in other address book fields is
453 * not allowed. Backends that don't return 'extra' row in address book
454 * data should not modify this object property.
455 * @var boolean
456 * @since 1.5.1
457 */
458 var $add_extra_field = false;
459
460 /**
461 * Constructor function.
462 */
463 function AddressBook() {
464 $this->localbackendname = _("Personal address book");
465 }
466
467 /**
468 * Return an array of backends of a given type,
469 * or all backends if no type is specified.
470 * @param string $type backend type
471 * @return array list of backends
472 */
473 function get_backend_list($type = '') {
474 $ret = array();
475 for ($i = 1 ; $i <= $this->numbackends ; $i++) {
476 if (empty($type) || $type == $this->backends[$i]->btype) {
477 $ret[] = &$this->backends[$i];
478 }
479 }
480 return $ret;
481 }
482
483
484 /* ========================== Public ======================== */
485
486 /**
487 * Add a new backend.
488 *
489 * @param string $backend backend name (without the abook_ prefix)
490 * @param mixed optional variable that is passed to the backend constructor.
491 * See each of the backend classes for valid parameters
492 * @return integer number of backends
493 */
494 function add_backend($backend, $param = '') {
495 static $backend_classes;
496 if (!isset($backend_classes)) {
497 $backend_classes = array();
498 }
499 if (!isset($backend_classes[$backend])) {
500 /**
501 * Support backend provided by plugins. Plugin function must
502 * return an associative array with as key the backend name ($backend)
503 * and as value the file including the path containing the backend class.
504 * i.e.: $aBackend = array('backend_template' => SM_PATH . 'plugins/abook_backend_template/functions.php')
505 *
506 * NB: Because the backend files are included from within this function they DO NOT have access to
507 * vars in the global scope. This function is the global scope for the included backend !!!
508 */
509 $aBackend = do_hook('abook_add_class');
510 if (isset($aBackend) && is_array($aBackend) && isset($aBackend[$backend])) {
511 require_once($aBackend[$backend]);
512 } else {
513 require_once(SM_PATH . 'functions/abook_'.$backend.'.php');
514 }
515 $backend_classes[$backend] = true;
516 }
517 $backend_name = 'abook_' . $backend;
518 $newback = new $backend_name($param);
519 //eval('$newback = new ' . $backend_name . '($param);');
520 if(!empty($newback->error)) {
521 $this->error = $newback->error;
522 return false;
523 }
524
525 $this->numbackends++;
526
527 $newback->bnum = $this->numbackends;
528 $this->backends[$this->numbackends] = $newback;
529
530 /* Store ID of first local backend added */
531 if ($this->localbackend == 0 && $newback->btype == 'local') {
532 $this->localbackend = $this->numbackends;
533 $this->localbackendname = $newback->sname;
534 }
535
536 return $this->numbackends;
537 }
538
539
540 /**
541 * create string with name and email address
542 *
543 * This function takes a $row array as returned by the addressbook
544 * search and returns an e-mail address with the full name or
545 * nickname optionally prepended.
546 * @param array $row address book entry
547 * @return string email address with real name prepended
548 */
549 function full_address($row) {
550 global $addrsrch_fullname, $data_dir, $username;
551 $prefix = getPref($data_dir, $username, 'addrsrch_fullname');
552 if (($prefix != "" || (isset($addrsrch_fullname) &&
553 $prefix == $addrsrch_fullname)) && $prefix != 'noprefix') {
554 $name = ($prefix == 'nickname' ? $row['nickname'] : $row['name']);
555 return $name . ' <' . trim($row['email']) . '>';
556 } else {
557 return trim($row['email']);
558 }
559 }
560
561 /**
562 * Search for entries in address books
563 *
564 * Return a list of addresses matching expression in
565 * all backends of a given type.
566 * @param string $expression search expression
567 * @param integer $bnum backend number. default to search in all backends
568 * @return array search results
569 */
570 function search($expression, $bnum = -1) {
571 $ret = array();
572 $this->error = '';
573
574 /* Search all backends */
575 if ($bnum == -1) {
576 $sel = $this->get_backend_list('');
577 $failed = 0;
578 for ($i = 0 ; $i < sizeof($sel) ; $i++) {
579 $backend = &$sel[$i];
580 $backend->error = '';
581 $res = $backend->search($expression);
582 if (is_array($res)) {
583 $ret = array_merge($ret, $res);
584 } else {
585 $this->error .= "<br />\n" . htmlspecialchars($backend->error);
586 $failed++;
587 }
588 }
589
590 /* Only fail if all backends failed */
591 if( $failed >= sizeof( $sel ) ) {
592 $ret = FALSE;
593 }
594
595 } else {
596
597 /* Search only one backend */
598
599 $ret = $this->backends[$bnum]->search($expression);
600 if (!is_array($ret)) {
601 $this->error .= "<br />\n" . htmlspecialchars($this->backends[$bnum]->error);
602 $ret = FALSE;
603 }
604 }
605
606 return( $ret );
607 }
608
609
610 /**
611 * Sorted search
612 * @param string $expression search expression
613 * @param integer $bnum backend number. default to search in all backends
614 * @return array search results
615 */
616 function s_search($expression, $bnum = -1) {
617
618 $ret = $this->search($expression, $bnum);
619 if ( is_array( $ret ) ) {
620 usort($ret, 'addressbook_cmp');
621 }
622 return $ret;
623 }
624
625
626 /**
627 * Lookup an address by alias.
628 * Only possible in local backends.
629 * @param string $alias
630 * @param integer backend number
631 * @return array lookup results. False, if not found.
632 */
633 function lookup($alias, $bnum = -1) {
634
635 $ret = array();
636
637 if ($bnum > -1) {
638 $res = $this->backends[$bnum]->lookup($alias);
639 if (is_array($res)) {
640 return $res;
641 } else {
642 $this->error = htmlspecialchars($this->backends[$bnum]->error);
643 return false;
644 }
645 }
646
647 $sel = $this->get_backend_list('local');
648 for ($i = 0 ; $i < sizeof($sel) ; $i++) {
649 $backend = &$sel[$i];
650 $backend->error = '';
651 $res = $backend->lookup($alias);
652 if (is_array($res)) {
653 if(!empty($res))
654 return $res;
655 } else {
656 $this->error = htmlspecialchars($backend->error);
657 return false;
658 }
659 }
660
661 return $ret;
662 }
663
664
665 /**
666 * Return all addresses
667 * @param integer $bnum backend number
668 * @return array search results
669 */
670 function list_addr($bnum = -1) {
671 $ret = array();
672
673 if ($bnum == -1) {
674 $sel = $this->get_backend_list('');
675 } else {
676 $sel = array(0 => &$this->backends[$bnum]);
677 }
678
679 for ($i = 0 ; $i < sizeof($sel) ; $i++) {
680 $backend = &$sel[$i];
681 $backend->error = '';
682 $res = $backend->list_addr();
683 if (is_array($res)) {
684 $ret = array_merge($ret, $res);
685 } else {
686 $this->error = htmlspecialchars($backend->error);
687 return false;
688 }
689 }
690
691 return $ret;
692 }
693
694 /**
695 * Create a new address
696 * @param array $userdata added address record
697 * @param integer $bnum backend number
698 * @return integer the backend number that the/ address was added
699 * to, or false if it failed.
700 */
701 function add($userdata, $bnum) {
702
703 /* Validate data */
704 if (!is_array($userdata)) {
705 $this->error = _("Invalid input data");
706 return false;
707 }
708 if (empty($userdata['firstname']) && empty($userdata['lastname'])) {
709 $this->error = _("Name is missing");
710 return false;
711 }
712 if (empty($userdata['email'])) {
713 $this->error = _("E-mail address is missing");
714 return false;
715 }
716 if (empty($userdata['nickname'])) {
717 $userdata['nickname'] = $userdata['email'];
718 }
719
720 if (eregi('[ \\:\\|\\#\\"\\!]', $userdata['nickname'])) {
721 $this->error = _("Nickname contains illegal characters");
722 return false;
723 }
724
725 /* Check that specified backend accept new entries */
726 if (!$this->backends[$bnum]->writeable) {
727 $this->error = _("Addressbook is read-only");
728 return false;
729 }
730
731 /* Add address to backend */
732 $res = $this->backends[$bnum]->add($userdata);
733 if ($res) {
734 return $bnum;
735 } else {
736 $this->error = htmlspecialchars($this->backends[$bnum]->error);
737 return false;
738 }
739
740 return false; // Not reached
741 } /* end of add() */
742
743
744 /**
745 * Remove the entries from address book
746 * @param mixed $alias entries that have to be removed. Can be string with nickname or array with list of nicknames
747 * @param integer $bnum backend number
748 * @return bool true if removed successfully. false if there s an error. $this->error contains error message
749 */
750 function remove($alias, $bnum) {
751
752 /* Check input */
753 if (empty($alias)) {
754 return true;
755 }
756
757 /* Convert string to single element array */
758 if (!is_array($alias)) {
759 $alias = array(0 => $alias);
760 }
761
762 /* Check that specified backend is writable */
763 if (!$this->backends[$bnum]->writeable) {
764 $this->error = _("Addressbook is read-only");
765 return false;
766 }
767
768 /* Remove user from backend */
769 $res = $this->backends[$bnum]->remove($alias);
770 if ($res) {
771 return $bnum;
772 } else {
773 $this->error = htmlspecialchars($this->backends[$bnum]->error);
774 return false;
775 }
776
777 return FALSE; /* Not reached */
778 } /* end of remove() */
779
780
781 /**
782 * Modify entry in address book
783 * @param string $alias nickname
784 * @param array $userdata newdata
785 * @param integer $bnum backend number
786 */
787 function modify($alias, $userdata, $bnum) {
788
789 /* Check input */
790 if (empty($alias) || !is_string($alias)) {
791 return true;
792 }
793
794 /* Validate data */
795 if(!is_array($userdata)) {
796 $this->error = _("Invalid input data");
797 return false;
798 }
799 if (empty($userdata['firstname']) && empty($userdata['lastname'])) {
800 $this->error = _("Name is missing");
801 return false;
802 }
803 if (empty($userdata['email'])) {
804 $this->error = _("E-mail address is missing");
805 return false;
806 }
807
808 if (eregi('[\\: \\|\\#"\\!]', $userdata['nickname'])) {
809 $this->error = _("Nickname contains illegal characters");
810 return false;
811 }
812
813 if (empty($userdata['nickname'])) {
814 $userdata['nickname'] = $userdata['email'];
815 }
816
817 /* Check that specified backend is writable */
818 if (!$this->backends[$bnum]->writeable) {
819 $this->error = _("Addressbook is read-only");;
820 return false;
821 }
822
823 /* Modify user in backend */
824 $res = $this->backends[$bnum]->modify($alias, $userdata);
825 if ($res) {
826 return $bnum;
827 } else {
828 $this->error = htmlspecialchars($this->backends[$bnum]->error);
829 return false;
830 }
831
832 return FALSE; /* Not reached */
833 } /* end of modify() */
834
835
836 } /* End of class Addressbook */
837
838 /**
839 * Generic backend that all other backends extend
840 * @package squirrelmail
841 * @subpackage addressbook
842 */
843 class addressbook_backend {
844
845 /* Variables that all backends must provide. */
846 /**
847 * Backend type
848 *
849 * Can be 'local' or 'remote'
850 * @var string backend type
851 */
852 var $btype = 'dummy';
853 /**
854 * Internal backend name
855 * @var string
856 */
857 var $bname = 'dummy';
858 /**
859 * Displayed backend name
860 * @var string
861 */
862 var $sname = 'Dummy backend';
863
864 /*
865 * Variables common for all backends, but that
866 * should not be changed by the backends.
867 */
868 /**
869 * Backend number
870 * @var integer
871 */
872 var $bnum = -1;
873 /**
874 * Error messages
875 * @var string
876 */
877 var $error = '';
878 /**
879 * Writeable flag
880 * @var bool
881 */
882 var $writeable = false;
883
884 /**
885 * Set error message
886 * @param string $string error message
887 * @return bool
888 */
889 function set_error($string) {
890 $this->error = '[' . $this->sname . '] ' . $string;
891 return false;
892 }
893
894
895 /* ========================== Public ======================== */
896
897 /**
898 * Search for entries in backend
899 *
900 * Working backend should support use of wildcards. * symbol
901 * should match one or more symbols. ? symbol should match any
902 * single symbol.
903 * @param string $expression
904 * @return bool
905 */
906 function search($expression) {
907 $this->set_error('search not implemented');
908 return false;
909 }
910
911 /**
912 * Find entry in backend by alias
913 * @param string $alias name used for id
914 * @return bool
915 */
916 function lookup($alias) {
917 $this->set_error('lookup not implemented');
918 return false;
919 }
920
921 /**
922 * List all entries in backend
923 *
924 * Working backend should provide this function or at least
925 * dummy function that returns empty array.
926 * @return bool
927 */
928 function list_addr() {
929 $this->set_error('list_addr not implemented');
930 return false;
931 }
932
933 /**
934 * Add entry to backend
935 * @param array userdata
936 * @return bool
937 */
938 function add($userdata) {
939 $this->set_error('add not implemented');
940 return false;
941 }
942
943 /**
944 * Remove entry from backend
945 * @param string $alias name used for id
946 * @return bool
947 */
948 function remove($alias) {
949 $this->set_error('delete not implemented');
950 return false;
951 }
952
953 /**
954 * Modify entry in backend
955 * @param string $alias name used for id
956 * @param array $newuserdata new data
957 * @return bool
958 */
959 function modify($alias, $newuserdata) {
960 $this->set_error('modify not implemented');
961 return false;
962 }
963 }
964 ?>