5100704d |
1 | <?php |
2 | |
35586184 |
3 | /** |
4 | * addressbook.php |
5 | * |
76911253 |
6 | * Copyright (c) 1999-2003 The SquirrelMail Project Team |
35586184 |
7 | * Licensed under the GNU GPL. For full terms see the file COPYING. |
8 | * |
81fa4801 |
9 | * Functions and classes for the addressbook system. |
35586184 |
10 | * |
11 | * $Id$ |
d6c32258 |
12 | * @package squirrelmail |
35586184 |
13 | */ |
14 | |
d6c32258 |
15 | /** |
81fa4801 |
16 | This is the path to the global site-wide addressbook. |
17 | It looks and feels just like a user's .abook file |
18 | If this is in the data directory, use "$data_dir/global.abook" |
19 | If not, specify the path as though it was accessed from the |
20 | src/ directory ("../global.abook" -> in main directory) |
21 | |
22 | If you don't want a global site-wide addressbook, comment these |
23 | two lines out. (They are disabled by default.) |
24 | |
25 | The global addressbook is unmodifiable by anyone. You must actually |
26 | use a shell script or whatnot to modify the contents. |
27 | |
91e51743 |
28 | global $data_dir, $address_book_global_filename; |
81fa4801 |
29 | $address_book_global_filename = "$data_dir/global.abook"; |
30 | |
81fa4801 |
31 | */ |
32 | |
81fa4801 |
33 | global $addrbook_dsn; |
34 | |
d6c32258 |
35 | /** |
81fa4801 |
36 | Create and initialize an addressbook object. |
37 | Returns the created object |
38 | */ |
39 | function addressbook_init($showerr = true, $onlylocal = false) { |
40 | global $data_dir, $username, $ldap_server, $address_book_global_filename; |
41 | global $addrbook_dsn, $addrbook_table; |
42 | |
43 | /* Create a new addressbook object */ |
44 | $abook = new AddressBook; |
45 | |
46 | /* |
47 | Always add a local backend. We use *either* file-based *or* a |
48 | database addressbook. If $addrbook_dsn is set, the database |
49 | backend is used. If not, addressbooks are stores in files. |
50 | */ |
51 | if (isset($addrbook_dsn) && !empty($addrbook_dsn)) { |
52 | /* Database */ |
53 | if (!isset($addrbook_table) || empty($addrbook_table)) { |
54 | $addrbook_table = 'address'; |
55 | } |
56 | $r = $abook->add_backend('database', Array('dsn' => $addrbook_dsn, |
57 | 'owner' => $username, |
58 | 'table' => $addrbook_table)); |
59 | if (!$r && $showerr) { |
60 | echo _("Error initializing addressbook database."); |
61 | exit; |
62 | } |
63 | } else { |
64 | /* File */ |
65 | $filename = getHashedFile($username, $data_dir, "$username.abook"); |
66 | $r = $abook->add_backend('local_file', Array('filename' => $filename, |
67 | 'create' => true)); |
68 | if(!$r && $showerr) { |
69 | printf( _("Error opening file %s"), $filename ); |
70 | exit; |
71 | } |
72 | |
73 | } |
74 | |
75 | /* This would be for the global addressbook */ |
76 | if (isset($address_book_global_filename)) { |
77 | $r = $abook->add_backend('global_file'); |
78 | if (!$r && $showerr) { |
79 | echo _("Error initializing global addressbook."); |
80 | exit; |
81 | } |
82 | } |
83 | |
84 | if ($onlylocal) { |
85 | return $abook; |
86 | } |
87 | |
88 | /* Load configured LDAP servers (if PHP has LDAP support) */ |
89 | if (isset($ldap_server) && is_array($ldap_server) && function_exists('ldap_connect')) { |
90 | reset($ldap_server); |
91 | while (list($undef,$param) = each($ldap_server)) { |
92 | if (is_array($param)) { |
93 | $r = $abook->add_backend('ldap_server', $param); |
94 | if (!$r && $showerr) { |
95 | printf( ' ' . _("Error initializing LDAP server %s:") . |
96 | "<BR>\n", $param['host']); |
97 | echo ' ' . $abook->error; |
98 | exit; |
99 | } |
100 | } |
101 | } |
102 | } |
4935919f |
103 | |
81fa4801 |
104 | /* Return the initialized object */ |
105 | return $abook; |
4935919f |
106 | } |
107 | |
108 | |
81fa4801 |
109 | /* |
110 | * Had to move this function outside of the Addressbook Class |
111 | * PHP 4.0.4 Seemed to be having problems with inline functions. |
112 | */ |
113 | function addressbook_cmp($a,$b) { |
4935919f |
114 | |
81fa4801 |
115 | if($a['backend'] > $b['backend']) { |
116 | return 1; |
117 | } else if($a['backend'] < $b['backend']) { |
118 | return -1; |
119 | } |
120 | |
121 | return (strtolower($a['name']) > strtolower($b['name'])) ? 1 : -1; |
4935919f |
122 | |
81fa4801 |
123 | } |
4935919f |
124 | |
125 | |
81fa4801 |
126 | /* |
127 | * This is the main address book class that connect all the |
128 | * backends and provide services to the functions above. |
129 | * |
130 | */ |
5100704d |
131 | |
81fa4801 |
132 | class AddressBook { |
4935919f |
133 | |
81fa4801 |
134 | var $backends = array(); |
135 | var $numbackends = 0; |
136 | var $error = ''; |
137 | var $localbackend = 0; |
138 | var $localbackendname = ''; |
139 | |
140 | // Constructor function. |
141 | function AddressBook() { |
142 | $localbackendname = _("Personal address book"); |
143 | } |
4935919f |
144 | |
81fa4801 |
145 | /* |
146 | * Return an array of backends of a given type, |
147 | * or all backends if no type is specified. |
148 | */ |
149 | function get_backend_list($type = '') { |
150 | $ret = array(); |
151 | for ($i = 1 ; $i <= $this->numbackends ; $i++) { |
152 | if (empty($type) || $type == $this->backends[$i]->btype) { |
153 | $ret[] = &$this->backends[$i]; |
154 | } |
4935919f |
155 | } |
81fa4801 |
156 | return $ret; |
157 | } |
4935919f |
158 | |
159 | |
81fa4801 |
160 | /* |
161 | ========================== Public ======================== |
162 | |
163 | Add a new backend. $backend is the name of a backend |
164 | (without the abook_ prefix), and $param is an optional |
165 | mixed variable that is passed to the backend constructor. |
166 | See each of the backend classes for valid parameters. |
167 | */ |
168 | function add_backend($backend, $param = '') { |
169 | $backend_name = 'abook_' . $backend; |
170 | eval('$newback = new ' . $backend_name . '($param);'); |
171 | if(!empty($newback->error)) { |
172 | $this->error = $newback->error; |
173 | return false; |
174 | } |
175 | |
176 | $this->numbackends++; |
177 | |
178 | $newback->bnum = $this->numbackends; |
179 | $this->backends[$this->numbackends] = $newback; |
180 | |
181 | /* Store ID of first local backend added */ |
182 | if ($this->localbackend == 0 && $newback->btype == 'local') { |
183 | $this->localbackend = $this->numbackends; |
184 | $this->localbackendname = $newback->sname; |
185 | } |
186 | |
187 | return $this->numbackends; |
188 | } |
4935919f |
189 | |
4935919f |
190 | |
2e542990 |
191 | /* |
192 | * This function takes a $row array as returned by the addressbook |
193 | * search and returns an e-mail address with the full name or |
194 | * nickname optionally prepended. |
195 | */ |
196 | |
197 | function full_address($row) { |
1ba8cd6b |
198 | global $addrsrch_fullname, $data_dir, $username; |
2e542990 |
199 | |
1ba8cd6b |
200 | if (($prefix = getPref($data_dir, $username, 'addrsrch_fullname') or |
e7c6d43c |
201 | isset($addrsrch_fullname) and $prefix = $addrsrch_fullname) |
202 | and $prefix !== 'noprefix') { |
b9d210e3 |
203 | $name = ($prefix === 'nickname') ? $row['nickname'] |
2e542990 |
204 | : $row['name']; |
205 | return $name . ' <' . trim($row['email']) . '>'; |
206 | } else { |
207 | return trim($row['email']); |
208 | } |
209 | } |
210 | |
81fa4801 |
211 | /* |
212 | Return a list of addresses matching expression in |
213 | all backends of a given type. |
214 | */ |
215 | function search($expression, $bnum = -1) { |
216 | $ret = array(); |
217 | $this->error = ''; |
218 | |
219 | /* Search all backends */ |
220 | if ($bnum == -1) { |
221 | $sel = $this->get_backend_list(''); |
222 | $failed = 0; |
223 | for ($i = 0 ; $i < sizeof($sel) ; $i++) { |
224 | $backend = &$sel[$i]; |
225 | $backend->error = ''; |
226 | $res = $backend->search($expression); |
227 | if (is_array($res)) { |
228 | $ret = array_merge($ret, $res); |
229 | } else { |
230 | $this->error .= "<br>\n" . $backend->error; |
231 | $failed++; |
75e19c7f |
232 | } |
233 | } |
4935919f |
234 | |
81fa4801 |
235 | /* Only fail if all backends failed */ |
236 | if( $failed >= sizeof( $sel ) ) { |
237 | $ret = FALSE; |
4935919f |
238 | } |
4935919f |
239 | |
81fa4801 |
240 | } else { |
4935919f |
241 | |
81fa4801 |
242 | /* Search only one backend */ |
4935919f |
243 | |
81fa4801 |
244 | $ret = $this->backends[$bnum]->search($expression); |
245 | if (!is_array($ret)) { |
246 | $this->error .= "<br>\n" . $this->backends[$bnum]->error; |
247 | $ret = FALSE; |
248 | } |
249 | } |
250 | |
251 | return( $ret ); |
4935919f |
252 | } |
253 | |
254 | |
81fa4801 |
255 | /* Return a sorted search */ |
256 | function s_search($expression, $bnum = -1) { |
257 | |
258 | $ret = $this->search($expression, $bnum); |
259 | if ( is_array( $ret ) ) { |
260 | usort($ret, 'addressbook_cmp'); |
261 | } |
262 | return $ret; |
263 | } |
4935919f |
264 | |
265 | |
81fa4801 |
266 | /* |
267 | * Lookup an address by alias. Only possible in |
268 | * local backends. |
269 | */ |
270 | function lookup($alias, $bnum = -1) { |
271 | |
272 | $ret = array(); |
273 | |
274 | if ($bnum > -1) { |
275 | $res = $this->backends[$bnum]->lookup($alias); |
276 | if (is_array($res)) { |
277 | return $res; |
278 | } else { |
279 | $this->error = $backend->error; |
280 | return false; |
281 | } |
282 | } |
283 | |
284 | $sel = $this->get_backend_list('local'); |
285 | for ($i = 0 ; $i < sizeof($sel) ; $i++) { |
286 | $backend = &$sel[$i]; |
287 | $backend->error = ''; |
288 | $res = $backend->lookup($alias); |
289 | if (is_array($res)) { |
290 | if(!empty($res)) |
291 | return $res; |
292 | } else { |
293 | $this->error = $backend->error; |
294 | return false; |
295 | } |
296 | } |
297 | |
298 | return $ret; |
4935919f |
299 | } |
300 | |
4935919f |
301 | |
81fa4801 |
302 | /* Return all addresses */ |
303 | function list_addr($bnum = -1) { |
304 | $ret = array(); |
305 | |
306 | if ($bnum == -1) { |
307 | $sel = $this->get_backend_list('local'); |
308 | } else { |
309 | $sel = array(0 => &$this->backends[$bnum]); |
310 | } |
311 | |
312 | for ($i = 0 ; $i < sizeof($sel) ; $i++) { |
313 | $backend = &$sel[$i]; |
314 | $backend->error = ''; |
315 | $res = $backend->list_addr(); |
316 | if (is_array($res)) { |
317 | $ret = array_merge($ret, $res); |
318 | } else { |
319 | $this->error = $backend->error; |
320 | return false; |
321 | } |
322 | } |
323 | |
324 | return $ret; |
325 | } |
4935919f |
326 | |
81fa4801 |
327 | /* |
328 | * Create a new address from $userdata, in backend $bnum. |
329 | * Return the backend number that the/ address was added |
330 | * to, or false if it failed. |
331 | */ |
332 | function add($userdata, $bnum) { |
4935919f |
333 | |
81fa4801 |
334 | /* Validate data */ |
335 | if (!is_array($userdata)) { |
336 | $this->error = _("Invalid input data"); |
337 | return false; |
338 | } |
339 | if (empty($userdata['firstname']) && empty($userdata['lastname'])) { |
340 | $this->error = _("Name is missing"); |
341 | return false; |
342 | } |
343 | if (empty($userdata['email'])) { |
344 | $this->error = _("E-mail address is missing"); |
345 | return false; |
346 | } |
347 | if (empty($userdata['nickname'])) { |
348 | $userdata['nickname'] = $userdata['email']; |
349 | } |
350 | |
351 | if (eregi('[ \\:\\|\\#\\"\\!]', $userdata['nickname'])) { |
352 | $this->error = _("Nickname contains illegal characters"); |
353 | return false; |
354 | } |
355 | |
356 | /* Check that specified backend accept new entries */ |
357 | if (!$this->backends[$bnum]->writeable) { |
358 | $this->error = _("Addressbook is read-only"); |
359 | return false; |
360 | } |
361 | |
362 | /* Add address to backend */ |
363 | $res = $this->backends[$bnum]->add($userdata); |
364 | if ($res) { |
365 | return $bnum; |
366 | } else { |
367 | $this->error = $this->backends[$bnum]->error; |
368 | return false; |
369 | } |
370 | |
371 | return false; // Not reached |
372 | } /* end of add() */ |
373 | |
374 | |
375 | /* |
376 | * Remove the user identified by $alias from backend $bnum |
377 | * If $alias is an array, all users in the array are removed. |
378 | */ |
379 | function remove($alias, $bnum) { |
380 | |
381 | /* Check input */ |
382 | if (empty($alias)) { |
383 | return true; |
384 | } |
385 | |
386 | /* Convert string to single element array */ |
387 | if (!is_array($alias)) { |
388 | $alias = array(0 => $alias); |
389 | } |
390 | |
391 | /* Check that specified backend is writable */ |
392 | if (!$this->backends[$bnum]->writeable) { |
393 | $this->error = _("Addressbook is read-only"); |
394 | return false; |
395 | } |
396 | |
397 | /* Remove user from backend */ |
398 | $res = $this->backends[$bnum]->remove($alias); |
399 | if ($res) { |
400 | return $bnum; |
401 | } else { |
402 | $this->error = $this->backends[$bnum]->error; |
403 | return false; |
404 | } |
405 | |
406 | return FALSE; /* Not reached */ |
407 | } /* end of remove() */ |
408 | |
409 | |
410 | /* |
411 | * Remove the user identified by $alias from backend $bnum |
412 | * If $alias is an array, all users in the array are removed. |
413 | */ |
414 | function modify($alias, $userdata, $bnum) { |
415 | |
416 | /* Check input */ |
417 | if (empty($alias) || !is_string($alias)) { |
418 | return true; |
419 | } |
420 | |
421 | /* Validate data */ |
422 | if(!is_array($userdata)) { |
423 | $this->error = _("Invalid input data"); |
424 | return false; |
425 | } |
426 | if (empty($userdata['firstname']) && empty($userdata['lastname'])) { |
427 | $this->error = _("Name is missing"); |
428 | return false; |
429 | } |
430 | if (empty($userdata['email'])) { |
431 | $this->error = _("E-mail address is missing"); |
432 | return false; |
433 | } |
434 | |
435 | if (eregi('[\\: \\|\\#"\\!]', $userdata['nickname'])) { |
436 | $this->error = _("Nickname contains illegal characters"); |
437 | return false; |
438 | } |
439 | |
440 | if (empty($userdata['nickname'])) { |
441 | $userdata['nickname'] = $userdata['email']; |
442 | } |
443 | |
444 | /* Check that specified backend is writable */ |
445 | if (!$this->backends[$bnum]->writeable) { |
446 | $this->error = _("Addressbook is read-only");; |
447 | return false; |
448 | } |
449 | |
450 | /* Modify user in backend */ |
451 | $res = $this->backends[$bnum]->modify($alias, $userdata); |
452 | if ($res) { |
453 | return $bnum; |
454 | } else { |
455 | $this->error = $this->backends[$bnum]->error; |
456 | return false; |
457 | } |
458 | |
459 | return FALSE; /* Not reached */ |
460 | } /* end of modify() */ |
4935919f |
461 | |
4935919f |
462 | |
81fa4801 |
463 | } /* End of class Addressbook */ |
464 | |
465 | /* |
466 | * Generic backend that all other backends extend |
467 | */ |
468 | class addressbook_backend { |
469 | |
470 | /* Variables that all backends must provide. */ |
471 | var $btype = 'dummy'; |
472 | var $bname = 'dummy'; |
473 | var $sname = 'Dummy backend'; |
4935919f |
474 | |
81fa4801 |
475 | /* |
476 | * Variables common for all backends, but that |
477 | * should not be changed by the backends. |
478 | */ |
479 | var $bnum = -1; |
480 | var $error = ''; |
481 | var $writeable = false; |
4935919f |
482 | |
81fa4801 |
483 | function set_error($string) { |
484 | $this->error = '[' . $this->sname . '] ' . $string; |
485 | return false; |
486 | } |
4935919f |
487 | |
75e19c7f |
488 | |
81fa4801 |
489 | /* ========================== Public ======================== */ |
490 | |
491 | function search($expression) { |
492 | $this->set_error('search not implemented'); |
493 | return false; |
494 | } |
495 | |
496 | function lookup($alias) { |
497 | $this->set_error('lookup not implemented'); |
498 | return false; |
a10110a5 |
499 | } |
81fa4801 |
500 | |
501 | function list_addr() { |
502 | $this->set_error('list_addr not implemented'); |
503 | return false; |
504 | } |
505 | |
506 | function add($userdata) { |
507 | $this->set_error('add not implemented'); |
508 | return false; |
509 | } |
510 | |
511 | function remove($alias) { |
512 | $this->set_error('delete not implemented'); |
513 | return false; |
514 | } |
515 | |
516 | function modify($alias, $newuserdata) { |
517 | $this->set_error('modify not implemented'); |
518 | return false; |
519 | } |
520 | |
521 | } |
522 | |
523 | /* Sort array by the key "name" */ |
524 | function alistcmp($a,$b) { |
525 | if ($a['backend'] > $b['backend']) { |
526 | return 1; |
527 | } else { |
528 | if ($a['backend'] < $b['backend']) { |
529 | return -1; |
530 | } |
531 | } |
532 | return (strtolower($a['name']) > strtolower($b['name'])) ? 1 : -1; |
533 | } |
534 | |
0419106e |
535 | |
536 | /* |
537 | PHP 5 requires that the class be made first, which seems rather |
538 | logical, and should have been the way it was generated the first time. |
539 | */ |
540 | |
541 | require_once(SM_PATH . 'functions/abook_local_file.php'); |
542 | require_once(SM_PATH . 'functions/abook_ldap_server.php'); |
543 | |
544 | /* Use this if you wanna have a global address book */ |
545 | if (isset($address_book_global_filename)) { |
546 | include_once(SM_PATH . 'functions/abook_global_file.php'); |
547 | } |
548 | |
549 | /* Only load database backend if database is configured */ |
550 | if(isset($addrbook_dsn) && !empty($addrbook_dsn)) { |
551 | include_once(SM_PATH . 'functions/abook_database.php'); |
552 | } |
553 | |
554 | |
2e542990 |
555 | ?> |