Skip to content
Open
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
161 changes: 161 additions & 0 deletions perllib/FixMyStreet/App/Controller/Licence.pm
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
package FixMyStreet::App::Controller::Licence;
use Moose;
use namespace::autoclean;

BEGIN { extends 'FixMyStreet::App::Controller::Form' }

use utf8;

# Discover and load all form classes in FixMyStreet::App::Form::Licence::*
my @ALL_FORM_CLASSES;
BEGIN {
require Module::Pluggable;
Module::Pluggable->import(
search_path => 'FixMyStreet::App::Form::Licence',
sub_name => '_form_classes',
require => 1,
);
@ALL_FORM_CLASSES = __PACKAGE__->_form_classes;
}

has feature => ( is => 'ro', default => 'licencing_forms' );

has index_template => ( is => 'ro', default => 'licence/index.html' );

# Build licence types from discovered form classes
sub licence_types {
my $self = shift;
my %types;
for my $class (@ALL_FORM_CLASSES) {
next unless $class->can('type') && $class->can('name');
my $type = $class->type;
$types{$type} = {
class => $class,
name => $class->name,
};
}
return \%types;
}

# Override parent Form.pm's index to 404 - you must specify a licence type
# (Without this, the inherited index would try to load a non-existent form)
sub index : Path : Args(0) {
my ($self, $c) = @_;
$c->detach('/page_error_404_not_found');
}

# GET/POST /licence/:type - show/process a specific licence form
sub show : Path : Args(1) {
my ($self, $c, $type) = @_;

my $licence_config = $self->licence_types->{lc $type}
or $c->detach('/page_error_404_not_found');

$c->stash->{form_class} = $licence_config->{class};
$c->stash->{licence_type} = lc $type;
$c->stash->{licence_name} = $licence_config->{name};

$c->forward('/auth/get_csrf_token');
$c->forward('form');
}

sub process_licence : Private {
my ($self, $c, $form) = @_;

my $data = $form->saved_data;
my $type = $c->stash->{licence_type};
my $name = $c->stash->{licence_name};

# Handle staff submitting on behalf of another user
my $contributing_as_another_user = $c->user_exists
&& $c->user->from_body
&& $data->{email}
&& $c->user->email ne $data->{email};

# Find or create user
my $user = $c->user_exists
? $c->user->obj
: $c->model('DB::User')->find_or_new({ email => $data->{email} });
$user->name($data->{name}) if $data->{name};
$user->phone($data->{phone}) if $data->{phone};
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this may be a problem in Claims as well; if the user already exists, its name/phone are not updated (which I think is right, because otherwise someone could override it only by specifying their email, if there's no confirmation). It looks like in Waste we're now setting a $report->set_extra_metadata(phone => $c->stash->{phone}); in these cases, maybe? Bit unclear, and hopefully won't matter, but thought worth noting to have a think.


# Build detail string from form fields, grouped by section
my $detail = "";
if ($form->can('fields_for_display')) {
my @sections;
for my $stage (@{ $form->fields_for_display }) {
next if $stage->{hide};
my @visible_fields = grep { !$_->{hide} } @{ $stage->{fields} };
next unless @visible_fields;

my $section = "";
$section .= "[$stage->{title}]\n" if $stage->{title};
for my $field (@visible_fields) {
$section .= "$field->{desc}: $field->{pretty}\n";
}
push @sections, $section;
}
$detail = join("\n", @sections);
}

my $category = "$name licence";

# Default to central London (Trafalgar Square) if geocoding didn't provide coordinates.
# This ensures the report can be viewed without National Grid conversion errors.
my $latitude = $data->{latitude} || 51.508;
my $longitude = $data->{longitude} || -0.128;

my $problem = $c->model('DB::Problem')->new({
non_public => 1,
category => $category,
used_map => $data->{latitude} ? 1 : 0,
title => $category,
detail => $detail,
postcode => $data->{postcode} || '',
latitude => $latitude,
longitude => $longitude,
areas => '',
send_questionnaire => 0,
bodies_str => $c->cobrand->body->id,
photo => $data->{photos},
state => 'unconfirmed',
cobrand => $c->cobrand->moniker,
cobrand_data => 'licence',
lang => $c->stash->{lang_code},
user => $user,
name => $user->name || '',
anonymous => 0,
extra => $data,
});

$c->stash->{detail} = $detail;

# Handle user creation/association
if ($contributing_as_another_user) {
$problem->set_extra_metadata(contributed_as => 'another_user');
$problem->set_extra_metadata(contributed_by => $c->user->id);
} elsif (!$problem->user->in_storage) {
$problem->user->insert();
} elsif ($c->user && $problem->user->id == $c->user->id) {
$problem->user->update();
} else {
$problem->user->discard_changes();
}

$problem->confirm;
$problem->insert;
$problem->create_related_things();

# Check for auto-response template
my $template = $problem->response_templates->search({ 'me.state' => $problem->state })->first;
$c->stash->{auto_response} = $template->text if $template;

$c->stash->{problem} = $problem;
$c->stash->{reference} = 'FMS' . $problem->id;

return 1;
}

__PACKAGE__->meta->make_immutable;

1;
72 changes: 2 additions & 70 deletions perllib/FixMyStreet/App/Form/Claims.pm
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,7 @@ package FixMyStreet::App::Form::Claims;
use HTML::FormHandler::Moose;
extends 'FixMyStreet::App::Form::Wizard';
use utf8;

use Path::Tiny;
use File::Copy;
use Digest::SHA qw(sha1_hex);
use File::Basename;

has c => ( is => 'ro' );

Expand All @@ -17,11 +13,10 @@ has finished_action => ( is => 'ro' );

has '+is_html5' => ( default => 1 );

has upload_dir => ( is => 'ro', default => sub {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rather than three similar upload_dir functions, you could have the one (in Wizard.pm) that calls $_[0]->upload_subdir instead of "uploads", then each class/subclass only has to set that to claims_files/licence_files etc.

has '+upload_dir' => ( default => sub {
my $cfg = FixMyStreet->config('PHOTO_STORAGE_OPTIONS');
my $dir = $cfg ? $cfg->{UPLOAD_DIR} : FixMyStreet->config('UPLOAD_DIR');
$dir = path($dir, "claims_files")->absolute(FixMyStreet->path_to())->mkdir;
return $dir;
path($dir, "claims_files")->absolute(FixMyStreet->path_to())->mkdir;
});

before _process_page_array => sub {
Expand Down Expand Up @@ -1098,26 +1093,6 @@ sub format_for_display {
return $value;
}

# params does not include file uploads which causes breaks the
# validation and value setting so we need to handle them here.
sub get_params {
my ($self, $c) = @_;

my @params = $c->req->body_params;

if ( $c->req->uploads ) {
for my $field ( keys %{ $c->req->uploads } ) {
next unless $self->field($field);
if ($self->field($field)->{type} eq 'FileIdUpload') {
$self->file_upload($field);
$params[0]->{$field} = $self->saved_data->{$field};
}
}
}

return @params;
}

# this makes sure that if any of the child fields have errors we mark the date
# as invalid, even if it's technically a valid date. This is mostly to catch
# range errors on the year. Otherwise we get an error at the top of the page
Expand All @@ -1138,47 +1113,4 @@ sub validate_datetime {
$field->add_error("Please enter a valid date") unless $valid;
}

sub file_upload {
my ($form, $field) = @_;

my $c = $form->{c};
my $saved_data = $form->saved_data;

my $upload = $c->req->upload($field);
if ( $upload ) {
FixMyStreet::PhotoStorage::base64_decode_upload($c, $upload);
my ($p, $n, $ext) = fileparse($upload->filename, qr/\.[^.]*/);
my $key = sha1_hex($upload->slurp) . $ext;
my $out = $form->upload_dir->child($key);
unless (copy($upload->tempname, $out)) {
$c->log->info('Couldn\'t copy temp file to destination: ' . $!);
$c->stash->{photo_error} = _("Sorry, we couldn't save your file(s), please try again.");
return;
}
# Then store the file hashes along with the original filenames for display
$saved_data->{$field} = { files => $key, filenames => [ $upload->raw_basename ] };
}
}

sub handle_upload {
my ($form, $field, $fields) = @_;

my $saved_data = $form->saved_data;
if ( $saved_data->{$field} ) {
$fields->{$field} = { default => $saved_data->{$field}->{files}, tags => $saved_data->{$field} };
}
}

sub process_upload {
my ($form, $field) = @_;

my $saved_data = $form->saved_data;
my $c = $form->{c};

if ( !$saved_data->{$field} && $c->req->params->{$field . '_fileid'} ) {
# The data was already passed in from when it was saved before (also in tags, from above)
$saved_data->{$field} = $form->field($field)->init_value;
}
}

1;
125 changes: 125 additions & 0 deletions perllib/FixMyStreet/App/Form/Licence.pm
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
package FixMyStreet::App::Form::Licence;

use Moose::Role;
use Path::Tiny;

=head1 NAME
FixMyStreet::App::Form::Licence - Role for TfL licence application forms
=head1 DESCRIPTION
This role provides shared functionality for all licence application forms,
including summary display methods.
File upload methods (file_upload, handle_upload, process_upload) are provided
by the base Wizard class.
=cut

# Upload directory for licence files (separate from claims)
# Note: This shadows the upload_dir from Wizard.pm when this role is composed
has upload_dir => ( is => 'ro', lazy => 1, default => sub {
my $cfg = FixMyStreet->config('PHOTO_STORAGE_OPTIONS');
my $dir = $cfg ? $cfg->{UPLOAD_DIR} : FixMyStreet->config('UPLOAD_DIR');
path($dir, "licence_files")->absolute(FixMyStreet->path_to())->mkdir;
});

=head2 fields_for_display
Returns an array of pages with their fields, formatted for display on the
summary page. Each page contains:
{
stage => 'page_name',
title => 'Page Title',
hide => 0/1, # optional
fields => [
{
name => 'field_name',
desc => 'Field Label',
type => 'Text',
pretty => 'Formatted value',
value => 'raw_value',
hide => 0/1, # optional
},
...
]
}
=cut

sub fields_for_display {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could this and format for display be shared with Claims? They seem pretty almost identical.
With other change for upload_dir, that would mean this file isn't needed at all then.

my ($form) = @_;

my $things = [];
for my $page ( @{ $form->pages } ) {
my $x = {
stage => $page->{name},
title => $page->{title},
( $page->tag_exists('hide') ? ( hide => $page->get_tag('hide') ) : () ),
fields => []
};

for my $f ( @{ $page->fields } ) {
my $field = $form->field($f);
next if $field->type eq 'Submit';
my $value = $form->saved_data->{$field->{name}} // '';
push @{$x->{fields}}, {
name => $field->{name},
desc => $field->{label},
type => $field->type,
pretty => $form->format_for_display( $field->{name}, $value ),
value => $value,
( $field->tag_exists('hide') ? ( hide => $field->get_tag('hide') ) : () ),
};
}

push @$things, $x;
}

return $things;
}

=head2 format_for_display
Converts a field value to a human-readable format for display.
Handles special cases:
- Select fields: returns the label for the selected value
- DateTime fields: formats as day/month/year
- Checkbox fields: returns 'Yes' or 'No'
- FileIdUpload fields: returns comma-separated filenames
=cut

sub format_for_display {
my ($form, $field_name, $value) = @_;
my $field = $form->field($field_name);

if ( $field->{type} eq 'Select' ) {
# Find label for the selected value
for my $opt (@{$field->options}) {
return $opt->{label} if defined $opt->{value} && $opt->{value} eq $value;
}
return $value;
} elsif ( $field->{type} eq 'DateTime' ) {
if ( ref $value eq 'DateTime' ) {
return join( '/', $value->day, $value->month, $value->year);
} elsif ( ref $value eq 'HASH' && $value->{day} ) {
return "$value->{day}/$value->{month}/$value->{year}";
}
return "";
} elsif ( $field->{type} eq 'Checkbox' ) {
return $value ? 'Yes' : 'No';
} elsif ( $field->{type} eq 'FileIdUpload' ) {
if ( ref $value eq 'HASH' && $value->{filenames} ) {
return join( ', ', @{ $value->{filenames} } );
}
return "";
}

return $value // '';
}

1;
Loading