From b9aff7e76eab19b36abd670e6fc75a8a3d821324 Mon Sep 17 00:00:00 2001 From: Aaron Trevena Date: Thu, 22 Jun 2006 14:56:27 +0000 Subject: [PATCH] fixed Maypole versions of Untaint and AsForm to pass pod::coverage tests, fixed test in 01basic.t git-svn-id: http://svn.maypole.perl.org/Maypole/trunk@497 48953598-375a-da11-a14b-00016c27c3ee --- lib/CGI/Untaint/Maypole.pm | 5 + lib/Maypole/Model/CDBI/AsForm.pm | 2 +- lib/Maypole/Model/CDBI/FromCGI.pm | 573 ++++++++++++++++-------------- t/01basics.t | 2 +- 4 files changed, 323 insertions(+), 259 deletions(-) diff --git a/lib/CGI/Untaint/Maypole.pm b/lib/CGI/Untaint/Maypole.pm index d096c27..38321ef 100644 --- a/lib/CGI/Untaint/Maypole.pm +++ b/lib/CGI/Untaint/Maypole.pm @@ -39,6 +39,11 @@ was created with. =cut +=head2 raw_data + +Returns the parameters the handler was created with as a hashref + +=cut sub raw_data { return shift->{__data}; diff --git a/lib/Maypole/Model/CDBI/AsForm.pm b/lib/Maypole/Model/CDBI/AsForm.pm index 47b0cbd..7956bbb 100644 --- a/lib/Maypole/Model/CDBI/AsForm.pm +++ b/lib/Maypole/Model/CDBI/AsForm.pm @@ -33,7 +33,7 @@ our @EXPORT = _options_from_objects _options_from_arrays _options_from_hashes _options_from_array _options_from_hash ); - + our $VERSION = '.10'; =head1 NAME diff --git a/lib/Maypole/Model/CDBI/FromCGI.pm b/lib/Maypole/Model/CDBI/FromCGI.pm index 8718f9b..e342a28 100644 --- a/lib/Maypole/Model/CDBI/FromCGI.pm +++ b/lib/Maypole/Model/CDBI/FromCGI.pm @@ -1,8 +1,23 @@ -package Maypole::Model::CDBI::FromCGI; +package Maypole::Model::CDBI::FromCGI; use strict; +=head1 NAME + +Maypole::Model:CDBI::FromCGI - Validate form input and populate Model objects + +=head1 SYNOPSIS + +... + +=head1 DESCRIPTION + +Provides a way to validate form input and populate Model Objects, based +on Class::DBI::FromCGI. + +=cut + use warnings; -# The base base model class for apps -- +# The base base model class for apps # provides good search and create functions use base qw(Exporter); @@ -13,12 +28,25 @@ our $Untainter = 'CGI::Untaint::Maypole'; our @EXPORT = qw/update_from_cgi create_from_cgi untaint_columns cgi_update_errors untaint_type _validate validate_all _do_update_all - _do_create_all classify_form_inputs/; + _do_create_all classify_form_inputs/; use Data::Dumper; # for debugging +=head1 METHODS + +=head2 untaint_columns + +Replicates Class::DBI::FromCGI method of same name : + + __PACKAGE__->untaint_columns( + printable => [qw/Title Director/], + integer => [qw/DomesticGross NumExplodingSheep], + date => [qw/OpeningDate/], + ); + +=cut sub untaint_columns { die "untaint_columns() needs a hash" unless @_ % 2; @@ -32,6 +60,12 @@ sub untaint_columns { $class->__untaint_types(\%types); } +=head2 untaint_type + + gets/sets untaint_type for a column, no equivilent in Class::DBI::FromCGI + +=cut + # get/set untaint_type for a column sub untaint_type { my ($class, $field, $new_type) = @_; @@ -45,132 +79,126 @@ sub untaint_type { return $handler || undef; } +=head2 cgi_update_errors + +returns cgi update errors + +=cut + sub cgi_update_errors { %{ shift->{_cgi_update_error} || {} } } -################### -# create_from_cgi # -################### +=head2 create_from_cgi -# Creates multiple objects from a cgi form. -# Errors are returned in cgi_update_errors -# -# simple usage: $beer->create_from_cgi($r); -# -# The last arg is flag to say whether to classify inputs or not. -# TODO : make 100% backward compatible -# +Based on the same method in Class::DBI::FromCGI. -sub create_from_cgi { - my ($self, $r, $params, $no_classify) = @_; - $self->_croak( "create_from_cgi can only be called as a class method") - if ref $self; - - my ($errors, $validated); - #print "*** create_from_cgi ***\n\n"; - # FromCGI interface compatibility - # params are ($h, $wanted) - if ($r->isa('CGI::Untaint')) { - #print "*** raw data ***" . Dumper($r->raw_data); - #print "*** wanted data ***" . Dumper($params); - - ($errors, $validated) = $self->_validate($r,$params); - #print "*** validated data ***" . Dumper($validated); - #print "*** errors data ***" . Dumper($errors); - } - else { - $params ||= $r->params; - my $classified = $no_classify ? {%$params}:$self->classify_form_inputs($params); - ($errors, $validated) = $self->validate_all($r, $classified); - } +Creates multiple objects from a cgi form. +Errors are returned in cgi_update_errors - if (keys %$errors) { - return bless { _cgi_update_error => $errors }, $self; - } - - # Insert all the data - my ($obj, $err ) = $self->_do_create_all($validated); - if ($err) { - return bless { _cgi_update_error => $err }, $obj ; - } - return $obj; +simple usage: $beer->create_from_cgi($r); + +The last arg is flag to say whether to classify inputs or not. +TODO : make 100% backward compatible + +=cut + +sub create_from_cgi { + my ($self, $r, $params, $no_classify) = @_; + $self->_croak( "create_from_cgi can only be called as a class method") + if ref $self; + + my ($errors, $validated); + # FromCGI interface compatibility + # params are ($h, $wanted) + if ($r->isa('CGI::Untaint')) { + ($errors, $validated) = $self->_validate($r,$params); + } else { + $params ||= $r->params; + my $classified = $no_classify ? {%$params}:$self->classify_form_inputs($params); + ($errors, $validated) = $self->validate_all($r, $classified); + } + + if (keys %$errors) { + return bless { _cgi_update_error => $errors }, $self; + } + + # Insert all the data + my ($obj, $err ) = $self->_do_create_all($validated); + if ($err) { + return bless { _cgi_update_error => $err }, $obj ; + } + return $obj; } -################### -# update_from_cgi # -################### +=head2 update_from_cgi + +returns 1 or nothing if errors -# returns 1 or nothing if errors - +=cut sub update_from_cgi { - my ($self, $r, $params, $no_classify) = @_; - $self->_croak( "update_from_cgi can only be called as an object method") - unless ref $self; - my ($errors, $validated, $wanted); - $self->{_cgi_update_error} = {}; - - #print "*** update_from_cgi talking ***\n\n"; - # FromCGI interface compatibility params are ($h, $wanted) - if ($r->isa('CGI::Untaint')) { - # REHASH the $wanted for updating: - # 1: we ignore any fields we dont have parmeter for. (safe ?) - # 2: we dont want to update fields unless they change - - my ($h, $wanted) = ($r, $params); - my @ignore = @{$wanted->{ignore} || []}; - push @ignore, $self->primary_column->name; - my $raw = $h->raw_data; - #print "*** raw data ****" . Dumper($raw); - foreach my $field ($self->columns) { - #print "*** field is $field ***\n"; - if (not defined $raw->{$field}) { - push @ignore, $field->name; - #print "*** ignoring $field because it is not present ***\n"; - next; - - } - # stupid inflation , cant get at raw db value easy, must call - # deflate ***FIXME**** - my $cur_val = ref $self->$field ? $self->$field->id : $self->$field; - if ($raw->{$field} eq $cur_val) { + my ($self, $r, $params, $no_classify) = @_; + $self->_croak( "update_from_cgi can only be called as an object method") unless ref $self; + my ($errors, $validated, $wanted); + $self->{_cgi_update_error} = {}; + + # FromCGI interface compatibility params are ($h, $wanted) + if ($r->isa('CGI::Untaint')) { + # REHASH the $wanted for updating: + # 1: we ignore any fields we dont have parmeter for. (safe ?) + # 2: we dont want to update fields unless they change + + my ($h, $wanted) = ($r, $params); + my @ignore = @{$wanted->{ignore} || []}; + push @ignore, $self->primary_column->name; + my $raw = $h->raw_data; + #print "*** raw data ****" . Dumper($raw); + foreach my $field ($self->columns) { + #print "*** field is $field ***\n"; + if (not defined $raw->{$field}) { + push @ignore, $field->name; + #print "*** ignoring $field because it is not present ***\n"; + next; + } + # stupid inflation , cant get at raw db value easy, must call + # deflate ***FIXME**** + my $cur_val = ref $self->$field ? $self->$field->id : $self->$field; + if ($raw->{$field} eq $cur_val) { #print "*** ignoring $field because unchanged ***\n"; - push @ignore, $field->name; - } - } - - $wanted->{ignore} = \@ignore; - #print "*** wanted ****" . Dumper($wanted); - ($errors, $validated) = $self->_validate($h,$wanted,1); - #print "*** validated data ****" . Dumper($validated); - #print "*** errors ****" . Dumper($errors); - } - else { - $params ||= $r->params; - my $classified = $no_classify ? {%$params}:$self->classify_form_inputs($params); - ($errors, $validated) = $self->validate_all($r, $classified,1); - #print "*** errors for validate all ****" . Dumper($errors); - } + push @ignore, $field->name; + } + } - if (keys %$errors) { - #print "*** we have errors ****" . Dumper($errors); - $self->{_cgi_update_error} = $errors; - return; - } - - # Update all the data - my ($obj, $err ) = $self->_do_update_all($validated); - if ($err) { - $self->{_cgi_update_error} = $err; - return; - } - return 1; + $wanted->{ignore} = \@ignore; + #print "*** wanted ****" . Dumper($wanted); + ($errors, $validated) = $self->_validate($h,$wanted,1); + #print "*** validated data ****" . Dumper($validated); + #print "*** errors ****" . Dumper($errors); + } else { + $params ||= $r->params; + my $classified = $no_classify ? {%$params}:$self->classify_form_inputs($params); + ($errors, $validated) = $self->validate_all($r, $classified,1); + #print "*** errors for validate all ****" . Dumper($errors); + } + + if (keys %$errors) { + #print "*** we have errors ****" . Dumper($errors); + $self->{_cgi_update_error} = $errors; + return; + } + + # Update all the data + my ($obj, $err ) = $self->_do_update_all($validated); + if ($err) { + $self->{_cgi_update_error} = $err; + return; + } + return 1; } - - -=head2 validate_all + +=head2 validate_all Validates (untaints) a hash of possibly mixed table params. It returns hashrefs of errors and validated data. @@ -178,113 +206,106 @@ of errors and validated data. =cut sub validate_all { - my ($self, $r, $classified, $updating) = @_; - - # Base case - validate this classes data - my $all = eval{ $r->config->{$self->table}{all_cols} } || - [$self->columns('All')]; - my $req = eval{ $r->config->{$self->table}{required_cols} } || - []; - my $ignore = eval{ $r->config->{$self->table}{ignore_cols} } || - []; - push @$ignore, $self->primary_column->name if $updating; - # ignore hashes of foreign inputs - foreach (keys %$classified) { - push @$ignore, $_ if ref $classified->{$_} eq 'HASH'; - } - my $h = $Untainter->new($classified); - my ($errs, $validated) = $self->_validate( - $h, {all => $all, required => $req, ignore => $ignore},$updating - ); - - #warn "Validated dump is " . Dumper($validated); - #warn "classified dump is " . Dumper($classified); - # Validate all foreign input - foreach my $field (keys %$classified) { - if (ref $classified->{$field} eq "HASH") { - my $data = $classified->{$field}; -# warn "Dump of fdata for $field: " . Dumper($data) if $r->debug; - my @usr_entered_vals = (); - my %required = map { $_ => 1 } - @{$r->config->{$self->table}{required_cols}}; - foreach ( values %$data ) { - push @usr_entered_vals, $_ if $_ ne ''; - } - - # filled in values - # IF we have some inputs for the related - if ( @usr_entered_vals ) { -# warn "user entered vals . " . Dumper(\@usr_entered_vals) if $r->debug; - my ($ferrs, $valid) = $self->related_class($r, $field)->validate_all($r, $classified->{$field}, $updating ); - $errs->{$field} = $ferrs if $ferrs; - $validated->{$field} = $valid; - } - else { + my ($self, $r, $classified, $updating) = @_; + + # Base case - validate this classes data + my $all = eval{ $r->config->{$self->table}{all_cols} } || + [$self->columns('All')]; + my $req = eval{ $r->config->{$self->table}{required_cols} } || + []; + my $ignore = eval{ $r->config->{$self->table}{ignore_cols} } || + []; + push @$ignore, $self->primary_column->name if $updating; + # ignore hashes of foreign inputs + foreach (keys %$classified) { + push @$ignore, $_ if ref $classified->{$_} eq 'HASH'; + } + my $h = $Untainter->new($classified); + my ($errs, $validated) = $self->_validate( + $h, {all => $all, required => $req, ignore => $ignore},$updating + ); + + #warn "Validated dump is " . Dumper($validated); + #warn "classified dump is " . Dumper($classified); + # Validate all foreign input + foreach my $field (keys %$classified) { + if (ref $classified->{$field} eq "HASH") { + my $data = $classified->{$field}; + # warn "Dump of fdata for $field: " . Dumper($data) if $r->debug; + my @usr_entered_vals = (); + my %required = map { $_ => 1 } + @{$r->config->{$self->table}{required_cols}}; + foreach ( values %$data ) { + push @usr_entered_vals, $_ if $_ ne ''; + } + + # filled in values + # IF we have some inputs for the related + if ( @usr_entered_vals ) { + # warn "user entered vals . " . Dumper(\@usr_entered_vals) if $r->debug; + my ($ferrs, $valid) = $self->related_class($r, $field)->validate_all($r, $classified->{$field}, $updating ); + $errs->{$field} = $ferrs if $ferrs; + $validated->{$field} = $valid; + } else { # Check its not requeired - if ($required{$field}) { - $errs->{$field}{FATAL} = "This is required. Please enter the required fields in this section." - } - - } - - } + if ($required{$field}) { + $errs->{$field}{FATAL} = "This is required. Please enter the required fields in this section." } - undef $errs unless keys %$errs; - return ($errs, $validated); + } + } + } + undef $errs unless keys %$errs; + return ($errs, $validated); } sub _validate { - my ($self, $h, $wanted, $updating) = @_; - my %required = map { $_ => 1 } @{$wanted->{required}}; - my %seen; - $seen{$_}++ foreach @{$wanted->{ignore}}; - my $errors = {}; - my $fields = {}; - $wanted->{all} = [ $self->columns ] unless @{$wanted->{all} || [] } ; - foreach my $field (@{$wanted->{required}}, @{$wanted->{all}}) { - next if $seen{$field}++; - my $type = $self->untaint_type($field) or - do { warn "No untaint type for $self 's field $field. Ignoring."; - next; - }; - my $value = $h->extract("-as_$type" => $field); - my $err = $h->error; - - # Required field error - if ($required{$field} and !ref($value) and $err =~ /^No input for/) { + my ($self, $h, $wanted, $updating) = @_; + my %required = map { $_ => 1 } @{$wanted->{required}}; + my %seen; + $seen{$_}++ foreach @{$wanted->{ignore}}; + my $errors = {}; + my $fields = {}; + $wanted->{all} = [ $self->columns ] unless @{$wanted->{all} || [] } ; + foreach my $field (@{$wanted->{required}}, @{$wanted->{all}}) { + next if $seen{$field}++; + my $type = $self->untaint_type($field) or + do { warn "No untaint type for $self 's field $field. Ignoring."; + next; + }; + my $value = $h->extract("-as_$type" => $field); + my $err = $h->error; + + # Required field error + if ($required{$field} and !ref($value) and $err =~ /^No input for/) { #($value eq '' or !defined $value)) - $errors->{$field} = "You must supply '$field'" - #unless ($updating and$self->field; - } elsif ($err) { + $errors->{$field} = "You must supply '$field'" + #unless ($updating and$self->field; + } elsif ($err) { - # 1: No inupt entered - if ($err =~ /^No input for/) - { + # 1: No inupt entered + if ($err =~ /^No input for/) { # A : Updating -- set the field to undef or '' - if ($updating) { - $fields->{$field} = eval{$self->column_nullable($field)} ? - undef : ''; - } - # B : Creating -- dont set a value and RDMS will put default - } - - # 2: A real untaint error -- just set the error - elsif ($err !~ /^No parameter for/) - { - $errors->{$field} = $err; - } - } else { - $fields->{$field} = $value - } + if ($updating) { + $fields->{$field} = eval{$self->column_nullable($field)} ? + undef : ''; } - undef $errors unless keys %$errors; - return ($errors, $fields); + # B : Creating -- dont set a value and RDMS will put default + } + + # 2: A real untaint error -- just set the error + elsif ($err !~ /^No parameter for/) { + $errors->{$field} = $err; + } + } else { + $fields->{$field} = $value + } + } + undef $errors unless keys %$errors; + return ($errors, $fields); } - - ################## # _do_create_all # ################## @@ -292,58 +313,59 @@ sub _validate { # Untaints and Creates objects from hashed params. # Returns parent object and errors. sub _do_create_all { - my ($self, $validated) = @_; - my $class = ref $self || $self; - my ($errors, $accssr); - - # Separate out related objects' data from main hash - my %related; - foreach (keys %$validated) { - $related{$_}= delete $validated->{$_} if ref $validated->{$_} eq 'HASH'; - } - # Make has_own/a rel type objects and put id in parent's data hash - foreach $accssr (keys %related) { - my $rel_meta = $self->related_meta('r', $accssr); - $self->_croak("No relationship found for $accssr to $class.") - unless $rel_meta; - my $rel_type = $rel_meta->{name}; - if ($rel_type =~ /(^has_own$|^has_a$)/) { - my $fclass= $rel_meta->{foreign_class}; - my ($rel_obj, $errs) = $fclass->_do_create_all($related{$accssr}); - # put id in parent's data hash - if (not keys %$errs) { $validated->{$accssr} = $rel_obj->id; } - else { $errors->{$accssr} = $errs; } - delete $related{$accssr}; # done with this - } - } - - # Make main object -- base case - my $me_obj = eval { $self->insert($validated) }; - if ($@) { - warn "Just failed making a " . $self. " FATAL Error is $@"; - $errors->{FATAL} = $@; - return (undef, $errors); - } + my ($self, $validated) = @_; + my $class = ref $self || $self; + my ($errors, $accssr); + + # Separate out related objects' data from main hash + my %related; + foreach (keys %$validated) { + $related{$_}= delete $validated->{$_} if ref $validated->{$_} eq 'HASH'; + } + # Make has_own/a rel type objects and put id in parent's data hash + foreach $accssr (keys %related) { + my $rel_meta = $self->related_meta('r', $accssr); + $self->_croak("No relationship found for $accssr to $class.") + unless $rel_meta; + my $rel_type = $rel_meta->{name}; + if ($rel_type =~ /(^has_own$|^has_a$)/) { + my $fclass= $rel_meta->{foreign_class}; + my ($rel_obj, $errs) = $fclass->_do_create_all($related{$accssr}); + # put id in parent's data hash + if (not keys %$errs) { + $validated->{$accssr} = $rel_obj->id; + } else { + $errors->{$accssr} = $errs; + } + delete $related{$accssr}; # done with this + } + } + + # Make main object -- base case + my $me_obj = eval { $self->insert($validated) }; + if ($@) { + warn "Just failed making a " . $self. " FATAL Error is $@"; + $errors->{FATAL} = $@; + return (undef, $errors); + } - if (eval{$self->model_debug}) { - if ($me_obj) { - warn "Just made a $self : $me_obj ( " . $me_obj->id . ")"; - } - else { - warn "Just failed making a " . $self. " FATAL Error is $@" if not $me_obj; - } - } + if (eval{$self->model_debug}) { + if ($me_obj) { + warn "Just made a $self : $me_obj ( " . $me_obj->id . ")"; + } else { + warn "Just failed making a " . $self. " FATAL Error is $@" if not $me_obj; + } + } - # Make other related (must_have, might_have, has_many , etc ) - foreach $accssr ( keys %related ) - { - my ($rel_obj, $errs) = - $me_obj->_create_related($accssr, $related{$accssr}); - $errors->{$accssr} = $errs if $errs; - } + # Make other related (must_have, might_have, has_many , etc ) + foreach $accssr ( keys %related ) { + my ($rel_obj, $errs) = + $me_obj->_create_related($accssr, $related{$accssr}); + $errors->{$accssr} = $errs if $errs; + } - undef $errors unless keys %$errors; - return ($me_obj, $errors); + undef $errors unless keys %$errors; + return ($me_obj, $errors); } @@ -468,6 +490,11 @@ sub _create_related { # # ############################################################################### + +=head2 classify_form_inputs + +=cut + sub classify_form_inputs { my ($self, $params, $delimiter) = @_; my %hashed = (); @@ -515,9 +542,41 @@ sub _column_type_for { ); return $map{$type} || ""; } - +=head1 MAINTAINER + +Maypole Developers + +=head1 AUTHORS +Peter Speltz, Aaron Trevena + +=head1 AUTHORS EMERITUS + +Tony Bowden + +=head1 TODO + +* complete documentation +* ensure full backward compatibility with Class::DBI::FromCGI + +=head1 BUGS and QUERIES + +Please direct all correspondence regarding this module to: + Maypole list. + +=head1 COPYRIGHT AND LICENSE + +Copyright 2003-2004 by Tony Bowden + +This library is free software; you can redistribute it and/or modify +it under the same terms as Perl itself. + +=head1 SEE ALSO + +L, L + +=cut 1; diff --git a/t/01basics.t b/t/01basics.t index 7da48b2..fb79598 100644 --- a/t/01basics.t +++ b/t/01basics.t @@ -39,7 +39,7 @@ like ($classdata{cgi},qr/^HTML::Element/,'classdata.cgi'); is ($classdata{table},'beer','classdata.table'); is ($classdata{name},'BeerDB::Beer','classdata.name'); is ($classdata{colnames},'Abv','classdata.colnames'); -is($classdata{columns}, 'abv brewery id name notes price score style url', +is($classdata{columns}, 'abv brewery id name notes price score style tasted url', 'classdata.columns'); is($classdata{list_columns}, 'score name price style brewery url', 'classdata.list_columns'); -- 2.39.2