File Coverage

blib/lib/XAO/DO/Web/FilloutForm.pm
Criterion Covered Total %
statement 54 426 12.6
branch 15 346 4.3
condition 4 252 1.5
subroutine 7 27 25.9
pod 11 17 64.7
total 91 1068 8.5


line stmt bran cond sub pod time code
1             =head1 NAME
2              
3             XAO::DO::Web::FilloutForm - support for HTML forms
4              
5             =head1 DESCRIPTION
6              
7             Fill out form object. Helps to create fill-out forms for registration
8             and so on. Checks that parameters are Ok and then displays either form
9             or thanks.
10              
11             Must be overriden with something which will put 'fields' parameter
12             into $self. Format is as array of hash references reference of the
13             following structure:
14              
15             [ { name => field name,
16             required => 0 || 1,
17             style => selection || text || textarea || email || phone ||
18             integer || dollars || real,
19             maxlength => maximum length,
20             minlength => minimum length,
21             param => name of parameter for form substitution,
22             text => description of parameter,
23             },
24             { ... }
25             ]
26              
27             If you do not care in what order fields are checked you can also
28             supply 'fields' as a hash reference:
29              
30             { name => {
31             required => 0 || 1,
32             style => selection || text || textarea || email || phone ||
33             integer || dollars || real,
34             maxlength => maximum length,
35             minlength => minimum length,
36             param => name of parameter for form substitution,
37             text => description of parameter,
38             },
39             name1 => { ... }
40             }
41              
42             When form filled out "form_ok" method is called, which must be
43             overridden in inherited object to do something good with
44             results. Alternatively reference to subroutine can be given through
45             'setup' method. This is suitable for using FilloutForm object without
46             overriding it.
47              
48             Displays form with PARAM.VALUE set to value, PARAM.NAME - to name,
49             PARAM.TEXT - to text, PARAM.REQUIRED to the 0/1 required flag, and
50             PARAM.HTML - to piece of HTML code if applicable (Country selection for
51             example).
52              
53             =head1 METHODS
54              
55             =over
56              
57             =cut
58              
59             ###############################################################################
60             package XAO::DO::Web::FilloutForm;
61 1     1   738 use strict;
  1         2  
  1         29  
62 1     1   5 use XAO::Utils qw(:args :debug :html);
  1         1  
  1         219  
63 1     1   6 use XAO::Errors qw(XAO::DO::Web::FilloutForm);
  1         1  
  1         6  
64 1     1   337 use base XAO::Objects->load(objname => 'Web::Page');
  1         2  
  1         4  
65              
66             our $VERSION='2.029';
67              
68             sub setup ($%);
69             sub field_desc ($$;$);
70             sub field_names ($);
71             sub display ($;%);
72             sub form_ok ($%);
73             sub form_phase ($);
74             sub check_form ($%);
75             sub pre_check_form ($%);
76             sub countries_list ();
77             sub us_continental_states_list ();
78             sub us_states_list ();
79             sub cc_list ($);
80             sub cc_validate ($%);
81             sub calculate_year ($$);
82              
83             ###############################################################################
84              
85             =item new (%)
86              
87             Overrided new method for those who prefer to use inheritance style.
88              
89             =cut
90              
91             sub new ($%) {
92 1     1 1 16 my $proto=shift;
93 1   33     5 my $class=ref($proto) || $proto;
94 1         4 my $args=get_args(\@_);
95 1         17 my $self=$proto->SUPER::new($args);
96              
97             # Setting up fields if required
98             #
99             $self->setup_fields(fields => $args->{'fields'},
100 1 50       27 values => $args->{'values'}) if $args->{'fields'};
101              
102             # Done
103             #
104 1         2 $self;
105             }
106              
107              
108             ###############################################################################
109              
110             # Setting object up for use as embedded form checker from other
111             # non-derived objects.
112             #
113             # Arguments are:
114             # fields => fields descriptions
115             # values => values for fields, unless this is set all values
116             # are cleaned
117             # extra_data => reference to any data, subroutines will then be
118             # able to access it.
119             # form_ok => form_ok subroutine reference (mandatory)
120             # pre_check_form => pre_check_form subroutine reference
121             # check_form => check_form subroutine reference
122             # submit_name => name of the submit button
123             # keep_form => display form template even when the form is complete
124             #
125             # Call to this subroutine is not required from derived objects, use
126             # method overriding instead when possible!
127             #
128              
129             sub setup ($%) {
130 0     0 0 0 my $self=shift;
131 0         0 my $args=get_args(\@_);
132              
133             # Fields and values
134             #
135             $self->setup_fields(
136             fields => $args->{'fields'},
137 0         0 values => $args->{'values'},
138             );
139              
140             # Handlers and special data:
141             # extra_data - passed to handlers as is.
142             # submit_name - name of submit button for pre-filled forms (change form).
143             # dont_sanitize - don't remove <> from CGI input
144             #
145 0         0 my @names=qw(extra_data submit_name form_ok pre_check_form check_form keep_form dont_sanitize);
146 0         0 @{$self}{@names}=@{$args}{@names};
  0         0  
  0         0  
147              
148 0   0     0 my $values=$args->{'values'} || {};
149 0         0 foreach my $fdata (@{$self->{'fields'}}) {
  0         0  
150 0         0 $fdata->{'value'}=$values->{$fdata->{'name'}};
151             }
152             }
153              
154             ###############################################################################
155              
156             =item setup_fields (%)
157              
158             Copying fields descriptions. We copy entire structure here because it
159             could be persistent and we do not want original data to be modified.
160              
161             =cut
162              
163             sub setup_fields ($%) {
164 0     0 1 0 my $self=shift;
165 0         0 my $args=get_args(\@_);
166 0         0 my $fields=$args->{'fields'};
167 0 0 0     0 return unless $fields && ref($fields);
168              
169 0         0 my $values=$args->{'values'};
170 0         0 my @copy;
171 0 0       0 foreach my $fdata (ref($fields) eq 'ARRAY' ? @{$fields}
  0         0  
172 0         0 : keys %{$fields}) {
173 0         0 my $name;
174 0 0       0 if(! ref($fdata)) {
175 0         0 $name=$fdata;
176 0         0 $fdata=$fields->{$name};
177 0         0 $fdata->{'name'}=$name;
178             }
179             else {
180 0         0 $name=$fdata->{'name'};
181             }
182              
183 0         0 my %cd;
184 0         0 @cd{keys %{$fdata}}=values %{$fdata};
  0         0  
  0         0  
185 0 0 0     0 $cd{'value'}=$values->{$name} if $values && $values->{$name};
186 0         0 push(@copy,\%cd);
187             }
188              
189 0         0 $self->{'fields'}=\@copy;
190             }
191              
192             ###############################################################################
193              
194             =item display (%)
195              
196             Displaying the form.
197              
198             =cut
199              
200             sub display ($;%) {
201 0     0 1 0 my $self=shift;
202 0         0 my $args=get_args(\@_);
203 0         0 my $cgi=$self->cgi;
204 0         0 my $fields=$self->{'fields'};
205 0 0       0 $fields || throw XAO::E::DO::Web::FilloutForm
206             "display - has not set fields for FilloutForm";
207 0         0 my $phase=$self->{'phase'}=$args->{'phase'};
208 0 0       0 $self->{'submit_name'}=$args->{'submit_name'} if $args->{'submit_name'};
209              
210             # Checking the type of fields argument we have - hash or
211             # array? Converting to array if it is a hash.
212             #
213 0 0       0 if(ref($fields) eq 'HASH') {
214 0         0 my @newf;
215 0         0 foreach my $name (keys %{$fields}) {
  0         0  
216 0         0 $fields->{$name}->{'name'}=$name;
217 0         0 push @newf,$fields->{$name};
218             }
219 0         0 $self->{'fields'}=$fields=\@newf;
220             }
221              
222             # Pre-checking form with external overridable function.
223             #
224 0         0 $self->pre_check_form($args);
225              
226             # Displayable object
227             #
228 0         0 my $obj=$self->object;
229              
230             # Special parameter named 'submit_name' contains submit button name
231             # and used for pre-filled forms - these forms usually already have
232             # valid data and we need some way to know when the form was really
233             # checked and corrected by user.
234             #
235 0         0 my $have_cgivalues=0;
236 0         0 my $have_submit=1;
237 0 0       0 if($self->{'submit_name'}) {
238             $have_submit=($cgi->param($self->{'submit_name'}) ||
239             $cgi->param($self->{'submit_name'}.'.x') ||
240 0 0 0     0 $cgi->param($self->{'submit_name'}.'.y')
241             ) ? 1 : 0;
242 0         0 $have_cgivalues=$have_submit;
243             }
244              
245             # First checking all parameters and collecting mistakes into errstr.
246             #
247             # Also creating hash with parameters for form diplaying while we are
248             # going through fields anyway.
249             #
250 0         0 my $errstr;
251             my %formparams;
252              
253 0   0     0 my $dont_sanitize=$self->{'dont_sanitize'} || $args->{'dont_sanitize'};
254              
255 0         0 foreach my $fdata (@{$fields}) {
  0         0  
256 0         0 my $name=$fdata->{'name'};
257              
258 0         0 my $cgivalue=$cgi->param($name);
259 0 0       0 $have_cgivalues++ if defined($cgivalue);
260              
261             # Unless we have a 'dont_sanitize' argument we remove angle
262             # brackets to prevent XSS attacks.
263             #
264 0 0 0     0 if(defined $cgivalue && !$dont_sanitize) {
265 0         0 $cgivalue=~s/[<>]/ /sg;
266             }
267              
268             # Checking form phase for multi-phased forms if required.
269             #
270 0 0 0     0 next if defined($fdata->{'phase'}) && $phase<$fdata->{'phase'};
271              
272 0         0 my $value=$fdata->{'newvalue'};
273 0 0       0 $value=$cgivalue unless defined($value);
274 0 0       0 if(!$have_cgivalues) {
275 0 0       0 $value=$fdata->{'value'} unless defined($value);
276 0 0       0 $value=$fdata->{'default'} unless defined($value);
277             }
278              
279             # Empty value is the same as undefined. Spaces are trimmed from the
280             # beginning and the end of the string.
281             #
282 0 0       0 $value="" unless defined $value;
283 0         0 $value=~s/^\s*(.*?)\s*$/$1/g;
284              
285             # Various checks depending on field style.
286             #
287 0         0 my $newerr;
288 0   0     0 my $style=$fdata->{'style'} || $fdata->{'type'} ||
289             throw $self "display - no style or type in field '$name'";
290 0 0 0     0 if(!length($value) && $fdata->{'required'}) {
    0 0        
    0 0        
    0 0        
    0 0        
    0 0        
    0 0        
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
291 0         0 $newerr=$self->Tx('Required field!');
292             }
293             elsif($fdata->{'maxlength'} && length($value) > $fdata->{'maxlength'}) {
294 0         0 $newerr=$self->Tx('Value is too long!');
295             }
296             elsif($fdata->{'minlength'} && length($value) &&
297             length($value) < $fdata->{'minlength'}) {
298 0         0 $newerr=$self->Tx("Value is too short!");
299             }
300             elsif($style eq 'text') {
301             # No checks for text
302             }
303             elsif($style eq 'textarea') {
304             # No checks for textarea
305             }
306             elsif($style eq 'file') {
307 0 0       0 if(!$value) {
308 0         0 $newerr=$self->Tx("No filename given");
309             }
310             }
311             elsif($style eq 'email') {
312 0 0 0     0 if(length($value) && $value !~ /^[\w\.\+\/\$\%\&\`{}'=-]+\@([a-z0-9-]+\.)+[a-z]+$/i) {
313 0         0 $newerr=$self->Tx("Value is not in the form of user\@host.domain!");
314             }
315             }
316             elsif($style eq 'usphone') {
317 0 0       0 $fdata->{'maxlength'}=15 unless $fdata->{'maxlength'};
318 0 0       0 if(length($value)) {
319 0         0 $value =~ s/\D//g;
320 0 0       0 if(length($value) == 7) {
    0          
    0          
321 0         0 $newerr=$self->Tx("Needs area code!");
322             }
323             elsif(length($value) == 11) {
324 0 0       0 if(substr($value,0,1) ne '1') {
325 0         0 $newerr=$self->Tx("Must be a US phone!");
326             }
327             }
328             elsif(length($value) != 10) {
329 0         0 $newerr=$self->Tx("Does not look like a right phone!");
330             }
331             else {
332 0         0 $value=~s/^.?(...)(...)(....)/($1) $2-$3/;
333             }
334             }
335             }
336             elsif($style eq 'phone') { # +99 (123) 456-78-90 x 123
337 0 0       0 $fdata->{'maxlength'}=30 unless $fdata->{'maxlength'};
338 0 0       0 if(length($value)) {
339 0         0 my ($p,$e)=split(/[a-zA-Z]+/,$value);
340              
341 0         0 $p=~s/\D//g;
342 0   0     0 $e||='';
343 0         0 $e=~s/\D//g;
344              
345 0 0       0 if(length($p)<10) {
    0          
    0          
346 0         0 $newerr=$self->Tx("Needs area code!");
347             }
348             elsif(length($p)==10) {
349 0         0 $p='1' . $p;
350             }
351             elsif(length($p)>13) {
352 0         0 $newerr=$self->Tx("Too many digits!");
353             }
354              
355 0 0       0 if(!$newerr) {
356 0         0 ($value=$p)=~s/^(.+)(...)(...)(....)$/+$1 ($2) $3-$4/;
357 0 0       0 $value.=" ext. $e" if $e;
358             }
359             }
360             }
361             elsif($style eq 'int' || $style eq 'integer' || $style eq 'number') {
362 0 0       0 if(length($value)) {
363 0 0       0 if($value =~ /^-?[\d,']+$/) {
364 0         0 $value=~s/[,']+//g;
365 0 0 0     0 if(defined($fdata->{'minvalue'}) && $value<$fdata->{'minvalue'}) {
366             $newerr=$self->Tx("Value is less than {{min}}",
367 0         0 { min => $fdata->{'minvalue'} });
368             }
369 0 0 0     0 if(defined($fdata->{'maxvalue'}) && $value>$fdata->{'maxvalue'}) {
370             $newerr=$self->Tx("Value is greater than {{max}}",
371 0         0 { max => $fdata->{'maxvalue'} });
372             }
373             }
374             else {
375 0         0 $newerr=$self->Tx("Is not an integer!");
376             }
377             }
378             }
379             elsif($style eq 'real') {
380 0 0       0 if(length($value)) {
381 0 0       0 if($value =~ /^-?[\d,'\.]+$/) {
382 0         0 $value=~s/[,']+//g;
383 0 0 0     0 if(defined($fdata->{'minvalue'}) && $value<$fdata->{'minvalue'}) {
384             $newerr=$self->Tx("Value is less than {{min}}",
385 0         0 { min => $fdata->{'minvalue'} });
386             }
387 0 0 0     0 if(defined($fdata->{'maxvalue'}) && $value>$fdata->{'maxvalue'}) {
388             $newerr=$self->Tx("Value is greater than {{max}}",
389 0         0 { max => $fdata->{'maxvalue'} });
390             }
391             }
392             else {
393 0         0 $newerr=$self->Tx("Is not a number!");
394             }
395             }
396             }
397             elsif($style eq 'password') {
398 0 0 0     0 if(length($value) && $fdata->{'pair'} &&
      0        
399             $value ne $cgi->param($fdata->{'pair'})) {
400 0         0 $newerr=$self->Tx("Does not match the copy!");
401             }
402             }
403             elsif($style eq 'country') {
404 0         0 my @cl=$self->countries_list();
405 0         0 my $match=0;
406 0         0 foreach my $c (@cl) {
407 0         0 $match=lc($c) eq lc($value);
408 0 0       0 last if $match;
409             }
410 0 0 0     0 if(length($value) && !$match) {
411 0         0 $newerr=$self->Tx("Unknown country");
412             }
413             }
414             elsif($style eq 'usstate' || $style eq 'uscontst') {
415 0 0       0 my @cl=$style eq 'usstate' ? $self->us_states_list()
416             : $self->us_continental_states_list();
417 0         0 my $match=0;
418 0   0     0 my $sv=substr($value || '',0,2);
419 0         0 foreach my $c (@cl) {
420 0         0 $match=lc(substr($c,0,2)) eq lc($sv);
421 0 0       0 last if $match;
422             }
423 0 0 0     0 if(length($value) && !$match) {
424 0         0 $newerr=$self->Tx("Unknown state");
425             }
426             }
427             elsif($style eq 'cctype') {
428 0         0 my @cl=$self->cc_list();
429 0         0 my $match=0;
430 0         0 foreach my $c (@cl) {
431 0         0 $match=lc($c) eq lc($value);
432 0 0       0 last if $match;
433             }
434 0 0 0     0 if(length($value) && !$match) {
435 0         0 $newerr=$self->Tx("Unknown credit card type");
436             }
437             }
438             elsif($style eq 'ccnum') {
439 0 0       0 if(length($value)) {
440 0 0       0 my $type=$fdata->{'pair'} ? $cgi->param($fdata->{'pair'}) : '';
441 0         0 $newerr=$self->cc_validate(type => $type, number => $value, validated => \$value);
442             }
443             }
444             elsif($style eq 'month') {
445 0 0       0 if(length($value)) {
446 0         0 $value=int($value);
447 0 0 0     0 if($value<1 || $value>12) {
448 0         0 $newerr=$self->Tx('Invalid month!');
449             }
450             }
451             }
452             elsif($style eq 'year') {
453 0 0 0     0 if($fdata->{'minyear'} && $fdata->{'maxyear'}) {
    0          
454 0         0 my $minyear=$self->calculate_year($fdata->{'minyear'});
455 0         0 my $maxyear=$self->calculate_year($fdata->{'maxyear'});
456 0 0       0 if(length($value)) {
457 0         0 $value=$self->calculate_year($value);
458 0 0       0 if($value<$minyear) {
    0          
459 0         0 $newerr=$self->Tx("Must be after {{year}}",
460             { year => $minyear });
461             }
462             elsif($value>$maxyear) {
463 0         0 $newerr=$self->Tx("Must be before {{year}}",
464             { year => $maxyear });
465             }
466             }
467             }
468             elsif(length($value)) {
469 0         0 $value=$self->calculate_year($value);
470 0 0 0     0 if($value<1900 || $value>2099) {
471 0         0 $newerr=$self->Tx('Invalid year!');
472             }
473             }
474             }
475             elsif($style eq 'checkbox') {
476              
477             # If checkbox is not checked we don't get any info about it
478             # in the cgi parameters. So we have to take a guess if the
479             # form was generally filled in, but we have an unchecked
480             # checkbox or this is the first display and form was not
481             # submitted yet.
482             #
483 0 0       0 if($have_cgivalues) {
484 0 0       0 $value=(defined $fdata->{'newvalue'} ? $fdata->{'newvalue'} : $cgivalue) ? 1 : 0;
    0          
485             }
486             else {
487 0 0       0 $value=(defined($fdata->{'value'}) ? $fdata->{'value'} : $fdata->{'default'}) ? 1 : 0;
    0          
488             }
489             }
490             elsif($style eq 'selection') {
491 0 0       0 if(length($value)) {
492 0         0 my $opt=$fdata->{'options'};
493 0 0       0 if(ref($opt) eq 'HASH') {
    0          
494 0 0       0 if(!defined $opt->{$value}) {
495 0         0 $newerr=$self->Tx('Bad option value!');
496             }
497             }
498             elsif(ref($opt) eq 'ARRAY') {
499 0         0 my $found;
500 0         0 for(my $i=0; $i<@$opt; $i+=2) {
501 0 0       0 next unless defined($opt->[$i+1]);
502 0 0       0 if($opt->[$i] eq $value) {
503 0         0 $found=1;
504 0         0 last;
505             }
506             }
507 0 0       0 if(!$found) {
508 0         0 $newerr=$self->Tx('Bad option value!');
509             }
510             }
511             else {
512 0         0 $newerr=$self->Tx('Unknown data in options!');
513             }
514             }
515             }
516             else {
517 0         0 $self->throw("display - unknown style '$style'");
518             }
519              
520             # If the form is not filled at all we empty the errstr
521             #
522 0 0 0     0 if($newerr && (!$have_submit || !$have_cgivalues)) {
      0        
523 0         0 $newerr = '';
524             }
525              
526             # Generating HTML for some field styles.
527             #
528 0   0     0 my $param=$fdata->{'param'} || uc($name);
529 0         0 my $seloptions;
530             my $selcompare;
531 0 0 0     0 if($style eq 'country') {
    0 0        
    0 0        
    0 0        
    0 0        
    0 0        
    0 0        
    0 0        
    0 0        
    0 0        
    0 0        
      0        
      0        
532 0         0 my @cl=$self->countries_list();
533             $seloptions=[
534             '' => 'Select Country',
535 0         0 (map { $_ => $_ } @cl),
  0         0  
536             ];
537 0     0   0 $selcompare=sub { return uc($_[0]) eq uc($_[1]) };
  0         0  
538             }
539             elsif($style eq 'usstate' || $style eq 'uscontst') {
540 0 0       0 my @cl=$style eq 'usstate' ? $self->us_states_list()
541             : $self->us_continental_states_list();
542             $seloptions=[
543             '' => 'Select State',
544 0         0 (map { uc(substr($_,0,2)) => $_ } @cl),
  0         0  
545             ];
546 0     0   0 $selcompare=sub { return uc($_[0]) eq uc($_[1]) };
  0         0  
547             }
548             elsif($style eq 'cctype') {
549 0         0 my @cl=$self->cc_list();
550             $seloptions=[
551             '' => 'Select Card Type',
552 0         0 (map { $_ => $_ } @cl),
  0         0  
553             ];
554 0     0   0 $selcompare=sub { return uc($_[0]) eq uc($_[1]) };
  0         0  
555             }
556             elsif($style eq 'month') {
557 0         0 my @cl=qw(January February March April May June July
558             August September October November December);
559             $seloptions=[
560             '' => 'Select Month',
561 0         0 (map { sprintf('%02u',$_) => sprintf('%02u - %s',$_,$cl[$_-1]) } (1..12)),
  0         0  
562             ];
563 0   0 0   0 $selcompare=sub { return defined $_[0] && length $_[0] && length $_[1] && $_[0] == $_[1] };
  0         0  
564             }
565             elsif($style eq 'year' && !$fdata->{'maxlength'} && $fdata->{'minyear'} && $fdata->{'maxyear'}) {
566 0         0 my $minyear=$self->calculate_year($fdata->{'minyear'});
567 0         0 my $maxyear=$self->calculate_year($fdata->{'maxyear'});
568             $seloptions=[
569             '' => 'Select Year',
570 0         0 (map { sprintf('%04u',$_) => sprintf('%04u',$_) } ($minyear..$maxyear)),
  0         0  
571             ];
572 0   0 0   0 $selcompare=sub { return defined $_[0] && length $_[0] && length $_[1] && $_[0] == $_[1] };
  0         0  
573             }
574             elsif($style eq 'checkbox') {
575             $fdata->{'html'}=$obj->expand(
576             path => '/bits/fillout-form/html-checkbox',
577             NAME => $name,
578             VALUE => $fdata->{'value'} || '',
579             CHECKED => $value ? ' checked' : '',
580 0 0 0     0 HTMLID => $fdata->{'htmlid'} || $name,
      0        
      0        
581             ERRSTR => $newerr || ''
582             );
583             }
584             elsif($style eq 'selection') {
585 0   0     0 $seloptions=$fdata->{'options'} ||
586             $self->throw("display - no 'options' for '$name' selection");
587             }
588             elsif($style eq 'text' || $style eq 'phone' || $style eq 'usphone' ||
589             $style eq 'ccnum' || $style eq 'email' || $style eq 'year' ||
590             $style eq 'number' || $style eq 'int' || $style eq 'integer' ||
591             $style eq 'real' ) {
592             $fdata->{'html'}=$obj->expand(
593             path => '/bits/fillout-form/html-text',
594             NAME => $name,
595             VALUE => defined($value) ? $value : '',
596             MAXLENGTH => $fdata->{'maxlength'} || 100,
597 0 0 0     0 SIZE => $fdata->{'size'} || 30,
    0 0        
598             ERRSTR => defined($newerr) ? $newerr : ''
599             );
600             }
601             elsif($style eq 'textarea') {
602             $fdata->{'html'}=$obj->expand(
603             path => '/bits/fillout-form/html-textarea',
604             NAME => $name,
605             VALUE => defined($value) ? $value : '',
606             SIZE => $fdata->{'size'} || 30,
607 0 0 0     0 ROWS => $fdata->{'rows'} || 8,
    0 0        
608             ERRSTR => defined($newerr) ? $newerr : ''
609             );
610             }
611             elsif($style eq 'file') {
612             $fdata->{'html'}=$obj->expand(
613             path => '/bits/fillout-form/html-file',
614             NAME => $name,
615 0 0 0     0 SIZE => $fdata->{'size'} || 30,
616             ERRSTR => defined($newerr) ? $newerr : ''
617             );
618             }
619             elsif($style eq 'password') {
620             $fdata->{'html'}=$obj->expand(
621             path => '/bits/fillout-form/html-password',
622             NAME => $name,
623             VALUE => defined $value ? $value : '',
624             MAXLENGTH => $fdata->{'maxlength'} || 100,
625 0 0 0     0 SIZE => $fdata->{'size'} || 30,
      0        
      0        
626             ERRSTR => $newerr || ''
627             );
628             }
629              
630             # Various selection fields above just set seloptions for uniform build.
631             #
632 0 0       0 if($seloptions) {
633 0         0 my $has_empty;
634             my $used_selected;
635 0         0 my $html='';
636             my $html_sub=sub {
637 0     0   0 my ($v,$t)=@_;
638 0 0 0     0 $has_empty=1 if !defined($v) || !length($v);
639 0 0       0 return unless defined($t);
640 0         0 my $sel='';
641 0 0       0 if(!$used_selected) {
642 0 0       0 my $equal=$selcompare ? $selcompare->($v,$value) : ($v eq $value);
643 0 0       0 if($equal) {
644 0         0 $sel=' selected';
645 0         0 $used_selected=1;
646             }
647             }
648 0         0 $html.=$obj->expand(
649             path => '/bits/fillout-form/html-select-option',
650             NAME => $name,
651             VALUE => $v,
652             TEXT => $t,
653             SELECTED=> $sel,
654             );
655 0 0       0 $formparams{"$param.RV_CURRENT_$v"}=$sel ? 1 : 0;
656 0         0 $formparams{"$param.RV_VALUE_$v"}=$v;
657 0         0 $formparams{"$param.RV_TEXT_$v"}=$t;
658 0         0 };
659              
660 0 0       0 if(ref($seloptions) eq 'HASH') {
    0          
661 0         0 foreach my $v (sort { $seloptions->{$a} cmp $seloptions->{$b} } keys %$seloptions) {
  0         0  
662 0         0 &{$html_sub}($v,$seloptions->{$v});
  0         0  
663             }
664             }
665             elsif(ref($seloptions) eq 'ARRAY') {
666 0         0 for(my $i=0; $i<@$seloptions; $i+=2) {
667 0         0 &{$html_sub}($seloptions->[$i],$seloptions->[$i+1]);
  0         0  
668             }
669             }
670             else {
671 0         0 throw $self "Unknown data type in 'options' name=$name";
672             };
673              
674             # We do not display 'Please select' if there is an empty
675             # value in the list, we assume that that empty value is a
676             # prompt of some sort.
677             #
678             # If there is no need for empty value and no need for a
679             # prompt -- use ('' => undef) as an indicator of that.
680             #
681 0         0 $formparams{"$param.HTML_OPTIONS"}=$html;
682              
683             # For compatibility with older code this is not included in
684             # HTML_OPTIONS
685             #
686 0 0       0 if(!$has_empty) {
687 0         0 $html='
688             t2ht($self->Tx('Please select')) .
689             ''.$html;
690             }
691              
692             # Final code
693             #
694 0 0 0     0 $fdata->{'html'}=$obj->expand(
695             path => '/bits/fillout-form/html-select',
696             NAME => $name,
697             VALUE => defined $value ? $value : '',
698             OPTIONS => $html,
699             ERRSTR => $newerr || ''
700             );
701             }
702              
703             # Adding error description to the list if there was an
704             # error. Storing value otherwise.
705             #
706 0 0       0 if($newerr) {
707 0   0     0 $errstr.=($fdata->{'text'} || $name) . ": " . $newerr . "
\n";
708 0         0 $fdata->{'errstr'}=$newerr;
709             }
710             else {
711 0         0 $fdata->{'value'}=$value;
712             }
713              
714             # Filling formparams hash
715             #
716 0 0       0 $formparams{"$param.VALUE"}=defined($value) ? $value : "";
717 0   0     0 $formparams{"$param.TEXT"}=$fdata->{'text'} || $name;
718 0         0 $formparams{"$param.NAME"}=$name;
719 0   0     0 $formparams{"$param.HTML"}=$fdata->{'html'} || "";
720 0 0       0 $formparams{"$param.REQUIRED"}=$fdata->{'required'} ? 1 : 0;
721 0   0     0 $formparams{"$param.SIZE"}=$fdata->{'size'} || 30;
722 0   0     0 $formparams{"$param.ROWS"}=$fdata->{'rows'} || 1;
723 0   0     0 $formparams{"$param.MAXLENGTH"}=$fdata->{'maxlength'} || 100;
724 0   0     0 $formparams{"$param.MINLENGTH"}=$fdata->{'minlength'} || 0;
725 0   0     0 $formparams{"$param.ERRSTR"}=$fdata->{'errstr'} || '';
726             }
727              
728             # Checking content for general compatibility by overriden
729             # method. Called only if data are basicly good.
730             #
731 0 0 0     0 if($have_submit && $have_cgivalues && !$errstr) {
      0        
732 0         0 my @rc=$self->check_form(merge_refs($args,\%formparams));
733 0 0       0 if(@rc<2) {
    0          
734 0   0     0 $formparams{"ERRSTR.CHECK_FORM"}=$errstr=($rc[0] || '');
735             }
736             elsif(scalar(@rc)%2 == 0) {
737 0         0 for(my $i=0; $i<@rc; $i+=2) {
738 0   0     0 my $e=($rc[$i] || '');
739 0 0       0 next unless $e;
740 0         0 my $fname=$rc[$i+1];
741 0 0       0 if($fname) {
742 0         0 my $fdata=$self->field_desc($fname);
743 0   0     0 my $param=$fdata->{'param'} || uc($fdata->{'name'});
744              
745 0 0       0 if($fdata->{'errstr'}) {
746 0 0       0 $fdata->{'errstr'}.=($fdata->{'errstr'} =~ /\.\s*$/ ? ' ' : '; ') . $e;
747 0         0 $formparams{"$param.ERRSTR"}=$fdata->{'errstr'};
748             }
749             else {
750 0         0 $fdata->{'errstr'}=$formparams{"$param.ERRSTR"}=$e;
751             }
752              
753 0 0       0 $errstr.="\n
" if $errstr;
754 0         0 $errstr.=$e;
755             }
756             else {
757 0 0       0 $errstr.="\n
" if $errstr;
758 0 0       0 $formparams{'ERRSTR.CHECK_FORM'}.="\n
" if $errstr;
759 0         0 $errstr.=$e;
760 0         0 $formparams{'ERRSTR.CHECK_FORM'}.=$e;
761             }
762             }
763             }
764             else {
765 0         0 throw $self "display - wrong number of results (".join('|',@rc).")";
766             }
767             }
768 0   0     0 $formparams{"ERRSTR.CHECK_FORM"}||='';
769              
770             # If the form is not filled at all we remove errstr's from
771             # individual fields.
772             #
773 0 0 0     0 if(!$have_submit || !$have_cgivalues) {
774 0         0 $errstr='';
775 0         0 foreach my $fdata (@{$fields}) {
  0         0  
776 0   0     0 my $param=$fdata->{'param'} || uc($fdata->{'name'});
777 0         0 $formparams{"$param.ERRSTR"}='';
778             }
779             }
780              
781             # If there were errors then displaying the form. We also display
782             # the form here if it is not yet filled out and if it is, but we we
783             # asked to keep displaying it using 'keep_form' setup parameter.
784             #
785 0         0 my $keep_form=$self->{'keep_form'};
786 0 0 0     0 if(!$have_submit || !$have_cgivalues || $errstr || $keep_form) {
      0        
      0        
787 0         0 my $eh;
788             my $et;
789 0 0 0     0 if($errstr && $have_cgivalues) {
790             $eh=$obj->expand(
791             path => '/bits/fillout-form/errstr',
792             ERRSTR => $errstr,
793 0         0 'ERRSTR.CHECK_FORM' => $formparams{"ERRSTR.CHECK_FORM"},
794             );
795 0         0 $et=$errstr;
796              
797             }
798             $obj->display($args,\%formparams,{
799             path => $args->{'form.path'},
800 0   0     0 template => $args->{'form.template'},
      0        
801             ERRSTR => $et || '',
802             'ERRSTR.HTML' => $eh || '',
803             });
804 0 0 0     0 return unless $keep_form && !$errstr && $have_cgivalues && $have_submit;
      0        
      0        
805             }
806              
807             # Our form is correct!
808             #
809 0         0 $self->form_ok(merge_refs($args,\%formparams));
810             }
811              
812             ###############################################################################
813              
814             =item field_desc ($)
815              
816             Returns field description by name. This is the correct way to get to the
817             value of a field from check_form() or form_ok() methods.
818              
819             If the optional second parameter set to true then on failure to find the
820             field the method will return undef instead of throwing an error.
821              
822             =cut
823              
824             sub field_desc ($$;$) {
825 0     0 1 0 my ($self,$name,$soft_failure)=@_;
826              
827 0   0     0 my $fields=$self->{'fields'} ||
828             throw $self "field_desc - has not set fields for FilloutForm";
829              
830 0 0       0 if(ref($fields) eq 'ARRAY') {
831 0         0 foreach my $fdata (@{$fields}) {
  0         0  
832 0 0       0 return $fdata if $fdata->{'name'} eq $name;
833             }
834             }
835             else {
836 0 0       0 return $fields->{$name} if $fields->{$name};
837             }
838              
839 0 0       0 return undef if $soft_failure;
840              
841 0         0 throw $self "field_desc - unknown field '$name' referred";
842             }
843              
844             ###############################################################################
845              
846             =item field_names ($)
847              
848             Returns field a list of all field names in the current form.
849              
850             =cut
851              
852             sub field_names ($) {
853 0     0 1 0 my $self=shift;
854              
855 0   0     0 my $fields=$self->{'fields'} ||
856             throw $self "field_names - has not set fields for FilloutForm";
857              
858 0 0       0 if(ref($fields) eq 'ARRAY') {
859 0         0 return map { $_->{'name'} } @$fields;
  0         0  
860             }
861             else {
862 0         0 return map { $_->{'name'} } keys %$fields;
  0         0  
863             }
864             }
865              
866             ###############################################################################
867              
868             =item form_ok
869              
870             Default handler for filled out form. Must be overriden!
871              
872             =cut
873              
874             sub form_ok ($%) {
875 0     0 1 0 my $self=shift;
876 0 0       0 if($self->{'form_ok'}) {
877             my $na=merge_refs(get_args(\@_),{
878 0         0 extra_data => $self->{'extra_data'},
879             });
880 0         0 return &{$self->{'form_ok'}}($self,$na);
  0         0  
881             }
882 0         0 throw $self 'form_ok - must be overriden in derived class or using form_ok parameter';
883             }
884              
885             ##
886             # High-level form content check. Should be overriden for real checks.
887             # Returns '' if there were no error or error text otherwise.
888             #
889             sub check_form ($%) {
890 0     0 0 0 my $self=shift;
891 0 0       0 if($self->{'check_form'}) {
892 0         0 my %na=%{get_args(\@_)};
  0         0  
893 0         0 $na{'extra_data'}=$self->{'extra_data'};
894 0         0 return &{$self->{'check_form'}}($self,\%na);
  0         0  
895             }
896 0         0 '';
897             }
898              
899             ###############################################################################
900              
901             =item pre_check_form (%)
902              
903             Pre-checking form. May be used if some values are calculated or copied
904             from another and should be checked later.
905              
906             Should stuff generated values into {'newvalue'} parameter.
907              
908             =cut
909              
910             sub pre_check_form ($%) {
911 0     0 1 0 my $self=shift;
912 0 0       0 if($self->{'pre_check_form'}) {
913 0         0 my $na=get_args(\@_);
914 0         0 $na->{'extra_data'}=$self->{'extra_data'};
915 0         0 return &{$self->{'pre_check_form'}}($self,$na);
  0         0  
916             }
917             }
918              
919             ###############################################################################
920              
921             =item countries_list ()
922              
923             Returns list of countries for selection. May be overriden if site
924             needs only a fraction of that.
925              
926             =cut
927              
928             sub countries_list () {
929 0     0 1 0 split(/\n/,<<'END_OF_LIST');
930             United States
931             Afghanistan
932             Albania
933             Algeria
934             American Samoa
935             Andorra
936             Angola
937             Anguilla
938             Antarctica
939             Antigua
940             Antilles
941             Arab Emirates
942             Argentina
943             Armenia
944             Aruba
945             Australia
946             Austria
947             Azerbaidjan
948             Bahamas
949             Bahrain
950             Bangladesh
951             Barbados
952             Barbuda
953             Belarus
954             Belgium
955             Belize
956             Benin
957             Bermuda
958             Bhutan
959             Bolivia
960             Bosnia Herz.
961             Botswana
962             Bouvet Isl.
963             Brazil
964             Brunei Dar.
965             Bulgaria
966             Burkina Faso
967             Burundi
968             C. African Rep.
969             Cambodia
970             Cameroon
971             Cambodia
972             Cameroon
973             Canada
974             Cape Verde
975             Cayman Islands
976             Chad
977             Chile
978             China
979             Christmas Isl.
980             Cocos Islands
981             Colombia
982             Comoros
983             Congo
984             Cook Islands
985             Costa Rica
986             Croatia
987             Cuba
988             Cyprus
989             Czech Republic
990             Denmark
991             Djibouti
992             Dominica
993             Dominican Rep.
994             East Timor
995             Ecuador
996             Egypt
997             England
998             El Salvador
999             Equat. Guinea
1000             Eritrea
1001             Estonia
1002             Ethiopia
1003             Falkland Isl.
1004             Faroe Islands
1005             Fiji
1006             Finland
1007             Former Czech.
1008             Former USSR
1009             France
1010             French Guyana
1011             French S. Terr.
1012             Gabon
1013             Gambia
1014             Georgia
1015             Germany
1016             Ghana
1017             Gibraltar
1018             Great Britain
1019             Greece
1020             Greenland
1021             Grenada
1022             Guadeloupe
1023             Grenada
1024             Guadeloupe
1025             Guam (USA)
1026             Guatemala
1027             Guinea
1028             Guinea Bissau
1029             Guyana
1030             Haiti
1031             Heard/McDonald
1032             Honduras
1033             Hong Kong
1034             Hungary
1035             Iceland
1036             India
1037             Indonesia
1038             Iran
1039             Iraq
1040             Ireland
1041             Israel
1042             Italy
1043             Ivory Coast
1044             Jamaica
1045             Japan
1046             Jordan
1047             Kazakhstan
1048             Kenya
1049             Kiribati
1050             Kuwait
1051             Kyrgyzstan
1052             Laos
1053             Latvia
1054             Lebanon
1055             Lesotho
1056             Liberia
1057             Libya
1058             Liechtenstein
1059             Lithuania
1060             Luxembourg
1061             Macau
1062             Macedonia
1063             Madagascar
1064             Malawi
1065             Malaysia
1066             Maldives
1067             Mali
1068             Malta
1069             Marshall Isl.
1070             Martinique
1071             Mauritania
1072             Mauritius
1073             Mayotte
1074             Mexico
1075             Mayotte
1076             Mexico
1077             Micronesia
1078             Moldavia
1079             Monaco
1080             Mongolia
1081             Montserrat
1082             Morocco
1083             Mozambique
1084             Myanmar
1085             N. Mariana Isl.
1086             Namibia
1087             Nauru
1088             Nepal
1089             Netherlands
1090             Neutral Zone
1091             New Caledonia
1092             New Zealand
1093             Nicaragua
1094             Niger
1095             Nigeria
1096             Niue
1097             Norfolk Island
1098             Northern Ireland
1099             North Korea
1100             Norway
1101             Oman
1102             Pakistan
1103             Palau
1104             Panama
1105             Papua New Guinea
1106             Paraguay
1107             Peru
1108             Philippines
1109             Pitcairn Isl.
1110             Poland
1111             Polynesia
1112             Portugal
1113             Puerto Rico
1114             Qatar
1115             Reunion
1116             Romania
1117             Russia
1118             Rwanda
1119             Samoa
1120             San Marino
1121             Saudi Arabia
1122             Scotland
1123             Senegal
1124             Seychelles
1125             Sierra Leone
1126             Singapore
1127             Sierra Leone
1128             Singapore
1129             Slovak Rep.
1130             Slovenia
1131             Solomon Isl.
1132             Somalia
1133             South Africa
1134             South Korea
1135             Spain
1136             Sri Lanka
1137             St Helena
1138             St Lucia
1139             St Pierre
1140             St Tome
1141             St Vincent
1142             Sudan
1143             Suriname
1144             Swaziland
1145             Sweden
1146             Switzerland
1147             Syrian Arab Republic
1148             Tadjikistan
1149             Taiwan
1150             Tanzania
1151             Thailand
1152             Tobago
1153             Togo
1154             Tokelau
1155             Tonga
1156             Trinidad & Tobago
1157             Tunisia
1158             Turopaque
1159             Turkmenistan
1160             Turks/Caicos Isl.
1161             Tuvalu
1162             Uganda
1163             Ukraine
1164             Uruguay
1165             Uzbekistan
1166             Vanuatu
1167             Vatican City
1168             Venezuela
1169             Vietnam
1170             Virg.Isl. (UK)
1171             Virg.Isl. (US)
1172             Wales
1173             Western Sahara
1174             Yemen
1175             Yugoslavia
1176             Zaire
1177             Zambia
1178             Zimbabwe
1179             END_OF_LIST
1180             }
1181              
1182             ###############################################################################
1183              
1184             =item us_continental_states_list ()
1185              
1186             Returns list of US continental states for selection. May be overriden
1187             if site needs only a fraction of that.
1188              
1189             =cut
1190              
1191             sub us_continental_states_list () {
1192 0     0 1 0 my $self=shift;
1193 0         0 my @list;
1194 0         0 foreach my $st ($self->us_states_list) {
1195 0 0       0 next if $st =~ /^AK/;
1196 0 0       0 next if $st =~ /^AS/;
1197 0 0       0 next if $st =~ /^FM/;
1198 0 0       0 next if $st =~ /^GU/;
1199 0 0       0 next if $st =~ /^HI/;
1200 0 0       0 next if $st =~ /^MH/;
1201 0 0       0 next if $st =~ /^MP/;
1202 0 0       0 next if $st =~ /^VI/;
1203 0         0 push(@list,$st);
1204             }
1205 0         0 @list;
1206             }
1207              
1208             ###############################################################################
1209              
1210             =item us_states_list ()
1211              
1212             Returns list of US states for selection. May be overriden if site
1213             needs only a fraction of that.
1214              
1215             =cut
1216              
1217             sub us_states_list () {
1218 0     0 1 0 split(/\n/,<<'END_OF_LIST');
1219             AL - Alabama
1220             AK - Alaska
1221             AS - American Samoa
1222             AZ - Arizona
1223             AR - Arkansas
1224             CA - California
1225             CO - Colorado
1226             CT - Connecticut
1227             DE - Delaware
1228             DC - District Of Columbia
1229             FM - Federated States Of Micronesia
1230             FL - Florida
1231             GA - Georgia
1232             GU - Guam
1233             HI - Hawaii
1234             ID - Idaho
1235             IL - Illinois
1236             IN - Indiana
1237             IA - Iowa
1238             KS - Kansas
1239             KY - Kentucky
1240             LA - Louisiana
1241             ME - Maine
1242             MH - Marshall Islands
1243             MD - Maryland
1244             MA - Massachusetts
1245             MI - Michigan
1246             MN - Minnesota
1247             MS - Mississippi
1248             MO - Missouri
1249             MT - Montana
1250             NE - Nebraska
1251             NV - Nevada
1252             NH - New Hampshire
1253             NJ - New Jersey
1254             NM - New Mexico
1255             NY - New York
1256             NC - North Carolina
1257             ND - North Dakota
1258             MP - Northern Mariana Islands
1259             OH - Ohio
1260             OK - Oklahoma
1261             OR - Oregon
1262             PW - Palau
1263             PA - Pennsylvania
1264             PR - Puerto Rico
1265             RI - Rhode Island
1266             SC - South Carolina
1267             SD - South Dakota
1268             TN - Tennessee
1269             TX - Texas
1270             UT - Utah
1271             VT - Vermont
1272             VI - Virgin Islands
1273             VA - Virginia
1274             WA - Washington
1275             WV - West Virginia
1276             WI - Wisconsin
1277             WY - Wyoming
1278             END_OF_LIST
1279             }
1280              
1281             ##
1282             # Returns a list of known Credit Card types. May be overriden. Should be
1283             # consistent with cc_validate.
1284             #
1285             sub cc_list ($) {
1286 0     0 0 0 split(/\n/,<<'END_OF_LIST');
1287             Visa
1288             American Express
1289             MasterCard
1290             Discover
1291             Diner's Club
1292             END_OF_LIST
1293             }
1294              
1295             ###############################################################################
1296              
1297             =item cc_validate (%)
1298              
1299             Returns error text if card number is invalid. Only checksum and
1300             consistence with card type is checked.
1301              
1302             Card number is taken from 'number' argument and card type from 'type'
1303             argument (optionally).
1304              
1305             Will store card number into a scalar reference given by 'validated'
1306             argument, if it exists and the card validates. Will store card type code
1307             into scalar reference given by 'typecode' argument if it exists and the
1308             card validates. Codes are:
1309              
1310             VI -- Visa
1311             AE -- American Express
1312             MC -- Mastercard
1313             DC -- Discover
1314              
1315             =cut
1316              
1317             sub cc_validate ($%) {
1318 4     4 1 184 my $self=shift;
1319 4         14 my $args=get_args(\@_);
1320              
1321 4         40 my $number=$args->{'number'};
1322 4         6 my $type=$args->{'type'};
1323              
1324             # General corrections and checks first.
1325             #
1326 4         16 $number=~s/\D//g;
1327 4 50       9 if(length($number)<13) {
1328 0         0 return $self->Tx('Number is too short!');
1329             }
1330              
1331             # Checksum first
1332             #
1333 4         6 my $sum=0;
1334 4         9 for(my $i=0; $i!=length($number)-1; $i++) {
1335 60         75 my $weight = substr($number, -1 * ($i + 2), 1) * (2 - ($i % 2));
1336 60 100       101 $sum += (($weight < 10) ? $weight : ($weight - 9));
1337             }
1338 4 50       9 if(substr($number,-1) ne (10-$sum%10)%10) {
1339             ### dprint "have ".substr($number,-1)." want ".(10-$sum%10)%10;
1340 0         0 return $self->Tx('Invalid number!');
1341             }
1342              
1343             # Guessing card type.
1344             #
1345 4         5 my $typecode;
1346 4         6 my $realtype='';
1347 4         4 my $reqlen;
1348 4 100       14 if($number =~ /^(?:34|37)/) {
    50          
    0          
    0          
1349 1         2 $realtype='american express';
1350 1         2 $typecode='AE';
1351 1         2 $reqlen=[15];
1352             }
1353             elsif($number =~ /^4/) {
1354 3         5 $realtype='visa';
1355 3         3 $typecode='VI';
1356 3         4 $reqlen=[13,16];
1357             }
1358             elsif($number =~ /^5/) {
1359 0         0 $realtype='master\s?card';
1360 0         0 $typecode='MC';
1361 0         0 $reqlen=[16];
1362             }
1363             elsif($number =~ /^6/) {
1364 0         0 $realtype='discover';
1365 0         0 $typecode='DC';
1366 0         0 $reqlen=[16];
1367             }
1368             else {
1369 0         0 return $self->Tx('Unknown card type!');
1370             }
1371 4 50       7 if($reqlen) {
1372 4 100       8 scalar(grep { length($number)==$_ } @$reqlen) ||
  7         22  
1373             return $self->Tx('Invalid number length!');
1374             }
1375              
1376             # Checking guessed type against the given type.
1377             #
1378 3 50 66     19 if($type && lc($type) !~ $realtype) {
1379 0         0 return $self->Tx('Number does not match card type!');
1380             }
1381              
1382             # Storing values if we were given these references.
1383             #
1384 3 50       7 ${$args->{'validated'}}=$number if $args->{'validated'};
  3         4  
1385 3 50       7 ${$args->{'typecode'}}=$typecode if $args->{'typecode'};
  3         4  
1386              
1387 3         10 return '';
1388             }
1389              
1390             ##
1391             # Calculates year - accepts value, +N, -N.
1392             #
1393             sub calculate_year ($$) {
1394 0     0 0 0 my $self=shift;
1395 0         0 my $year=shift;
1396 0 0       0 if(substr($year,0,1) eq '+') {
    0          
    0          
    0          
1397 0         0 $year=(localtime)[5]+1900+substr($year,1);
1398             }
1399             elsif(substr($year,0,1) eq '-') {
1400 0         0 $year=(localtime)[5]+1900-substr($year,1);
1401             }
1402             elsif($year < 20) {
1403 0         0 $year+=2000;
1404             }
1405             elsif($year < 100) {
1406 0         0 $year+=1900;
1407             }
1408 0         0 $year;
1409             }
1410              
1411             ##
1412             # Returns form phase for multi-page forms. Taken from 'phase' argument
1413             # to 'display' method.
1414             #
1415             sub form_phase ($) {
1416 0     0 0 0 my $self=shift;
1417 0   0     0 return $self->{'phase'} || 1;
1418             }
1419              
1420             ###############################################################################
1421              
1422             sub Tx ($$;$) {
1423 1     1 0 3 my $self=shift;
1424 1         2 my $text=shift;
1425 1   50     4 my $values=shift || { };
1426              
1427 1 50       7 if($self->can('Tx_translate')) {
1428 0         0 $text=$self->Tx_translate($text,$values);
1429             }
1430              
1431 1         2 $text=~s/
1432             \{\{(\w+)\}\}
1433             /
1434 0 0       0 exists $values->{$1} ? $values->{$1} : ''
1435             /xesg;
1436              
1437 1         5 return $text;
1438             }
1439              
1440             ###############################################################################
1441             1;
1442             __END__