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