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