File Coverage

blib/lib/CGI/Application/Plugin/GenVal.pm
Criterion Covered Total %
statement 30 207 14.4
branch 0 118 0.0
condition 0 12 0.0
subroutine 10 17 58.8
pod 1 1 100.0
total 41 355 11.5


line stmt bran cond sub pod time code
1             package CGI::Application::Plugin::GenVal;
2            
3 1     1   57268 use strict;
  1         4  
  1         53  
4 1     1   7 use warnings;
  1         3  
  1         84  
5 1     1   7 use Carp;
  1         8  
  1         110  
6            
7 1     1   6 use vars qw ( $VERSION @ISA @EXPORT_OK %EXPORT_TAGS );
  1         3  
  1         328  
8            
9             require Exporter;
10             @ISA = qw(Exporter);
11            
12             @EXPORT_OK = ( 'GenVal' );
13            
14             %EXPORT_TAGS = (
15             all => [ 'GenVal' ],
16             std => [ 'GenVal' ],
17             );
18            
19             $VERSION = '0.01_02';
20            
21             my $genval;
22            
23             sub import {
24             ### The real object is in ::guts
25 1     1   344 $genval = new CGI::Application::Plugin::GenVal::guts( $_[1] );
26 1         152 CGI::Application::Plugin::GenVal->export_to_level(1, @_);
27             }#sub
28            
29             sub GenVal {
30             ### Grab CGI::Application based object and keep a reference to it
31 0 0   0 1 0 unless ( $genval->{params}->{__ca_obj} ) {
32 0         0 $genval->{params}->{__ca_obj} = shift;
33             }#unless
34 0         0 return $genval;
35             }#sub
36            
37            
38             package CGI::Application::Plugin::GenVal::guts;
39            
40 1     1   8 use strict;
  1         3  
  1         43  
41 1     1   6 use warnings;
  1         2  
  1         39  
42 1     1   6 use Carp;
  1         3  
  1         653  
43 1     1   2260 use Perl6::Junction qw /any/;
  1         37663  
  1         9492  
44            
45             ### Create simple object
46             sub new {
47 1     1   3 my $class = shift;
48 1         4 my $obj = {};
49 1         5 bless( $obj, $class );
50 1         3 return $obj;
51             }#sub
52            
53            
54             ### Method for generating a HTML input tag
55             sub gen_input {
56 0     0     my $self = shift;
57            
58             ### Make sure they aren't trying to call directly
59 0 0         croak( "Can only be called as a method" ) unless ( ref( $self ) eq 'CGI::Application::Plugin::GenVal::guts' );
60            
61             ### Get CGI::Application based object
62 0           my $ca = $self->{params}->{__ca_obj};
63            
64             ### Input must be passed in as a single hash reference
65 0 0 0       croak( 'Must receive a single hash reference' ) if ( @_ > 1 || ref $_[0] ne 'HASH' );
66            
67 0           my $input = shift;
68            
69 0           my $inputhtml;
70            
71             ### Generate style attribute from input schema
72             ### Styles default to {all} then overloaded with style for specific type
73             ### values are cloned so as not to effect referenced data
74 0           my $attrib_raw = {};
75 0 0         if ( $input->{style}->{all}->{ $input->{error}->{type} }->{style} ) {
76 0           _gen_input_addstyle( $attrib_raw, $input->{style}->{all}->{ $input->{error}->{type} }->{style} );
77             }#if
78 0 0         if ( $input->{style}->{ $input->{details}->{type} }->{ $input->{error}->{type} }->{style} ) {
79 0           _gen_input_addstyle( $attrib_raw, $input->{style}->{ $input->{details}->{type} }->{ $input->{error}->{type} }->{style} );
80             }#if
81            
82             ### Create style attribute for CGI.pm HTML generation
83 0           my $attrib_final = _gen_input_tag( $attrib_raw );
84            
85             ### Create Text field
86 0 0         if ( $input->{details}->{type} eq 'text' ) {
    0          
    0          
87 0           return $ca->query->textfield(
88             -name=>$input->{field},
89             -value=>$input->{value},
90             -size=>$input->{details}->{size},
91             -maxlength=>$input->{details}->{max},
92             %$attrib_final,
93             );
94             }#if
95            
96             ### Create password field
97             elsif ( $input->{details}->{type} eq 'password' ) {
98 0           return $ca->query->password_field(
99             -name=>$input->{field},
100             -value=>$input->{value},
101             -size=>$input->{details}->{size},
102             -maxlength=>$input->{details}->{max},
103             %$attrib_final,
104             );
105             }#if
106            
107             ### Create select field
108             ### Data for select fields is loaded from a subroutine or method
109             elsif ( $input->{details}->{type} eq 'select' ) {
110 0           my ( $labels, $values, $default );
111 0 0         if ( $input->{details}->{source} =~ /^sub self (.*?)$/ ) {
    0          
112 0           eval " ( \$labels, \$values, \$default ) = \$ca->$1( \$input->{value} ); ";
113 0 0         if ($@) {
114 0           croak( "Error getting values for select '$input->{field}': $@" );
115             # $ca->error( $ca->loc( "Error getting values for select '%1': %2", $field, $@ ) );
116             }#if
117             }#if
118             elsif ( $input->{details}->{source} =~ /^sub (.*?)$/ ) {
119 0           eval " ( \$labels, \$values, \$default ) = $1( \$input->{value} ); ";
120 0 0         if ($@) {
121 0           croak( "Error getting values for select '$input->{field}': $@" );
122             # $ca->error( $ca->loc( "Error getting values for select '%1': %2", $field, $@ ) );
123             }#if
124             }#elsif
125 0           return $ca->query->popup_menu(
126             -name=>$input->{field},
127             -values=>$values,
128             -default=>$default,
129             -labels=>$labels,
130             %$attrib_final,
131             );
132             }#if
133            
134             ### Load custom input HTML field
135             ### All HTML for input field comes from a method or subroutine
136             ### These are passed the field value and CGI.pm style attributes
137 0 0         if ( $input->{details}->{type} eq 'custom' ) {
138 0           my $html;
139 0 0         if ( $input->{details}->{source} =~ /^sub self (.*?)$/ ) {
    0          
140 0           eval " \$html = \$ca->$1( \$input->{value}, \$attrib_final ); ";
141 0 0         if ($@) {
142 0           croak( "Error getting html for custom field '$input->{field}': $@" );
143             # $ca->error( $ca->loc( "Error getting html for custom field '$field': $@" ) );
144             }#if
145             }#if
146             elsif ( $input->{details}->{source} =~ /^sub (.*?)$/ ) {
147 0           eval " \$html = $1( \$input->{value}, \$attrib_final ); ";
148 0 0         if ($@) {
149 0           croak( "Error getting html for custom field '$input->{field}': $@" );
150             # $ca->error( $ca->loc( "Error getting html for custom field '$field': $@" ) );
151             }#if
152             }#elsif
153 0           return $html;
154             }#if
155             }#sub
156            
157            
158             sub _gen_input_addstyle {
159             ### Make sure they aren't trying to call directly
160 0 0   0     croak( "Cannot be called directly" ) unless ( caller eq 'CGI::Application::Plugin::GenVal::guts' );
161            
162             ### Clone referenced style data from %$list
163 0           my ( $attrib, $list ) = @_;
164 0           while( my ( $key, $value ) = each %$list ) {
165 0           $attrib->{style}->{$key} = $value;
166             }#while
167             }#sub
168            
169            
170             sub _gen_input_tag {
171             ### Make sure they aren't trying to call directly
172 0 0   0     croak( "Cannot be called directly" ) unless ( caller eq 'CGI::Application::Plugin::GenVal::guts' );
173            
174             ### Create CGI.pm style tag
175 0           my ( $attrib ) = @_;
176 0           my $return = {};
177 0           while( my ( $key, $value ) = each %{ $attrib->{style} } ) {
  0            
178 0           $return->{'-style'} .= "$key: $value; ";
179             }#while
180 0           return $return;
181             }#sub
182            
183            
184             ### Generate DFV (Data::FormValidator) profile for Perl and JavaScript
185             ### Returns both profiles and YAML schema reference
186             sub gen_dfv {
187 0     0     my $self = shift;
188            
189             ### Make sure they aren't trying to call directly
190 0 0         croak( "Can only be called as a method" ) unless ( ref( $self ) eq 'CGI::Application::Plugin::GenVal::guts' );
191            
192             ### Get CGI::Application based object
193 0           my $ca = $self->{params}->{__ca_obj};
194            
195             ### Input must be passed in as a single hash reference
196 0 0 0       croak( 'Must receive a single hash reference' ) if ( @_ > 1 || ref $_[0] ne 'HASH' );
197            
198 0           my ( $dfv ) = shift;
199            
200             ### Some defaults
201 0 0         $dfv->{prefix} = 'err_' unless $dfv->{prefix};
202 0 0         $dfv->{any_errors} = 'some_errors' unless $dfv->{any_errors};
203 0 0         $dfv->{required} = [] unless $dfv->{any_errors};
204            
205             ### also accepts required, required_hash, constraints_loaded
206            
207             ### These two are required
208 0 0         unless ( $dfv->{schema} ) {
209 0           croak( 'Need input schema' );
210             }#unless
211 0 0         unless ( $dfv->{form} ) {
212 0           croak( 'Need input form' );
213             }#unless
214            
215             ### Load YAML schema from file unless it's passed in
216 0 0         unless ( ref $dfv->{schema} ) {
217 0           my $capackage = ref $ca;
218 0 0 0       if ( defined( &{ "${capackage}::YAML" } ) && ref( $ca->YAML ) eq 'CGI::Application::Plugin::YAML' ) {
  0            
219 0           $dfv->{schema} = $ca->YAML->LoadFile( $dfv->{schema} );
220             }#if
221             else {
222 0           require YAML::Any;
223 0           $dfv->{schema} = YAML::Any::LoadFile( $dfv->{schema} );
224             }#else
225             }#unless
226            
227             ### Make skeleton Perl profile
228 0           my $dfv_profile_perl = {
229             required => [],
230             optional => [],
231             constraint_methods => {},
232             msgs => {
233             prefix => $dfv->{prefix},
234             any_errors => $dfv->{any_errors},
235 0           %{ $dfv->{msgs} },
236             },
237             };
238            
239             ### Make skeleton JavaScript profile
240 0           my $dfv_profile_js = {
241             required => [],
242             optional => [],
243             constraints => {},
244             msgs => {
245             prefix => $dfv->{prefix},
246             any_errors => $dfv->{any_errors},
247 0           %{ $dfv->{msgs} },
248             },
249             };
250            
251             ### Generate required fields list from schema
252 0           while ( my ($field, $settings) = each ( %{ $dfv->{schema}->{field_input} } ) ) {
  0            
253 0 0         next if ( any( @{ $dfv->{required} } ) eq $field );
  0            
254 0 0 0       if ( $settings->{required} && lc $settings->{required} ne 'no' ) {
255 0           push( @{ $dfv->{required} }, $field );
  0            
256             }#if
257             }#while
258            
259             ### Generate optional fields, also required fields from required_hash
260             ### required_hash can either be $hashref->{field}->{required} = 1
261             ### or $hashref->{field} = 1
262 0           foreach my $field ( @{ $dfv->{schema}->{ $dfv->{form} } } ) {
  0            
263 0 0         next if ( any( @{ $dfv->{required} } ) eq $field );
  0            
264 0 0         if ( ref $dfv->{required_hash}->{$field} eq 'HASH' ) {
265 0 0         if ( $dfv->{required_hash}->{$field}->{required} ) {
266 0           push( @{ $dfv->{required} }, $field );
  0            
267             }#if
268             else {
269 0           push( @{ $dfv->{optional} }, $field );
  0            
270             }#else
271             }#if
272             else {
273 0 0         if ( $dfv->{required_hash}->{$field} ) {
274 0           push( @{ $dfv->{required} }, $field );
  0            
275             }#if
276             else {
277 0           push( @{ $dfv->{optional} }, $field );
  0            
278             }#else
279             }#else
280             }#foreach
281            
282             ### Load required and optional into perl and js profiles
283 0           $dfv_profile_perl->{required} = $dfv->{required};
284 0           $dfv_profile_perl->{optional} = $dfv->{optional};
285 0           $dfv_profile_js->{required} = $dfv->{required};
286 0           $dfv_profile_js->{optional} = $dfv->{optional};
287            
288             ### Generate constraints
289             ### Perl regexps get compiled and put into the new style constraint_methods
290             ### JavaScript ones do not
291             ### A rough check is done to see if the regexp is JavaScript compatible
292 0           while ( my ($field, $settings) = each ( %{ $dfv->{schema}->{field_input} } ) ) {
  0            
293             ### Check for array of constraints
294 0 0         if ( ref $settings->{constraint} eq 'ARRAY' ) {
    0          
295 0           foreach my $constraint ( @{ $settings->{constraint} } ) {
  0            
296 0           my ( $constraint_perl, $constraint_js );
297             ### Check for hash ref
298 0 0         if ( ref $constraint eq 'HASH' ) {
299 0           ( $constraint_perl, $constraint_js ) = _gen_dfv_constraints_hash( $dfv, $constraint );
300             }#if
301             else {
302 0           ( $constraint_perl, $constraint_js ) = _gen_dfv_constraints( $dfv, $constraint );
303             }#else
304             ### ??? Do I need to set [] first?
305 0           push( @{ $dfv_profile_perl->{constraint_methods}->{$field} }, $constraint_perl );
  0            
306 0 0         if ( $constraint_js ) {
307 0           push( @{ $dfv_profile_js->{constraints}->{$field} }, $constraint_js );
  0            
308             }#if
309             }#foreach
310             }#if
311             ### Check for constraint hash
312             elsif ( ref $settings->{constraint} eq 'HASH' ) {
313 0           my ( $constraint_perl, $constraint_js ) = _gen_dfv_constraints_hash( $dfv, $settings->{constraint} );
314 0           $dfv_profile_perl->{constraint_methods}->{$field} = $constraint_perl;
315 0 0         if ( $constraint_js ) {
316 0           $dfv_profile_js->{constraints}->{$field} = $constraint_js;
317             }#if
318             }#elsif
319             ### Load constraint
320             else {
321 0           my ( $constraint_perl, $constraint_js ) = _gen_dfv_constraints( $dfv, $settings->{constraint} );
322 0           $dfv_profile_perl->{constraint_methods}->{$field} = $constraint_perl;
323 0 0         if ( $constraint_js ) {
324 0           $dfv_profile_js->{constraints}->{$field} = $constraint_js;
325             }#if
326             }#else
327             }#while
328            
329             ### Use Data::JavaScript to convert the Perl JS profile to JavaScript code
330 0           require Data::JavaScript;
331 0 0         unless ( $ca->GenVal->{params}->{__JS_IMPORTED} ) {
332 0           import Data::JavaScript;
333 0           $ca->GenVal->{params}->{__JS_IMPORTED} = 1;
334             }#unless
335            
336 0           my $jsprofile = jsdump('dfv_profile', $dfv_profile_js);
337            
338             ### Create the JavaScript validation function
339 0           my $jsverify = qq~
340             \n~;
355            
356 0           return ( $dfv_profile_perl, $jsverify, $dfv->{schema} );
357             }#sub
358            
359            
360             ### Generate dfv constraints
361             sub _gen_dfv_constraints {
362             ### Make sure they aren't trying to call directly
363 0 0   0     croak( "Cannot be called directly" ) unless ( caller eq 'CGI::Application::Plugin::GenVal::guts' );
364            
365 0           my ( $dfv, $constraint ) = @_;
366 0           my ( $return_perl, $return_js );
367            
368             ### Create subroutine references for Perl profile
369             ## JS doesn't support this
370 0 0         if ( $constraint =~ /^subref (.*?)$/ ) {
    0          
    0          
    0          
371 0           $return_perl = \&{$1};
  0            
372             }#if
373            
374             ### Create methods
375             elsif ( $constraint =~ /^method (.*?)$/ ) {
376 0           my $method = $1;
377 0           my $methodname;
378 0 0         if ( $method =~ /^([a-z0-9_]*)?\(/i ) {
379 0           $methodname = $1;
380             }#if
381             else {
382 0           $methodname = $method;
383             }#else
384            
385             ### Load constraint methods from DFV::Constraints
386 0 0         unless ( $dfv->{constraints_loaded}->{$methodname} ) {
387 0           require Data::FormValidator::Constraints;
388 0 0         if ( any( @Data::FormValidator::Constraints::EXPORT_OK ) eq $methodname ) {
389 0           Data::FormValidator::Constraints->import( $methodname );
390             }#if
391 0           $dfv->{constraints_loaded}->{$methodname} = 1;
392             }#unless
393            
394             ### Load straight into Perl profile
395 0           my $evaltext = qq~\$return_perl = $method;~;
396 0           eval $evaltext;
397 0 0         die( "Error loading constraint method $method: $@" ) if $@;
398            
399             ### Extract paramater for conversion to JS
400             ### New style DFV methods are converted to old style ones for JavaScript
401 0 0         if ( $method =~ /^(.*)?\(\s?(.+?)\s?\)$/ ) {
402 0           my $methodname = $1;
403 0           my $params = $2;
404 0           eval "\$params = [$params];";
405 0 0         die( "Error loading constraint params $params: $@" ) if $@;
406             ### Remove FV_ from start of name
407 0           my $name = $methodname;
408 0           $name =~ s/^FV_//i;
409 0           $return_js = {
410             constraint => $methodname,
411             params => $params,
412             name => $name,
413             };
414             }#if
415             else {
416 0           $method =~ s/[\(\)]//g;
417 0           $return_js = $method;
418             }#else
419             }#elsif
420            
421             ### Create regexps
422             ### Js compatible
423             elsif ( $constraint =~ m#^/.*?/i?$# ) {
424             ### Compile regexp into Perl profile
425 0           my $evaltext = qq~\$return_perl = qr$constraint;~;
426 0           eval $evaltext;
427 0 0         die( "Error compiling regexp $constraint: $@" ) if $@;
428             ### Pass to JS
429 0           $return_js = $constraint;
430             }#elsif
431            
432             ### Perl only
433             elsif ( $constraint =~ m#^/.*?/[sixm]*$# ) {
434             ### Compile regexp into Perl profile
435 0           my $evaltext = qq~\$return_perl = qr$constraint;~;
436 0           eval $evaltext;
437 0 0         die( "Error compiling regexp $constraint: $@" ) if $@;
438             }#elsif
439 0           return ( $return_perl, $return_js );
440             }#sub
441            
442            
443             ### Generate contraints from a hash with contraint details
444             sub _gen_dfv_constraints_hash {
445             ### Make sure they aren't trying to call directly
446 0 0   0     croak( "Cannot be called directly" ) unless ( caller eq 'CGI::Application::Plugin::GenVal::guts' );
447            
448 0           my ( $dfv, $constraint ) = @_;
449            
450             ### Prepare return variables
451 0           my ( $return_perl, $return_js ) = ( {}, {} );
452            
453             ### Generate constraint from {constraint} key
454 0           my ( $constraint_perl, $constraint_js ) = _gen_dfv_constraints( $dfv, $constraint->{constraint} );
455 0           $return_perl->{constraint_method} = $constraint_perl;
456 0 0         if ( $constraint_js ) {
457 0           $return_js = $constraint_js;
458             }#if
459            
460             ### Copy additional contraint details
461 0           while ( my ($key, $value) = each ( %$constraint ) ) {
462 0 0         next if ( $key eq 'constraint' );
463 0           $return_perl->{$key} = $value;
464 0 0         if ( $return_js->{constraint} ) {
465 0           $return_js->{$key} = $value;
466             }#if
467             }#while
468 0 0         $return_js = undef unless ( keys %$return_js );
469 0           return ( $return_perl, $return_js );
470             }#sub
471            
472            
473             1;
474            
475             __END__