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