File Coverage

blib/lib/HTML/FormFu/Constraint.pm
Criterion Covered Total %
statement 149 160 93.1
branch 71 100 71.0
condition 15 19 78.9
subroutine 25 27 92.5
pod 1 9 11.1
total 261 315 82.8


line stmt bran cond sub pod time code
1             package HTML::FormFu::Constraint;
2              
3 401     401   2372 use strict;
  401         480  
  401         15024  
4             our $VERSION = '2.05'; # VERSION
5              
6 401     401   1466 use Moose;
  401         437  
  401         2406  
7 401     401   1750523 use MooseX::Attribute::FormFuChained;
  401         560  
  401         11844  
8             extends 'HTML::FormFu::Processor';
9              
10 401     401   147653 use HTML::FormFu::Exception::Constraint;
  401         1029  
  401         15814  
11 401         22705 use HTML::FormFu::Util qw(
12             DEBUG_CONSTRAINTS
13             DEBUG_CONSTRAINTS_WHEN
14             debug
15 401     401   2413 );
  401         463  
16 401     401   1539 use Clone ();
  401         473  
  401         5752  
17 401     401   1265 use Carp qw( croak );
  401         450  
  401         17911  
18 401     401   1550 use List::Util 1.33 qw( any all first );
  401         10542  
  401         23365  
19 401     401   1661 use Scalar::Util qw( reftype blessed );
  401         544  
  401         356761  
20              
21             has not => ( is => 'rw', traits => ['FormFuChained'] );
22             has force_errors => ( is => 'rw', traits => ['FormFuChained'] );
23             has when => ( is => 'rw', traits => ['FormFuChained'] );
24             has only_on_reps => ( is => 'rw', traits => ['FormFuChained'] );
25              
26             sub repeatable_repeat {
27 49     49 0 103 my ( $self, $repeatable, $new_block ) = @_;
28              
29 49         114 my $block_fields = $new_block->get_fields;
30              
31             # rename any 'when' fields
32             {
33 49         60 my $when = $self->when;
  49         1042  
34              
35 49 100       240 if ( my $name = $when->{field} ) {
    50          
36 1         4 my $field = $repeatable->get_field_with_original_name( $name,
37             $block_fields );
38              
39 1 50       3 if ( defined $field ) {
40 1 50       4 DEBUG_CONSTRAINTS && debug(
41             sprintf
42             "Repeatable renaming constraint 'when{field}' '%s' to '%s'",
43             $name, $field->nested_name,
44             );
45              
46 1         4 $when->{field} = $field->nested_name;
47             }
48             }
49             elsif ( my $names = $when->{fields} ) {
50 0         0 for my $name (@$names) {
51 0         0 my $field = $repeatable->get_field_with_original_name( $name,
52             $block_fields );
53              
54 0 0       0 if ( defined $field ) {
55 0         0 $when->{field} = $field->nested_name;
56             }
57             }
58             }
59             }
60             }
61              
62       485 0   sub pre_process { }
63              
64             sub process {
65 443     443 0 486 my ( $self, $params ) = @_;
66              
67 443 100       1039 return unless $self->_run_this_rep;
68              
69 428         1124 my $value = $self->_find_field_value($params);
70              
71 428         462 my @errors;
72              
73             # check when condition
74 428 100       1456 if ( !$self->_process_when($params) ) {
75 29 50       125 DEBUG_CONSTRAINTS && debug('fail when() check - skipping constraint');
76 29         88 return;
77             }
78              
79 399 100       868 if ( ref $value eq 'ARRAY' ) {
80 14         30 push @errors, eval { $self->constrain_values( $value, $params ) };
  14         71  
81              
82 14 100       40 if ($@) {
83 1         12 push @errors,
84             $self->mk_errors( {
85             pass => 0,
86             message => $@,
87             } );
88             }
89             }
90             else {
91 385         446 my $ok = eval { $self->constrain_value( $value, $params ) };
  385         1358  
92              
93 385 50       2779 DEBUG_CONSTRAINTS && debug( 'CONSTRAINT RETURN VALUE' => $ok );
94 385 50       689 DEBUG_CONSTRAINTS && debug( '$@' => $@ );
95              
96 385 100 100     2987 push @errors,
97             $self->mk_errors( {
98             pass => ( $@ || !$ok ) ? 0 : 1,
99             message => $@,
100             } );
101             }
102              
103 399         1249 return @errors;
104             }
105              
106             sub _run_this_rep {
107 477     477   470 my ($self) = @_;
108              
109 477 100       12404 my $only_on_reps = $self->only_on_reps
110             or return 1;
111              
112 61 50       144 my $current_rep = $self->field->repeatable_count
113             or return 1;
114              
115 61 100 100     253 $only_on_reps = [$only_on_reps]
116             if ( reftype($only_on_reps) || '' ) ne 'ARRAY';
117              
118 61     67   284 return first { $current_rep == $_ } @$only_on_reps;
  67         271  
119             }
120              
121             sub _find_field_value {
122 428     428   482 my ( $self, $params ) = @_;
123              
124 428         1425 my $value = $self->get_nested_hash_value( $params, $self->nested_name );
125              
126             my @fields_with_this_name
127 428         492 = @{ $self->form->get_fields( { nested_name => $self->nested_name } ) };
  428         1208  
128              
129 428 100       1272 if ( @fields_with_this_name > 1 ) {
130 10         25 my $field = $self->parent;
131 10         8 my $index;
132              
133 10         20 for ( my $i = 0; $i <= $#fields_with_this_name; ++$i ) {
134 15 100       32 if ( $fields_with_this_name[$i] eq $field ) {
135 10         9 $index = $i;
136 10         10 last;
137             }
138             }
139              
140 10 50       19 croak 'did not find ourself - how can this happen?'
141             if !defined $index;
142              
143 10 50 50     36 if ( ( reftype($value) || '' ) eq 'ARRAY' ) {
    0          
144 10         14 $value = $value->[$index];
145             }
146             elsif ( $index == 0 ) {
147              
148             # keep $value
149             }
150             else {
151 0         0 undef $value;
152             }
153             }
154              
155 428         743 return $value;
156             }
157              
158             sub constrain_values {
159 23     23 0 52 my ( $self, $values, $params ) = @_;
160              
161 23         33 my @errors;
162              
163 23         49 for my $value (@$values) {
164 45         46 my $ok = eval { $self->constrain_value( $value, $params ) };
  45         118  
165              
166 45 50       110 DEBUG_CONSTRAINTS && debug( 'CONSTRAINT RETURN VALUE' => $ok );
167 45 50       91 DEBUG_CONSTRAINTS && debug( '$@' => $@ );
168              
169 45 100 66     321 push @errors,
170             $self->mk_errors( {
171             pass => ( $@ || !$ok ) ? 0 : 1,
172             message => $@,
173             } );
174             }
175              
176 23         52 return @errors;
177             }
178              
179             sub constrain_value {
180 0     0 0 0 croak "constrain_value() should be overridden";
181             }
182              
183             sub mk_errors {
184 438     438 0 527 my ( $self, $args ) = @_;
185              
186 438         560 my $pass = $args->{pass};
187 438         546 my $message = $args->{message};
188              
189 438         406 my @errors;
190 438   100     11316 my $force = $self->force_errors || $self->parent->force_errors;
191              
192 438 100 100     1571 if ( !$pass || $force ) {
193 128         697 my $error = $self->mk_error($message);
194              
195 128 100       589 $error->forced(1) if $pass;
196              
197 128         497 push @errors, $error;
198             }
199              
200 438         877 return @errors;
201             }
202              
203             sub mk_error {
204 196     196 0 318 my ( $self, $err ) = @_;
205              
206 196 50 33     963 if ( !blessed $err || !$err->isa('HTML::FormFu::Exception::Constraint') ) {
207 196         6151 $err = HTML::FormFu::Exception::Constraint->new;
208             }
209              
210 196         358 return $err;
211             }
212              
213             sub _process_when {
214 539     539   901 my ( $self, $params ) = @_;
215              
216             # returns 1 if when condition is fulfilled or not defined
217             # returns 0 if when condition is defined and not fulfilled
218             # If it's a callback, return callback's return value (so 'when'
219             # condition is met if callback returns a true value)
220              
221             # get when condition
222 539         13153 my $when = $self->when;
223 539 100       1774 return 1 if !defined $when;
224              
225             # check type of 'when'
226 76 50       164 croak "Parameter 'when' is not a hash ref" if ref $when ne 'HASH';
227              
228             # field or callback must be defined
229 76         127 my $when_field = $when->{field};
230 76         92 my $when_fields = $when->{fields};
231 76         80 my $when_any_field = $when->{any_field};
232 76         88 my $when_callback = $when->{callback};
233              
234             croak
235             "'field', 'fields', 'any_field' or 'callback' key must be defined in 'when'"
236 76 50   163   417 if all { !defined } $when_field, $when_fields, $when_any_field,
  163         228  
237             $when_callback;
238              
239             # Callback will be the preferred thing
240 76 100       240 if ($when_callback) {
241             ## no critic (ProhibitNoStrict);
242 401     401   2030 no strict 'refs';
  401         528  
  401         183291  
243 20         91 return $when_callback->( $params, $self );
244             }
245              
246 56         60 my $any;
247             my @when_fields_value;
248              
249 56 100       101 if ($when_any_field) {
250 10 50       20 croak "'any_field' is set to an empty list" if !@$when_any_field;
251              
252 10         11 $any = 1;
253              
254 10         26 @$when_fields = @$when_any_field;
255             }
256              
257 56 100       94 if ($when_fields) {
258 17 50       28 croak "'fields' is set to an empty list" if !@$when_fields;
259              
260 17         26 for my $name (@$when_fields) {
261 54         93 my $value = $self->get_nested_hash_value( $params, $name );
262              
263 54 50       96 push @when_fields_value, $value
264             if defined $value;
265             }
266             }
267             else {
268              
269             # nothing to constrain if field doesn't exist
270 39         118 my $value = $self->get_nested_hash_value( $params, $when_field );
271              
272 39 50       110 push @when_fields_value, $value
273             if defined $value;
274             }
275              
276 56 50       141 DEBUG_CONSTRAINTS_WHEN && debug( 'WHEN_FIELDS_VALUES' => \@when_fields_value );
277              
278 56 50       109 if ( !@when_fields_value ) {
279 0 0       0 DEBUG_CONSTRAINTS_WHEN
280             && debug("No 'when' fields values exist - returning false");
281 0         0 return 0;
282             }
283              
284 56         57 my @values;
285              
286 56 100       165 if ( defined( my $value = $when->{value} ) ) {
    100          
287 24         38 push @values, $value;
288             }
289             elsif ( defined( my $values = $when->{values} ) ) {
290 10         24 push @values, @$values;
291             }
292              
293             # determine if condition is fulfilled
294 56         52 my @ok;
295              
296 56 100       94 if (@values) {
297 34         54 for my $value (@when_fields_value) {
298 34     50   141 push @ok, any { $value eq $_ } @values;
  50         129  
299             }
300             }
301             else {
302 22         24 for my $value (@when_fields_value) {
303 59 100       84 push @ok, $value ? 1 : 0;
304             }
305             }
306              
307 56 50       113 DEBUG_CONSTRAINTS_WHEN && debug( "'when' value matches" => \@ok );
308              
309             my $return
310             = $any
311 21 50   21   33 ? any { $when->{not} ? !$_ : $_ } @ok
312 56 100   49   246 : all { $when->{not} ? !$_ : $_ } @ok;
  49 100       114  
313              
314 56 50       411 DEBUG_CONSTRAINTS_WHEN && debug( "'when' return value" => $return );
315              
316 56         208 return $return;
317             }
318              
319             sub fetch_error_message {
320 0     0 1 0 my ( $self ) = @_;
321              
322 0         0 my $error = HTML::FormFu::Exception::Constraint->new({
323             form => $self->form,
324             parent => $self->parent,
325             processor => $self,
326             });
327              
328 0         0 return $error->message;
329             }
330              
331             sub clone {
332 51     51 0 56 my $self = shift;
333              
334 51         216 my $clone = $self->SUPER::clone(@_);
335              
336 51 100       1169 if ( defined( my $when = $self->when ) ) {
337 1         28 $clone->when( Clone::clone $when );
338             }
339              
340 51         1146 return $clone;
341             }
342              
343             __PACKAGE__->meta->make_immutable;
344              
345             1;
346              
347             __END__
348              
349             =head1 NAME
350              
351             HTML::FormFu::Constraint - Constrain User Input
352              
353             =head1 VERSION
354              
355             version 2.05
356              
357             =head1 SYNOPSIS
358              
359             ---
360             elements:
361             - type: Text
362             name: foo
363             constraints:
364             - type: Length
365             min: 8
366             when:
367             field: bar
368             values: [ 1, 3, 5 ]
369             - type: Text
370             name: bar
371             constraints:
372             - Integer
373             - Required
374             constraints:
375             - SingleValue
376              
377             =head1 DESCRIPTION
378              
379             User input is processed in the following order:
380              
381             =over
382              
383             =item L<Filters|HTML::FormFu::Filter>
384              
385             =item L<Constraints|HTML::FormFu::Constraint>
386              
387             =item L<Inflators|HTML::FormFu::Inflator>
388              
389             =item L<Validators|HTML::FormFu::Validator>
390              
391             =item L<Transformers|HTML::FormFu::Transformer>
392              
393             =back
394              
395             See L<HTML::FormFu/"FORM LOGIC AND VALIDATION"> for further details.
396              
397             L<HTML::FormFu/constraints> can be called on any L<form|HTML::FormFu>,
398             L<block element|HTML::FormFu::Element::Block> (includes fieldsets) or
399             L<field element|HTML::FormFu::Role::Element::Field>.
400              
401             If called on a field element, no C<name> argument should be passed.
402              
403             If called on a L<form|HTML::FormFu> or
404             L<block element|HTML::FormFu::Element::Block>, if no C<name> argument is
405             provided, a new constraint is created for and added to every field on that
406             form or block.
407              
408             See L<HTML::FormFu/"FORM LOGIC AND VALIDATION"> for further details.
409              
410             =head1 METHODS
411              
412             =head2 type
413              
414             Returns the C<type> argument originally used to create the constraint.
415              
416             =head2 not
417              
418             If true, inverts the results of the constraint - such that input that would
419             otherwise fail will pass, and vise-versa.
420              
421             This value is ignored by some constraints - see the documentation for
422             individual constraints for details.
423              
424             =head2 only_on_reps
425              
426             Argument: \@repeatable_count
427              
428             For constraints added to fields within a
429             L<Repeatable|HTML::FormFu::Element::Repeatable> element, if C<only_on_reps>
430             is set, the constraint will only be run for fields whose
431             L<repeatable_count|HTML::FormFu::Role::Element::Field/repeatable_count>
432             matches one of these set values.
433              
434             Not available for the constraints listed in
435             L<HTML::FormFu::Element::Repeatable/"Unsupported Constraints">.
436              
437             =head2 message
438              
439             Arguments: $string
440              
441             Set the message which will be displayed if the constraint fails.
442              
443             =head2 message_xml
444              
445             Arguments: $string
446              
447             Variant of L</message> which ensures the value won't be XML-escaped.
448              
449             =head2 message_loc
450              
451             Arguments: $string
452              
453             Variant of L</message> which uses L<localize|HTML::FormFu/localize> to
454             create the message.
455              
456             =head2 localize_args
457              
458             Provide arguments that should be passed to L<localize|HTML::FormFu/localize>
459             to replace C<[_1]>, C<[_2]>, etc. in the localized string.
460              
461             =head2 force_errors
462              
463             See L<HTML::FormFu/force_errors> for details.
464              
465             =head2 parent
466              
467             Returns the L<field|HTML::FormFu::Role::Element::Field> object that the constraint
468             is associated with.
469              
470             =head2 form
471              
472             Returns the L<HTML::FormFu> object that the constraint's field is attached
473             to.
474              
475             =head2 name
476              
477             Shorthand for C<< $constraint->parent->name >>
478              
479             =head2 when
480              
481             Defines a condition for the constraint. Only when the condition is fulfilled
482             the constraint will be applied.
483              
484             This method expects a hashref.
485              
486             The C<field> or C<callback> must be supplied, all other fields are optional.
487              
488             If C<value> or C<values> is not supplied, the constraint will pass if the
489             named field's value is true.
490              
491             The following keys are supported:
492              
493             =over
494              
495             =item field
496              
497             Nested-name of form field that shall be checked against - if C<< when->{value} >>
498             is set, the C<when> condition passes if the named field's value matches that,
499             otherwise the C<when> condition passes if the named field's value is true.
500              
501             =item fields
502              
503             Array-ref of nested-names that shall be checked. The C<when> condition passes
504             if all named-fields' values pass, using the same rules as C<field> above.
505              
506             =item any_field
507              
508             Array-ref of nested-names that shall be checked. The C<when> condition passes
509             if any named-fields' values pass, using the same rules as C<field> above.
510              
511             =item value
512              
513             Expected value in the form field 'field'
514              
515             =item values
516              
517             Array of multiple values, one must match to fulfill the condition
518              
519             =item not
520              
521             Inverts the when condition - value(s) must not match
522              
523             =item callback
524              
525             A callback subroutine-reference or fully resolved subroutine name can be
526             supplied to perform complex checks. An hashref of all parameters is passed
527             to the callback sub. In this case all other keys are ignored, including C<not>.
528             You need to return a true value for the constraint to be applied or a false
529             value to not apply it.
530              
531             The callback subroutine receives 2 arguments:
532              
533             =over 8
534              
535             =item 1
536              
537             C<$params> (hashref of all submitted parameters)
538              
539             =item 2
540              
541             C<$constraint> (the Constraint object)
542              
543             =back
544              
545             =back
546              
547             =head1 EXPERIMENTAL METHODS
548              
549             =head2 fetch_error_message
550              
551             Return value: $string
552              
553             Attempt to return the error message that would be used if this constraint
554             generated an error.
555              
556             This will generally be correct for simple constraints with a fixed message or
557             which use a placeholder from a known value, such as
558             L<HTML::FormFu::Constraint::Min/min>.
559             This will generally C<not> return the correct message for constraints which
560             use L<HTML::FormFu::Role::Constraint::Others/others>, where the field with an
561             error is not known without actually fully processing a form submission.
562              
563             =head1 CORE CONSTRAINTS
564              
565             =over
566              
567             =item L<HTML::FormFu::Constraint::AllOrNone>
568              
569             =item L<HTML::FormFu::Constraint::ASCII>
570              
571             =item L<HTML::FormFu::Constraint::AutoSet>
572              
573             =item L<HTML::FormFu::Constraint::Bool>
574              
575             =item L<HTML::FormFu::Constraint::Callback>
576              
577             =item L<HTML::FormFu::Constraint::CallbackOnce>
578              
579             =item L<HTML::FormFu::Constraint::DateTime>
580              
581             =item L<HTML::FormFu::Constraint::DependOn>
582              
583             =item L<HTML::FormFu::Constraint::Email>
584              
585             =item L<HTML::FormFu::Constraint::Equal>
586              
587             =item L<HTML::FormFu::Constraint::File>
588              
589             =item L<HTML::FormFu::Constraint::File::MIME>
590              
591             =item L<HTML::FormFu::Constraint::File::MaxSize>
592              
593             =item L<HTML::FormFu::Constraint::File::MinSize>
594              
595             =item L<HTML::FormFu::Constraint::File::Size>
596              
597             =item L<HTML::FormFu::Constraint::Integer>
598              
599             =item L<HTML::FormFu::Constraint::Length>
600              
601             =item L<HTML::FormFu::Constraint::MaxLength>
602              
603             =item L<HTML::FormFu::Constraint::MaxRange>
604              
605             =item L<HTML::FormFu::Constraint::MinLength>
606              
607             =item L<HTML::FormFu::Constraint::MinRange>
608              
609             =item L<HTML::FormFu::Constraint::MinMaxFields>
610              
611             =item L<HTML::FormFu::Constraint::Number>
612              
613             =item L<HTML::FormFu::Constraint::Printable>
614              
615             =item L<HTML::FormFu::Constraint::Range>
616              
617             =item L<HTML::FormFu::Constraint::Regex>
618              
619             =item L<HTML::FormFu::Constraint::Required>
620              
621             =item L<HTML::FormFu::Constraint::Set>
622              
623             =item L<HTML::FormFu::Constraint::SingleValue>
624              
625             =item L<HTML::FormFu::Constraint::Word>
626              
627             =back
628              
629             =head1 NON-CORE CONSTRAINTS AVAILABLE ON CPAN
630              
631             =over
632              
633             =item L<HTML::FormFu::Constraint::reCAPTCHA>
634              
635             =back
636              
637             =head1 CAVEATS
638              
639             See L<HTML::FormFu::Element::Repeatable/"Unsupported Constraints">
640             for a list of constraints that won't work within
641             L<HTML::FormFu::Element::Repeatable>.
642              
643             =head1 AUTHOR
644              
645             Carl Franks, C<cfranks@cpan.org>
646              
647             Based on the original source code of L<HTML::Widget::Constraint>, by
648             Sebastian Riedel, C<sri@oook.de>.
649              
650             =head1 LICENSE
651              
652             This library is free software, you can redistribute it and/or modify it under
653             the same terms as Perl itself.
654              
655             =cut