aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDave Arter <davea@mysociety.org>2019-04-05 18:49:32 +0100
committerDave Arter <davea@mysociety.org>2019-06-06 11:59:21 +0100
commitff3b747698f577876bff25512ac137c6677ccab7 (patch)
tree8d11c4376cbb2d7a60b79c94fc9827ef3c417d81
parent1a1be49646218b2217e25e82e6666749f78dc612 (diff)
[Hounslow] Add general enquiries functionality
This functionality allows a cobrand to replace the /contact form with a form that creates hidden reports which are sent via Open311. The form also allows file uploads in addition to photos. This functionality is currently enabled for the Hounslow cobrand and others cobrands can enable it by defining setup_general_enquiries_stash which primarily sets up the appropriate categories and default values for the report.
-rw-r--r--perllib/FixMyStreet/App/Controller/Contact.pm16
-rw-r--r--perllib/FixMyStreet/App/Controller/Contact/Enquiry.pm119
-rw-r--r--perllib/FixMyStreet/App/Model/PhotoSet.pm19
-rw-r--r--perllib/FixMyStreet/Cobrand/Hounslow.pm40
-rw-r--r--perllib/FixMyStreet/PhotoStorage.pm28
-rw-r--r--t/app/controller/contact.t31
-rw-r--r--t/app/controller/contact_enquiry.t226
-rw-r--r--t/app/controller/sample.pdfbin0 -> 12501 bytes
-rw-r--r--templates/web/base/contact/enquiry/index.html79
-rw-r--r--templates/web/base/contact/enquiry/submit.html1
-rw-r--r--templates/web/hounslow/main_nav_items.html58
11 files changed, 598 insertions, 19 deletions
diff --git a/perllib/FixMyStreet/App/Controller/Contact.pm b/perllib/FixMyStreet/App/Controller/Contact.pm
index fb525fc1f..e2b76ca60 100644
--- a/perllib/FixMyStreet/App/Controller/Contact.pm
+++ b/perllib/FixMyStreet/App/Controller/Contact.pm
@@ -26,9 +26,14 @@ Functions to run on both GET and POST contact requests.
sub auto : Private {
my ($self, $c) = @_;
+ $c->forward('/auth/get_csrf_token');
+}
+
+sub begin : Private {
+ my ($self, $c) = @_;
+ $c->forward('/begin');
$c->forward('setup_request');
$c->forward('determine_contact_type');
- $c->forward('/auth/get_csrf_token');
}
=head2 index
@@ -106,7 +111,14 @@ sub determine_contact_type : Private {
$c->stash->{rejecting_report} = 1;
}
} elsif ( $c->cobrand->abuse_reports_only ) {
- $c->detach( '/page_error_404_not_found' );
+ # General enquiries replaces contact form if enabled
+ if ( $c->cobrand->can('setup_general_enquiries_stash') ) {
+ $c->res->redirect( '/contact/enquiry' );
+ $c->detach;
+ return 1;
+ } else {
+ $c->detach( '/page_error_404_not_found' );
+ }
}
return 1;
diff --git a/perllib/FixMyStreet/App/Controller/Contact/Enquiry.pm b/perllib/FixMyStreet/App/Controller/Contact/Enquiry.pm
new file mode 100644
index 000000000..5b1c4980f
--- /dev/null
+++ b/perllib/FixMyStreet/App/Controller/Contact/Enquiry.pm
@@ -0,0 +1,119 @@
+package FixMyStreet::App::Controller::Contact::Enquiry;
+
+use Moose;
+use namespace::autoclean;
+use Path::Tiny;
+use File::Copy;
+use Digest::SHA qw(sha1_hex);
+use File::Basename;
+
+BEGIN { extends 'Catalyst::Controller'; }
+
+sub auto : Private {
+ my ($self, $c) = @_;
+
+ unless ( $c->cobrand->call_hook('setup_general_enquiries_stash') ) {
+ $c->res->redirect( '/' );
+ $c->detach;
+ }
+}
+
+# This needs to be defined here so /contact/begin doesn't get run instead.
+sub begin : Private {
+ my ($self, $c) = @_;
+
+ $c->forward('/begin');
+}
+
+sub index : Path : Args(0) {
+ my ( $self, $c, $preserve_session ) = @_;
+
+ # Make sure existing files aren't lost if we're rendering this
+ # page as a result of validation error.
+ delete $c->session->{enquiry_files} unless $preserve_session;
+
+ $c->stash->{field_errors}->{name} = _("Please enter your full name.") if $c->stash->{field_errors}->{name};
+}
+
+sub submit : Path('submit') : Args(0) {
+ my ( $self, $c ) = @_;
+
+ unless ($c->req->method eq 'POST' && $c->forward("/report/new/check_form_submitted") ) {
+ $c->res->redirect( '/contact/enquiry' );
+ return;
+ }
+
+ # General enquiries are always private reports, and aren't
+ # located by the user on the map
+ $c->set_param('non_public', 1);
+ $c->set_param('pc', '');
+ $c->set_param('skipped', 1);
+
+ $c->forward('/report/new/initialize_report');
+ $c->forward('/report/new/check_for_category');
+ $c->forward('/auth/check_csrf_token');
+ $c->forward('/report/new/process_report');
+ $c->forward('/report/new/process_user');
+ $c->forward('handle_uploads');
+ $c->forward('/photo/process_photo');
+ $c->go('index', [ 1 ]) unless $c->forward('/report/new/check_for_errors');
+ $c->forward('/report/new/save_user_and_report');
+ $c->forward('confirm_report');
+ $c->stash->{success} = 1;
+
+ # Don't want these lingering around for the next time.
+ delete $c->session->{enquiry_files};
+}
+
+sub confirm_report : Private {
+ my ( $self, $c ) = @_;
+
+ my $report = $c->stash->{report};
+
+ # We don't ever want to modify an existing user, as general enquiries don't
+ # require any kind of email confirmation.
+ $report->user->insert unless $report->user->in_storage;
+ $report->confirm();
+ $report->update;
+}
+
+sub handle_uploads : Private {
+ my ( $self, $c ) = @_;
+
+ # NB. For simplicity's sake this relies on the UPLOAD_DIR config key provided
+ # when using the FileSystem PHOTO_STORAGE_BACKEND. Should your FMS site not
+ # be using this storage backend, you must ensure that UPLOAD_DIR is set
+ # in order for general enquiries uploads to work.
+ my $cfg = FixMyStreet->config('PHOTO_STORAGE_OPTIONS');
+ my $dir = $cfg ? $cfg->{UPLOAD_DIR} : FixMyStreet->config('UPLOAD_DIR');
+ $dir = path($dir, "enquiry_files")->absolute(FixMyStreet->path_to());
+ $dir->mkpath;
+
+ my $files = $c->session->{enquiry_files} || {};
+ foreach ($c->req->upload) {
+ my $upload = $c->req->upload($_);
+ if ($upload->type !~ /^image/) {
+ # It's not a photo so remove it before /photo/process_photo rejects it
+ delete $c->req->uploads->{$_};
+
+ # For each file, copy it into place in a subdir of PHOTO_STORAGE_OPTIONS.UPLOAD_DIR
+ FixMyStreet::PhotoStorage::base64_decode_upload($c, $upload);
+ # Hash each file to get its filename, but preserve the file extension
+ # so content-type is correct when POSTing to Open311.
+ my ($p, $n, $ext) = fileparse($upload->filename, qr/\.[^.]*/);
+ my $key = sha1_hex($upload->slurp) . $ext;
+ my $out = path($dir, $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 in report->extra along with the original filenames
+ $files->{$key} = $upload->raw_basename;
+ }
+ }
+ $c->session->{enquiry_files} = $files;
+ $c->stash->{report}->set_extra_metadata(enquiry_files => $files);
+}
+
+1;
diff --git a/perllib/FixMyStreet/App/Model/PhotoSet.pm b/perllib/FixMyStreet/App/Model/PhotoSet.pm
index 8621286b0..1d9ccd7cd 100644
--- a/perllib/FixMyStreet/App/Model/PhotoSet.pm
+++ b/perllib/FixMyStreet/App/Model/PhotoSet.pm
@@ -121,23 +121,8 @@ has ids => ( # Arrayref of $fileid tuples (always, so post upload/raw data proc
return ();
}
- # base64 decode the file if it's encoded that way
- # Catalyst::Request::Upload doesn't do this automatically
- # unfortunately.
- my $transfer_encoding = $upload->headers->header('Content-Transfer-Encoding');
- if (defined $transfer_encoding && $transfer_encoding eq 'base64') {
- my $decoded = decode_base64($upload->slurp);
- if (open my $fh, '>', $upload->tempname) {
- binmode $fh;
- print $fh $decoded;
- close $fh
- } else {
- my $c = $self->c;
- $c->log->info('Couldn\'t open temp file to save base64 decoded image: ' . $!);
- $c->stash->{photo_error} = _("Sorry, we couldn't save your image(s), please try again.");
- return ();
- }
- }
+ # Make sure any base64 encoding is handled.
+ FixMyStreet::PhotoStorage::base64_decode_upload($self->c, $upload);
# get the photo into a variable
my $photo_blob = eval {
diff --git a/perllib/FixMyStreet/Cobrand/Hounslow.pm b/perllib/FixMyStreet/Cobrand/Hounslow.pm
index 39f8e902f..ab61c0203 100644
--- a/perllib/FixMyStreet/Cobrand/Hounslow.pm
+++ b/perllib/FixMyStreet/Cobrand/Hounslow.pm
@@ -119,6 +119,7 @@ sub open311_config {
$row->set_extra_fields(@$extra);
$params->{multi_photos} = 1;
+ $params->{upload_files} = 1;
}
sub open311_munge_update_params {
@@ -140,6 +141,45 @@ sub open311_skip_report_fetch {
# Make sure fetched report description isn't shown.
sub filter_report_description { "" }
+sub setup_general_enquiries_stash {
+ my $self = shift;
+
+ my @bodies = $self->{c}->model('DB::Body')->active->for_areas(( $self->council_area_id ))->all;
+ my %bodies = map { $_->id => $_ } @bodies;
+ my @contacts #
+ = $self->{c} #
+ ->model('DB::Contact') #
+ ->active
+ ->search(
+ {
+ 'me.body_id' => [ keys %bodies ]
+ },
+ {
+ prefetch => 'body',
+ order_by => 'me.category',
+ }
+ )->all;
+ @contacts = grep {
+ my $group = $_->get_extra_metadata('group') || '';
+ $group eq 'Other' || $group eq 'General Enquiries';
+ } @contacts;
+ $self->{c}->stash->{bodies} = \%bodies;
+ $self->{c}->stash->{bodies_to_list} = \%bodies;
+ $self->{c}->stash->{contacts} = \@contacts;
+ $self->{c}->stash->{missing_details_bodies} = [];
+ $self->{c}->stash->{missing_details_body_names} = [];
+
+ $self->{c}->set_param('title', "General Enquiry");
+ # Can't use (0, 0) for lat lon so default to the rough location
+ # of Hounslow Highways HQ.
+ $self->{c}->stash->{latitude} = 51.469;
+ $self->{c}->stash->{longitude} = -0.35;
+
+ return 1;
+}
+
+sub abuse_reports_only { 1 }
+
sub lookup_site_code_config { {
buffer => 50, # metres
url => "https://tilma.mysociety.org/mapserver/hounslow",
diff --git a/perllib/FixMyStreet/PhotoStorage.pm b/perllib/FixMyStreet/PhotoStorage.pm
index a441fb718..558c93749 100644
--- a/perllib/FixMyStreet/PhotoStorage.pm
+++ b/perllib/FixMyStreet/PhotoStorage.pm
@@ -37,5 +37,33 @@ sub get_fileid {
}
+=head2 base64_decode_upload
+
+base64 decode the temporary on-disk uploaded file if
+it's encoded that way. Modifies the file in-place.
+Catalyst::Request::Upload doesn't do this automatically
+unfortunately.
+
+=cut
+
+sub base64_decode_upload {
+ my ( $c, $upload ) = @_;
+
+ my $transfer_encoding = $upload->headers->header('Content-Transfer-Encoding');
+ if (defined $transfer_encoding && $transfer_encoding eq 'base64') {
+ my $decoded = decode_base64($upload->slurp);
+ if (open my $fh, '>', $upload->tempname) {
+ binmode $fh;
+ print $fh $decoded;
+ close $fh
+ } else {
+ $c->log->info('Couldn\'t open temp file to save base64 decoded image: ' . $!);
+ $c->stash->{photo_error} = _("Sorry, we couldn't save your file(s), please try again.");
+ return ();
+ }
+ }
+
+}
+
1;
diff --git a/t/app/controller/contact.t b/t/app/controller/contact.t
index 842f27dd5..c74aaa5ff 100644
--- a/t/app/controller/contact.t
+++ b/t/app/controller/contact.t
@@ -6,6 +6,16 @@ sub abuse_reports_only { 1; }
1;
+package FixMyStreet::Cobrand::GeneralEnquiries;
+
+use base 'FixMyStreet::Cobrand::Default';
+
+sub abuse_reports_only { 1 }
+
+sub setup_general_enquiries_stash { 1 }
+
+1;
+
package main;
use FixMyStreet::TestMech;
@@ -492,6 +502,27 @@ subtest 'check can limit contact to abuse reports' => sub {
}
};
+subtest 'check redirected to correct form for general enquiries cobrand' => sub {
+ FixMyStreet::override_config {
+ 'ALLOWED_COBRANDS' => [ 'generalenquiries' ],
+ }, sub {
+ $mech->get( '/contact' );
+ is $mech->res->code, 200, "got 200 for final destination";
+ is $mech->res->previous->code, 302, "got 302 for redirect";
+ is $mech->uri->path, '/contact/enquiry';
+
+ $mech->get_ok( '/contact?id=' . $problem_main->id, 'can visit for abuse report' );
+
+ my $token = FixMyStreet::App->model("DB::Token")->create({
+ scope => 'moderation',
+ data => { id => $problem_main->id }
+ });
+
+ $mech->get_ok( '/contact?m=' . $token->token, 'can visit for moderation complaint' );
+
+ }
+};
+
$problem_main->delete;
done_testing();
diff --git a/t/app/controller/contact_enquiry.t b/t/app/controller/contact_enquiry.t
new file mode 100644
index 000000000..fe3962c8e
--- /dev/null
+++ b/t/app/controller/contact_enquiry.t
@@ -0,0 +1,226 @@
+use FixMyStreet::TestMech;
+use Path::Tiny;
+use File::Temp 'tempdir';
+
+# disable info logs for this test run
+FixMyStreet::App->log->disable('info');
+END { FixMyStreet::App->log->enable('info'); }
+
+my $mech = FixMyStreet::TestMech->new;
+
+my $body = $mech->create_body_ok( 2483, 'Hounslow Borough Council' );
+my $contact = $mech->create_contact_ok(
+ body_id => $body->id,
+ category => 'General Enquiry',
+ email => 'genenq@example.com',
+ non_public => 1,
+);
+my $contact2 = $mech->create_contact_ok(
+ body_id => $body->id,
+ category => 'FOI Request',
+ email => 'foi@example.com',
+ non_public => 1,
+);
+my $contact3 = $mech->create_contact_ok(
+ body_id => $body->id,
+ category => 'Other',
+ email => 'other@example.com',
+);
+my $contact4 = $mech->create_contact_ok(
+ body_id => $body->id,
+ category => 'Carriageway Defect',
+ email => 'potholes@example.com',
+);
+$contact->update( { extra => { group => 'General Enquiries' } } );
+$contact2->update( { extra => { group => 'General Enquiries' } } );
+$contact3->update( { extra => { group => 'Other' } } );
+
+FixMyStreet::override_config { ALLOWED_COBRANDS => ['bromley'], }, sub {
+ subtest 'redirected to / if general enquiries not enabled' => sub {
+ $mech->get( '/contact/enquiry' );
+ is $mech->res->code, 200, "got 200 for final destination";
+ is $mech->res->previous->code, 302, "got 302 for redirect";
+ is $mech->uri->path, '/';
+ };
+};
+
+FixMyStreet::override_config {
+ ALLOWED_COBRANDS => ['hounslow'],
+ MAPIT_URL => 'http://mapit.uk/',
+}, sub {
+ subtest 'Non-general enquiries category not shown' => sub {
+ $mech->get_ok( '/contact/enquiry' );
+ $mech->content_lacks('Carriageway Defect');
+ $mech->content_contains('FOI Request');
+ };
+
+ subtest 'Enquiry can be submitted when logged out' => sub {
+ my $problems = FixMyStreet::App->model('DB::Problem')->to_body( $body->id );
+
+ $mech->get_ok( '/contact/enquiry' );
+ $mech->submit_form_ok( {
+ with_fields => {
+ name => 'Test User',
+ username => 'testuser@example.org',
+ category => 'Other',
+ detail => 'This is a general enquiry',
+ }
+ } );
+ is $mech->uri->path, '/contact/enquiry/submit';
+ $mech->content_contains("Thank you for your enquiry");
+
+ is $problems->count, 1, 'problem created';
+ my $problem = $problems->first;
+ is $problem->category, 'Other', 'problem has correct category';
+ is $problem->detail, 'This is a general enquiry', 'problem has correct detail';
+ is $problem->non_public, 1, 'problem created non_public';
+ is $problem->postcode, '';
+ is $problem->used_map, 0;
+ is $problem->latitude, 51.469, 'Problem has correct latitude';
+ is $problem->longitude, -0.35, 'Problem has correct longitude';
+ ok $problem->confirmed, 'problem confirmed';
+ is $problem->user->name, undef, 'User created without name';
+ is $problem->name, 'Test User', 'Report created with correct name';
+ is $problem->user->email, 'testuser@example.org', 'User created with correct email';
+ };
+
+ subtest 'Enquiry can be submitted when logged in' => sub {
+ my $problems = FixMyStreet::App->model('DB::Problem')->to_body( $body->id );
+ my $user = $problems->first->user;
+ $problems->delete_all;
+
+ $mech->log_in_ok( $user->email );
+
+ $mech->get_ok( '/contact/enquiry' );
+ $mech->submit_form_ok( {
+ with_fields => {
+ name => 'Test User',
+ category => 'FOI Request',
+ detail => 'This is a general enquiry',
+ }
+ } );
+ is $mech->uri->path, '/contact/enquiry/submit';
+ $mech->content_contains("Thank you for your enquiry");
+
+ is $problems->count, 1, 'problem created';
+ my $problem = $problems->first;
+ is $problem->category, 'FOI Request', 'problem has correct category';
+ is $problem->detail, 'This is a general enquiry', 'problem has correct detail';
+ is $problem->non_public, 1, 'problem created non_public';
+ is $problem->postcode, '';
+ is $problem->used_map, 0;
+ is $problem->latitude, 51.469, 'Problem has correct latitude';
+ is $problem->longitude, -0.35, 'Problem has correct longitude';
+ ok $problem->confirmed, 'problem confirmed';
+ is $problem->name, 'Test User', 'Report created with correct name';
+ is $problem->user->name, 'Test User', 'User name updated in DB';
+ is $problem->user->email, 'testuser@example.org', 'Report user has correct email';
+
+ $mech->log_out_ok;
+ };
+
+ subtest 'User name not changed if logged out when making report' => sub {
+ my $problems = FixMyStreet::App->model('DB::Problem')->to_body( $body->id );
+ my $user = $problems->first->user;
+ $problems->delete_all;
+
+ is $user->name, 'Test User', 'User has correct name';
+
+ $mech->get_ok( '/contact/enquiry' );
+ $mech->submit_form_ok( {
+ with_fields => {
+ name => 'Simon Neil',
+ username => 'testuser@example.org',
+ category => 'General Enquiry',
+ detail => 'This is a general enquiry',
+ }
+ } );
+ is $mech->uri->path, '/contact/enquiry/submit';
+ $mech->content_contains("Thank you for your enquiry");
+
+ is $problems->count, 1, 'problem created';
+ my $problem = $problems->first;
+ is $problem->category, 'General Enquiry', 'problem has correct category';
+ is $problem->detail, 'This is a general enquiry', 'problem has correct detail';
+ is $problem->non_public, 1, 'problem created non_public';
+ is $problem->postcode, '';
+ is $problem->used_map, 0;
+ is $problem->latitude, 51.469, 'Problem has correct latitude';
+ is $problem->longitude, -0.35, 'Problem has correct longitude';
+ ok $problem->confirmed, 'problem confirmed';
+ is $problem->name, 'Simon Neil', 'Report created with correct name';
+ is $problem->user->email, 'testuser@example.org', 'Report user has correct email';
+ $user->discard_changes;
+ is $user->name, 'Test User', 'User name in DB not changed';
+
+ $mech->log_out_ok;
+ };
+
+};
+
+my $sample_jpeg = path(__FILE__)->parent->child("sample.jpg");
+ok $sample_jpeg->exists, "sample image $sample_jpeg exists";
+my $sample_pdf = path(__FILE__)->parent->child("sample.pdf");
+ok $sample_pdf->exists, "sample PDF $sample_pdf exists";
+
+subtest "Check photo & file upload works" => sub {
+ my $UPLOAD_DIR = tempdir( CLEANUP => 1 );
+
+ FixMyStreet::override_config {
+ ALLOWED_COBRANDS => [ 'hounslow' ],
+ MAPIT_URL => 'http://mapit.uk/',
+ PHOTO_STORAGE_BACKEND => 'FileSystem',
+ PHOTO_STORAGE_OPTIONS => {
+ UPLOAD_DIR => $UPLOAD_DIR,
+ },
+ }, sub {
+ my $problems = FixMyStreet::App->model('DB::Problem')->to_body( $body->id );
+ $problems->delete_all;
+
+
+ $mech->get_ok('/contact/enquiry');
+ my ($csrf) = $mech->content =~ /name="token" value="([^"]*)"/;
+
+ $mech->post( '/contact/enquiry/submit',
+ Content_Type => 'form-data',
+ Content =>
+ {
+ submit_problem => 1,
+ token => $csrf,
+ name => 'Test User',
+ username => 'testuser@example.org',
+ category => 'Other',
+ detail => 'This is a general enquiry',
+ photo1 => [ $sample_jpeg, undef, Content_Type => 'image/jpeg' ],
+ photo2 => [ $sample_pdf, undef, Content_Type => 'application/pdf' ],
+ }
+ );
+ ok $mech->success, 'Made request with two files uploaded';
+
+ is $problems->count, 1, 'problem created';
+ my $problem = $problems->first;
+ is $problem->detail, 'This is a general enquiry', 'problem has correct detail';
+ is $problem->non_public, 1, 'problem created non_public';
+ is $problem->postcode, '';
+ is $problem->used_map, 0;
+ is $problem->latitude, 51.469, 'Problem has correct latitude';
+ is $problem->longitude, -0.35, 'Problem has correct longitude';
+ ok $problem->confirmed, 'problem confirmed';
+
+ my $image_file = path($UPLOAD_DIR, '74e3362283b6ef0c48686fb0e161da4043bbcc97.jpeg');
+ ok $image_file->exists, 'Photo uploaded to temp';
+
+ my $photoset = $problem->get_photoset();
+ is $photoset->num_images, 1, 'Found just 1 image';
+ is $photoset->data, '74e3362283b6ef0c48686fb0e161da4043bbcc97.jpeg';
+
+ my $pdf_file = path($UPLOAD_DIR, 'enquiry_files', '90f7a64043fb458d58de1a0703a6355e2856b15e.pdf');
+ ok $pdf_file->exists, 'PDF uploaded to temp';
+
+ is_deeply $problem->get_extra_metadata('enquiry_files'), {
+ '90f7a64043fb458d58de1a0703a6355e2856b15e.pdf' => 'sample.pdf'
+ }, 'enquiry file info stored OK';
+
+ };
+};
+done_testing();
diff --git a/t/app/controller/sample.pdf b/t/app/controller/sample.pdf
new file mode 100644
index 000000000..9ac5be44b
--- /dev/null
+++ b/t/app/controller/sample.pdf
Binary files differ
diff --git a/templates/web/base/contact/enquiry/index.html b/templates/web/base/contact/enquiry/index.html
new file mode 100644
index 000000000..8b8bba1b7
--- /dev/null
+++ b/templates/web/base/contact/enquiry/index.html
@@ -0,0 +1,79 @@
+[% INCLUDE 'header.html',
+ bodyclass = 'twothirdswidthpage',
+ title = loc('General Enquiry')
+%]
+
+<form method="post" action="/contact/enquiry/submit" class="validate" enctype="multipart/form-data">
+ <input type="hidden" name="token" value="[% csrf_token %]">
+ <input type="hidden" name="submit_problem" value="1">
+ <fieldset>
+ [% INCLUDE 'errors.html' %]
+
+ <label for="form_name">[% loc('Your name') %]</label>
+ [% IF field_errors.name %]
+ <div class="form-error">[% field_errors.name %]</div>
+ [% END %]
+ <input type="text" class="form-control required" name="name" id="form_name" value="[% ( form_name OR c.user.name ) | html %]" size="30">
+
+ <label for="form_email">[% loc('Your email') %]</label>
+ [% IF field_errors.username %]
+ <div class="form-error">[% field_errors.username %]</div>
+ [% END %]
+ <input type="text" class="form-control required email" name="username" id="form_email" [% "disabled" IF c.user.email %] value="[% ( email OR c.user.email ) | html %]" size="30">
+
+ <div class="form-group">
+ <label for="form_phone">[% loc('Your phone number') %]</label>
+ <span class="required-text required-text--optional">optional</span>
+ <input type="text" class="form-control extra.phone" name="extra.phone" id="form_phone" value="[% report.user.phone | html %]" size="30">
+ </div>
+
+ <label for="form_category">[% loc('Subject') %]</label>
+ [% IF field_errors.category %]
+ <div class="form-error">[% field_errors.category %]</div>
+ [% END %]
+ <select class="form-control required" name="category" id="category">
+ <option value="">[% loc('-- Please select --') %]</option>
+ [% FOREACH contact IN contacts %]
+ <option value="[% contact.category | html %]" [% "selected" IF report.category == contact.category %]>[% contact.category | html %]</option>
+ [% END %]
+ </select>
+
+ <label for="form_detail">[% loc('Message') %]</label>
+ [% IF field_errors.detail %]
+ <div class="form-error">[% field_errors.detail %]</div>
+ [% END %]
+ <textarea class="form-control required" name="detail" id="form_detail" rows="7" cols="50">[% report.detail | html %]</textarea>
+
+ <input type="hidden" name="upload_fileid" value="[% upload_fileid %]">
+ <label for="form_photo">
+ [% loc('Photos/Documents') %]
+ </label>
+
+ [% IF field_errors.photo %]
+ <p class='form-error'>[% field_errors.photo %]</p>
+ [% END %]
+
+ <div id="form_photos">
+ [% IF upload_fileid OR report.get_extra_metadata('enquiry_files').keys.count %]
+ <p>[% loc('You have already attached files to this report. Note that you can attach a maximum of 3 to this report (if you try to upload more, the oldest will be removed).') %]</p>
+ [% FOREACH id IN upload_fileid.split(',') %]
+ <img align="right" src="/photo/temp.[% id %]" alt="">
+ [% END %]
+
+ [% IF report.get_extra_metadata('enquiry_files') %]
+ [% FOREACH id IN report.get_extra_metadata('enquiry_files').values %]
+ [% id %]<br />
+ [% END %]
+ <input type="hidden" name="enquiry_files" value="[% enquiry_files | html %]" />
+ [% END %]
+ [% END %]
+ <p><input type="file" name="photo1" id="form_photo"></p>
+ <p><input type="file" name="photo2" id="form_photo2"></p>
+ <p><input type="file" name="photo3" id="form_photo3"></p>
+ </div>
+
+ <input class="final-submit green-btn" type="submit" value="[% loc('Send') %]">
+
+ </fieldset></form>
+
+[% INCLUDE 'footer.html' pagefooter = 'yes' %]
diff --git a/templates/web/base/contact/enquiry/submit.html b/templates/web/base/contact/enquiry/submit.html
new file mode 100644
index 000000000..050fd31a9
--- /dev/null
+++ b/templates/web/base/contact/enquiry/submit.html
@@ -0,0 +1 @@
+[% INCLUDE 'contact/submit.html' %]
diff --git a/templates/web/hounslow/main_nav_items.html b/templates/web/hounslow/main_nav_items.html
new file mode 100644
index 000000000..f333e08f3
--- /dev/null
+++ b/templates/web/hounslow/main_nav_items.html
@@ -0,0 +1,58 @@
+[%~ IF problem ~%]
+ [%~ INCLUDE navitem uri='/report/new?longitude=' _ problem.longitude _ '&amp;latitude=' _ problem.latitude label=loc('Report another problem here') attrs='class="report-a-problem-btn"' ~%]
+[%~ ELSIF latitude AND longitude ~%]
+ [%~ INCLUDE navitem uri='/report/new?longitude=' _ longitude _ '&amp;latitude=' _ latitude label=loc('Report a problem here') attrs='class="report-a-problem-btn"' ~%]
+[%~ ELSIF homepage_template ~%]
+ [%~ INCLUDE navitem uri='/report' label=loc('Report a problem') attrs='class="report-a-problem-btn"' ~%]
+[%~ ELSE ~%]
+ [%~ INCLUDE navitem uri='/' label=loc('Report a problem') attrs='class="report-a-problem-btn"' ~%]
+[%~ END ~%]
+
+[%~ IF c.user_exists ~%]
+ [%~ INCLUDE navitem uri='/my' label=loc('Your account') ~%]
+[%~ ELSE ~%]
+ [%~ INCLUDE navitem uri='/auth' label=loc('Sign in') ~%]
+[%~ END ~%]
+
+[%~ IF c.user_exists AND c.user.has_body_permission_to('planned_reports') ~%]
+ [%~ INCLUDE navitem always_url=1 uri='/my/planned' label=loc('Shortlist') ~%]
+[%~ END ~%]
+
+
+[%~ UNLESS hide_all_reports_link ~%]
+ [%~
+ IF c.user_exists AND c.user.categories.size;
+ categories = c.user.categories_string | uri;
+ cat_suffix = "?filter_category=" _ categories;
+ END;
+
+ reports_uri = '/reports';
+ IF body_name;
+ body_name = body_name | uri;
+ reports_uri = "${reports_uri}/${body_name}";
+ END;
+
+ INCLUDE navitem uri=reports_uri label=loc('All reports') suffix=cat_suffix;
+ ~%]
+[%~ END ~%]
+
+[%~
+ IF pc;
+ pc_uri = pc | uri;
+ pc_suffix = "/list?pc=" _ pc_uri;
+ END;
+
+ INCLUDE navitem uri='/alert' label=loc('Local alerts') suffix=pc_suffix;
+~%]
+
+[%~ INCLUDE navitem uri='/contact/enquiry' label=loc('Contact') ~%]
+
+[%~ INCLUDE navitem uri='/faq' label=loc('Help') ~%]
+
+[%~ UNLESS hide_privacy_link ~%]
+ [%~ INCLUDE navitem uri=c.cobrand.privacy_policy_url label=loc('Privacy') liattrs='class="nav-menu__item--privacy"' ~%]
+[%~ END ~%]
+
+[%~ IF c.user_exists AND c.cobrand.admin_allow_user(c.user) ~%]
+ [%~ INCLUDE navitem uri='/admin' label=loc('Admin') ~%]
+[%~ END ~%]