40bb8a3cce82be2411e9a148cb1b4a2971bf26ab
[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                 next unless ($vc_fld);
675
676                 for (@$values)
677                 {
678                         my $value = $_;
679                         print "$vc_fld:$value\n";
680                 }
681         }
682         print "END:VCARD\n";
683 }
684
685 sub action_verify
686 {
687         my $cn = param ('cn');
688         $cn = shift if (@_);
689         die unless ($cn);
690
691         my $person = LiCoM::Person->load ($cn);
692         die unless ($person);
693
694         my ($mail) = $person->get ('mail');
695         $mail ||= '';
696
697         my $message;
698         my $password = $person->password ();
699
700         if (!$password)
701         {
702                 $password = pwgen ();
703                 $person->password ($password);
704         }
705
706         $message = qq(The password for the record &quot;$cn&quot; is &quot;$password&quot;.);
707
708         if ($mail)
709         {
710                 if (action_verify_send_mail ($person))
711                 {
712                         $message .= qq( A request for verification has been sent to $mail.);
713                 }
714         }
715         else
716         {
717                 $message .= q( There was no e-mail address, thus no verification request could be sent.);
718         }
719
720         print qq(\t\t<div class="message">$message</div>\n);
721
722         action_detail ($cn);
723 }
724
725 sub action_verify_send_mail
726 {
727         my $person = shift;
728         my $owner = LiCoM::Person->load ($UserCN);
729         my $smh;
730
731         my ($owner_mail) = $owner->get ('mail');
732         if (!$owner_mail)
733         {
734                 my $cn = uri_escape ($UserCN);
735                 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);
736                 return (0);
737         }
738
739         my $max_width = 0;
740         for (keys %FieldNames)
741         {
742                 $max_width = length $FieldNames{$_} if ($max_width < length $FieldNames{$_});
743         }
744         $max_width++;
745
746         my $person_name = $person->name ();
747         my ($person_mail) = $person->get ('mail');
748         my $person_gn = $person->firstname ();
749         my $password = $person->password ();
750
751         my $host = $ENV{'HTTP_HOST'};
752         my $url = 'http://' . $host . $MySelf;
753         
754         open ($smh, "| /usr/sbin/sendmail -t -f $owner_mail") or die ("open pipe to sendmail: $!");
755         print $smh <<EOM;
756 To: $person_name <$person_mail>
757 From: $UserCN <$owner_mail>
758 Subject: Please verify our entry in my address book
759
760 Hello $person_gn,
761
762 the following is your entry in my address book:
763 EOM
764         for (@MultiFields)
765         {
766                 my $field = $_;
767                 my $print = defined ($FieldNames{$field}) ? $FieldNames{$field} : $field;
768                 my @values = $person->get ($field);
769
770                 for (@values)
771                 {
772                         printf $smh ('%'.$max_width."s: %-s\n", $print, $_);
773                 }
774         }
775         print $smh <<EOM;
776
777 If this entry is outdated or incomplete, please take a minute and correct it.
778   Address:  $url
779  Username: $person_name
780  Password: $password
781
782 Thank you very much :) Regards,
783 $UserCN
784 EOM
785         close ($smh);
786
787         return (1);
788 }
789
790 sub html_start
791 {
792         my $title = shift;
793         $title = q(Lightweight Contact Manager) unless ($title);
794
795         print <<EOF;
796 Content-Type: text/html; charset=UTF-8
797
798 <html>
799         <head>
800                 <title>$title</title>
801                 <style type="text/css">
802                 <!--
803                 \@media screen
804                 {
805                         a
806                         {
807                                 color: blue;
808                                 background-color: inherit;
809                                 text-decoration: none;
810                         }
811
812                         a:hover
813                         {
814                                 text-decoration: underline;
815                         }
816
817                         a:visited
818                         {
819                                 color: navy;
820                                 background-color: inherit;
821                         }
822
823                         body
824                         {
825                                 color: black;
826                                 background-color: white;
827                         }
828
829                         div.error
830                         {
831                                 color: red;
832                                 background-color: yellow;
833
834                                 font-weight: bold;
835                                 padding: 1ex;
836                                 border: 2px solid red;
837                         }
838
839                         div.foot
840                         {
841                                 color: gray;
842                                 background-color: white;
843
844                                 position: fixed;
845                                 top: auto;
846                                 right: 0px;
847                                 bottom: 0px;
848                                 left: 0px;
849
850                                 font-size: x-small;
851                                 text-align: right;
852                                 border-top: 1px solid black;
853                                 width: 100%;
854                         }
855
856                         div.foot a
857                         {
858                                 color: black;
859                                 background-color: inherit;
860                                 text-decoration: none;
861                         }
862
863                         div.foot a:hover
864                         {
865                                 text-decoration: underline;
866                         }
867
868                         div.menu
869                         {
870                                 border-top: 1px solid black;
871                                 margin-top: 1ex;
872                                 font-weight: bold;
873                         }
874
875                         div.menu a
876                         {
877                                 color: blue;
878                                 background-color: transparent;
879                         }
880
881                         div.topmenu
882                         {
883                                 margin-bottom: 1ex;
884                                 padding-bottom: 1ex;
885                                 border-bottom: 1px solid black;
886                         }
887
888                         div.topmenu form
889                         {
890                                 display: inline;
891                                 margin-right: 5ex;
892                         }
893
894                         h1
895                         {
896                                 position: absolute;
897                                 top: 1ex;
898                                 right: 1ex;
899                                 bottom: auto;
900                                 left: auto;
901
902                                 font-size: 100%;
903                                 font-weight: bold;
904                         }
905
906                         img
907                         {
908                                 border: none;
909                         }
910
911                         table.list
912                         {
913                                 width: 100%;
914                         }
915
916                         table.list td
917                         {
918                                 empty-cells: show;
919                         }
920
921                         td
922                         {
923                                 color: black;
924                                 background-color: #cccccc;
925                                 vertical-align: top;
926                         }
927
928                         th
929                         {
930                                 color: black;
931                                 background-color: #999999;
932                                 padding: 0.3ex;
933                                 text-align: left;
934                                 vertical-align: top;
935                         }
936                 }
937
938                 \@media print
939                 {
940                         a
941                         {
942                                 color: inherit;
943                                 background-color: inherit;
944                                 text-decoration: underline;
945                         }
946                         
947                         div.topmenu, div.menu
948                         {
949                                 display: none;
950                         }
951
952                         div.foot
953                         {
954                                 font-size: 50%;
955                                 text-align: right;
956                         }
957
958                         h1
959                         {
960                                 display: none;
961                         }
962
963                         h2
964                         {
965                                 font-size: 100%;
966                         }
967
968                         table
969                         {
970                                 border-collapse: collapse;
971                         }
972
973                         table.list
974                         {
975                                 width: 100%;
976                         }
977
978                         table.list td
979                         {
980                                 empty-cells: show;
981                         }
982
983                         table.list th
984                         {
985                                 border-bottom-width: 2px;
986                         }
987
988                         td, th
989                         {
990                                 border: 1px solid black;
991                                 vertical-align: top;
992                         }
993
994                         th
995                         {
996                                 font-weight: bold;
997                                 text-align: center;
998                         }
999                 }
1000                 //-->
1001                 </style>
1002         </head>
1003
1004         <body>
1005 EOF
1006         if ($UserID)
1007         {
1008                 my $search = param ('search') || '';
1009                 print <<EOF;
1010                 <div class="topmenu">
1011                         <form action="$MySelf" method="post">
1012                                 <input type="hidden" name="action" value="browse" />
1013                                 <input type="submit" name="button" value="Browse" />
1014                         </form>
1015                         <form action="$MySelf" method="post">
1016                                 <input type="hidden" name="action" value="search" />
1017                                 <input type="text" name="search" value="$search" />
1018                                 <input type="submit" name="button" value="Search" />
1019                         </form>
1020                         <form action="$MySelf" method="post">
1021                                 <input type="hidden" name="action" value="edit" />
1022                                 <input type="hidden" name="dn" value="" />
1023                                 <input type="submit" name="button" value="Add New" />
1024                         </form>
1025                 </div>
1026 EOF
1027         }
1028         print "\t\t<h1>$title</h1>\n";
1029 }
1030
1031 sub html_end
1032 {
1033         print <<EOF;
1034                 <div class="foot">
1035                         &quot;Lightweight Contact Manager&quot;,
1036                         written 2005 by <a href="http://verplant.org/">Florian octo Forster</a>
1037                         &lt;octo at verplant.org&gt;
1038                 </div>
1039         </body>
1040 </html>
1041 EOF
1042 }
1043
1044 sub pwgen
1045 {
1046         my $len = @_ ? shift : 6;
1047         my $retval = '';
1048
1049         while (!$retval)
1050         {
1051                 my $numbers = 0;
1052                 my $lchars  = 0;
1053                 my $uchars  = 0;
1054                 
1055                 while (length ($retval) < $len)
1056                 {
1057                         my $chr = int (rand (128));
1058
1059                         if ($chr >= 48 and $chr < 58)
1060                         {
1061                                 $numbers++;
1062                         }
1063                         elsif ($chr >= 65 and $chr < 91)
1064                         {
1065                                 $uchars++;
1066                         }
1067                         elsif ($chr >= 97 and $chr < 123)
1068                         {
1069                                 $lchars++;
1070                         }
1071                         else
1072                         {
1073                                 next;
1074                         }
1075                         $retval .= chr ($chr);
1076                 }
1077
1078                 $retval = '' if (!$numbers or !$lchars or !$uchars);
1079         }
1080
1081         return ($retval);
1082 }
1083
1084 sub verify_fields
1085 {
1086         my @errors = ();
1087         for (param ('uri'))
1088         {
1089                 my $val = $_;
1090                 next unless ($val);
1091
1092                 if ($val !~ m#^[a-zA-Z]+://#)
1093                 {
1094                         push (@errors, 'URIs have to begin with a protocol, e.g. &quot;http://&quot;, &quot;ftp://&quot; etc.');
1095                         last;
1096                 }
1097         }
1098
1099         for (param ('homephone'), param ('cellphone'), param ('officephone'), param ('fax'))
1100         {
1101                 my $number = $_;
1102                 next unless ($number);
1103
1104                 if ($number !~ m/^\+/)
1105                 {
1106                         push (@errors, 'Telephone numbers have to begin with the country code, e.g. &quot;+49 911 123456&quot;');
1107                         last;
1108                 }
1109         }
1110
1111         print qq(\t\t<div class="error">\n) if (@errors);
1112         for (my $i = 0; $i < scalar (@errors); $i++)
1113         {
1114                 my $e = $errors[$i];
1115
1116                 print "<br />\n" if ($i);
1117                 print "\t\t\t$e";
1118         }
1119         print qq(\n\t\t</div>\n\n) if (@errors);
1120
1121         return (scalar (@errors));
1122 }
1123
1124 sub get_contacts
1125 {
1126         my $contacts = @_ ? shift : {};
1127
1128         for (@MultiFields)
1129         {
1130                 my $field = $_;
1131                 my @values = grep { $_ } (param ($field));
1132
1133                 next unless (@values);
1134
1135                 if ($field eq 'homephone' or $field eq 'cellphone' or $field eq 'officephone' or $field eq 'fax')
1136                 {
1137                         for (@values)
1138                         {
1139                                 $_ =~ s/\D//g;
1140                                 $_ = '+' . $_;
1141                         }
1142                 }
1143                 
1144                 $contacts->{$field} = [@values] if (@values);
1145         }
1146
1147         return ($contacts);
1148 }