8a616b179f6841d62c313a898ea3d7f822b40bd2
[licom.git] / book.cgi
1 #!/usr/bin/perl
2
3 use strict;
4 use warnings;
5 use lib (qw(lib));
6
7 use CGI (':cgi');
8 use CGI::Carp (qw(fatalsToBrowser));
9 use URI::Escape;
10 use Data::Dumper;
11
12 use Person;
13
14 our $Debug = 0;
15 our %Config = ();
16
17 our @MultiFields = (qw(address homephone cellphone officephone fax mail uri group));
18
19 our %FieldNames = 
20 (
21         address         => 'Address',
22         homephone       => 'Home Phone',
23         cellphone       => 'Cellphone',
24         officephone     => 'Office Phone',
25         fax             => 'FAX',
26         mail            => 'E-Mail',
27         uri             => 'URI (Homepage)',
28         group           => 'Group'
29 );
30
31 our $MySelf = $ENV{'SCRIPT_NAME'};
32
33 our $Action = param ('action');
34 $Action ||= 'default';
35
36 our %Actions =
37 (
38         default => \&action_default,
39         detail  => \&action_detail,
40         edit    => \&action_edit,
41         save    => \&action_save,
42         search  => \&action_search
43 );
44
45 read_config ();
46
47 # make sure AuthLDAPRemoteUserIsDN is enabled.
48 die unless ($ENV{'REMOTE_USER'});
49 $Config{'base_dn'} = $ENV{'REMOTE_USER'};
50
51 Person->connect
52 (
53         uri     => $Config{'uri'},
54         base_dn => $Config{'base_dn'},
55         bind_dn => $Config{'bind_dn'},
56         password => $Config{'password'}
57 ) or die;
58
59 our ($UserCN, $UserID) = Person->get_user ($Config{'base_dn'});
60
61 if (!$UserID and $Action ne 'save')
62 {
63         $Action = 'edit';
64 }
65
66 print <<HEADER;
67 Content-Type: text/html; charset=UTF-8
68
69 HEADER
70
71 print_html_start ("octo's Address Book");
72
73 if (!$UserCN)
74 {
75         die;
76 }
77
78 if (!defined ($Actions{$Action}))
79 {
80         die;
81 }
82
83 $Actions{$Action}->();
84
85 #print qq#<div>Authenticated as ($UserCN, $UserID, #, $Config{'base_dn'}, qq#)</div>\n#;
86
87 print_html_end ();
88
89 Person->disconnect ();
90
91 exit (0);
92
93 ###
94
95 sub action_default
96 {
97         print "<code>action_default</code>\n";
98 }
99
100 sub action_detail
101 {
102         my $cn = param ('cn');
103         die unless ($cn);
104
105         my $person = Person->load ($cn);
106         if (!$person)
107         {
108                 print qq(\t<div>Entry &quot;$cn&quot; could not be loaded from DB.</div>\n);
109                 return;
110         }
111
112         my $cn_esc = uri_escape ($cn);
113
114         print <<EOF;
115         <table class="detail">
116                 <tr>
117                         <th>Name</th>
118                         <td>$cn</td>
119                 </tr>
120 EOF
121         for (@MultiFields)
122         {
123                 my $field = $_;
124                 my $values = $person->get ($field);
125                 my $num = scalar (@$values);
126                 my $print = defined ($FieldNames{$field}) ? $FieldNames{$field} : $field;
127
128                 next unless ($num);
129
130                 print "\t\t<tr>\n";
131                 if ($num > 1)
132                 {
133                         print qq(\t\t\t<th rowspan="$num">$print</th>\n);
134                 }
135                 else
136                 {
137                         print qq(\t\t\t<th>$print</th>\n);
138                 }
139
140                 for (my $i = 0; $i < $num; $i++)
141                 {
142                         my $val = $values->[$i];
143                         print "\t\t<tr>\n" if ($i);
144                         print "\t\t\t<td>$val</td>\n",
145                         "\t\t</tr>\n";
146                 }
147         }
148         print <<EOF;
149         </table>
150         <div class="detail menu">
151                 [<a href="$MySelf?action=edit&cn=$cn_esc">edit</a>]
152         </div>
153 EOF
154 }
155
156 sub action_search
157 {
158         my $search = param ('search');
159
160         $search ||= '';
161         $search =~ s/[^\s\w]//g;
162
163         if (!$search)
164         {
165                 print qq(\t<div class="error">Sorry, the empty search is not allowed.</div>\n);
166                 action_default ();
167                 return;
168         }
169
170         my @patterns = split (m/\s+/, $search);
171         my @filter = ();
172
173         for (@patterns)
174         {
175                 my $pattern = "$_*";
176                 push (@filter, [[lastname => $pattern], [firstname => $pattern]]);
177         }
178
179         my @matches = Person->search (@filter);
180
181         if (!@matches)
182         {
183                 print qq(\t<div>No entries matched your search.</div>\n);
184                 return;
185         }
186
187         print qq(\t<ul class="result">\n);
188         for (@matches)
189         {
190                 my $person = $_;
191                 my $cn = $person->name ();
192                 my $cn_esc = uri_escape ($cn);
193
194                 print qq(\t\t<li><a href="$MySelf?action=detail&cn=$cn_esc">$cn</a></li>\n);
195         }
196         print qq(\t</ul>\n);
197 }
198
199 sub action_edit
200 {
201         my %opts = @_;
202
203         my $cn = param ('cn');
204
205         $cn = $opts{'cn'} if (defined ($opts{'cn'}));
206         $cn ||= '';
207
208         if (!$UserID)
209         {
210                 $cn = $UserCN;
211         }
212
213         my $person;
214
215         my $lastname;
216         my $firstname;
217
218         my $contacts = {};
219         $contacts->{$_} = [] for (@MultiFields);
220
221         if ($cn)
222         {
223                 $person = Person->load ($cn);
224
225                 if (!$person)
226                 {
227                         print qq(\t<div class="error">Unable to load CN &quot;$cn&quot;. Sorry.</div>\n);
228                         return;
229                 }
230         
231                 $lastname    = $person->lastname ();
232                 $firstname   = $person->firstname ();
233                 $contacts->{'address'}     = $person->address ();
234                 $contacts->{'homephone'}   = $person->homephone ();
235                 $contacts->{'cellphone'}   = $person->cellphone ();
236                 $contacts->{'officephone'} = $person->officephone ();
237                 $contacts->{'fax'}         = $person->fax ();
238                 $contacts->{'mail'}        = $person->mail ();
239                 $contacts->{'uri'}         = $person->uri ();
240                 $contacts->{'group'}       = $person->group ();
241         }
242
243         $lastname    = param ('lastname')    if (param ('lastname')  and $UserID);
244         $firstname   = param ('firstname')   if (param ('firstname') and $UserID);
245
246         get_contacts ($contacts);
247         
248         $lastname    =   $opts{'lastname'}     if (defined ($opts{'lastname'}));
249         $firstname   =   $opts{'firstname'}    if (defined ($opts{'firstname'}));
250         for (@MultiFields)
251         {
252                 my $field = $_;
253                 @{$contacts->{$field}} = @{$opts{$field}} if (defined ($opts{$field}));
254         }
255
256         if ($cn)
257         {
258                 print "<h2>Edit contact $cn</h2>\n";
259         }
260         else
261         {
262                 print "<h2>Create new contact</h2>\n";
263         }
264
265         my $selector = sub
266         {
267                 my $selected = @_ ? shift : '';
268
269                 my @options =
270                 (
271                         [none           => '-- Contact --'],
272                         [address        => 'Address'],
273                         [homephone      => 'Home Phone'],
274                         [cellphone      => 'Cellphone'],
275                         [officephone    => 'Office Phone'],
276                         [fax            => 'FAX'],
277                         [mail           => 'E-Mail'],
278                         [uri            => 'URI (Homepage)'],
279                         [group          => 'Group']
280                 );
281
282                 print qq(<select name="c_type">\n);
283                 for (@options)
284                 {
285                         my ($field, $print) = @$_;
286                         my $sel = $field eq $selected ? ' selected="selected"' : '';
287                         print qq(\t\t\t\t<option value="$field"$sel>$print</option>\n);
288                 }
289                 print qq(\t\t\t</select>);
290         };
291
292         print <<EOF;
293         <form action="$MySelf" method="post">
294         <input type="hidden" name="action" value="save" />
295         <input type="hidden" name="cn" value="$cn" />
296         <table class="edit">
297                 <tr>
298                         <td>Lastname</td>
299 EOF
300         if ($UserID)
301         {
302                 print qq(\t\t\t<td><input type="text" name="lastname" value="$lastname" /></td>\n);
303         }
304         else
305         {
306                 print qq(\t\t\t<td>$lastname</td>\n);
307         }
308         print <<EOF;
309                 </tr>
310                 <tr>
311                         <td>Firstname</td>
312 EOF
313         if ($UserID)
314         {
315                 print qq(\t\t\t<td><input type="text" name="firstname" value="$firstname" /></td>\n);
316         }
317         else
318         {
319                 print qq(\t\t\t<td>$firstname</td>\n);
320         }
321         
322         print "\t\t</tr>\n";
323
324         for (@MultiFields)
325         {
326                 my $field = $_;
327                 my @values = @{$contacts->{$field}};
328
329                 @values = ('') unless (@values);
330                 
331                 for (@values)
332                 {
333                         my $value = $_;
334                         print "\t\t<tr>\n",
335                         "\t\t\t<td>";
336                         $selector->($field);
337                         print "</td>\n", <<EOF;
338                         <td><input type="text" name="c_value" value="$value" /></td>
339                 </tr>
340 EOF
341                 }
342         }
343
344         print "\t\t<tr>\n",
345         "\t\t\t<td>";
346         $selector->();
347         print "</td>\n", <<EOF;
348                         <td><input type="text" name="c_value" value="" /></td>
349                 </tr>
350                 <tr>
351                         <td colspan="2"><input type="submit" name="button" value="Save" /></td>
352                 </tr>
353         </table>
354         </form>
355 EOF
356 }
357
358 sub action_save
359 {
360         my $cn = $UserID ? param ('cn') : $UserCN;
361
362         if ($cn)
363         {
364                 action_update ();
365                 return;
366         }
367
368         die unless ($UserID);
369
370         if (!param ('lastname') or !param ('firstname'))
371         {
372                 print qq(\t<div class="error">You have to give both, first and lastname, to identify this record.</div>\n);
373                 action_edit (cn => '');
374                 return;
375         }
376
377         my $lastname  = param ('lastname');
378         my $firstname = param ('firstname');
379
380         my $contacts = get_contacts ();
381
382         my $person = Person->create (lastname => $lastname, firstname => $firstname, %$contacts);
383
384         if (!$person)
385         {
386                 print qq(\t<div class="error">Unable to save entry. Sorry.</div>\n);
387                 return;
388         }
389         
390         $cn = $person->name ();
391
392         action_edit (cn => $cn);
393 }
394
395 sub action_update
396 {
397         my $cn = $UserID ? param ('cn') : $UserCN;
398         my $person = Person->load ($cn);
399
400         die unless ($person);
401
402         if ($UserID)
403         {
404                 my $lastname  = param ('lastname');
405                 my $firstname = param ('firstname');
406
407                 $person->lastname  ($lastname)  if ($lastname);
408                 $person->firstname ($firstname) if ($firstname);
409
410                 $cn = $person->name ();
411         }
412
413         my $contacts = get_contacts ();
414
415         for (@MultiFields)
416         {
417                 my $field = $_;
418                 
419                 if (defined ($contacts->{$field}))
420                 {
421                         my $values = $contacts->{$field};
422                         $person->set ($field, $values);
423                 }
424                 else
425                 {
426                         $person->set ($field, []);
427                 }
428         }
429
430         action_edit (cn => $cn);
431 }
432
433 sub print_html_start
434 {
435         my $title = shift;
436         $title = 'Search for names' unless ($title);
437
438         print <<EOF;
439 <html>
440 <head>
441 <title>$title</title>
442 <style type="text/css">
443 <!--
444 body
445 {
446         color: black;
447         background-color: white;
448 }
449
450 div.error
451 {
452         color: red;
453         background-color: yellow;
454         
455         font-weight: bold;
456         padding: 1ex;
457         border: 2px solid red;
458 }
459
460 div.foot
461 {
462         color: gray;
463         background-color: white;
464
465         position: absolute;
466         top: auto;
467         right: 0px;
468         bottom: 0px;
469         left: 0px;
470         
471         font-size: x-small;
472         text-align: right;
473         border-top: 1px solid black;
474         width: 100%;
475 }
476
477 div.menu form
478 {
479         display: inline;
480         margin-right: 5ex;
481 }
482
483 img
484 {
485         border: none;
486 }
487
488 td
489 {
490         color: black;
491         background-color: #cccccc;
492 }
493
494 th
495 {
496         color: black;
497         background-color: #999999;
498         padding: 0.3ex;
499         text-align: left;
500         vertical-align: top;
501 }
502 //-->
503 </style>
504 </head>
505
506 <body>
507 EOF
508         if ($UserID)
509         {
510                 my $search = param ('search') || '';
511                 print <<EOF;
512         <div class="menu">
513                 <form action="$MySelf" method="post">
514                         <input type="hidden" name="action" value="search" />
515                         <input type="text" name="search" value="$search" />
516                         <input type="submit" name="button" value="Search" />
517                 </form>
518                 <form action="$MySelf" method="post">
519                         <input type="hidden" name="action" value="edit" />
520                         <input type="hidden" name="dn" value="" />
521                         <input type="submit" name="button" value="Add New" />
522                 </form>
523         </div>
524         <hr />
525 EOF
526         }
527         print "\t<h1>octo's Lightweight Address Book</h1>\n";
528 }
529
530 sub print_html_end
531 {
532         print <<EOF;
533                 <div class="foot">octo's Lightweight Address Book &lt;octo at verplant.org&gt;</div>
534         </body>
535 </html>
536 EOF
537 }
538
539 sub read_config
540 {
541         my $file = '/var/www/html/cgi.verplant.org/address/book.conf';
542         my $fh;
543
544         open ($fh, "< $file") or die ("open ($file): $!");
545         for (<$fh>)
546         {
547                 chomp;
548                 my $line = $_;
549
550                 if ($line =~ m/^(\w+):\s*"(.+)"\s*$/)
551                 {
552                         my $key = lc ($1);
553                         my $val = $2;
554
555                         $Config{$key} = $val;
556                 }
557         }
558
559         close ($fh);
560
561         for (qw(uri bind_dn password))
562         {
563                 die ("Not defined: $_") unless (defined ($Config{$_}));
564         }
565 }
566
567 sub get_contacts
568 {
569         my $contacts = @_ ? shift : {};
570
571         if (param ('c_value'))
572         {
573                 my @c_values = param ('c_value');
574                 my @c_types  = param ('c_type');
575
576                 my %cts = ();
577
578                 die if (scalar (@c_values) != scalar (@c_types));
579
580                 for (my $i = 0; $i < scalar (@c_values); $i++)
581                 {
582                         my $type  = $c_types[$i];
583                         my $value = $c_values[$i];
584
585                         $cts{$type} = [] unless (defined ($cts{$type}));
586                         push (@{$cts{$type}}, $value) if ($value);
587                 }
588
589                 for (@MultiFields)
590                 {
591                         my $type = $_;
592                         @{$contacts->{$type}} = @{$cts{$type}} if (defined ($cts{$type}));
593                 }
594         }
595
596         return ($contacts);
597 }