80239b50a0de8339e607f8e962520452abca39bc
[licom.git] / licom.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 LiCoM::Config (qw(get_config));
13 use LiCoM::Person;
14
15 our $Debug = 0;
16 our $Config = {};
17
18 our @MultiFields = (qw(address homephone cellphone officephone fax mail uri group));
19
20 our %FieldNames = 
21 (
22         address         => 'Address',
23         homephone       => 'Home Phone',
24         cellphone       => 'Cell Phone',
25         officephone     => 'Office Phone',
26         fax             => 'FAX',
27         mail            => 'E-Mail',
28         uri             => 'URI (Homepage)',
29         group           => 'Group'
30 );
31
32 our $MySelf = $ENV{'SCRIPT_NAME'};
33
34 our $Action = param ('action');
35 $Action ||= 'default';
36
37 our %Actions =
38 (
39         browse  => [\&html_start, \&action_browse,  \&html_end],
40         default => [\&html_start, \&action_browse,  \&html_end],
41         detail  => [\&html_start, \&action_detail,  \&html_end],
42         edit    => [\&html_start, \&action_edit,    \&html_end],
43         list    => [\&html_start, \&action_list,    \&html_end],
44         save    => [\&html_start, \&action_save,    \&html_end],
45         search  => [\&html_start, \&action_search,  \&html_end],
46         verify  => [\&html_start, \&action_verify,  \&html_end],
47         vcard   => \&action_vcard
48 );
49
50 $Config = get_config ();
51
52 # make sure AuthLDAPRemoteUserIsDN is enabled.
53 die unless ($ENV{'REMOTE_USER'});
54 $Config->{'base_dn'} = $ENV{'REMOTE_USER'};
55
56 die unless (defined ($Config->{'uri'}) and defined ($Config->{'base_dn'})
57         and defined ($Config->{'bind_dn'}) and defined ($Config->{'password'}));
58
59 LiCoM::Person->connect
60 (
61         uri     => $Config->{'uri'},
62         base_dn => $Config->{'base_dn'},
63         bind_dn => $Config->{'bind_dn'},
64         password => $Config->{'password'}
65 ) or die;
66
67 our ($UserCN, $UserID) = LiCoM::Person->get_user ($Config->{'base_dn'});
68
69 if (!$UserID and $Action ne 'save')
70 {
71         $Action = 'edit';
72 }
73
74 if (!$UserCN)
75 {
76         die;
77 }
78
79 if (!defined ($Actions{$Action}))
80 {
81         die;
82 }
83
84 if (ref ($Actions{$Action}) eq 'CODE')
85 {
86         $Actions{$Action}->();
87 }
88 elsif (ref ($Actions{$Action}) eq 'ARRAY')
89 {
90         for (@{$Actions{$Action}})
91         {
92                 $_->();
93         }
94 }
95
96 LiCoM::Person->disconnect ();
97
98 exit (0);
99
100 ###
101
102 sub action_browse
103 {
104         my $group = param ('group');
105         $group = shift if (@_);
106         $group ||= '';
107
108         my @all;
109         if ($group)
110         {
111                 @all = LiCoM::Person->search ([[group => $group]]);
112         }
113         else
114         {
115                 @all = LiCoM::Person->search ();
116         }
117
118         if (!$group)
119         {
120                 my @nogroup = ();
121                 my %groups = ();
122                 for (@all)
123                 {
124                         my $person = $_;
125                         my @g = $person->get ('group');
126
127                         $groups{$_} = (defined ($groups{$_}) ? $groups{$_} + 1 : 1) for (@g);
128
129                         push (@nogroup, $person) if (!@g);
130                 }
131                 @all = @nogroup;
132
133                 print qq(\t\t<h2>Contact Groups</h2>\n\t\t<ul class="groups">\n);
134                 for (sort (keys (%groups)))
135                 {
136                         my $group = $_;
137                         my $group_esc = uri_escape ($group);
138                         my $num = $groups{$group};
139
140                         print qq(\t\t\t<li><a href="$MySelf?action=browse&group=$group_esc">$group</a> ($num)</li>\n);
141                 }
142                 if (!%groups)
143                 {
144                         print qq(\t\t\t<li class="empty">There are no groups yet.</li>\n);
145                 }
146                 print qq(\t\t</ul>\n\n);
147         }
148
149         if ($group)
150         {
151                 print qq(\t\t<h2>Contact Group &quot;$group&quot;</h2>\n);
152         }
153         else
154         {
155                 print qq(\t\t<h2>Contacts without a group</h2>\n);
156         }
157
158         print qq(\t\t<ul class="results">\n);
159         for (sort { $a->name () cmp $b->name () } (@all))
160         {
161                 my $person = $_;
162                 my $cn = $person->name ();
163                 my $cn_esc = uri_escape ($cn);
164
165                 print qq(\t\t\t<li><a href="$MySelf?action=detail&cn=$cn_esc">$cn</a></li>\n);
166         }
167         if (!@all)
168         {
169                 print "\t\t\t<li>There are no matching entries.</li>\n";
170         }
171         print qq(\t\t</ul>\n\n);
172
173         print qq(\t\t<div class="menu">\n);
174         if ($group)
175         {
176                 my $group_esc = uri_escape ($group);
177                 print qq(\t\t\t[<a href="$MySelf?action=list&group=$group_esc">List</a>]\n),
178                 qq(\t\t\t[<a href="$MySelf?action=browse">Back</a>]\n);
179         }
180         else
181         {
182                 print qq(\t\t\t[<a href="$MySelf?action=list">List</a>]\n);
183         }
184         print qq(\t\t</div>\n);
185 }
186
187 sub action_list
188 {
189         my $group = param ('group');
190         $group = shift if (@_);
191         $group ||= '';
192
193         my $title = $group ? "List of group &quot;$group&quot;" : 'List of all addresses';
194         my @fields = (qw(address homephone cellphone officephone fax mail));
195
196         my @all = ();
197         if ($group)
198         {
199                 @all = LiCoM::Person->search ([[group => $group]]);
200         }
201         else
202         {
203                 @all = LiCoM::Person->search ();
204         }
205
206         print <<EOF;
207                 <h2>$title</h2>
208
209                 <table class="list">
210                         <tr>
211                                 <th>Name</th>
212 EOF
213         for (@fields)
214         {
215                 print "\t\t\t\t<th>" . (defined ($FieldNames{$_}) ? $FieldNames{$_} : $_) . "</th>\n";
216         }
217         print "\t\t\t</tr>\n";
218
219         for (sort { $a->name () cmp $b->name () } (@all))
220         {
221                 my $person = $_;
222                 my $sn = $person->lastname ();
223                 my $gn = $person->firstname ();
224
225                 print "\t\t\t<tr>\n",
226                 "\t\t\t\t<td>$sn, $gn</td>\n";
227
228                 for (@fields)
229                 {
230                         my $field = $_;
231                         my @values = $person->get ($field);
232                         print "\t\t\t\t<td>" . join ('<br />', @values) . "</td>\n";
233                 }
234
235                 print "\t\t\t</tr>\n";
236         }
237         print "\t\t</table>\n\n";
238
239         if ($group)
240         {
241                 my $group_esc = uri_escape ($group);
242                 print qq(\t\t<div class="menu">[<a href="$MySelf?action=browse&group=$group_esc">Back</a>]</div>\n);
243         }
244         else
245         {
246                 print qq(\t\t<div class="menu">[<a href="$MySelf?action=browse">Back</a>]</div>\n);
247         }
248 }
249
250 sub action_detail
251 {
252         my $cn = param ('cn');
253         $cn = shift if (@_);
254         die unless ($cn);
255
256         my $person = LiCoM::Person->load ($cn);
257         if (!$person)
258         {
259                 print qq(\t<div>Entry &quot;$cn&quot; could not be loaded from DB.</div>\n);
260                 return;
261         }
262
263         print qq(\t\t<h2>Details for $cn</h2>\n);
264
265         my $cn_esc = uri_escape ($cn);
266
267         print <<EOF;
268                 <table class="detail">
269                         <tr>
270                                 <th>Name</th>
271                                 <td>$cn</td>
272                         </tr>
273 EOF
274         for (@MultiFields)
275         {
276                 my $field = $_;
277                 my $values = $person->get ($field);
278                 my $num = scalar (@$values);
279                 my $print = defined ($FieldNames{$field}) ? $FieldNames{$field} : $field;
280
281                 next unless ($num);
282
283                 print "\t\t\t<tr>\n";
284                 if ($num > 1)
285                 {
286                         print qq(\t\t\t\t<th rowspan="$num">$print</th>\n);
287                 }
288                 else
289                 {
290                         print qq(\t\t\t\t<th>$print</th>\n);
291                 }
292
293                 for (my $i = 0; $i < $num; $i++)
294                 {
295                         my $val = $values->[$i];
296
297                         if ($field eq 'group')
298                         {
299                                 my $val_esc = uri_escape ($val);
300                                 $val = qq(<a href="$MySelf?action=browse&group=$val_esc">$val</a>);
301                         }
302                         elsif ($field eq 'uri')
303                         {
304                                 my $uri = $val;
305                                 $uri = qq(http://$val) unless ($val =~ m#^[a-z]+://#);
306                                 $val = qq(<a href="$uri" class="extern">$val</a>);
307                         }
308                         elsif ($field eq 'mail')
309                         {
310                                 $val = qq(<a href="mailto:$val" class="mail">$val</a>);
311                         }
312                         
313                         print "\t\t\t<tr>\n" if ($i);
314                         print "\t\t\t\t<td>$val</td>\n",
315                         "\t\t\t</tr>\n";
316                 }
317         }
318         print <<EOF;
319                 </table>
320
321                 <div class="menu">
322                         [<a href="$MySelf?action=verify&cn=$cn_esc">Verify</a>]
323                         [<a href="$MySelf?action=vcard&cn=$cn_esc">vCard</a>]
324                         [<a href="$MySelf?action=edit&cn=$cn_esc">Edit</a>]
325                 </div>
326
327 EOF
328 }
329
330 sub action_search
331 {
332         my $search = param ('search');
333
334         $search ||= '';
335         $search =~ s/[^\s\w]//g;
336
337         if (!$search)
338         {
339                 print qq(\t<div class="error">Sorry, the empty search is not allowed.</div>\n);
340                 action_default ();
341                 return;
342         }
343
344         my @patterns = split (m/\s+/, $search);
345         my @filter = ();
346
347         for (@patterns)
348         {
349                 my $pattern = "$_*";
350                 push (@filter, [[lastname => $pattern], [firstname => $pattern]]);
351         }
352
353         my @matches = LiCoM::Person->search (@filter);
354
355         if (!@matches)
356         {
357                 print qq(\t<div>No entries matched your search.</div>\n);
358                 return;
359         }
360
361         if (scalar (@matches) == 1)
362         {
363                 my $person = shift (@matches);
364                 my $cn = $person->name ();
365                 action_detail ($cn);
366                 return;
367         }
368
369         print qq(\t<ul class="result">\n);
370         for (sort { $a->name () cmp $b->name () } (@matches))
371         {
372                 my $person = $_;
373                 my $cn = $person->name ();
374                 my $cn_esc = uri_escape ($cn);
375
376                 print qq(\t\t<li><a href="$MySelf?action=detail&cn=$cn_esc">$cn</a></li>\n);
377         }
378         print qq(\t</ul>\n);
379 }
380
381 sub action_edit
382 {
383         my %opts = @_;
384
385         my $cn = param ('cn');
386
387         $cn = $opts{'cn'} if (defined ($opts{'cn'}));
388         $cn ||= '';
389
390         if (!$UserID)
391         {
392                 $cn = $UserCN;
393         }
394
395         my $person;
396
397         my $lastname;
398         my $firstname;
399
400         my $contacts = {};
401         $contacts->{$_} = [] for (@MultiFields);
402
403         if ($cn)
404         {
405                 $person = LiCoM::Person->load ($cn);
406
407                 if (!$person)
408                 {
409                         print qq(\t<div class="error">Unable to load CN &quot;$cn&quot;. Sorry.</div>\n);
410                         return;
411                 }
412         
413                 $lastname    = $person->lastname ();
414                 $firstname   = $person->firstname ();
415
416                 for (@MultiFields)
417                 {
418                         $contacts->{$_} = $person->get ($_);
419                 }
420         }
421
422         $lastname    = param ('lastname')    if (param ('lastname')  and $UserID);
423         $firstname   = param ('firstname')   if (param ('firstname') and $UserID);
424
425         get_contacts ($contacts);
426         
427         $lastname    =   $opts{'lastname'}     if (defined ($opts{'lastname'}));
428         $firstname   =   $opts{'firstname'}    if (defined ($opts{'firstname'}));
429         for (@MultiFields)
430         {
431                 my $field = $_;
432                 @{$contacts->{$field}} = @{$opts{$field}} if (defined ($opts{$field}));
433         }
434
435         if ($cn)
436         {
437                 print "\t\t<h2>Edit contact $cn</h2>\n";
438         }
439         else
440         {
441                 print "\t\t<h2>Create new contact</h2>\n";
442         }
443
444         print <<EOF;
445                 <form action="$MySelf" method="post">
446                 <input type="hidden" name="action" value="save" />
447                 <input type="hidden" name="cn" value="$cn" />
448                 <table class="edit">
449                         <tr>
450                                 <th>Lastname</th>
451 EOF
452         if ($UserID)
453         {
454                 print qq(\t\t\t\t<td><input type="text" name="lastname" value="$lastname" /></td>\n);
455         }
456         else
457         {
458                 print qq(\t\t\t\t<td>$lastname</td>\n);
459         }
460         print <<EOF;
461                         </tr>
462                         <tr>
463                                 <th>Firstname</th>
464 EOF
465         if ($UserID)
466         {
467                 print qq(\t\t\t\t<td><input type="text" name="firstname" value="$firstname" /></td>\n);
468         }
469         else
470         {
471                 print qq(\t\t\t\t<td>$firstname</td>\n);
472         }
473         
474         print "\t\t\t</tr>\n";
475
476         for (@MultiFields)
477         {
478                 my $field = $_;
479                 my $print = defined ($FieldNames{$field}) ? $FieldNames{$field} : $field;
480                 my @values = @{$contacts->{$field}};
481
482                 next if (!$UserID and $field eq 'group');
483
484                 push (@values, '');
485                 
486                 for (@values)
487                 {
488                         my $value = $_;
489
490                         print <<EOF;
491                         <tr>
492                                 <th>$print</th>
493                                 <td><input type="text" name="$field" value="$value" /></td>
494                         </tr>
495 EOF
496                 }
497         }
498
499         print <<EOF;
500                         <tr>
501                                 <th colspan="2" class="menu">
502 EOF
503         if ($UserID)
504         {
505                 print <<EOF;
506                                         <input type="submit" name="button" value="Cancel" />
507                                         <input type="submit" name="button" value="Apply" />
508 EOF
509         }
510         print <<EOF;
511                                         <input type="submit" name="button" value="Save" />
512                                 </th>
513                         </tr>
514                 </table>
515                 </form>
516 EOF
517 }
518
519 sub action_save
520 {
521         my $cn = $UserID ? param ('cn') : $UserCN;
522
523         if (verify_fields ())
524         {
525                 action_edit (cn => $cn);
526                 return;
527         }
528
529         if ($cn)
530         {
531                 action_update ();
532                 return;
533         }
534
535         die unless ($UserID);
536
537         my $button = lc (param ('button'));
538         $button ||= 'save';
539
540         if ($button eq 'cancel')
541         {
542                 action_browse ();
543                 return;
544         }
545
546         if (!param ('lastname') or !param ('firstname'))
547         {
548                 print qq(\t<div class="error">You have to give both, first and lastname, to identify this record.</div>\n);
549                 action_edit (cn => '');
550                 return;
551         }
552
553         my $lastname  = param ('lastname');
554         my $firstname = param ('firstname');
555
556         my $contacts = get_contacts ();
557
558         my $person = LiCoM::Person->create (lastname => $lastname, firstname => $firstname, %$contacts);
559
560         if (!$person)
561         {
562                 print qq(\t<div class="error">Unable to save entry. Sorry.</div>\n);
563                 return;
564         }
565         
566         $cn = $person->name ();
567
568         if ($button eq 'apply')
569         {
570                 action_edit (cn => $cn);
571         }
572         else
573         {
574                 action_detail ($cn);
575         }
576 }
577
578 sub action_update
579 {
580         my $cn = $UserID ? param ('cn') : $UserCN;
581         my $person = LiCoM::Person->load ($cn);
582
583         die unless ($person);
584
585         my $button = lc (param ('button'));
586         $button ||= 'save';
587
588         if ($UserID and $button eq 'cancel')
589         {
590                 action_detail ($cn);
591                 return;
592         }
593
594         if ($UserID)
595         {
596                 my $lastname  = param ('lastname');
597                 my $firstname = param ('firstname');
598
599                 $person->lastname  ($lastname)  if ($lastname  and $lastname  ne $person->lastname ());
600                 $person->firstname ($firstname) if ($firstname and $firstname ne $person->firstname ());
601
602                 $cn = $person->name ();
603         }
604
605         my $contacts = get_contacts ();
606
607         for (@MultiFields)
608         {
609                 my $field = $_;
610                 
611                 next if (!$UserID and $field eq 'group');
612
613                 if (defined ($contacts->{$field}))
614                 {
615                         my $values = $contacts->{$field};
616                         $person->set ($field, $values);
617                 }
618                 else
619                 {
620                         $person->set ($field, []);
621                 }
622         }
623
624         if ($button eq 'apply' or !$UserID)
625         {
626                 action_edit (cn => $cn);
627         }
628         else
629         {
630                 action_detail ($cn);
631         }
632 }
633
634 sub action_vcard
635 {
636         my $cn = param ('cn');
637         $cn = shift if (@_);
638         die unless ($cn);
639
640         my $person = LiCoM::Person->load ($cn);
641         die unless ($person);
642
643         my %vcard_types =
644         (
645                 homephone       => 'TEL;TYPE=home,voice',
646                 cellphone       => 'TEL;TYPE=cell',
647                 officephone     => 'TEL;TYPE=work,voice',
648                 fax             => 'TEL;TYPE=fax',
649                 mail            => 'EMAIL',
650                 uri             => 'URL',
651                 group           => 'ORG'
652         );
653
654         my $sn = $person->lastname ();
655         my $gn = $person->firstname ();
656         my $cn_esc = uri_escape ($cn);
657
658         print <<EOF;
659 Content-Type: text/x-vcard
660 Content-Disposition: attachment; filename="$cn.vcf"
661
662 BEGIN:VCARD
663 VERSION:3.0
664 FN: $cn
665 N: $sn;$gn
666 EOF
667
668         for (@MultiFields)
669         {
670                 my $field = $_;
671                 my $vc_fld = $vcard_types{$field};
672                 my $values = $person->get ($field);
673
674                 for (@$values)
675                 {
676                         my $value = $_;
677                         print "$vc_fld:$value\n";
678                 }
679         }
680         print "END:VCARD\n";
681 }
682
683 sub action_verify
684 {
685         my $cn = param ('cn');
686         $cn = shift if (@_);
687         die unless ($cn);
688
689         my $person = LiCoM::Person->load ($cn);
690         die unless ($person);
691
692         my ($mail) = $person->get ('mail');
693         $mail ||= '';
694
695         my $message;
696         my $password = $person->password ();
697
698         if (!$password)
699         {
700                 $password = pwgen ();
701                 $person->password ($password);
702         }
703
704         $message = qq(The password for the record &quot;$cn&quot; is &quot;$password&quot;.);
705
706         if ($mail)
707         {
708                 if (action_verify_send_mail ($person))
709                 {
710                         $message .= qq( A request for verification has been sent to $mail.);
711                 }
712         }
713         else
714         {
715                 $message .= q( There was no e-mail address, thus no verification request could be sent.);
716         }
717
718         print qq(\t\t<div class="message">$message</div>\n);
719
720         action_detail ($cn);
721 }
722
723 sub action_verify_send_mail
724 {
725         my $person = shift;
726         my $owner = LiCoM::Person->load ($UserCN);
727         my $smh;
728
729         my ($owner_mail) = $owner->get ('mail');
730         if (!$owner_mail)
731         {
732                 my $cn = uri_escape ($UserCN);
733                 print qq(\t\t<div class="error">You have no email set in your own profile. <a href="$MySelf?action=edit&cn=$cn">Edit it now</a>!</div>\n);
734                 return (0);
735         }
736
737         my $max_width = 0;
738         for (keys %FieldNames)
739         {
740                 $max_width = length $FieldNames{$_} if ($max_width < length $FieldNames{$_});
741         }
742         $max_width++;
743
744         my $person_name = $person->name ();
745         my ($person_mail) = $person->get ('mail');
746         my $person_gn = $person->firstname ();
747         my $password = $person->password ();
748
749         my $host = $ENV{'HTTP_HOST'};
750         my $url = 'http://' . $host . $MySelf;
751         
752         open ($smh, "| /usr/sbin/sendmail -t -f $owner_mail") or die ("open pipe to sendmail: $!");
753         print $smh <<EOM;
754 To: $person_name <$person_mail>
755 From: $UserCN <$owner_mail>
756 Subject: Please verify our entry in my address book
757
758 Hello $person_gn,
759
760 the following is your entry in my address book:
761 EOM
762         for (@MultiFields)
763         {
764                 my $field = $_;
765                 my $print = defined ($FieldNames{$field}) ? $FieldNames{$field} : $field;
766                 my @values = $person->get ($field);
767
768                 for (@values)
769                 {
770                         printf $smh ('%'.$max_width."s: %-s\n", $print, $_);
771                 }
772         }
773         print $smh <<EOM;
774
775 If this entry is outdated or incomplete, please take a minute and correct it.
776   Address:  $url
777  Username: $person_name
778  Password: $password
779
780 Thank you very much :) Regards,
781 $UserCN
782 EOM
783         close ($smh);
784
785         return (1);
786 }
787
788 sub html_start
789 {
790         my $title = shift;
791         $title = q(Lightweight Contact Manager) unless ($title);
792
793         print <<EOF;
794 Content-Type: text/html; charset=UTF-8
795
796 <html>
797         <head>
798                 <title>$title</title>
799                 <style type="text/css">
800                 <!--
801                 \@media screen
802                 {
803                         a
804                         {
805                                 color: blue;
806                                 background-color: inherit;
807                                 text-decoration: none;
808                         }
809
810                         a:hover
811                         {
812                                 text-decoration: underline;
813                         }
814
815                         a:visited
816                         {
817                                 color: navy;
818                                 background-color: inherit;
819                         }
820
821                         body
822                         {
823                                 color: black;
824                                 background-color: white;
825                         }
826
827                         div.error
828                         {
829                                 color: red;
830                                 background-color: yellow;
831
832                                 font-weight: bold;
833                                 padding: 1ex;
834                                 border: 2px solid red;
835                         }
836
837                         div.foot
838                         {
839                                 color: gray;
840                                 background-color: white;
841
842                                 position: fixed;
843                                 top: auto;
844                                 right: 0px;
845                                 bottom: 0px;
846                                 left: 0px;
847
848                                 font-size: x-small;
849                                 text-align: right;
850                                 border-top: 1px solid black;
851                                 width: 100%;
852                         }
853
854                         div.foot a
855                         {
856                                 color: black;
857                                 background-color: inherit;
858                                 text-decoration: none;
859                         }
860
861                         div.foot a:hover
862                         {
863                                 text-decoration: underline;
864                         }
865
866                         div.menu
867                         {
868                                 border-top: 1px solid black;
869                                 margin-top: 1ex;
870                                 font-weight: bold;
871                         }
872
873                         div.menu a
874                         {
875                                 color: blue;
876                                 background-color: transparent;
877                         }
878
879                         div.topmenu
880                         {
881                                 margin-bottom: 1ex;
882                                 padding-bottom: 1ex;
883                                 border-bottom: 1px solid black;
884                         }
885
886                         div.topmenu form
887                         {
888                                 display: inline;
889                                 margin-right: 5ex;
890                         }
891
892                         h1
893                         {
894                                 position: absolute;
895                                 top: 1ex;
896                                 right: 1ex;
897                                 bottom: auto;
898                                 left: auto;
899
900                                 font-size: 100%;
901                                 font-weight: bold;
902                         }
903
904                         img
905                         {
906                                 border: none;
907                         }
908
909                         table.list
910                         {
911                                 width: 100%;
912                         }
913
914                         table.list td
915                         {
916                                 empty-cells: show;
917                         }
918
919                         td
920                         {
921                                 color: black;
922                                 background-color: #cccccc;
923                                 vertical-align: top;
924                         }
925
926                         th
927                         {
928                                 color: black;
929                                 background-color: #999999;
930                                 padding: 0.3ex;
931                                 text-align: left;
932                                 vertical-align: top;
933                         }
934                 }
935
936                 \@media print
937                 {
938                         a
939                         {
940                                 color: inherit;
941                                 background-color: inherit;
942                                 text-decoration: underline;
943                         }
944                         
945                         div.topmenu, div.menu
946                         {
947                                 display: none;
948                         }
949
950                         div.foot
951                         {
952                                 font-size: 50%;
953                                 text-align: right;
954                         }
955
956                         h1
957                         {
958                                 display: none;
959                         }
960
961                         h2
962                         {
963                                 font-size: 100%;
964                         }
965
966                         table
967                         {
968                                 border-collapse: collapse;
969                         }
970
971                         table.list
972                         {
973                                 width: 100%;
974                         }
975
976                         table.list td
977                         {
978                                 empty-cells: show;
979                         }
980
981                         table.list th
982                         {
983                                 border-bottom-width: 2px;
984                         }
985
986                         td, th
987                         {
988                                 border: 1px solid black;
989                                 vertical-align: top;
990                         }
991
992                         th
993                         {
994                                 font-weight: bold;
995                                 text-align: center;
996                         }
997                 }
998                 //-->
999                 </style>
1000         </head>
1001
1002         <body>
1003 EOF
1004         if ($UserID)
1005         {
1006                 my $search = param ('search') || '';
1007                 print <<EOF;
1008                 <div class="topmenu">
1009                         <form action="$MySelf" method="post">
1010                                 <input type="hidden" name="action" value="browse" />
1011                                 <input type="submit" name="button" value="Browse" />
1012                         </form>
1013                         <form action="$MySelf" method="post">
1014                                 <input type="hidden" name="action" value="search" />
1015                                 <input type="text" name="search" value="$search" />
1016                                 <input type="submit" name="button" value="Search" />
1017                         </form>
1018                         <form action="$MySelf" method="post">
1019                                 <input type="hidden" name="action" value="edit" />
1020                                 <input type="hidden" name="dn" value="" />
1021                                 <input type="submit" name="button" value="Add New" />
1022                         </form>
1023                 </div>
1024 EOF
1025         }
1026         print "\t\t<h1>$title</h1>\n";
1027 }
1028
1029 sub html_end
1030 {
1031         print <<EOF;
1032                 <div class="foot">
1033                         &quot;Lightweight Contact Manager&quot;,
1034                         written 2005 by <a href="http://verplant.org/">Florian octo Forster</a>
1035                         &lt;octo at verplant.org&gt;
1036                 </div>
1037         </body>
1038 </html>
1039 EOF
1040 }
1041
1042 sub pwgen
1043 {
1044         my $len = @_ ? shift : 6;
1045         my $retval = '';
1046
1047         while (!$retval)
1048         {
1049                 my $numbers = 0;
1050                 my $lchars  = 0;
1051                 my $uchars  = 0;
1052                 
1053                 while (length ($retval) < $len)
1054                 {
1055                         my $chr = int (rand (128));
1056
1057                         if ($chr >= 48 and $chr < 58)
1058                         {
1059                                 $numbers++;
1060                         }
1061                         elsif ($chr >= 65 and $chr < 91)
1062                         {
1063                                 $uchars++;
1064                         }
1065                         elsif ($chr >= 97 and $chr < 123)
1066                         {
1067                                 $lchars++;
1068                         }
1069                         else
1070                         {
1071                                 next;
1072                         }
1073                         $retval .= chr ($chr);
1074                 }
1075
1076                 $retval = '' if (!$numbers or !$lchars or !$uchars);
1077         }
1078
1079         return ($retval);
1080 }
1081
1082 sub verify_fields
1083 {
1084         my @errors = ();
1085         for (param ('uri'))
1086         {
1087                 my $val = $_;
1088                 next unless ($val);
1089
1090                 if ($val !~ m#^[a-zA-Z]+://#)
1091                 {
1092                         push (@errors, 'URIs have to begin with a protocol, e.g. &quot;http://&quot;, &quot;ftp://&quot; etc.');
1093                         last;
1094                 }
1095         }
1096
1097         for (param ('homephone'), param ('cellphone'), param ('officephone'), param ('fax'))
1098         {
1099                 my $number = $_;
1100                 next unless ($number);
1101
1102                 if ($number !~ m/^\+/)
1103                 {
1104                         push (@errors, 'Telephone numbers have to begin with the country code, e.g. &quot;+49 911 123456&quot;');
1105                         last;
1106                 }
1107         }
1108
1109         print qq(\t\t<div class="error">\n) if (@errors);
1110         for (my $i = 0; $i < scalar (@errors); $i++)
1111         {
1112                 my $e = $errors[$i];
1113
1114                 print "<br />\n" if ($i);
1115                 print "\t\t\t$e";
1116         }
1117         print qq(\n\t\t</div>\n\n) if (@errors);
1118
1119         return (scalar (@errors));
1120 }
1121
1122 sub get_contacts
1123 {
1124         my $contacts = @_ ? shift : {};
1125
1126         for (@MultiFields)
1127         {
1128                 my $field = $_;
1129                 my @values = grep { $_ } (param ($field));
1130
1131                 next unless (@values);
1132
1133                 if ($field eq 'homephone' or $field eq 'cellphone' or $field eq 'officephone' or $field eq 'fax')
1134                 {
1135                         for (@values)
1136                         {
1137                                 $_ =~ s/\D//g;
1138                                 $_ = '+' . $_;
1139                         }
1140                 }
1141                 
1142                 $contacts->{$field} = [@values] if (@values);
1143         }
1144
1145         return ($contacts);
1146 }