File Coverage

lib/CGI/FormBuilder/Config/Simple.pm
Criterion Covered Total %
statement 15 230 6.5
branch 0 110 0.0
condition 0 19 0.0
subroutine 5 13 38.4
pod 8 8 100.0
total 28 380 7.3


line stmt bran cond sub pod time code
1             package CGI::FormBuilder::Config::Simple;
2              
3 1     1   26368 use warnings;
  1         2  
  1         70  
4 1     1   8 use strict;
  1         3  
  1         30  
5 1     1   7 use Carp;
  1         6  
  1         93  
6 1     1   4067 use Data::Dumper;
  1         17657  
  1         82  
7 1     1   1099 use CGI::FormBuilder;
  1         28735  
  1         3071  
8              
9             =head1 NAME
10              
11             CGI::FormBuilder::Config::Simple - deploy web forms w/ .ini file
12              
13             =head1 VERSION
14              
15             Version 0.12
16              
17             =cut
18              
19             our $VERSION = '0.12';
20              
21             =head1 SYNOPSIS
22              
23             This module exists to synthesize the abstractions of
24             CGI::FormBuilder with those of Config::Simple to make it nearly
25             possible to deploy a working form and database application
26             by simply configuring an ini file. Add to that config file
27             your data processing routines, perhaps a template from the
28             design team and you are done. This module handles converting
29             a config file into a form, validating user input and all that.
30              
31             A developer would still be required to write methods to process
32             their data, but much of the rest of the work will be covered
33             by this modules' methods, and those of the ones just cited
34             from which it inherits its methods.
35              
36             For some sample code, please see:
37             t/My/Module/Test.pm
38             which provides scaffolding for the test suite.
39              
40             -- signup.cgi --
41              
42             use lib qw(lib);
43             use MyModule::Signup;
44             # see below for details . . .
45              
46             my $debug_level = 0; # raise to 3 for noisy logs
47             my $signup = MyModule::Signup->new({ config_file => '/path/to/config/file.ini' });
48             # should create a config object respecting ->param() method
49             # and embed that object at $self->{'cfg'}
50             my $signup_form_html = $signup->render_web_form('sign_up',$debug_level) or
51             carp("$0 died rendering a signup form. $signup->errstr. $!");
52              
53             print <<"END_OF_HTML";
54             Content-Type: text/html; charset=utf-8 \n\n
55             $signup_form_html
56             END_OF_HTML
57              
58             1;
59              
60             -- /lib/MyModule/Signup.pm --
61              
62             package MyModule::Signup;
63              
64             use base 'CGI::FormBuilder::Config::Simple';
65              
66             sub new {
67             my $class = shift;
68             my $defaults = shift;
69             my $self = {};
70              
71             $self->{'cfg'} = Config::Simple::Extended->new(
72             { filename => $defaults->{'config_file'} } );
73             # or use its ->inherit() method to overload configurations
74              
75             my $db = $self->{'cfg'}->get_block('db');
76             $self->{'dbh'} = MyModule::DB->connect($db);
77             # a DBI->connect() object
78              
79             # whatever else you need in your constructor
80              
81             bless $self, $class;
82             return $self;
83             }
84              
85             sub sample_data_processing_method {
86             my $self = shift;
87              
88             . . .
89              
90             return;
91             }
92              
93             sub get_that_field_options {
94             my $self = shift;
95             my @options = ('an_option','another_option');
96             return \@options;
97             }
98              
99             # the code above should render, validate and process your data
100             # Now write a config file looking like this, and your are done
101              
102             -- conf.d/apps.example.com/signup_form.ini --
103              
104             [db]
105             . . .
106              
107              
108             [signup_form]
109            
110             template=/home/webapps/signup/conf.d/apps.example.com/tmpl/signup_form.tmpl.html
111             fieldsets=sample_fieldset
112             title='Signup Form'
113             submit='Lets Get Started'
114             header=1
115             name='signup'
116             method='post'
117             debug=0
118             # debug = 0 | 1 | 2 | 3
119             reset=1
120             fieldsubs=1
121             keepextras=1
122             custom_validation_methods=
123              
124             ;action=$script
125             ;values=\%hash | \@array
126             ;validate=\%hash
127             ;required=[qw()]
128              
129             [signup_form_sample_fieldset]
130             fields=this_field,that_field,another_field
131             process_protocol=sample_data_processing_method
132             enabled=1
133            
134             [signup_form_sample_fieldset_this_field]
135             name=this_field
136             label='This field'
137             type=text
138             fieldset=sample_fieldset
139             require=1
140             validate='/\w+/'
141             validation_error='this_field should be made up of words'
142             enabled=1
143            
144             [signup_form_sample_fieldset_that_field]
145             name=that_field
146             label='That field'
147             type=select
148             options=&get_that_field_options
149             ;options=choice_a,choice_b,choice_c
150             fieldset=sample_fieldset
151             require=1
152             validate=&get_that_field_options
153             validation_error='that_field should include only legal options'
154             enabled=1
155            
156             [signup_form_sample_fieldset_another_field]
157             . . .
158              
159             =head1 METHODS
160              
161             =head2 ->render_web_form('form_name',$debug_level)
162              
163             Given an object, with a configuration object accessible at
164             $self->{'cfg'}, honoring the ->param() method provided by
165             Config::Simple and Config::Simple::Extended (but possibly
166             others), render the html for a web form for service.
167              
168             This method takes an optional second argument, used to set
169             the debug level. Use 0 or undefined for quiet operation.
170              
171             Use 1 or greater to see information about the form being
172             validated, 2 or greater to watch the fieldsets being validated,
173             3 or greater to watch the fields being validated and 4 or
174             greater to dump the contents of pre-defined field options
175             during field validation.
176              
177             Use a 5 or greater to see information about the form being
178             built, 6 or greater to watch the fieldsets being built, 7 or
179             greater to watch the fields being built and 8 or greater to
180             dump the contents of pre-defined field options.
181              
182             =cut
183              
184             sub render_web_form {
185 0     0 1   my $self = shift;
186 0           my $form_name = shift;
187 0   0       my $debug = shift || 0;
188              
189 0           my $form_attributes = $self->{'cfg'}->get_block("$form_name");
190 0           my %attributes;
191 0           FORM_ATTRIBUTE: foreach my $attribute (keys %{$form_attributes}){
  0            
192 0 0         if($attribute eq 'custom_validation_methods'){ next FORM_ATTRIBUTE; }
  0            
193 0           my $value = $form_attributes->{$attribute};
194 0           $attributes{$attribute} = $value;
195             }
196 0 0         if($self->{'invalid'}){
197 0           $attributes{'fields'} = $self->{'fields'};
198             }
199 0           my $form = CGI::FormBuilder->new( %attributes );
200 0           $form->{'cgi_fb_cfg_simple_form_name'} = $form_name;
201 0 0         if($debug > 0){
202 0           print STDERR Dumper(\%attributes);
203             }
204             # print STDERR Dumper($form);
205              
206 0           my $html;
207 0           my $fieldsets = $self->{'cfg'}->param("$form_name.fieldsets");
208 0           my @fieldsets = split /,/,$fieldsets;
209              
210             # print Dumper(\@fieldsets);
211 0           foreach my $fieldset (@fieldsets) {
212 0 0         if($debug > 1){
213             # print STDERR "Now building fieldset: " . Dumper($fieldset) . "\n";
214             }
215 0           $self->build_fieldset($form,$fieldset,$debug);
216             }
217 0 0         if ($form->submitted) {
218 0           my $invalid = $self->validate_form($form,$debug);
219 0 0         print STDERR "our \$invalid is: $invalid \n" if($debug > 1);
220 0 0         unless($invalid){
221 0           $html = $self->process_form($form,$debug);
222             } else {
223 0           $self->{'invalid'} = 1;
224 0           $form->tmpl_param( DISPLAY_ERRORS => 1 );
225 0           $form->tmpl_param( ERRORS => $self->errstr() );
226             # print STDERR 'Our data validation errors include: ' . $self->errstr();
227 0           $html = $form->render(header => $self->{'cfg'}->param("$form_name.header"));
228             }
229             } else {
230             # Print out the form
231 0           $html = $form->render(header => $self->{'cfg'}->param("$form_name.header"));
232             }
233              
234 0           $self->{'form'} = $form;
235 0           return $html;
236             }
237              
238             =head2 $self->process_form($form,$debug_level)
239              
240             In your My::Module which inherits from this one, you need
241             to write a method for every fieldset.process_protocol in the
242             configuration file.
243              
244             This method will be called by the ->render_web_form() method
245             and cycle through each fieldset and execute your application
246             specific database interactions and other required data
247             processing.
248              
249             =cut
250              
251             sub process_form {
252 0     0 1   my $self = shift;
253 0           my $form = shift;
254 0           my $debug_level = shift;
255 0 0         unless(defined($debug_level)){ $debug_level = 0; }
  0            
256 0           my $field = $form->fields;
257 0           my $form_name = $form->{'cgi_fb_cfg_simple_form_name'};
258              
259 0           print STDERR "Now processing form . . . \n"; # Dumper($field);
260              
261 0           my $fieldsets = $self->{'cfg'}->param("$form_name.fieldsets");
262 0           my @fieldsets = split /,/,$fieldsets;
263              
264 0           my $html;
265 0           foreach my $fieldset (@fieldsets) {
266 0           my $stanza = $form_name . '_' . $fieldset;
267 0           my $process_protocol = $self->{'cfg'}->param("$stanza.process_protocol");
268 0 0         if($debug_level > 0){
269 0           print STDERR "Our process_protocol is: $process_protocol for fieldset $stanza \n";
270             }
271 0           $html .= $self->$process_protocol($form_name,$field,$debug_level);
272             }
273              
274 0           return $html;
275             }
276              
277             =head2 ->validate_form()
278              
279             This method validates each fieldset defined in the configuration
280             file for a form, returning 0 if all fields validate, and
281             otherwise a positive integer representing a count of fields
282             which failed the validation test.
283              
284             This method will also process each method listed in the
285             custom_validation_methods attribute of the stanza named for
286             the form. Each of these methods, which must be written by
287             the user writing the module which inherits from this one,
288             should return a positive integer for invalid data or a zero
289             (0) if the data that method checks is valid.
290              
291             =cut
292              
293             sub validate_form {
294 0     0 1   my $self = shift;
295 0           my $form = shift;
296 0           my $debug = shift;
297              
298 0           my $invalid = 0;
299 0           my $form_name = $form->{'cgi_fb_cfg_simple_form_name'};
300 0 0         print STDERR "Now running ->validate_form() method for $form_name \n" if($debug > 0);
301              
302 0           my $fieldsets = $self->{'cfg'}->param("$form_name.fieldsets");
303 0           my @fieldsets = split /,/,$fieldsets;
304              
305 0           foreach my $fieldset (@fieldsets) {
306 0 0         if($debug > 0){
307 0           print STDERR "Now validating fieldset: " . Dumper($fieldset) . "\n";
308             }
309 0           $invalid += $self->validate_fieldset($form,$fieldset,$debug);
310             }
311              
312 0           my @custom_validation_methods = $self->{'cfg'}->param("$form_name.custom_validation_methods");
313 0           foreach my $method (@custom_validation_methods){
314 0 0         print STDERR "Now running ->$method() method for $form_name \n" if($debug > 1);
315 0           $invalid += $self->$method($form,$debug);
316             }
317 0           return $invalid;
318             }
319              
320             =head2 ->validate_fieldset()
321              
322             This method validates each field defined in the configuration
323             file for a fieldset, returning 0 if all fields validate, and
324             otherwise a positive integer representing a count of fields
325             which failed the validation test.
326              
327             =cut
328              
329             sub validate_fieldset {
330 0     0 1   my $self = shift;
331 0           my $form = shift;
332 0           my $fieldset = shift;
333 0           my $debug = shift;
334 0 0         print STDERR "Now running ->validate_fieldset() method for $fieldset \n" if($debug > 1);
335              
336 0           my $invalid = 0;
337 0           my $form_name = $form->{'cgi_fb_cfg_simple_form_name'};
338 0           my $stanza = $form_name . '_' . $fieldset;
339 0 0         if($debug > 1){
340 0           print STDERR "->validate_fieldset() now validating $stanza \n";
341             }
342 0 0         if($self->{'cfg'}->param("$stanza.enabled")){
343 0           my $stanza = $form_name . '_' . $fieldset;
344 0           my $fields = $self->{'cfg'}->param("$stanza.fields");
345 0           foreach my $field (@{$fields}) {
  0            
346 0           my $field_stanza = $stanza . '_' . $field;
347 0 0         if($debug > 1){
348             # print STDERR "validating field: $field_stanza \n";
349             }
350 0 0         if($self->{'cfg'}->param("$field_stanza.enabled")){
351 0           my $result = $self->validate_field($form,$fieldset,$field,$debug);
352 0           print STDERR "$field is $form->$field and yields $result \n";
353 0           $invalid += $result;
354             }
355             }
356             } else {
357 0           print STDERR "The $fieldset fieldset has not been enabled \n";
358             }
359              
360 0           return $invalid;
361             }
362              
363             =head2 ->validate_field()
364              
365             This method validates a field, returning 0 if the field
366             validates, and otherwise 1. It uses the validate attribute
367             from the configuration file to make a regex comparison.
368             If that validate has a value beginning with an ampersand '&',
369             the code reference is interpretted as an object method.
370              
371             This method must either return an array of array_references of
372             key->value pairs representing valid options (for a selector),
373             or otherwise an integer reflecting whether the field is invalid,
374             again 0 for valid or 1 for invalid.
375              
376             The user must write the code reference in the module which
377             inherits from this one.
378              
379             Presently having that coderef return the array of array_refs
380             has been tested, but the case where it returns 0 or 1, has
381             not yet been exercised in testing. Buyer Beware and please
382             share your experience, bug reports, new test cases and patches.
383              
384             =cut
385              
386             sub validate_field {
387 0     0 1   my $self = shift;
388 0           my $form = shift;
389 0           my $fieldset = shift;
390 0           my $field = shift;
391 0           my $debug = shift;
392              
393 0           my $invalid = 0;
394 0           my $form_name = $form->{'cgi_fb_cfg_simple_form_name'};
395 0           my $field_stanza = $form_name . '_' . $fieldset . '_' . $field;
396              
397 0 0         if($self->{'cfg'}->param("$field_stanza.enabled")){
398 0 0         print STDERR "Now running ->validate_field() method for $fieldset.$field \n" if($debug > 2);
399 0 0         if($self->{'cfg'}->param("$field_stanza.validate") !~ m/^&/){
400 0           my $regex = $self->{'cfg'}->param("$field_stanza.validate");
401 0           $regex =~ s/^\///;
402 0           $regex =~ s/\/$//;
403 0 0         if($self->{'cfg'}->param("$field_stanza.require")){
404 0 0 0       if($form->$field =~ m/$regex/){
    0          
    0          
    0          
405 0           $invalid = 0;
406             } elsif(!(length($form->$field))){
407 0           $invalid = 1;
408             } elsif(!(defined($form->$field))){
409 0           $invalid = 1;
410             } elsif (defined($form->$field) && $form->$field eq ''){
411 0           $invalid = 1;
412             } else {
413 0           $invalid = 1;
414             }
415             } else {
416 0 0 0       if($form->$field =~ m/$regex/){
    0          
417 0           $invalid = 0;
418             } elsif($form->$field eq '' || !defined($form->$field)){
419 0           $invalid = 0;
420             } else {
421 0           $invalid = 1;
422             }
423             }
424             } else {
425 0           my $options = $self->{'cfg'}->param("$field_stanza.validate");
426 0           $options =~ s/^&//;
427 0   0       my $valid_options = $self->$options() || $self->errstr("write a method called ->$options");
428 0 0         print STDERR Dumper($valid_options) if($debug > 3);
429 0           $invalid = 1;
430 0 0         if(ref($valid_options) eq 'ARRAY'){
431 0           FIELD_OPTION: foreach my $option (@{$valid_options}){
  0            
432 0           my ($key,$value) = @{$option};
  0            
433 0 0         if($form->$field =~ $key){
434 0           $invalid = 0;
435 0           last FIELD_OPTION;
436             }
437             }
438             } else {
439             # this branch has not yet been tested . . .
440 0           $invalid = $valid_options;
441             }
442             }
443             }
444              
445 0 0         if($invalid){
446 0           my $msg = 'For field: ' . $field . ', our value is: ';
447 0 0         $msg .= $form->$field if(defined($form->$field));
448 0           $msg .= ' and validation rule is: ' . $self->{'cfg'}->param("$field_stanza.validate");
449 0 0         $self->errstr($msg) if($debug > 2);
450 0           $self->errstr($self->{'cfg'}->param("$field_stanza.validation_error"));
451             }
452              
453 0           return $invalid;
454             }
455              
456             =head2 $self->build_fieldset($form,$fieldset,$debug_level)
457              
458             Parses the configuration object for the fields required to
459             build a form's fieldset and calls ->build_field() for each
460             field listed in the fields attribute of the fieldset stanza
461             in that configuration file, to compile the pieces necessary
462             to configure the CGI::FormBuilder $form object.
463              
464             =cut
465              
466             sub build_fieldset {
467 0     0 1   my $self = shift;
468 0           my $form = shift;
469 0           my $fieldset = shift;
470 0           my $debug = shift;
471              
472             # print STDERR "Now being asked to build a fieldset \n";
473 0           my $form_name = $form->{'cgi_fb_cfg_simple_form_name'};
474 0           my $stanza = $form_name . '_' . $fieldset;
475 0 0         if($debug > 5){
476 0           print STDERR "->build_fieldset() now processing $stanza \n";
477             }
478 0 0         if($self->{'cfg'}->param("$stanza.enabled")){
479 0           my $stanza = $form_name . '_' . $fieldset;
480 0           my $fields = $self->{'cfg'}->param("$stanza.fields");
481 0           foreach my $field (@{$fields}) {
  0            
482 0           my $field_stanza = $stanza . '_' . $field;
483 0 0         if($debug > 5){
484 0           print STDERR "seeking field stanza: $field_stanza \n";
485             }
486 0 0         unless($self->{'cfg'}->param("$field_stanza.disabled")){
487             # print STDERR Dumper($field),"\n";
488 0           $self->build_field($form,$fieldset,$field,$debug);
489             }
490             }
491             } else {
492 0           print STDERR "The $fieldset fieldset has not been enabled \n";
493             }
494 0           return;
495             }
496              
497             =head2 $self->build_field($form,$fieldset,$field,$debug_level)
498              
499             Parses the configuration object for the attributes used to
500             configure a CGI::FormBuilder->field() object. In reading
501             the field attributes from the configuration file, it ignores
502             'validation_error', 'enabled' and the now deprecated 'disabled'.
503              
504             =cut
505              
506             sub build_field {
507 0     0 1   my $self = shift;
508 0           my $form = shift;
509 0           my $fieldset = shift;
510 0           my $field = shift;
511 0           my $debug = shift;
512              
513 0           my $form_name = $form->{'cgi_fb_cfg_simple_form_name'};
514 0           my $block = $form_name . '_' . $fieldset . '_' . $field;
515 0 0         if($debug > 6){
516 0           print STDERR "Our next block is: $block \n";
517             }
518 0           my $field_attributes = $self->{'cfg'}->get_block($block);
519              
520 0           my @attributes;
521 0           FIELD_ATTRIBUTE: foreach my $attribute (keys %{$field_attributes}){
  0            
522 0 0         if($debug > 6){
523 0           print STDERR "My attribute is: $attribute \n";
524             }
525 0           my @values = ();
526 0 0         if($attribute eq 'validation_error'){ next FIELD_ATTRIBUTE; }
  0            
527 0 0         if($attribute eq 'enabled'){ next FIELD_ATTRIBUTE; }
  0            
528 0 0         if($attribute eq 'disabled'){ next FIELD_ATTRIBUTE; }
  0            
529 0           my $value = $field_attributes->{$attribute};
530 0 0         if(defined($value)){
531 0 0         if($value =~ m/^&/){
    0          
532 0           $value =~ s/^&//;
533 0   0       my $values = $self->$value() || $self->errstr("write a method called ->$value");
534 0 0         if($attribute eq 'label'){
    0          
    0          
535 0 0         if($debug > 6){
536 0           print STDERR Dumper($values);
537             }
538 0           push @attributes, $attribute => $values;
539             } elsif($attribute eq 'options'){
540 0 0         if(ref($values) eq 'ARRAY'){
    0          
541 0 0         if($debug > 7){
542 0           print STDERR Dumper(\@{$values});
  0            
543             }
544 0           push @attributes, $attribute => \@{$values};
  0            
545             } elsif(ref($values) eq 'HASH'){
546 0 0         if($debug > 7){
547 0           print STDERR Dumper(\%{$values});
  0            
548             }
549 0           push @attributes, $attribute => \%{$values};
  0            
550             } else {
551 0           print STDERR '$values is ' . Dumper($values);
552             }
553             } elsif($attribute eq 'value'){
554 0 0         if($debug > 6){
555 0           print STDERR Dumper(\@{$values});
  0            
556             }
557 0           push @attributes, $attribute => \@{$values};
  0            
558             }
559             } elsif($value !~ m/^&/) {
560 0           push @attributes, $attribute => $value;
561             } else {
562 0           print STDERR "Failed to catch and handle $value for $attribute \n";
563             }
564             }
565             }
566              
567 0           $form->field(@attributes);
568 0           return;
569             }
570              
571             =head2 errstr('Error description')
572              
573             Append its argument, if any, to the error string, and return
574             the result, returning undef if no error message has been set.
575             Each error is prepended with a
  • list item tag, and the
  • 576             results are imagined to be rendered in html between
    577             unordered list tags.
    578              
    579             =cut
    580              
    581             sub errstr {
    582 0     0 1   my $self = shift;
    583 0   0       my $error = shift || '';
    584 0 0 0       $self->{'errstr'} .= "
  • " . $error . "\n" if(defined($error) && ($error ne '') );
  • 585 0           return $self->{'errstr'};
    586             }
    587              
    588             =head1 AUTHOR
    589              
    590             Hugh Esco, C<< >>
    591              
    592             =head1 BUGS
    593              
    594             Please report any bugs or feature requests
    595             to C
    596             at rt.cpan.org>, or through the web interface at
    597             L.
    598             I will be notified, and then you'll automatically be notified
    599             of progress on your bug as I make changes.
    600              
    601             =head1 SUPPORT
    602              
    603             You can find documentation for this module with the perldoc command.
    604              
    605             perldoc CGI::FormBuilder::Config::Simple
    606              
    607             You can also look for information at:
    608              
    609             =over 4
    610              
    611             =item * RT: CPAN's request tracker
    612              
    613             L
    614              
    615             =item * AnnoCPAN: Annotated CPAN documentation
    616              
    617             L
    618              
    619             =item * CPAN Ratings
    620              
    621             L
    622              
    623             =item * Search CPAN
    624              
    625             L
    626              
    627             =back
    628              
    629             =head1 ACKNOWLEDGEMENTS
    630              
    631             My appreciation to our team at YMD Partners, LLC, Bruce Dixon
    632             and Ida Hakim. Thank you for being a part of this business
    633             and for the contributions you make to serving our clients.
    634              
    635             I want to acknowledge the support of the Green Party of Texas
    636             for making possible development of this module. An exciting
    637             if simple project of theirs serves as the first real world test
    638             of this idea which had been kicking about my head for a while.
    639              
    640             And of course this work would not have been possible without
    641             the prior contributions to the CPAN repository made by Sherzod
    642             Ruzmetov, author of Config::Simple and Nate Wiger, author of
    643             CGI::FormBuilder, nor of course all the brilliant folks who
    644             developed Perl.
    645              
    646             =head1 COPYRIGHT & LICENSE
    647              
    648             Copyright 2010 Hugh Esco.
    649              
    650             This program is free software; you can redistribute it and/or
    651             modify it under the terms of the GNU General Public License
    652             as published by the Free Software Foundation; version 2 dated
    653             June, 1991 or at your option any later version.
    654              
    655             This program is distributed in the hope that it will be useful,
    656             but WITHOUT ANY WARRANTY; without even the implied warranty of
    657             MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    658             GNU General Public License for more details.
    659              
    660             A copy of the GNU General Public License is available in the source tree;
    661             if not, write to the Free Software Foundation, Inc.,
    662             59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
    663              
    664             =cut
    665              
    666             1; # End of CGI::FormBuilder::Config::Simple