Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions Changes
Original file line number Diff line number Diff line change
@@ -1,5 +1,24 @@
{{$NEXT}}

- Constraint when() with not(1) now applies the constraint when the
named field is missing from the submission, such as an unchecked
Checkbox. Previously a missing field caused the constraint to be
skipped regardless of not(), so default_empty_value was needed on
the Checkbox. RT#45409

- Behaviour change: Constraint when() with fields() now requires
every named field to be present and match. Previously a missing
field was silently ignored.

- Fields without a name no longer emit "Use of uninitialized value"
warnings when a form is processed or rendered: an unnamed Submit or
Button with a value triggered one via force_default, and the Select,
Textarea, Checkboxgroup, Radiogroup, ContentButton and Date string
renderers emitted name="" with a warning. The name attribute is now
omitted, matching the other Input elements. RT#106557

- New github org: uperl

- Migrate from List::MoreUtils to List::SomeUtils

- Fix CVE-2026-19873: bound the Repeatable element's counter_name
Expand Down
23 changes: 22 additions & 1 deletion README.pod
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,14 @@

=encoding UTF-8

=head1 NAME

HTML::FormFu - HTML Form Creation, Rendering and Validation Framework

=head1 VERSION

version 2.08

=head1 SYNOPSIS

Note: These examples make use of L<HTML::FormFu::Model::DBIC>. As of
Expand Down Expand Up @@ -268,6 +276,19 @@ used as the return value for L</submitted>.
If L</indicator> is not set, L</submitted> will return true if a value for
any known fieldname was submitted.

=head2 repeatable_max_counter

Arguments: $number

Default Value: C<100>

The default L<max_counter|HTML::FormFu::Element::Repeatable/max_counter>
for all L<Repeatable|HTML::FormFu::Element::Repeatable> elements in this
form. Individual Repeatable elements can override this by setting their own
L<max_counter|HTML::FormFu::Element::Repeatable/max_counter>.

Set to C<0> to disable clamping form-wide.

=head2 auto_fieldset

Arguments: 1
Expand Down Expand Up @@ -1999,7 +2020,7 @@ Carl Franks <cpan@fireartist.com>

=head1 COPYRIGHT AND LICENSE

This software is copyright (c) 2018 by Carl Franks.
This software is copyright (c) 2026, 2018, 2016, 2015, 2012, 2011 by Carl Franks.

This is free software; you can redistribute it and/or modify it under
the same terms as the Perl 5 programming language system itself.
Expand Down
2 changes: 1 addition & 1 deletion dist.ini
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ name = HTML-FormFu
author = Carl Franks <cpan@fireartist.com>
license = Perl_5
copyright_holder = Carl Franks
copyright_year = 2021
copyright_year = 2026
main_module = lib/HTML/FormFu.pm

[Prereqs]
Expand Down
37 changes: 18 additions & 19 deletions lib/HTML/FormFu/Constraint.pm
Original file line number Diff line number Diff line change
Expand Up @@ -259,30 +259,22 @@ sub _process_when {
croak "'fields' is set to an empty list" if !@$when_fields;

for my $name (@$when_fields) {
my $value = $self->get_nested_hash_value( $params, $name );

push @when_fields_value, $value
if defined $value;
push @when_fields_value,
$self->get_nested_hash_value( $params, $name );
}
}
else {

# nothing to constrain if field doesn't exist
my $value = $self->get_nested_hash_value( $params, $when_field );

push @when_fields_value, $value
if defined $value;
push @when_fields_value,
$self->get_nested_hash_value( $params, $when_field );
}

# a field missing from the submission (e.g. an unchecked Checkbox) has
# an undefined value, which is treated as not matching - so with 'not'
# set, the condition is fulfilled and the constraint is applied. RT#45409

DEBUG_CONSTRAINTS_WHEN
&& debug( 'WHEN_FIELDS_VALUES' => \@when_fields_value );

if ( !@when_fields_value ) {
DEBUG_CONSTRAINTS_WHEN
&& debug("No 'when' fields values exist - returning false");
return 0;
}

my @values;

if ( defined( my $value = $when->{value} ) ) {
Expand All @@ -297,12 +289,13 @@ sub _process_when {

if (@values) {
for my $value (@when_fields_value) {
push @ok, any { $value eq $_ } @values;
push @ok,
( defined $value && any { $value eq $_ } @values ) ? 1 : 0;
}
}
else {
for my $value (@when_fields_value) {
push @ok, $value ? 1 : 0;
push @ok, ( defined $value && $value ) ? 1 : 0;
}
}

Expand Down Expand Up @@ -492,10 +485,14 @@ Nested-name of form field that shall be checked against - if C<< when->{value} >
is set, the C<when> condition passes if the named field's value matches that,
otherwise the C<when> condition passes if the named field's value is true.

If the named field is missing from the submission altogether (as an unchecked
Checkbox is), its value is treated as not matching.

=item fields

Array-ref of nested-names that shall be checked. The C<when> condition passes
if all named-fields' values pass, using the same rules as C<field> above.
A field missing from the submission does not pass.

=item any_field

Expand All @@ -512,7 +509,9 @@ Array of multiple values, one must match to fulfill the condition

=item not

Inverts the when condition - value(s) must not match
Inverts the when condition - value(s) must not match. A field missing from the
submission counts as not matching, so a constraint with C<not> set is applied
when, for example, the named Checkbox is unchecked.

=item callback

Expand Down
2 changes: 1 addition & 1 deletion lib/HTML/FormFu/Element.pm
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ sub name {

if ( @_ > 1 ) {

if ( $name =~ /[\.\[\]]/ ) {
if ( defined $name && $name =~ /[\.\[\]]/ ) {
croak <<'ERROR_MESSAGE';
element names may not contain periods or square brackets
see documentation on nested_names() for details
Expand Down
13 changes: 9 additions & 4 deletions lib/HTML/FormFu/Element/Checkboxgroup.pm
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,11 @@ sub _string_field {

# radiogroup_tag template

my $name_attr
= defined $render->{nested_name}
? sprintf( qq{ name="%s"}, $render->{nested_name} )
: q{};

my $html .= sprintf "<span%s>\n", process_attrs( $render->{attributes} );

for my $option ( @{ $render->{options} } ) {
Expand All @@ -174,8 +179,8 @@ sub _string_field {
;

my $input = sprintf
qq{<input name="%s" type="%s" value="%s"%s />\n},
$render->{nested_name},
qq{<input%s type="%s" value="%s"%s />\n},
$name_attr,
$render->{input_type},
$item->{value},
process_attrs( $item->{attributes} ),
Expand Down Expand Up @@ -205,8 +210,8 @@ sub _string_field {
;

my $input = sprintf
qq{<input name="%s" type="%s" value="%s"%s />\n},
$render->{nested_name},
qq{<input%s type="%s" value="%s"%s />\n},
$name_attr,
$render->{input_type},
$option->{value},
process_attrs( $option->{attributes} ),
Expand Down
11 changes: 7 additions & 4 deletions lib/HTML/FormFu/Element/ContentButton.pm
Original file line number Diff line number Diff line change
Expand Up @@ -46,10 +46,13 @@ sub _string_field {

# content_button template

my $html .= sprintf qq{<button name="%s" type="%s"},
$render->{nested_name},
$render->{field_type},
;
my $html = "<button";

if ( defined $render->{nested_name} ) {
$html .= sprintf qq{ name="%s"}, $render->{nested_name};
}

$html .= sprintf qq{ type="%s"}, $render->{field_type};

if ( defined $render->{value} ) {
$html .= sprintf qq{ value="%s"}, $render->{value};
Expand Down
14 changes: 9 additions & 5 deletions lib/HTML/FormFu/Element/Date.pm
Original file line number Diff line number Diff line change
Expand Up @@ -381,12 +381,12 @@ sub _build_number_list {
sub _build_name {
my ( $self, $type ) = @_;

my $name
= defined $self->$type->{name}
? $self->$type->{name}
: sprintf "%s_%s", $self->name, $type;
return $self->$type->{name} if defined $self->$type->{name};

return $name;
# a Date element without a name has unnamed sub-fields
return if !defined $self->name;

return sprintf "%s_%s", $self->name, $type;
}

sub _add_inflator {
Expand Down Expand Up @@ -437,6 +437,10 @@ sub process {
sub process_input {
my ( $self, $input ) = @_;

# a Date element without a name has no input to combine
return $self->SUPER::process_input($input)
if !defined $self->nested_name;

my %value;

my @order = @{ $self->field_order };
Expand Down
10 changes: 7 additions & 3 deletions lib/HTML/FormFu/Element/Select.pm
Original file line number Diff line number Diff line change
Expand Up @@ -62,9 +62,13 @@ sub _string_field {

# select_tag template

my $html .= sprintf qq{<select name="%s"%s>\n},
$render->{nested_name},
process_attrs( $render->{attributes} );
my $html = "<select";

if ( defined $render->{nested_name} ) {
$html .= sprintf qq{ name="%s"}, $render->{nested_name};
}

$html .= sprintf "%s>\n", process_attrs( $render->{attributes} );

for my $option ( @{ $render->{options} } ) {
if ( exists $option->{group} ) {
Expand Down
11 changes: 7 additions & 4 deletions lib/HTML/FormFu/Element/Textarea.pm
Original file line number Diff line number Diff line change
Expand Up @@ -36,10 +36,13 @@ sub _string_field {

# textarea_tag template

my $html = sprintf qq{<textarea name="%s"%s>},
$render->{nested_name},
process_attrs( $render->{attributes} ),
;
my $html = "<textarea";

if ( defined $render->{nested_name} ) {
$html .= sprintf qq{ name="%s"}, $render->{nested_name};
}

$html .= sprintf "%s>", process_attrs( $render->{attributes} );

if ( defined $render->{value} ) {
$html .= $render->{value};
Expand Down
3 changes: 3 additions & 0 deletions lib/HTML/FormFu/Role/Element/Field.pm
Original file line number Diff line number Diff line change
Expand Up @@ -386,6 +386,9 @@ sub process_input {
my $original = $self->value;
my $name = $self->nested_name;

# a field without a name has no input to process
return if !defined $name;

# set input to default value (defined before calling FormFu->process)
if ( $submitted && $self->force_default && defined $default ) {
$self->set_nested_hash_value( $input, $name, $default );
Expand Down
Loading
Loading