File Coverage

blib/lib/CGI/ValidOp/Object.pm
Criterion Covered Total %
statement 126 132 95.4
branch 43 50 86.0
condition 27 31 87.1
subroutine 19 19 100.0
pod 0 13 0.0
total 215 245 87.7


line stmt bran cond sub pod time code
1             #===============================================================================
2             #
3             # FILE: CGI/ValidOp/Object.pm
4             #
5             # DESCRIPTION: Object-level parameters for CGI::ValidOp
6             #
7             # FILES: ---
8             # BUGS: ---
9             # NOTES: ---
10             # AUTHOR: Erik Hollensbe (),
11             # COMPANY: OpenSourcery, LLC
12             # VERSION: 1.0
13             # CREATED: 01/13/2008 03:48:07 PST
14             # REVISION: $Id$
15             #===============================================================================
16              
17             package CGI::ValidOp::Object;
18              
19 11     11   2537 use strict;
  11         20  
  11         423  
20 11     11   55 use warnings;
  11         22  
  11         362  
21              
22 11     11   58 use Carp qw(croak confess);
  11         16  
  11         665  
23 11     11   60 use base qw(CGI::ValidOp::Base);
  11         1273  
  11         3387  
24 11     11   2228 use CGI::ValidOp::Param;
  11         24  
  11         260  
25 11     11   55 use Data::Dumper;
  11         18  
  11         20166  
26              
27             sub PROPERTIES {
28             {
29 15     15 0 144 name => undef,
30             -min_objects => 0,
31             -max_objects => 0,
32             -fields_required => [],
33             -construct_object => undef,
34             }
35             }
36              
37             # constructor. requires a name (text) and an args definition (hash of array)
38             #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
39             sub init {
40 17     17 0 28 my $self = shift;
41 17         37 my ($name, $args) = (@_);
42              
43 17 100       93 croak ("No name") unless defined $name;
44 15 100       63 croak ("No arguments") unless $args;
45 13 100       59 croak ("Args must be a hash") unless ref $args eq 'HASH';
46              
47 11         72 $self->SUPER::init($args);
48 11         85 $self->set_name( { name => $name } );
49              
50 11         32 $self->{_param_template} = { };
51              
52 11         41 foreach my $arg (keys %$args) {
53 33 50       241 if ($arg =~ /^-/) {
54 0         0 $arg =~ s/^-//;
55 0         0 $self->$arg($args->{"-$arg"});
56             } else {
57 33         40 my ($label, @checks) = @{$args->{$arg}};
  33         99  
58 33         223 $self->{_param_template}{$arg} = CGI::ValidOp::Param->new(
59             {
60             name => $arg,
61             label => $label,
62             checks => \@checks,
63             }
64             );
65             }
66             }
67              
68 11         31 $self->{_validated} = 0;
69 11         38 $self->{_errors} = [];
70 11         24 $self->{_objects} = [];
71              
72 11         74 return $self;
73             }
74              
75             # sets a var on an object. requires a hash with a name and value which would
76             # supposedly come from the CGI object.
77             #
78             # A lot of validation happens here. It probably shouldn't, but it's much
79             # cleaner this way.
80             #
81             # Builds C::V::Param objects out from this data and fills an array of hash with
82             # it in _objects.
83             #
84             # While this could be used to set one thing at a time, set_vars() is probably
85             # better for that, and conforms to the rest of the external API.
86             #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
87             sub set_var {
88 63     63 0 1296 my $self = shift;
89 63         96 my ($args) = @_;
90              
91 63 100 66     360 croak ("args must be hash")
92             unless (defined $args and ref $args eq 'HASH');
93 61 100 100     390 croak ("missing parameters in args hash")
94             unless (defined $args->{name} and exists $args->{value});
95              
96             # XXX: this regex parses foo[0][key] into "foo", 0, "key". Don't touch it.
97 55 100       1231 $args->{name} =~ /^([^\[]+?)\[(\d+?)\]\[([^\]]+?)\]$/
98             || $args->{name} =~ /^object--(\w+)--(\d+)--(\w+)/;
99 55         172 my ($param_name, $index, $key) = ($1, $2, $3);
100              
101 55 50 66     289 unless (defined($param_name) && defined($index) && defined($key)) {
      66        
102 17 50       30 ($param_name, $index, $key) = map { defined($_) ? $_ : "Unknown" } ($param_name, $index, $key);
  51         286  
103 17         457 croak ("Invalid parameter ($args->{name}, $param_name, $index, $key) in ".__PACKAGE__."::set_var(): not enough data")
104             }
105 38 100       139 croak ("Name does not match this object")
106             unless ($param_name eq $self->name);
107              
108 36 50       106 unless (defined($self->{_param_template}{$key})) {
109 0         0 $self->{_param_template}{$key} = new CGI::ValidOp::Param(
110             {
111             name => $key,
112             label => $key,
113             checks => []
114             }
115             );
116             }
117              
118             # croak ("Parameter ($key) for object (".$self->name.") does not match object template")
119             # unless (defined($self->{_param_template}{$key}));
120              
121 36   100     138 $self->{_objects}[$index] ||= { };
122              
123 36         110 my $param = $self->{_param_template}{$key};
124              
125 36         152 $param = $param->clone;
126              
127 36         140 $param->name($args->{name});
128 36         114 $param->tainted($args->{value});
129              
130 36         83 $self->{_objects}[$index]{$key} = $param;
131              
132 36         147 return $param;
133             }
134              
135             # sets multiple vars on an object. key => value association. See set_var() for
136             # more information.
137             #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
138             sub set_vars {
139 12     12 0 142 my $self = shift;
140 12         20 my ($args) = @_;
141              
142 12 100 66     120 croak ("args must be hash")
143             unless (defined $args and ref $args eq 'HASH');
144              
145 10         47 while (my ($name, $value) = each %$args) {
146 20         76 $self->set_var({ name => $name, value => $value });
147             }
148              
149 10         39 return 1;
150             }
151              
152             # Normalizes objects so that they have all parameters and constraints.
153             #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
154             sub normalize_objects {
155 18     18 0 22 my $self = shift;
156              
157 18         26 @{$self->{_objects}} = grep defined($_), @{$self->{_objects}};
  18         47  
  18         59  
158              
159 18         27 foreach my $object (@{$self->{_objects}}) {
  18         40  
160 28         35 foreach my $template_name (keys %{$self->{_param_template}}) {
  28         83  
161 86 100       186 if (!exists($object->{$template_name})) {
162 4         17 $object->{$template_name} = $self->{_param_template}{$template_name}->clone;
163             }
164             }
165              
166 28         67 foreach my $param_name (keys %$object) {
167             # XXX: this is a bit dirty, but I didn't want to modify Param's API.
168             # yet another reason not to call validate() twice.
169 86 100 100     233 if (
170 86         156 scalar grep $param_name, @{$self->fields_required} and
  12         52  
171             !scalar grep 'required', @{$object->{$param_name}{checks}}
172             )
173             {
174 8         28 $object->{$param_name}->required(1);
175 8         9 push @{$object->{$param_name}{checks}}, 'required';
  8         26  
176             }
177             }
178             }
179            
180 18         34 return 1;
181             }
182              
183             # Validates all the params on the object.
184             #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
185             sub validate {
186 28     28 0 462 my $self = shift;
187              
188 28 100       98 return if $self->{_validated};
189              
190             # this should croak because someone flubbed an ops definition.
191 18 100 100     46 croak ("min_objects is greater than max_objects")
192             if ($self->min_objects gt $self->max_objects and $self->max_objects gt 0);
193              
194              
195 16         55 $self->normalize_objects;
196              
197 16         21 foreach my $object (@{$self->{_objects}}) {
  16         35  
198 26         53 foreach my $param_name (keys %$object) {
199             # XXX: this is a bit of a hack. Since we want encoded entities and
200             # this is tightly coupled in Param, we override param's {value}
201             # value with the value returned. I'm not sure if this is such a hot
202             # idea, but ATM can't think of a better one.
203             #
204             # e.g., this could lead to double-encoding if validate is called
205             # twice.
206 82         243 $object->{$param_name}{value} = $object->{$param_name}->value;
207             }
208             }
209              
210 10         51 $self->global_errors("object violation: min_objects (".$self->min_objects.") has been violated")
211 16 100 100     45 if ($self->min_objects and $self->min_objects gt @{$self->{_objects}});
212              
213 6         30 $self->global_errors("object violation: max_objects (".$self->max_objects.") has been violated")
214 16 100 100     52 if ($self->max_objects and $self->max_objects lt @{$self->{_objects}});
215              
216 16         31 $self->{_validated} = 1;
217              
218 16         87 return;
219             }
220              
221             #
222             # global_errors is a private interface that is an acccessor (with append only)
223             # to set errors that are global to this class of objects.
224             #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
225             sub global_errors {
226 10     10 0 15 my $self = shift;
227              
228 10         25 push @{$self->{_errors}}, $_ for (@_);
  4         15  
229              
230 10         54 return $self->{_errors};
231             }
232              
233             # object_errors is another external interface. it provides the errors for our
234             # parameters.
235             #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
236             sub object_errors {
237 2     2 0 6 my $self = shift;
238              
239 2         8 $self->validate;
240              
241 2         5 my $objects = [ ];
242              
243 2         5 foreach my $object (@{$self->{_objects}}) {
  2         7  
244 4   100     11 push @$objects, { map { $_ => ($object->{$_}->errors || [ ]) } keys %$object };
  12         41  
245             }
246              
247 2         10 return { global_errors => $self->global_errors, object_errors => $objects };
248             }
249              
250             # objects is the external interface to the end-user. it's passed through validop
251             #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
252             sub objects {
253 10     10 0 20 my $self = shift;
254              
255 10         39 $self->validate;
256            
257 10         18 my $objects = [ ];
258              
259 10         18 foreach my $object (@{$self->{_objects}}) {
  10         31  
260 14 50       39 if ($self->construct_object) {
261 0 0       0 my $new_obj = $self->construct_object->new(
262             {
263             map {
264 0         0 (
265             $_ => (
266             defined( $object->{$_}->value )
267             ? $object->{$_}->value
268             : undef
269             )
270             )
271             } keys %$object
272             }
273             );
274              
275 0         0 push @$objects, $new_obj;
276             } else {
277 66 100       212 push @$objects, {
278             map {
279 14         41 $_ => (
280             defined( $object->{$_}->value )
281             ? $object->{$_}->value
282             : undef )
283             } keys %$object
284             };
285             }
286             }
287              
288 10         105 return $objects;
289             }
290              
291             #
292             # Accessors
293             #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
294             sub max_objects {
295 73     73 0 102 my $self = shift;
296              
297 73 100       173 $self->{max_objects} = shift
298             if (defined $_[0]);
299              
300 73         300 return $self->{max_objects};
301             }
302              
303             #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
304             sub min_objects {
305 71     71 0 95 my $self = shift;
306 71 100       177 $self->{min_objects} = shift
307             if (defined $_[0]);
308 71         278 return $self->{min_objects};
309             }
310              
311             #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
312             sub fields_required {
313 103     103 0 134 my $self = shift;
314 103 100       292 $self->{fields_required} = shift
315             if (defined $_[0]);
316 103         373 return $self->{fields_required};
317             }
318              
319             #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
320             sub construct_object {
321 25     25 0 39 my $self = shift;
322 25 100       77 $self->{construct_object} = shift if (@_);
323 25         84 return $self->{construct_object};
324             }
325              
326             'validop';
327              
328             __END__