File Coverage

blib/lib/HTML/FormsDj.pm
Criterion Covered Total %
statement 99 317 31.2
branch 26 136 19.1
condition 6 23 26.0
subroutine 18 35 51.4
pod 5 13 38.4
total 154 524 29.3


\n),
line stmt bran cond sub pod time code
1             package HTML::FormsDj;
2              
3 1     1   40521 use strict;
  1         3  
  1         40  
4 1     1   6 use warnings;
  1         1  
  1         44  
5              
6             our $VERSION = '0.03';
7              
8 1     1   1054 use Data::FormValidator;
  1         49004  
  1         79  
9 1     1   15 use Data::FormValidator::Constraints;
  1         5  
  1         8  
10 1     1   119 use Data::Dumper;
  1         2  
  1         50  
11 1     1   1016 use Carp::Heavy;
  1         139  
  1         31  
12 1     1   1842 use Digest::SHA;
  1         6047  
  1         61  
13 1     1   10 use Carp;
  1         3  
  1         3804  
14              
15             our $_csrftoken;
16              
17             sub new {
18 1     1 0 813 my($this, %param) = @_;
19 1   33     8 my $class = ref($this) || $this;
20 1         2 my $self = \%param;
21 1         3 bless $self, $class;
22              
23              
24 1 50 33     9 if (exists $self->{meta}->{fields} && exists $self->{meta}->{fieldsets}) {
25 0         0 croak 'Either use meta->fields or meta->fieldsets, not both!';
26             }
27              
28 1 50       16 if (! exists $self->{field}) {
29 0         0 croak 'No FIELDS hash specified!';
30             }
31              
32 1 50       4 if (! exists $self->{meta}) {
33 0         0 $self->{meta} = {};
34             }
35              
36 1 50 33     8 if (! exists $self->{meta}->{fields} && ! exists $self->{meta}->{fieldsets}) {
37             # generate them if the user doesn't bother
38 1         3 $self->{meta}->{fields} = [];
39 1         2 foreach my $field (sort keys %{$self->{field}}) {
  1         7  
40 2         5 my $n = $field;
41 2         7 $n =~ s/^(.)/uc($1)/e;
  2         8  
42 2         3 push @{$self->{meta}->{fields}}, { field => $field, label => $n };
  2         10  
43             }
44             }
45              
46 1 50       5 if (exists $self->{csrf}) {
47 0 0 0     0 if ($self->{csrf} && ! $_csrftoken) {
48 0         0 my $sha = Digest::SHA->new('SHA-256');
49 0         0 $sha->reset();
50 0         0 $self->{sha} = $sha;
51 0         0 $_csrftoken = $self->_gen_csrf_token();
52             }
53             }
54             else {
55 1         4 $self->{csrf} = 0;
56             }
57              
58 1         3 return $self;
59             }
60              
61             sub cleandata {
62 0     0 0 0 my($this, %data) = @_;
63              
64             # construct validator structs
65 0         0 my(@required, @optional, %input, %attrs, %constraints);
66              
67 0         0 $this->{isclean} = 0;
68              
69 0 0       0 if ($this->{csrf}) {
70 0 0       0 if(! $this->_check_csrf(%data)) {
71             # CSRF check failed, so we don't tamper with input
72             # further. die and done.
73 0         0 return ();
74             }
75             }
76              
77 0 0       0 if (exists $this->{dfv}) {
78             # override all
79 0         0 %input = %{$this->{dfv}};
  0         0  
80             }
81             else {
82             # generate dfv hash
83 0         0 foreach my $field (keys %{$this->{field}}) {
  0         0  
84 0 0       0 if($this->{field}->{$field}->{required}) {
85 0         0 push @required, $field;
86             }
87             else {
88 0         0 push @optional, $field;
89             }
90 0         0 $constraints{ $field } = $this->{field}->{$field}->{validate};
91 0   0     0 $input{ $field } = $data{ $field } || qq();
92             }
93             }
94              
95 0 0       0 if (exists $this->{attributes}) {
96             # there are dfv options, pass them as is
97 0         0 %attrs = %{$this->{attributes}};
  0         0  
98             }
99 0 0       0 if(! exists $attrs{required}) {
100 0         0 $attrs{required} = \@required;
101             }
102 0 0       0 if(! exists $attrs{optional}) {
103 0         0 $attrs{optional} = \@optional;
104             }
105 0 0       0 if(! exists $attrs{constraint_methods}) {
106 0         0 $attrs{constraint_methods} = \%constraints;
107             }
108              
109             # validate the input
110 0         0 my $results = Data::FormValidator->check(\%input, \%attrs);
111              
112 0 0 0     0 if ($results->has_invalid or $results->has_missing) {
113             # store errors for later output
114 0         0 $this->{isclean} = 0;
115 0 0       0 if ( $results->has_missing ) {
116 0         0 foreach my $field ( $results->missing ) {
117 0         0 $this->{missing}->{$field} = 1;
118             }
119             }
120 0 0       0 if ( $results->has_invalid ) {
121 0         0 foreach my $field ( $results->invalid ) {
122 0         0 my $failed = $results->invalid( $field );
123 0 0       0 if (ref($failed) eq 'HASH') {
124 0         0 $this->{invalid}->{$field} = join ', ', @{$failed->{$field}};
  0         0  
125             }
126             else {
127 0         0 $this->{invalid}->{$field} = join ', ', @{$failed};
  0         0  
128             }
129             }
130             }
131             }
132             else {
133 0 0       0 if(exists $this->{clean}) {
134             # call the custom clean() closure supplied by the user
135 0         0 ($this->{isclean}, $this->{error}) = $this->{clean}(%{$results->valid});
  0         0  
136             }
137             else {
138 0         0 $this->{isclean} = 1;
139             }
140             }
141              
142              
143             # store cleaned and raw data
144 0         0 $this->{cleaned} = $results->valid;
145 0         0 $this->{raw} = \%data;
146              
147 0         0 return %{$this->{cleaned}};
  0         0  
148             }
149              
150             sub clean {
151 0     0 0 0 my($this) = @_;
152 0         0 return $this->{isclean};
153             }
154              
155             sub error {
156 0     0 0 0 my($this) = @_;
157 0 0       0 if(exists $this->{error}) {
158 0         0 return $this->{error};
159             }
160             else {
161 0         0 return qq();
162             }
163             }
164              
165             sub _check_csrf {
166 0     0   0 my ($this, %data) = @_;
167              
168 0 0       0 if (! exists $data{csrftoken}) {
169 0         0 $this->{error} = 'CSRF ERROR: CSRF token is not supplied with POST data!';
170 0         0 return 0;
171             }
172              
173 0 0       0 if (! exists $this->{'_csrf_cookie'}) {
174 0         0 $this->{error} = 'CSRF ERROR: CSRF cookie is not set correctly(notexist)!';
175 0         0 return 0;
176             }
177             else {
178 0 0       0 if(! $this->{'_csrf_cookie'} ) {
179 0         0 $this->{error} = 'CSRF ERROR: CSRF cookie is not set correctly(undef)!';
180 0         0 return 0;
181             }
182             }
183              
184 0         0 my $posttoken = $data{csrftoken}; # hidden post var
185 0         0 my $cookietoken = $this->{'_csrf_cookie'}; # cookie
186              
187 0 0       0 if ($posttoken ne $cookietoken) {
188 0         0 $this->{error} = 'CSRF ERROR: supplied COOKIE csrftoken doesnt match stored csrf token!';
189 0         0 $this->{error} .= sprintf "
post: %s
cookie: %s", $posttoken, $cookietoken;
190 0         0 return 0;
191             }
192              
193 0         0 return 1;
194             }
195              
196             sub as_p {
197 1     1 1 1410 my($this) = @_;
198 1         2 my $html;
199 1         4 $this->_normalize();
200              
201 1 50       4 if ($this->{csrf}) {
202 0         0 $html = $this->csrftoken();
203             }
204              
205 1 50       6 if (exists $this->{meta}->{fields}) {
206             # just an array of fields
207 1         1 foreach my $field( @{$this->{meta}->{fields}}) {
  1         3  
208 2         15 $html .= $this->_p_field($field);
209             }
210             }
211             else {
212             # it's a fieldset
213 0         0 foreach my $fieldset (@{$this->{meta}->{fieldsets}}) {
  0         0  
214 0         0 my $htmlfields;
215 0         0 foreach my $field (@{$fieldset->{fields}}) {
  0         0  
216 0         0 $htmlfields .= $this->_p_field($field);
217             }
218 0         0 $html .= $this->_fieldset(
219 0         0 join(' ', @{$fieldset->{classes}}),
220             $fieldset->{id},
221             $fieldset->{legend},
222             $htmlfields
223             );
224             }
225             }
226              
227 1         4 return $html;
228             }
229              
230             sub as_table {
231 0     0 1 0 my($this) = @_;
232 0         0 my $html;
233 0         0 $this->_normalize();
234              
235 0 0       0 if ($this->{csrf}) {
236 0         0 $html = $this->csrftoken();
237             }
238              
239 0 0       0 if (exists $this->{meta}->{fields}) {
240             # just an array of fields
241 0         0 foreach my $field( @{$this->{meta}->{fields}}) {
  0         0  
242 0         0 $html .= $this->_tr_field($field);
243             }
244 0         0 return $this->_table('formtable', $html);
245             }
246             else {
247             # it's a fieldset
248 0         0 foreach my $fieldset (@{$this->{meta}->{fieldsets}}) {
  0         0  
249 0         0 my $htmlfields;
250 0         0 foreach my $field (@{$fieldset->{fields}}) {
  0         0  
251 0         0 $htmlfields .= $this->_tr_field($field);
252             }
253 0         0 $html .= $this->_table($fieldset->{id}, $htmlfields, $fieldset->{legend});
254             }
255             }
256              
257 0         0 return $html;
258             }
259              
260             sub as_is {
261 1     1 1 16 my($this) = @_;
262 1         5 $this->_normalize();
263 1         3 return $this->{meta};
264             }
265              
266             sub fields {
267 0     0 1 0 my($this) = @_;
268 0 0       0 if (exists $this->{meta}->{fields}) {
269 0         0 return @{$this->{meta}->{fields}};
  0         0  
270             }
271             else {
272 0         0 return ();
273             }
274             }
275              
276             sub fieldsets {
277 0     0 1 0 my($this) = @_;
278 0 0       0 if (exists $this->{meta}->{fieldsets}) {
279 0         0 return @{ $this->{meta}->{fieldsets} };
  0         0  
280             }
281             else {
282 0         0 return ();
283             }
284             }
285              
286             sub dumpmeta {
287 0     0 0 0 my($this) = @_;
288 0         0 my $dump = Dumper($this->{meta});
289 0         0 $dump =~ s/^\$VAR1 = / /;
290 0         0 return sprintf qq(
%s
), $dump;
291             }
292              
293             sub csrftoken {
294 0     0 0 0 my($this) = @_;
295 0 0       0 if ($this->{csrf}) {
296 0         0 return sprintf qq(), $_csrftoken;
297             }
298             else {
299 0         0 return qq();
300             }
301             }
302              
303             sub getcsrf {
304 0     0 0 0 my($this) = @_;
305 0 0       0 if ($this->{csrf}) {
306 0         0 return $_csrftoken;
307             }
308             else {
309 0         0 return qq();
310             }
311             }
312              
313             sub csrfcookie {
314 0     0 0 0 my($this, $token) = @_;
315 0 0       0 if ($this->{csrf}) {
316 0         0 $this->{'_csrf_cookie'} = $token;
317             }
318 0         0 return 1;
319             }
320              
321             #
322             # INTERNALS HERE
323             #
324              
325             sub _message {
326 2     2   4 my($this, $message, $id) = @_;
327 2         11 return sprintf qq(%s), $id, $message;
328             }
329              
330             sub _tr_field {
331 0     0   0 my($this, $field) = @_;
332 0         0 return $this->_tr(
333 0         0 join(q( ), @{$field->{classes}}),
334             $field->{id},
335             $this->_label(
336             $field->{id} . '_input',
337             $field->{label}
338             ),
339             $this->_input(
340             $field->{id} . '_input',
341             $field->{type},
342             $field->{field},
343             $field->{value},
344             $field->{default} # hashref, arrayref or scalar
345             ) .
346             $this->_message($field->{message}, $field->{id} . '_message')
347             );
348             }
349              
350             sub _tr {
351 0     0   0 my($this, $class, $id, $label, $input) = @_;
352 0         0 return sprintf qq(
%s%s
353             $id, $class, $label, $class, $input;
354             }
355              
356             sub _table {
357 0     0   0 my($this, $id, $cdata, $legend) = @_;
358 0         0 my $html = sprintf qq(), $id; \n), $legend; %s
359 0 0       0 if ($legend) {
360 0         0 $html .= sprintf qq(
%s
361             }
362 0         0 $html .= sprintf qq(
\n), $cdata;
363 0         0 return $html;
364             }
365              
366             sub _normalize_field {
367 4     4   6 my($this, $field) = @_;
368              
369 4 50       15 if (! exists $field->{label}) {
370 0         0 $field->{label} = $field->{field};
371 0         0 $field->{label} =~ s/^(.)/uc($1)/e;
  0         0  
372             }
373              
374 4 50 33     11 if (exists $this->{markrequired} && $this->{field}->{$field->{field}}->{required}) {
375 0 0       0 if ($this->{markrequired} eq 'asterisk') {
    0          
376 0         0 $field->{label} = $field->{label} . ' *';
377             }
378             elsif ($this->{markrequired} eq 'bold') {
379 0         0 $field->{label} = $this->_b($field->{label});
380             }
381             else {
382 0         0 $field->{label} = $field->{label} . $this->{markrequired};
383             }
384             }
385              
386 4 100       11 if (! exists $field->{classes}) {
387 2         6 $field->{classes} = [ qw(formfield) ];
388             }
389              
390 4 100       8 if (! exists $field->{id}) {
391 2         8 $field->{id} = 'id_formfield_' . $field->{field};
392             }
393              
394 4 100       10 if (! exists $field->{message}) {
395 2         4 $field->{message} = qq();
396             }
397 4 50       14 if (exists $this->{invalid}->{$field->{field}}) {
398 0 0       0 if (! exists $field->{message}) {
399 0         0 $field->{message} = 'invalid input';
400             }
401 0         0 $field->{error} = $this->{invalid}->{$field->{field}};
402             }
403              
404 4 50       17 if (exists $this->{missing}->{$field->{field}}) {
405 0 0       0 if (! exists $field->{message}) {
406 0         0 $field->{message} = 'missing input';
407             }
408 0         0 $field->{error} = 'missing input';
409             }
410              
411 4 50       12 if (! exists $this->{raw}->{$field->{field}}) {
412 4         6 $field->{value} = qq();
413             }
414             else {
415 0         0 $field->{value} = $this->{raw}->{$field->{field}};
416             }
417              
418 4 50       12 if (! exists $this->{field}->{$field->{field}}->{type}) {
419 0         0 $field->{type} = 'text';
420             }
421             else {
422 4         10 $field->{type} = $this->{field}->{$field->{field}}->{type};
423             }
424              
425 4 100       33 if (! exists $field->{default}) {
426 2         7 $field->{default} = qq();
427             }
428              
429 4         10 return $field;
430             }
431              
432             sub _normalize {
433 2     2   3 my($this) = @_;
434              
435 2 50       9 if (exists $this->{meta}->{fields}) {
436 2         3 my @normalized;
437 2         3 foreach my $field( @{$this->{meta}->{fields}}) {
  2         6  
438 4 50       11 if (! exists $field->{field}) {
439 0         0 carp 'unnamed field, ignoring!';
440 0         0 next;
441             }
442              
443 4         9 push @normalized, $this->_normalize_field($field);
444             }
445 2         8 $this->{meta}->{fields} = \@normalized;
446             }
447              
448 2 50       6 if (exists $this->{meta}->{fieldsets}) {
449              
450 0         0 my @fieldsets;
451 0         0 foreach my $fieldset (@{$this->{meta}->{fieldsets}}) {
  0         0  
452 0 0       0 if (! exists $fieldset->{id}) {
453 0 0       0 if (! exists $fieldset->{name}) {
454 0         0 $fieldset->{id} = 'id_fieldset_' . $.;
455             }
456             else {
457 0         0 $fieldset->{id} = 'id_fieldset_' . $fieldset->{name};
458             }
459             }
460              
461 0 0       0 if (! exists $fieldset->{classes}) {
462 0         0 $fieldset->{classes} = [ qw(formfieldset) ];
463             }
464              
465 0 0       0 if (! exists $fieldset->{legend}) {
466 0         0 $fieldset->{legend} = qq();
467             }
468              
469 0         0 my @normalized;
470 0         0 foreach my $field (@{$fieldset->{fields}}) {
  0         0  
471 0 0       0 if (! exists $field->{field}) {
472 0         0 carp 'unnamed field, ignoring!';
473 0         0 next;
474             }
475 0         0 push @normalized, $this->_normalize_field($field);
476             }
477              
478 0         0 $fieldset->{fields} = \@normalized;
479 0         0 push @fieldsets, $fieldset;
480             }
481 0         0 $this->{meta}->{fieldsets} = \@fieldsets;
482             }
483              
484 2         4 return;
485             }
486              
487              
488             sub _fieldset {
489 0     0   0 my($this, $class, $id, $legend, $cdata) = @_;
490 0         0 return sprintf qq(
%s\n%s\n
\n),
491             $class, $id, $legend, $cdata;
492             }
493              
494             sub _p_field {
495 2     2   3 my($this, $field) = @_;
496 2         12 return $this->_p(
497 2         4 join(' ', @{$field->{classes}}),
498             $field->{id},
499             $this->_label(
500             $field->{id} . '_input',
501             $field->{label}
502             ) .
503             $this->_input(
504             $field->{id} . '_input',
505             $field->{type},
506             $field->{field},
507             $field->{value},
508             $field->{default} # hashref, arrayref or scalar
509             ) .
510             $this->_message($field->{message}, $field->{id} . '_message')
511             );
512             }
513              
514             sub _p {
515 2     2   3 my ($this, $class, $id, $cdata) = @_;
516 2         13 return sprintf qq(

%s

\n), $class, $id, $cdata;
517             }
518              
519             sub _label {
520 2     2   4 my ($this, $id, $name) = @_;
521 2         14 return sprintf qq(\n ), $id, $name;
522             }
523              
524             sub _input {
525 2     2   4 my ($this, $id, $type, $name, $value, $default) = @_;
526              
527 2         3 my $html;
528              
529 2 50 66     21 if ($type eq 'text' || $type eq 'password') {
    0          
    0          
    0          
530 2 50       5 if (! $value) {
531 2         3 $value = $default;
532             }
533 2         7 $html = sprintf qq(\n \n), $type, $id, $name, $value;
534             }
535             elsif ($type eq 'choice') {
536 0         0 my $html = sprintf qq(\n
537 0 0       0 if (ref($default) eq 'HASH') {
    0          
538 0         0 foreach my $option (sort keys %{$default}) {
  0         0  
539 0         0 $html .= sprintf qq(\n ), $option, $default->{$option};
540             }
541             }
542             elsif (ref($default) eq 'ARRAY') {
543 0         0 foreach my $option (@{$default}) {
  0         0  
544 0         0 my $selected = qq();
545 0 0       0 if ($value eq $option->{value}) {
546 0         0 $selected = ' selected';
547             }
548 0         0 $html .= sprintf qq(\n ), $option->{value}, $selected, $option->{label};
549             }
550             }
551 0         0 $html .= qq(\n \n);
552              
553             }
554             elsif ($type eq 'option') {
555 0         0 $html = qq(\n
    \n);
556 0 0       0 if (ref($default) eq 'HASH') {
    0          
557 0         0 foreach my $option (sort keys %{$default}) {
  0         0  
558 0         0 my $checked = qq();
559 0 0       0 if ($value eq $option->{value}) {
560 0         0 $checked = qq( checked="checked");
561             }
562 0         0 $html .= qq(
  • ) . $this->_label(
  • 563             $id . $option,
    564             sprintf (qq(), $option, $name, $checked)
    565             . $default->{$option}
    566             ) .
    567             qq(\n\n);
    568             }
    569             }
    570             elsif (ref($default) eq 'ARRAY') {
    571 0         0 foreach my $option (@{$default}) {
      0         0  
    572 0         0 my $checked = qq();
    573 0 0       0 if ($value eq $option->{value}) {
    574 0         0 $checked = qq( checked="checked");
    575             }
    576 0         0 $html .= qq(
  • ) . $this->_label(
  • 577             $id . $option->{value},
    578             sprintf (qq(), $option->{value}, $name, $checked)
    579             . $option->{label}
    580             ) .
    581             qq(\n\n);
    582             ;
    583             }
    584             }
    585 0         0 $html .= qq(\n);
    586             }
    587             elsif ($type eq 'textarea') {
    588 0         0 $html = sprintf qq(\n), $id, $name, $value;
    589             }
    590 2         13 return $html;
    591             }
    592              
    593             sub _b {
    594 0     0     my($this, $cdata) = @_;
    595 0           return sprintf qq(%s), $cdata;
    596             }
    597              
    598             sub _gen_csrf_token {
    599 0     0     my($this) = @_;
    600 0           $this->{sha}->add(rand(10));
    601 0           $this->{sha}->add(time);
    602 0           my $csrftoken = $this->{sha}->hexdigest();
    603 0           $this->{sha}->reset();
    604 0           return $csrftoken;
    605             }
    606              
    607             1;
    608              
    609             __END__