aboutsummaryrefslogtreecommitdiffstats
path: root/perllib/FixMyStreet/App/Form
diff options
context:
space:
mode:
Diffstat (limited to 'perllib/FixMyStreet/App/Form')
-rw-r--r--perllib/FixMyStreet/App/Form/Field/JSON.pm42
-rw-r--r--perllib/FixMyStreet/App/Form/Field/Postcode.pm50
-rw-r--r--perllib/FixMyStreet/App/Form/Page/Simple.pm25
-rw-r--r--perllib/FixMyStreet/App/Form/Page/Waste.pm11
-rw-r--r--perllib/FixMyStreet/App/Form/Waste.pm52
-rw-r--r--perllib/FixMyStreet/App/Form/Waste/AboutYou.pm38
-rw-r--r--perllib/FixMyStreet/App/Form/Waste/Enquiry.pm48
-rw-r--r--perllib/FixMyStreet/App/Form/Waste/Report.pm65
-rw-r--r--perllib/FixMyStreet/App/Form/Waste/Request.pm64
-rw-r--r--perllib/FixMyStreet/App/Form/Waste/UPRN.pm37
-rw-r--r--perllib/FixMyStreet/App/Form/Wizard.pm115
11 files changed, 547 insertions, 0 deletions
diff --git a/perllib/FixMyStreet/App/Form/Field/JSON.pm b/perllib/FixMyStreet/App/Form/Field/JSON.pm
new file mode 100644
index 000000000..4da4ef2b0
--- /dev/null
+++ b/perllib/FixMyStreet/App/Form/Field/JSON.pm
@@ -0,0 +1,42 @@
+package FixMyStreet::App::Form::Field::JSON;
+
+use HTML::FormHandler::Moose;
+extends 'HTML::FormHandler::Field::Hidden';
+
+use JSON::MaybeXS;
+use MIME::Base64;
+
+has '+inflate_method' => ( default => sub { \&inflate_json } );
+has '+deflate_method' => ( default => sub { \&deflate_json } );
+has '+fif_from_value' => ( default => 1 );
+
+sub inflate_json {
+ my ($self, $value) = @_;
+ return $value unless $value;
+ $value = decode_json(decode_base64($value));
+ return $value;
+}
+
+sub deflate_json {
+ my ($self, $value) = @_;
+ return $value unless $value;
+ $value = encode_base64(encode_json($value), "");
+ return $value;
+}
+
+__PACKAGE__->meta->make_immutable;
+use namespace::autoclean;
+
+1;
+
+__END__
+
+=pod
+
+=encoding UTF-8
+
+=head1 NAME
+
+FixMyStreet::App::Form::Field::JSON - used to store some data in a hidden field
+
+=cut
diff --git a/perllib/FixMyStreet/App/Form/Field/Postcode.pm b/perllib/FixMyStreet/App/Form/Field/Postcode.pm
new file mode 100644
index 000000000..093ae66a3
--- /dev/null
+++ b/perllib/FixMyStreet/App/Form/Field/Postcode.pm
@@ -0,0 +1,50 @@
+package FixMyStreet::App::Form::Field::Postcode;
+
+use HTML::FormHandler::Moose;
+extends 'HTML::FormHandler::Field::Text';
+
+use mySociety::PostcodeUtil;
+
+apply(
+ [
+ {
+ transform => sub {
+ my ( $value, $field ) = @_;
+ $value =~ s/[^A-Z0-9]//i;
+ return mySociety::PostcodeUtil::canonicalise_postcode($value);
+ }
+ },
+ {
+ check => sub { mySociety::PostcodeUtil::is_valid_postcode(shift) },
+ message => 'Sorry, we did not recognise that postcode.',
+ }
+ ]
+);
+
+
+__PACKAGE__->meta->make_immutable;
+use namespace::autoclean;
+
+1;
+
+__END__
+
+=pod
+
+=encoding UTF-8
+
+=head1 NAME
+
+FixMyStreet::App::Form::Field::Postcode - validates postcode using mySociety::PostcodeUtil
+
+=head1 DESCRIPTION
+
+Validates that the input looks like a postcode using L<mySociety::PostcodeUtil>.
+Widget type is 'text'.
+
+=head1 DEPENDENCIES
+
+L<mySociety::PostcodeUtil>
+
+=cut
+
diff --git a/perllib/FixMyStreet/App/Form/Page/Simple.pm b/perllib/FixMyStreet/App/Form/Page/Simple.pm
new file mode 100644
index 000000000..89a871e2e
--- /dev/null
+++ b/perllib/FixMyStreet/App/Form/Page/Simple.pm
@@ -0,0 +1,25 @@
+package FixMyStreet::App::Form::Page::Simple;
+use Moose;
+extends 'HTML::FormHandler::Page';
+
+# What page to go to after successful submission of this page
+has next => ( is => 'ro', isa => 'Str|CodeRef' );
+
+# A function that will be called to generate an update_field_list parameter
+has update_field_list => (
+ is => 'ro',
+ isa => 'CodeRef',
+ predicate => 'has_update_field_list',
+);
+
+# A function called after all form processing, just before template display
+# (to e.g. set up the map)
+has post_process => (
+ is => 'ro',
+ isa => 'CodeRef',
+);
+
+# Catalyst action to forward to once this page has been reached
+has finished => ( is => 'ro', isa => 'CodeRef' );
+
+1;
diff --git a/perllib/FixMyStreet/App/Form/Page/Waste.pm b/perllib/FixMyStreet/App/Form/Page/Waste.pm
new file mode 100644
index 000000000..5275cae7f
--- /dev/null
+++ b/perllib/FixMyStreet/App/Form/Page/Waste.pm
@@ -0,0 +1,11 @@
+package FixMyStreet::App::Form::Page::Waste;
+use Moose;
+extends 'FixMyStreet::App::Form::Page::Simple';
+
+# Title to use for this page
+has title => ( is => 'ro', isa => 'Str' );
+
+# Special template to use in preference to the default
+has template => ( is => 'ro', isa => 'Str' );
+
+1;
diff --git a/perllib/FixMyStreet/App/Form/Waste.pm b/perllib/FixMyStreet/App/Form/Waste.pm
new file mode 100644
index 000000000..c430506c7
--- /dev/null
+++ b/perllib/FixMyStreet/App/Form/Waste.pm
@@ -0,0 +1,52 @@
+package FixMyStreet::App::Form::Waste;
+
+use HTML::FormHandler::Moose;
+extends 'FixMyStreet::App::Form::Wizard';
+
+has c => ( is => 'ro' );
+
+has default_page_type => ( is => 'ro', isa => 'Str', default => 'Waste' );
+
+has finished_action => ( is => 'ro' );
+
+before _process_page_array => sub {
+ my ($self, $pages) = @_;
+ foreach my $page (@$pages) {
+ $page->{type} = $self->default_page_type
+ unless $page->{type};
+ }
+};
+
+# Add some functions to the form to pass through to the current page
+has '+current_page' => (
+ handles => {
+ title => 'title',
+ template => 'template',
+ }
+);
+
+sub wizard_finished {
+ my ($form, $action) = @_;
+ my $c = $form->c;
+ my $success = $c->forward($action, [ $form ]);
+ if (!$success) {
+ $form->add_form_error('Something went wrong, please try again');
+ foreach (keys %{$c->stash->{field_errors}}) {
+ $form->add_form_error("$_: " . $c->stash->{field_errors}{$_});
+ }
+ }
+ return $success;
+}
+
+# Make sure we can have pre-ticked things on the first page
+before after_build => sub {
+ my $self = shift;
+
+ my $saved_data = $self->previous_form ? $self->previous_form->saved_data : $self->saved_data;
+
+ my $c = $self->c;
+
+ map { $saved_data->{$_} = 1 } grep { /^(service|container)-/ && $c->req->params->{$_} } keys %{$c->req->params};
+};
+
+1;
diff --git a/perllib/FixMyStreet/App/Form/Waste/AboutYou.pm b/perllib/FixMyStreet/App/Form/Waste/AboutYou.pm
new file mode 100644
index 000000000..d5bb3df2b
--- /dev/null
+++ b/perllib/FixMyStreet/App/Form/Waste/AboutYou.pm
@@ -0,0 +1,38 @@
+package FixMyStreet::App::Form::Waste::AboutYou;
+
+use utf8;
+use HTML::FormHandler::Moose::Role;
+use FixMyStreet::SMS;
+
+has_field name => (
+ type => 'Text',
+ label => 'Your name',
+ required => 1,
+ validate_method => sub {
+ my $self = shift;
+ $self->add_error('Please enter your full name.')
+ if length($self->value) < 5
+ || $self->value !~ m/\s/
+ || $self->value =~ m/\ba\s*n+on+((y|o)mo?u?s)?(ly)?\b/i;
+ },
+);
+
+has_field phone => (
+ type => 'Text',
+ label => 'Telephone number',
+ validate_method => sub {
+ my $self = shift;
+ my $parsed = FixMyStreet::SMS->parse_username($self->value);
+ $self->add_error('Please provide a valid phone number')
+ unless $parsed->{phone};
+ }
+);
+
+has_field email => (
+ type => 'Email',
+ tags => {
+ hint => 'If you provide an email address, we can send you order status updates'
+ },
+);
+
+1;
diff --git a/perllib/FixMyStreet/App/Form/Waste/Enquiry.pm b/perllib/FixMyStreet/App/Form/Waste/Enquiry.pm
new file mode 100644
index 000000000..fa85d5d4c
--- /dev/null
+++ b/perllib/FixMyStreet/App/Form/Waste/Enquiry.pm
@@ -0,0 +1,48 @@
+package FixMyStreet::App::Form::Waste::Enquiry;
+
+use utf8;
+use HTML::FormHandler::Moose;
+extends 'FixMyStreet::App::Form::Waste';
+
+# First page has dynamic fields, so is set in code
+
+has_field category => ( type => 'Hidden' );
+has_field service_id => ( type => 'Hidden' );
+
+has_page about_you => (
+ fields => ['name', 'phone', 'email', 'continue'],
+ title => 'About you',
+ next => 'summary',
+);
+
+with 'FixMyStreet::App::Form::Waste::AboutYou';
+
+has_page summary => (
+ fields => ['submit'],
+ title => 'Submit missed collection',
+ template => 'waste/summary_enquiry.html',
+ finished => sub {
+ return $_[0]->wizard_finished('process_enquiry_data');
+ },
+ next => 'done',
+);
+
+has_page done => (
+ title => 'Enquiry sent',
+ template => 'waste/confirmation.html',
+);
+
+has_field continue => (
+ type => 'Submit',
+ value => 'Continue',
+ element_attr => { class => 'govuk-button' },
+ order => 999
+);
+
+has_field submit => (
+ type => 'Submit',
+ value => 'Submit',
+ element_attr => { class => 'govuk-button' }
+);
+
+1;
diff --git a/perllib/FixMyStreet/App/Form/Waste/Report.pm b/perllib/FixMyStreet/App/Form/Waste/Report.pm
new file mode 100644
index 000000000..589e75d48
--- /dev/null
+++ b/perllib/FixMyStreet/App/Form/Waste/Report.pm
@@ -0,0 +1,65 @@
+package FixMyStreet::App::Form::Waste::Report;
+
+use utf8;
+use HTML::FormHandler::Moose;
+extends 'FixMyStreet::App::Form::Waste';
+
+# First page has dynamic fields, so is set in code
+
+has_page about_you => (
+ fields => ['name', 'email', 'phone', 'continue'],
+ title => 'About you',
+ next => 'summary',
+);
+
+with 'FixMyStreet::App::Form::Waste::AboutYou';
+
+has_page summary => (
+ fields => ['submit'],
+ title => 'Submit missed collection',
+ template => 'waste/summary_report.html',
+ finished => sub {
+ return $_[0]->wizard_finished('process_report_data');
+ },
+ next => 'done',
+);
+
+has_page done => (
+ title => 'Missed collection sent',
+ template => 'waste/confirmation.html',
+);
+
+has_field category => (
+ type => 'Hidden',
+ default => 'Report missed collection'
+);
+
+has_field continue => (
+ type => 'Submit',
+ value => 'Continue',
+ element_attr => { class => 'govuk-button' },
+);
+
+has_field submit => (
+ type => 'Submit',
+ value => 'Report collection as missed',
+ element_attr => { class => 'govuk-button' },
+ order => 999,
+);
+
+sub validate {
+ my $self = shift;
+ my $any = 0;
+
+ foreach ($self->all_fields) {
+ $any = 1 if $_->name =~ /^service-/ && ($_->value || $self->saved_data->{$_->name});
+ }
+ $self->add_form_error('Please specify what was missed')
+ unless $any;
+
+ $self->add_form_error('Please specify at least one of phone or email')
+ unless $self->field('phone')->is_inactive || $self->field('phone')->value || $self->field('email')->value;
+}
+
+1;
+
diff --git a/perllib/FixMyStreet/App/Form/Waste/Request.pm b/perllib/FixMyStreet/App/Form/Waste/Request.pm
new file mode 100644
index 000000000..e7caaa206
--- /dev/null
+++ b/perllib/FixMyStreet/App/Form/Waste/Request.pm
@@ -0,0 +1,64 @@
+package FixMyStreet::App::Form::Waste::Request;
+
+use utf8;
+use HTML::FormHandler::Moose;
+extends 'FixMyStreet::App::Form::Waste';
+
+# First page has dynamic fields, so is set in code
+
+has_page about_you => (
+ fields => ['name', 'email', 'phone', 'continue'],
+ title => 'About you',
+ next => 'summary',
+);
+
+with 'FixMyStreet::App::Form::Waste::AboutYou';
+
+has_page summary => (
+ fields => ['submit'],
+ title => 'Submit container request',
+ template => 'waste/summary_request.html',
+ finished => sub {
+ return $_[0]->wizard_finished('process_request_data');
+ },
+ next => 'done',
+);
+
+has_page done => (
+ title => 'Container request sent',
+ template => 'waste/confirmation.html',
+);
+
+has_field category => (
+ type => 'Hidden',
+ default => 'Request new container',
+);
+
+has_field continue => (
+ type => 'Submit',
+ value => 'Continue',
+ element_attr => { class => 'govuk-button' },
+);
+
+has_field submit => (
+ type => 'Submit',
+ value => 'Request new containers',
+ element_attr => { class => 'govuk-button' },
+ order => 999,
+);
+
+sub validate {
+ my $self = shift;
+ my $any = 0;
+
+ foreach ($self->all_fields) {
+ $any = 1 if $_->name =~ /^container-/ && ($_->value || $self->saved_data->{$_->name});
+ }
+ $self->add_form_error('Please specify what you need')
+ unless $any;
+
+ $self->add_form_error('Please specify at least one of phone or email')
+ unless $self->field('phone')->is_inactive || $self->field('phone')->value || $self->field('email')->value;
+}
+
+1;
diff --git a/perllib/FixMyStreet/App/Form/Waste/UPRN.pm b/perllib/FixMyStreet/App/Form/Waste/UPRN.pm
new file mode 100644
index 000000000..d0ac7b3cb
--- /dev/null
+++ b/perllib/FixMyStreet/App/Form/Waste/UPRN.pm
@@ -0,0 +1,37 @@
+package FixMyStreet::App::Form::Waste::UPRN;
+
+use utf8;
+use HTML::FormHandler::Moose;
+extends 'HTML::FormHandler';
+
+use mySociety::PostcodeUtil qw(is_valid_postcode);
+
+has '+field_name_space' => ( default => 'FixMyStreet::App::Form::Field' );
+
+has cobrand => ( is => 'ro' );
+
+has_field postcode => (
+ required => 1,
+ type => 'Postcode',
+ validate_method => sub {
+ my $self = shift;
+ return if $self->has_errors; # Called even if already failed
+ my $data = $self->form->cobrand->bin_addresses_for_postcode($self->value);
+ if (!@$data) {
+ $self->add_error('Sorry, we did not find any results for that postcode');
+ }
+ push @$data, { value => 'missing', label => 'I can’t find my address' };
+ $self->value($data);
+ },
+ tags => { autofocus => 1 },
+);
+
+has_field go => (
+ type => 'Submit',
+ value => 'Go',
+ element_attr => { class => 'govuk-button' },
+);
+
+__PACKAGE__->meta->make_immutable;
+
+1;
diff --git a/perllib/FixMyStreet/App/Form/Wizard.pm b/perllib/FixMyStreet/App/Form/Wizard.pm
new file mode 100644
index 000000000..edb7b0c5c
--- /dev/null
+++ b/perllib/FixMyStreet/App/Form/Wizard.pm
@@ -0,0 +1,115 @@
+package FixMyStreet::App::Form::Wizard;
+# ABSTRACT: create a multi-page form, based on HTML::FormHandler::Wizard, but not numbered
+
+use HTML::FormHandler::Moose;
+extends 'HTML::FormHandler';
+with ('HTML::FormHandler::BuildPages', 'HTML::FormHandler::Pages' );
+
+sub is_wizard { 1 } # So build_active is called
+
+sub build_page_name_space { 'FixMyStreet::App::Form::Page' }
+has '+field_name_space' => ( default => 'FixMyStreet::App::Form::Field' );
+
+# Internal attributes and fields to handle multi-page forms
+has page_name => ( is => 'ro', isa => 'Str' );
+has current_page => ( is => 'ro', lazy => 1,
+ default => sub { $_[0]->page($_[0]->page_name) },
+ predicate => 'has_current_page',
+);
+
+has saved_data_encoded => ( is => 'ro', isa => 'Maybe[Str]' );
+has saved_data => ( is => 'rw', lazy => 1, isa => 'HashRef', default => sub {
+ $_[0]->field('saved_data')->inflate_json($_[0]->saved_data_encoded) || {};
+});
+has previous_form => ( is => 'ro', isa => 'Maybe[HTML::FormHandler]' );
+has csrf_token => ( is => 'ro', isa => 'Str' );
+
+has_field saved_data => ( type => 'JSON' );
+has_field token => ( type => 'Hidden', required => 1 );
+has_field process => ( type => 'Hidden', required => 1 );
+
+sub next {
+ my $self = shift;
+ my $next = $self->current_page->next;
+ if (ref $next eq 'CODE') {
+ $next = $next->($self->saved_data);
+ }
+ return $next;
+}
+
+# Override HFH default and set current page only to active
+sub build_active {
+ my $self = shift;
+
+ my %active;
+ foreach my $fname ($self->current_page->all_fields) {
+ $active{$fname} = 1;
+ }
+
+ foreach my $page ( $self->all_pages ) {
+ foreach my $fname ( $page->all_fields ) {
+ my $field = $self->field($fname);
+ $field->inactive(1) unless $active{$fname};
+ }
+ }
+}
+
+# Stuff to set up as soon as we have a form
+sub after_build {
+ my $self = shift;
+ my $page = $self->current_page;
+
+ my $saved_data = $self->previous_form ? $self->previous_form->saved_data : $self->saved_data;
+
+ $self->init_object($saved_data); # For filling in existing values
+ $self->saved_data($saved_data);
+
+ # Fill in internal fields
+ $self->update_field(saved_data => { default => $saved_data });
+ $self->update_field(token => { default => $self->csrf_token });
+ $self->update_field(process => { default => $page->name });
+
+ # Update field list with any dynamic things (eg user-based, address lookup, geocoding)
+ if ($page->has_update_field_list) {
+ my $updates = $page->update_field_list->($self) || {};
+ foreach my $field_name (keys %$updates) {
+ $self->update_field($field_name, $updates->{$field_name});
+ }
+ }
+}
+
+# After a form has been processed, run any post process functions
+after 'process' => sub {
+ my $self = shift;
+ my $page = $self->current_page;
+ $page->post_process->($self) if $page->post_process;
+};
+
+after 'validate_form' => sub {
+ my $self = shift;
+
+ if ($self->validated) {
+ # Update saved_data for the next page
+ my $saved_data = { %{$self->saved_data}, %{$self->value} };
+ delete $saved_data->{process};
+ delete $saved_data->{token};
+ delete $saved_data->{saved_data};
+ $self->saved_data($saved_data);
+ $self->field('saved_data')->_set_value($saved_data);
+
+ # And check to see if there is a function to call on the page
+ my $page = $self->current_page;
+ if ($page->finished) {
+ my $success = $page->finished->($self);
+ if (!$success) {
+ $self->add_form_error('Something went wrong, please try again')
+ unless $self->has_form_errors;
+ $self->validated(0);
+ }
+ }
+ }
+};
+
+__PACKAGE__->meta->make_immutable;
+use namespace::autoclean;
+1;