source: trunk/lib/Vote/Model/Vote.pm @ 99

Last change on this file since 99 was 99, checked in by nanardon, 15 years ago
  • change also ballot_item having a remap value equal to one we want to remap
File size: 20.4 KB
Line 
1package Vote::Model::Vote;
2
3use strict;
4use warnings;
5use base 'Catalyst::Model';
6use Vote;
7use DBI;
8use Mail::Mailer;
9
10=head1 NAME
11
12Vote::Model::Vote - Catalyst Model
13
14=head1 DESCRIPTION
15
16Catalyst Model.
17
18=cut
19
20sub new {
21    my ($class) = @_;
22    my $db = DBI->connect(
23        'dbi:Pg:' . Vote->config->{db},
24        undef, undef,
25        {
26            RaiseError => 0,
27            AutoCommit => 0,
28            PrintWarn => 0,
29            PrintError => 1,
30        }
31    ) or return;
32    $db->do(q{set DATESTYLE to 'DMY'});
33   
34    bless {
35        db => $db,
36    }, $class;
37}
38
39sub db { $_[0]->{db} }
40
41sub mail_header {
42    return(
43        'Content-Type' => 'text/plain; charset=UTF-8; format=flowed',
44        'Content-Transfer-Encoding' => '8bit',
45        'X-Epoll-version' => $Vote::VERSION,
46    );
47}
48
49sub random_string {
50    my $lenght = $_[-1] || 8;
51
52    return join('', map { ('a'..'z', 'A'..'Z', 0..9)[rand 62] } (1..$lenght));
53}
54
55sub gen_enc_passwd {
56    my ($self, $passwd) = @_;
57
58    $passwd ||= random_string(8);
59    return(crypt($passwd, '$1$' . random_string(8) . '$'));
60}
61
62sub list_comming_vote {
63    my ($self) = @_;
64
65    my $sth = $self->db->prepare_cached(
66        q{
67        select id from poll where
68        (start > now() and "end" > now()) or
69        "end" is null or start is null
70        }
71    );
72
73    $sth->execute;
74    my @id;
75    while(my $res = $sth->fetchrow_hashref) {
76        push(@id, $res->{id});
77    }
78
79    @id
80}
81
82
83sub list_running_vote {
84    my ($self) = @_;
85
86    my $sth = $self->db->prepare_cached(
87        q{
88        select id from poll where start < now() and "end" > now()
89        }
90    );
91
92    $sth->execute;
93    my @id;
94    while(my $res = $sth->fetchrow_hashref) {
95        push(@id, $res->{id});
96    }
97
98    @id
99}
100
101sub list_closed_vote {
102    my ($self) = @_;
103
104    my $sth = $self->db->prepare_cached(
105        q{
106        select id from poll where
107        start < now() and "end" < now()
108        }
109    );
110
111    $sth->execute;
112    my @id;
113    while(my $res = $sth->fetchrow_hashref) {
114        push(@id, $res->{id});
115    }
116
117    @id
118}
119
120sub vote_param {
121    my ($self, $voteid, %attr) = @_;
122
123    keys %attr or return;
124    my @online_f = qw(label start end owner password);
125
126    if (grep { exists($attr{$_}) } @online_f) {
127        my $sth = $self->db->prepare_cached(
128            q{update poll set } .
129            join(',', map { qq("$_" = ?) } grep { exists $attr{$_} } @online_f) .
130            q{ where id = ?}
131        );
132        $sth->execute((map { $attr{$_} } grep { exists $attr{$_} } @online_f), $voteid)
133            or do {
134            $self->db->rollback;
135            return;
136        };
137    }
138
139    # vote settings in settings table
140    foreach my $var (keys %attr) {
141        grep { $var eq $_ } @online_f and next;
142        $self->vote_set_settings($voteid, $var, $attr{$var});
143    }
144    1
145}
146
147sub vote_status {
148    my ($self, $id) = @_;
149   
150    my $sth = $self->db->prepare_cached(
151        q{
152        select (start > now() or start is null) as before,
153               "end" < now() as after
154        from poll
155        where id = ?
156        }
157    );
158    $sth->execute($id);
159    my $res = $sth->fetchrow_hashref;
160    $sth->finish;
161    $res or return;
162    if ($res->{before}) {
163        return 'BEFORE';
164    } elsif ($res->{after}) {
165        return 'AFTER';
166    } else {
167        return 'RUNNING';
168    }
169}
170
171sub vote_info {
172    my ($self, $id) = @_;
173
174    my $sth = $self->db->prepare_cached(
175        q{
176        select *,
177        to_char("start", 'DD/MM/YYYY') as dstart,
178        to_char("start", 'HH24:MI:SS') as hstart,
179        to_char("end", 'DD/MM/YYYY') as dend,
180        to_char("end", 'HH24:MI:SS') as hend
181        from poll where id = ?
182        }
183    );
184
185    $sth->execute($id);
186    my $res = $sth->fetchrow_hashref;
187    $sth->finish;
188    if ($res) {
189        my $get = $self->db->prepare_cached(
190            q{select var, val from settings where poll = ?}
191        );
192        $get->execute($id);
193        while (my $set = $get->fetchrow_hashref) {
194            $res->{$set->{var}} = $set->{val};
195        }
196    }
197    $res->{free_choice} ||= 0; # avoiding undef
198    $res
199}
200
201sub vote_set_settings {
202    my ($self, $poll, $var, $val) = @_;
203
204    my $upd = $self->db->prepare_cached(
205        q{update settings set val = ? where poll = ? and var = ?}
206    );
207
208    if ($upd->execute($val, $poll, $var) == 0) {
209        my $add = $self->db->prepare_cached(
210            q{insert into settings (poll, var, val) values (?,?,?)}
211        );
212
213        $add->execute($poll, $var, $val);
214    }
215}
216
217sub vote_signing {
218    my ($self, $id) = @_;
219
220    my $sth = $self->db->prepare_cached(
221        q{
222        select *, voting.key as vkey from voting left join signing
223        on signing.key = voting.key
224        where poll = ? order by voting.mail
225        }
226    );
227    $sth->execute($id);
228    my @people;
229    while (my $res = $sth->fetchrow_hashref) {
230        push(@people, $res);
231    }
232    @people
233}
234
235sub vote_voting {
236    my ($self, $voteid) = @_;
237
238    my $sth = $self->db->prepare_cached(
239        q{
240        select key from voting
241        where poll = ? order by voting.mail
242        }
243    );
244    $sth->execute($voteid);
245    my @people;
246    while (my $res = $sth->fetchrow_hashref) {
247        push(@people, $res->{key});
248    }
249    @people
250}
251
252sub voting_info {
253    my ($self, $id) = @_;
254
255    my $sth = $self->db->prepare_cached(
256        q{
257        select *, voting.key as vkey from voting left join signing
258        on signing.key = voting.key
259        where voting.key = ?
260        }
261    );
262    $sth->execute($id);
263
264    my $res = $sth->fetchrow_hashref;
265    $sth->finish;
266    $res
267}
268
269sub vote_choices {
270    my ($self, $id) = @_;
271
272    my $sth = $self->db->prepare_cached(
273        q{
274        select key from choice where poll = ?
275        order by label
276        }
277    );
278    $sth->execute($id);
279    my @ch;
280    while (my $res = $sth->fetchrow_hashref) {
281        push(@ch, $res->{key});
282    }
283    @ch
284}
285
286sub choice_info {
287    my ($self, $chid) = @_;
288    my $sth = $self->db->prepare_cached(
289        q{select * from choice where key = ?}
290    );
291    $sth->execute($chid);
292    my $res = $sth->fetchrow_hashref;
293    $sth->finish;
294    $res
295}
296
297sub vote_add_choice {
298    my ($self, $voteid, $label) = @_;
299
300    my $sth = $self->db->prepare_cached(
301        q{insert into choice (poll, label) values (?,?)}
302    );
303
304    $sth->execute($voteid, $label) or do {
305        $self->db->rollback;
306        return;
307    };
308
309    1
310}
311
312sub modify_choice {
313    my ($self, $chid, $label) = @_;
314
315    my $sth = $self->db->prepare_cached(
316        q{update choice set label = ? where key = ?}
317    );
318    $sth->execute($label, $chid);
319}
320
321sub delete_choice {
322    my ($self, $chid) = @_;
323
324    my $sth = $self->db->prepare_cached(
325        q{delete from choice where key = ?}
326    );
327
328    $sth->execute($chid);
329}
330
331sub voting_info_id {
332    my ($self, $mail, $voteid) = @_;
333
334    my $sth = $self->db->prepare_cached(
335        q{
336        select * from voting where mail = ? and poll = ?
337        }
338    );
339    $sth->execute($mail, $voteid);
340    my $res = $sth->fetchrow_hashref();
341    $sth->finish;
342    $res
343}
344
345sub _register_signing {
346    my ($self, $mail, $voteid, $referal) = @_;
347
348    my $vinfo = $self->voting_info_id($mail, $voteid) or return;
349
350    my $sth = $self->db->prepare_cached(
351        q{
352        insert into signing (key, referal) values (?,?)
353        }
354    );
355    $sth->execute($vinfo->{key}, $referal) or do {
356        $self->db->rollback;
357        return;
358    };
359
360    1;
361}
362
363sub gen_uid {
364    unpack("H*", join("", map { chr(rand(256)) } (0..15)))
365}
366
367sub _register_ballot {
368    my ($self, $voteid, $choice, $fchoice) = @_;
369
370    my $addb = $self->db->prepare_cached(
371        q{
372        insert into ballot (id, poll, invalid) values (?,?,?)
373        }
374    );
375    my $uid = gen_uid;
376    $addb->execute($uid, $voteid, scalar(@{$fchoice || []}) ? undef : 'f') or do {
377        self->db->rollback;
378        return;
379    };
380
381    my $addbc = $self->db->prepare_cached(
382        q{
383        insert into ballot_item (id, value, fromlist) values (?,?,?)
384        }
385    );
386    foreach (@{ $choice || []}) {
387        $addbc->execute($uid, $_, 't') or do {
388            $self->db->rollback;
389            return;
390        };
391    }
392    foreach (@{ $fchoice || []}) {
393        $_ or next;
394        $addbc->execute($uid, $_, 'f') or do {
395            $self->db->rollback;
396            return;
397        };
398    }
399
400    $uid;
401}
402
403sub register_ballot {
404    my ($self, $vid, $voteid, $choice, $fchoice, $referal) = @_;
405
406    my $uid;
407    for (0..2) { # 3 try
408    # First we register voting has voted
409    $self->_register_signing($vid, $voteid, $referal) or return; # TODO error ?
410
411    # registring choices
412    $uid = $self->_register_ballot($voteid, $choice, $fchoice);
413    defined($uid) and last;
414
415    }
416    # everything went fine, saving!
417    $self->db->commit;
418
419   
420    $uid
421}
422
423sub mail_ballot_confirm {
424    my ($self, $vid, $voteid, $info) = @_;
425    my $voteinfo = $self->vote_info($voteid) or return;
426    $info->{ballotid} or return;
427    my $mailer = new Mail::Mailer 'smtp', Server => (Vote->config->{smtp} || 'localhost');
428    $mailer->open({
429        From => $vid, # TODO allow to configure this
430        To => $vid,
431        Subject => 'Confirmation de vote: ' . $voteinfo->{label},
432        mail_header(),
433    });
434    print $mailer <<EOF;
435
436Vous venez de participer au vote:
437
438--------
439$voteinfo->{label}
440--------
441
442Votre bulletin est idéntifié sous le numéro:
443$info->{ballotid}
444
445Les résultats seront disponibles à cet url:
446$info->{url}
447
448Cordialement.
449EOF
450    $mailer->close
451        or warn "couldn't send whole message: $!\n";
452
453}
454
455sub vote_voting_count {
456    my ($self, $id) = @_;
457
458    my $sth = $self->db->prepare_cached(
459        q{
460        select count(*) from voting
461        where poll = ?
462        }
463    );
464    $sth->execute($id);
465    my $res = $sth->fetchrow_hashref;
466    $sth->finish;
467    $res->{count}
468}
469
470sub signing_count { vote_signing_count(@_) }
471
472sub vote_signing_count {
473    my ($self, $voteid) = @_;
474
475    my $sth = $self->db->prepare_cached(
476        q{
477        select count(*) from signing join voting
478        on voting.key = signing.key where poll = ?
479        }
480    );
481
482    $sth->execute($voteid);
483    my $res = $sth->fetchrow_hashref;
484    $sth->finish;
485    $res->{count}
486}
487
488sub ballot_count { vote_ballot_count(@_) }
489
490sub vote_ballot_count {
491    my ($self, $voteid) = @_;
492
493    my $sth = $self->db->prepare_cached(
494        q{
495        select count(*) from ballot where poll = ?
496        }
497    );
498
499    $sth->execute($voteid);
500    my $res = $sth->fetchrow_hashref;
501    $sth->finish;
502    $res->{count}
503}
504
505sub ballot_count_nonull { vote_ballot_count_nonull(@_) }
506
507sub vote_ballot_count_nonull {
508    my ($self, $voteid) = @_;
509
510    my $sth = $self->db->prepare_cached(
511        q{
512        select count(*) from ballot where poll = ?
513        and id in (select id from ballot_item) and
514        (invalid = 'false' or invalid is null)
515        }
516    );
517
518    $sth->execute($voteid);
519    my $res = $sth->fetchrow_hashref;
520    $sth->finish;
521    $res->{count}
522}
523
524sub auth_voting {
525    my ($self, $poll, $mail, $password) = @_;
526    my $userinfo = $self->voting_info_id($mail, $poll) or return;
527
528    $userinfo->{passwd} or return;
529    if (crypt($password, $userinfo->{passwd} || '') eq $userinfo->{passwd}) {
530        return 1;
531    } else {
532        return 0;
533    }
534}
535
536sub auth_poll {
537    my ($self, $voteid, $passwd) = @_;
538
539    my $vinfo = $self->vote_info($voteid) or return;
540
541    $vinfo->{password} or return;
542    $passwd or return;
543    if (crypt($passwd, $vinfo->{password} || '') eq $vinfo->{password}) {
544        return 1;
545    } else {
546        return 0;
547    }
548}
549
550sub voting_has_sign {
551    my ($self, $poll, $user) = @_;
552
553    my $sth = $self->db->prepare_cached(
554        q{
555        select date from signing join voting
556        on voting.key = signing.key
557        where poll = ? and mail = ?
558        }
559    );
560
561    $sth->execute($poll, $user);
562    my $res = $sth->fetchrow_hashref;
563    $sth->finish;
564    return $res->{date}
565}
566
567# Requete de decompte des voix:
568
569sub vote_results_count {
570    my ($self, $voteid) = @_;
571
572    my $sth = $self->db->prepare(
573        q{
574        select count(ballot.id), value from ballot left join ballot_item
575        on ballot.id = ballot_item.id where ballot.poll = ? and invalid = 'false'
576        group by value
577        order by count
578        }
579    );
580    $sth->execute($voteid);
581    my @results;
582    while (my $res = $sth->fetchrow_hashref) {
583        push(@results, $res);
584    }
585    @results;
586}
587
588sub vote_results_nonull {
589    my ($self, $voteid) = @_;
590
591    my $sth = $self->db->prepare(
592        q{
593        select count(ballot.id), coalesce(corrected, value) as value
594        from ballot join ballot_item
595        on ballot.id = ballot_item.id where ballot.poll = ? and
596        (invalid = 'false' or invalid is null)
597        group by coalesce(corrected, value)
598        order by count desc
599        }
600    );
601    $sth->execute($voteid);
602    my @results;
603    while (my $res = $sth->fetchrow_hashref) {
604        push(@results, $res);
605    }
606    \@results;
607}
608
609sub list_vote_ballot {
610    my ($self, $voteid) = @_;
611
612    my $sth = $self->db->prepare_cached(
613        q{
614        select id from ballot where poll = ?
615        order by id
616        }
617    );
618    $sth->execute($voteid);
619    my @ids;
620    while (my $res = $sth->fetchrow_hashref) {
621        push(@ids, $res->{id});
622    }
623    @ids
624}
625
626sub list_vote_ballot_needvalid {
627    my ($self, $voteid) = @_;
628
629    my $sth = $self->db->prepare_cached(
630        q{
631        select id from ballot where poll = ?
632        and invalid is null order by id
633        }
634    );
635    $sth->execute($voteid);
636    my @ids;
637    while (my $res = $sth->fetchrow_hashref) {
638        push(@ids, $res->{id});
639    }
640    @ids
641}
642
643sub ballot_info {
644    my ($self, $ballotid) = @_;
645
646    my $sth = $self->db->prepare_cached(
647        q{ select * from ballot where id = ? }
648    );
649
650    $sth->execute($ballotid);
651    my $res = $sth->fetchrow_hashref;
652    $sth->finish;
653    $res
654}
655
656sub mark_ballot_invalid {
657    my ($self, $ballotid, $invalid) = @_;
658
659    my $sth = $self->db->prepare_cached(
660        q{update ballot set invalid = ? where id = ?}
661    );
662
663    $sth->execute($invalid ? 't' : 'f', $ballotid);
664}
665
666sub ballot_items {
667    my ($self, $ballotid) = @_;
668
669    my $sth = $self->db->prepare_cached(
670        q{select *, value as v from ballot_item where id = ?}
671    );
672    $sth->execute($ballotid);
673    my @ids;
674    while (my $res = $sth->fetchrow_hashref) {
675        push(@ids, $res);
676    }
677    \@ids
678}
679
680sub vote_ballot_untrusted_values {
681    my ($self, $voteid) = @_;
682
683    my $getval = $self->db->prepare_cached(
684        q{
685        select value from ballot join ballot_item
686        on ballot.id = ballot_item.id
687        where poll = ? and fromlist = false and corrected is null
688        group by value order by value
689        }
690    );
691    $getval->execute($voteid);
692    my @vals;
693    while (my $res = $getval->fetchrow_hashref) {
694        push(@vals, $res->{value});
695    }
696    @vals
697}
698
699sub vote_ballot_values {
700    my ($self, $voteid) = @_;
701
702    my $getval = $self->db->prepare_cached(
703        q{
704        select coalesce(corrected, value) as value from ballot join ballot_item
705        on ballot.id = ballot_item.id
706        where poll = ?
707        group by coalesce(corrected, value) order by coalesce(corrected, value)
708        }
709    );
710    $getval->execute($voteid);
711    my @vals;
712    while (my $res = $getval->fetchrow_hashref) {
713        push(@vals, $res->{value});
714    }
715    @vals
716}
717
718sub vote_map_value {
719    my ($self, $voteid, $from, $to) = @_;
720
721    my $sth = $self->db->prepare_cached(
722        q{
723        update ballot_item set corrected = ? where
724        id in (select id from ballot where poll = ?)
725        and (value = ? or corrected = ?)
726        }
727    );
728
729    $sth->execute($to, $voteid, $from, $from) or $self->db->rollback;
730    $self->db->commit;
731}
732
733sub addupd_voting {
734    my ($self, $voteid, $mail, $id) = @_;
735
736    my $upd = $self->db->prepare_cached(
737        q{
738        update voting set label = ? where mail = ? and poll = ?
739        }
740    );
741
742    if ($upd->execute($id || '', $mail, $voteid) == 0) {
743        my $add = $self->db->prepare_cached(q{
744            insert into voting (poll, label, mail) values (?,?,?)
745        });
746
747        $add->execute($voteid, $id || '', $mail);
748    }
749}
750
751sub delete_voting {
752    my ($self, $key) = @_;
753
754    $self->voting_has_sign($key) and return;
755    my $sth = $self->db->prepare_cached(
756        q{delete from voting where key = ?}
757    );
758
759    $sth->execute($key);
760}
761
762sub voting_from_file {
763    my ($self, $voteid, $fh, $delete) = @_;
764
765    if ($delete) {
766        my $sth = $self->db->prepare(q{delete from voting where poll = ?});
767        $sth->execute($voteid);
768    }
769
770    while (my $line = <$fh>) {
771        chomp($line);
772        my ($mail, $name) = split(';', $line);
773        $mail or do {
774            $self->db->rollback;
775            return;
776        };
777        $self->addupd_voting($voteid, $mail, $name || '');
778    }
779    1;
780}
781
782sub mail_passwd_ifnul {
783    my ($self, $voteid, $mailinfo) = @_;
784
785    my $list_voting = $self->db->prepare_cached(
786        q{select key from voting where poll = ? and passwd is null or passwd = ''}
787    );
788
789    $list_voting->execute($voteid);
790    while (my $res = $list_voting->fetchrow_hashref) {
791        $self->mail_voting_passwd($res->{key}, $mailinfo);
792    }
793}
794
795sub mail_voting_passwd {
796    my ($self, $id, $mailinfo) = @_;
797   
798    my $vinfo = $self->voting_info($id) or return;
799    my $voteinfo = $self->vote_info($vinfo->{poll});
800
801    my $passwd = random_string(8);
802    my $encpasswd = $self->gen_enc_passwd($passwd);
803
804    my $upd_voting = $self->db->prepare_cached(
805        q{update voting set passwd = ? where key = ?}
806    );
807
808    $upd_voting->execute($encpasswd, $id);
809
810    my $date = $voteinfo->{dstart} && $voteinfo->{dend}
811        ? sprintf("\n" . 'Vous pourrez voter entre le %s %s et le %s %s' . "\n",
812            $voteinfo->{dstart}, $voteinfo->{hstart}, $voteinfo->{dend}, $voteinfo->{hend})
813        : '';
814
815    # TODO complete this properly:
816    my $mailer = new Mail::Mailer 'smtp', Server => (Vote->config->{smtp} || 'localhost');
817    $mailer->open({
818        From => $voteinfo->{owner},
819        To => $vinfo->{mail},
820        Subject => 'Invitation a voter: ' . $voteinfo->{label},
821        'X-Epoll-poll' => $id,
822        mail_header(),
823    });
824    print $mailer <<EOF;
825Vous êtes convié à participer a ce vote:
826
827--------
828$voteinfo->{label}
829--------
830
831à l'adresse:
832
833$mailinfo->{voteurl}
834$date
835Votre identifiant est: $vinfo->{mail}
836Votre mot de passe est: $passwd
837
838Conserver précieusement ces identifiants, il ne vous seront pas retransmit.
839
840Cordialement.
841EOF
842    $mailer->close or warn "couldn't send whole message: $!\n";
843
844    $self->db->commit;
845}
846
847sub poll_request_info {
848    my ($self, $rid) = @_;
849
850    my $sth = $self->db->prepare_cached(
851        q{select * from poll_request where id = ?}
852    );
853
854    $sth->execute($rid);
855    my $res = $sth->fetchrow_hashref;
856    $sth->finish;
857    $res
858}
859
860sub poll_from_request {
861    my ($self, $rid, $passwd) = @_;
862    my $rinfo = $self->poll_request_info($rid) or return;
863
864    my $encpasswd = $self->gen_enc_passwd($passwd);
865
866    my $getpollid = $self->db->prepare_cached(
867        q{select nextval('poll_id_seq')}
868    );
869    $getpollid->execute();
870    my $newpollid = $getpollid->fetchrow_hashref->{nextval};
871   
872    my $newpoll = $self->db->prepare_cached(
873        q{insert into poll (id, label, owner, password) values (?,?,?,?)}
874    );
875
876    $newpoll->execute($newpollid, $rinfo->{label}, $rinfo->{mail}, $encpasswd);
877    # set some default
878    $self->vote_param($newpollid,
879        free_choice => 0,
880        choice_count => 1,
881    );     
882
883    my $delreq = $self->db->prepare_cached(
884        q{delete from poll_request where id = ?}
885    );
886
887    $delreq->execute($rid);
888    $self->db->commit;
889
890    $newpollid
891}
892
893sub create_poll_request {
894    my ($self, %info) = @_;
895
896    $info{mail} or return;
897    my $addreq = $self->db->prepare_cached(
898        q{insert into poll_request (id, label, mail) values (?,?,?)}
899    );
900
901    my $reqid = gen_uid;
902
903    $addreq->execute($reqid, $info{label}, $info{mail});
904    my $mailer = new Mail::Mailer 'smtp', Server => (Vote->config->{smtp} || 'localhost');
905    $mailer->open({
906        From => 'Voting system <nomail@nomail.com>', # TODO allow to configure this
907        To => $info{mail},
908        Subject => 'Votre nouveau vote',
909        mail_header(),
910    });
911    print $mailer <<EOF;
912
913Vous avez demandez la création d'un nouveau vote:
914$info{label}
915
916Pour valider votre demande, veuiller allez visitez la page:
917$info{url}/$reqid
918
919A bientÃŽt
920EOF
921    $mailer->close
922        or warn "couldn't send whole message: $!\n";
923    $self->db->commit;
924    1;
925}
926
927=head1 AUTHOR
928
929Thauvin Olivier
930
931=head1 LICENSE
932
933This library is free software, you can redistribute it and/or modify
934it under the same terms as Perl itself.
935
936=cut
937
9381;
Note: See TracBrowser for help on using the repository browser.