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