Changed structure of perl modules to be more useful.
[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                 push (@values, '');
483                 
484                 for (@values)
485                 {
486                         my $value = $_;
487
488                         print <<EOF;
489                         <tr>
490                                 <th>$print</th>
491                                 <td><input type="text" name="$field" value="$value" /></td>
492                         </tr>
493 EOF
494                 }
495         }
496
497         print <<EOF;
498                         <tr>
499                                 <th colspan="2" class="menu">
500 EOF
501         if ($UserID)
502         {
503                 print <<EOF;
504                                         <input type="submit" name="button" value="Cancel" />
505                                         <input type="submit" name="button" value="Apply" />
506 EOF
507         }
508         print <<EOF;
509                                         <input type="submit" name="button" value="Save" />
510                                 </th>
511                         </tr>
512                 </table>
513                 </form>
514 EOF
515 }
516
517 sub action_save
518 {
519         my $cn = $UserID ? param ('cn') : $UserCN;
520
521         if (verify_fields ())
522         {
523                 action_edit (cn => $cn);
524                 return;
525         }
526
527         if ($cn)
528         {
529                 action_update ();
530                 return;
531         }
532
533         die unless ($UserID);
534
535         my $button = lc (param ('button'));
536         $button ||= 'save';
537
538         if ($button eq 'cancel')
539         {
540                 action_browse ();
541                 return;
542         }
543
544         if (!param ('lastname') or !param ('firstname'))
545         {
546                 print qq(\t<div class="error">You have to give both, first and lastname, to identify this record.</div>\n);
547                 action_edit (cn => '');
548                 return;
549         }
550
551         my $lastname  = param ('lastname');
552         my $firstname = param ('firstname');
553
554         my $contacts = get_contacts ();
555
556         my $person = LiCoM::Person->create (lastname => $lastname, firstname => $firstname, %$contacts);
557
558         if (!$person)
559         {
560                 print qq(\t<div class="error">Unable to save entry. Sorry.</div>\n);
561                 return;
562         }
563         
564         $cn = $person->name ();
565
566         if ($button eq 'apply')
567         {
568                 action_edit (cn => $cn);
569         }
570         else
571         {
572                 action_detail ($cn);
573         }
574 }
575
576 sub action_update
577 {
578         my $cn = $UserID ? param ('cn') : $UserCN;
579         my $person = LiCoM::Person->load ($cn);
580
581         die unless ($person);
582
583         my $button = lc (param ('button'));
584         $button ||= 'save';
585
586         if ($UserID and $button eq 'cancel')
587         {
588                 action_detail ($cn);
589                 return;
590         }
591
592         if ($UserID)
593         {
594                 my $lastname  = param ('lastname');
595                 my $firstname = param ('firstname');
596
597                 $person->lastname  ($lastname)  if ($lastname  and $lastname  ne $person->lastname ());
598                 $person->firstname ($firstname) if ($firstname and $firstname ne $person->firstname ());
599
600                 $cn = $person->name ();
601         }
602
603         my $contacts = get_contacts ();
604
605         for (@MultiFields)
606         {
607                 my $field = $_;
608                 
609                 if (defined ($contacts->{$field}))
610                 {
611                         my $values = $contacts->{$field};
612                         $person->set ($field, $values);
613                 }
614                 else
615                 {
616                         $person->set ($field, []);
617                 }
618         }
619
620         if ($button eq 'apply' or !$UserID)
621         {
622                 action_edit (cn => $cn);
623         }
624         else
625         {
626                 action_detail ($cn);
627         }
628 }
629
630 sub action_vcard
631 {
632         my $cn = param ('cn');
633         $cn = shift if (@_);
634         die unless ($cn);
635
636         my $person = LiCoM::Person->load ($cn);
637         die unless ($person);
638
639         my %vcard_types =
640         (
641                 homephone       => 'TEL;TYPE=home,voice',
642                 cellphone       => 'TEL;TYPE=cell',
643                 officephone     => 'TEL;TYPE=work,voice',
644                 fax             => 'TEL;TYPE=fax',
645                 mail            => 'EMAIL',
646                 uri             => 'URL',
647                 group           => 'ORG'
648         );
649
650         my $sn = $person->lastname ();
651         my $gn = $person->firstname ();
652         my $cn_esc = uri_escape ($cn);
653
654         print <<EOF;
655 Content-Type: text/x-vcard
656 Content-Disposition: attachment; filename="$cn.vcf"
657
658 BEGIN:VCARD
659 VERSION:3.0
660 FN: $cn
661 N: $sn;$gn
662 EOF
663
664         for (@MultiFields)
665         {
666                 my $field = $_;
667                 my $vc_fld = $vcard_types{$field};
668                 my $values = $person->get ($field);
669
670                 for (@$values)
671                 {
672                         my $value = $_;
673                         print "$vc_fld:$value\n";
674                 }
675         }
676         print "END:VCARD\n";
677 }
678
679 sub action_verify
680 {
681         my $cn = param ('cn');
682         $cn = shift if (@_);
683         die unless ($cn);
684
685         my $person = LiCoM::Person->load ($cn);
686         die unless ($person);
687
688         my ($mail) = $person->get ('mail');
689         $mail ||= '';
690
691         my $message;
692         my $password = $person->password ();
693
694         if (!$password)
695         {
696                 $password = pwgen ();
697                 $person->password ($password);
698         }
699
700         $message = qq(The password for the record &quot;$cn&quot; is &quot;$password&quot;.);
701
702         if ($mail)
703         {
704                 if (action_verify_send_mail ($person))
705                 {
706                         $message .= qq( A request for verification has been sent to $mail.);
707                 }
708         }
709         else
710         {
711                 $message .= q( There was no e-mail address, thus no verification request could be sent.);
712         }
713
714         print qq(\t\t<div class="message">$message</div>\n);
715
716         action_detail ($cn);
717 }
718
719 sub action_verify_send_mail
720 {
721         my $person = shift;
722         my $owner = LiCoM::Person->load ($UserCN);
723         my $smh;
724
725         my ($owner_mail) = $owner->get ('mail');
726         if (!$owner_mail)
727         {
728                 my $cn = uri_escape ($UserCN);
729                 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);
730                 return (0);
731         }
732
733         my $max_width = 0;
734         for (keys %FieldNames)
735         {
736                 $max_width = length $FieldNames{$_} if ($max_width < length $FieldNames{$_});
737         }
738         $max_width++;
739
740         my $person_name = $person->name ();
741         my ($person_mail) = $person->get ('mail');
742         my $person_gn = $person->firstname ();
743         my $password = $person->password ();
744
745         my $host = $ENV{'HTTP_HOST'};
746         my $url = 'http://' . $host . $MySelf;
747         
748         open ($smh, "| /usr/sbin/sendmail -t -f $owner_mail") or die ("open pipe to sendmail: $!");
749         print $smh <<EOM;
750 To: $person_name <$person_mail>
751 From: $UserCN <$owner_mail>
752 Subject: Please verify our entry in my address book
753
754 Hello $person_gn,
755
756 the following is your entry in my address book:
757 EOM
758         for (@MultiFields)
759         {
760                 my $field = $_;
761                 my $print = defined ($FieldNames{$field}) ? $FieldNames{$field} : $field;
762                 my @values = $person->get ($field);
763
764                 for (@values)
765                 {
766                         printf $smh ('%'.$max_width."s: %-s\n", $print, $_);
767                 }
768         }
769         print $smh <<EOM;
770
771 If this entry is outdated or incomplete, please take a minute and correct it.
772   Address:  $url
773  Username: $person_name
774  Password: $password
775
776 Thank you very much :) Regards,
777 $UserCN
778 EOM
779         close ($smh);
780
781         return (1);
782 }
783
784 sub html_start
785 {
786         my $title = shift;
787         $title = q(Lightweight Contact Manager) unless ($title);
788
789         print <<EOF;
790 Content-Type: text/html; charset=UTF-8
791
792 <html>
793         <head>
794                 <title>$title</title>
795                 <style type="text/css">
796                 <!--
797                 \@media screen
798                 {
799                         a
800                         {
801                                 color: blue;
802                                 background-color: inherit;
803                                 text-decoration: none;
804                         }
805
806                         a:hover
807                         {
808                                 text-decoration: underline;
809                         }
810
811                         a:visited
812                         {
813                                 color: navy;
814                                 background-color: inherit;
815                         }
816
817                         body
818                         {
819                                 color: black;
820                                 background-color: white;
821                         }
822
823                         div.error
824                         {
825                                 color: red;
826                                 background-color: yellow;
827
828                                 font-weight: bold;
829                                 padding: 1ex;
830                                 border: 2px solid red;
831                         }
832
833                         div.foot
834                         {
835                                 color: gray;
836                                 background-color: white;
837
838                                 position: fixed;
839                                 top: auto;
840                                 right: 0px;
841                                 bottom: 0px;
842                                 left: 0px;
843
844                                 font-size: x-small;
845                                 text-align: right;
846                                 border-top: 1px solid black;
847                                 width: 100%;
848                         }
849
850                         div.foot a
851                         {
852                                 color: black;
853                                 background-color: inherit;
854                                 text-decoration: none;
855                         }
856
857                         div.foot a:hover
858                         {
859                                 text-decoration: underline;
860                         }
861
862                         div.menu
863                         {
864                                 border-top: 1px solid black;
865                                 margin-top: 1ex;
866                                 font-weight: bold;
867                         }
868
869                         div.menu a
870                         {
871                                 color: blue;
872                                 background-color: transparent;
873                         }
874
875                         div.topmenu
876                         {
877                                 margin-bottom: 1ex;
878                                 padding-bottom: 1ex;
879                                 border-bottom: 1px solid black;
880                         }
881
882                         div.topmenu form
883                         {
884                                 display: inline;
885                                 margin-right: 5ex;
886                         }
887
888                         h1
889                         {
890                                 position: absolute;
891                                 top: 1ex;
892                                 right: 1ex;
893                                 bottom: auto;
894                                 left: auto;
895
896                                 font-size: 100%;
897                                 font-weight: bold;
898                         }
899
900                         img
901                         {
902                                 border: none;
903                         }
904
905                         table.list
906                         {
907                                 width: 100%;
908                         }
909
910                         table.list td
911                         {
912                                 empty-cells: show;
913                         }
914
915                         td
916                         {
917                                 color: black;
918                                 background-color: #cccccc;
919                                 vertical-align: top;
920                         }
921
922                         th
923                         {
924                                 color: black;
925                                 background-color: #999999;
926                                 padding: 0.3ex;
927                                 text-align: left;
928                                 vertical-align: top;
929                         }
930                 }
931
932                 \@media print
933                 {
934                         a
935                         {
936                                 color: inherit;
937                                 background-color: inherit;
938                                 text-decoration: underline;
939                         }
940                         
941                         div.topmenu, div.menu
942                         {
943                                 display: none;
944                         }
945
946                         div.foot
947                         {
948                                 font-size: 50%;
949                                 text-align: right;
950                         }
951
952                         h1
953                         {
954                                 display: none;
955                         }
956
957                         h2
958                         {
959                                 font-size: 100%;
960                         }
961
962                         table
963                         {
964                                 border-collapse: collapse;
965                         }
966
967                         table.list
968                         {
969                                 width: 100%;
970                         }
971
972                         table.list td
973                         {
974                                 empty-cells: show;
975                         }
976
977                         table.list th
978                         {
979                                 border-bottom-width: 2px;
980                         }
981
982                         td, th
983                         {
984                                 border: 1px solid black;
985                                 vertical-align: top;
986                         }
987
988                         th
989                         {
990                                 font-weight: bold;
991                                 text-align: center;
992                         }
993                 }
994                 //-->
995                 </style>
996         </head>
997
998         <body>
999 EOF
1000         if ($UserID)
1001         {
1002                 my $search = param ('search') || '';
1003                 print <<EOF;
1004                 <div class="topmenu">
1005                         <form action="$MySelf" method="post">
1006                                 <input type="hidden" name="action" value="browse" />
1007                                 <input type="submit" name="button" value="Browse" />
1008                         </form>
1009                         <form action="$MySelf" method="post">
1010                                 <input type="hidden" name="action" value="search" />
1011                                 <input type="text" name="search" value="$search" />
1012                                 <input type="submit" name="button" value="Search" />
1013                         </form>
1014                         <form action="$MySelf" method="post">
1015                                 <input type="hidden" name="action" value="edit" />
1016                                 <input type="hidden" name="dn" value="" />
1017                                 <input type="submit" name="button" value="Add New" />
1018                         </form>
1019                 </div>
1020 EOF
1021         }
1022         print "\t\t<h1>$title</h1>\n";
1023 }
1024
1025 sub html_end
1026 {
1027         print <<EOF;
1028                 <div class="foot">
1029                         &quot;Lightweight Contact Manager&quot;,
1030                         written 2005 by <a href="http://verplant.org/">Florian octo Forster</a>
1031                         &lt;octo at verplant.org&gt;
1032                 </div>
1033         </body>
1034 </html>
1035 EOF
1036 }
1037
1038 sub pwgen
1039 {
1040         my $len = @_ ? shift : 6;
1041         my $retval = '';
1042
1043         while (!$retval)
1044         {
1045                 my $numbers = 0;
1046                 my $lchars  = 0;
1047                 my $uchars  = 0;
1048                 
1049                 while (length ($retval) < $len)
1050                 {
1051                         my $chr = int (rand (128));
1052
1053                         if ($chr >= 48 and $chr < 58)
1054                         {
1055                                 $numbers++;
1056                         }
1057                         elsif ($chr >= 65 and $chr < 91)
1058                         {
1059                                 $uchars++;
1060                         }
1061                         elsif ($chr >= 97 and $chr < 123)
1062                         {
1063                                 $lchars++;
1064                         }
1065                         else
1066                         {
1067                                 next;
1068                         }
1069                         $retval .= chr ($chr);
1070                 }
1071
1072                 $retval = '' if (!$numbers or !$lchars or !$uchars);
1073         }
1074
1075         return ($retval);
1076 }
1077
1078 sub verify_fields
1079 {
1080         my @errors = ();
1081         for (param ('uri'))
1082         {
1083                 my $val = $_;
1084                 next unless ($val);
1085
1086                 if ($val !~ m#^[a-zA-Z]+://#)
1087                 {
1088                         push (@errors, 'URIs have to begin with a protocol, e.g. &quot;http://&quot;, &quot;ftp://&quot; etc.');
1089                         last;
1090                 }
1091         }
1092
1093         for (param ('homephone'), param ('cellphone'), param ('officephone'), param ('fax'))
1094         {
1095                 my $number = $_;
1096                 next unless ($number);
1097
1098                 if ($number !~ m/^\+/)
1099                 {
1100                         push (@errors, 'Telephone numbers have to begin with the country code, e.g. &quot;+49 911 123456&quot;');
1101                         last;
1102                 }
1103         }
1104
1105         print qq(\t\t<div class="error">\n) if (@errors);
1106         for (my $i = 0; $i < scalar (@errors); $i++)
1107         {
1108                 my $e = $errors[$i];
1109
1110                 print "<br />\n" if ($i);
1111                 print "\t\t\t$e";
1112         }
1113         print qq(\n\t\t</div>\n\n) if (@errors);
1114
1115         return (scalar (@errors));
1116 }
1117
1118 sub get_contacts
1119 {
1120         my $contacts = @_ ? shift : {};
1121
1122         for (@MultiFields)
1123         {
1124                 my $field = $_;
1125                 my @values = grep { $_ } (param ($field));
1126
1127                 next unless (@values);
1128
1129                 if ($field eq 'homephone' or $field eq 'cellphone' or $field eq 'officephone' or $field eq 'fax')
1130                 {
1131                         for (@values)
1132                         {
1133                                 $_ =~ s/\D//g;
1134                                 $_ = '+' . $_;
1135                         }
1136                 }
1137                 
1138                 $contacts->{$field} = [@values] if (@values);
1139         }
1140
1141         return ($contacts);
1142 }