aboutsummaryrefslogtreecommitdiffstats
path: root/perllib/FixMyStreet/App/Controller
diff options
context:
space:
mode:
authorMarius Halden <marius.h@lden.org>2017-05-28 21:31:42 +0200
committerMarius Halden <marius.h@lden.org>2017-05-28 21:31:42 +0200
commit987124b09a32248414faf4d0d6615d43b29ac6f6 (patch)
treea549db8af723c981d3b346e855f25d6fd5ff8aa7 /perllib/FixMyStreet/App/Controller
parentdbf56159e44c1560a413022451bf1a1c4cb22a52 (diff)
parenta085b63ce09f87e83b75cda9b9cd08aadfe75d61 (diff)
Merge tag 'v2.0.4' into fiksgatami-dev
Diffstat (limited to 'perllib/FixMyStreet/App/Controller')
-rw-r--r--perllib/FixMyStreet/App/Controller/Admin.pm93
-rw-r--r--perllib/FixMyStreet/App/Controller/Admin/DefectTypes.pm113
-rw-r--r--perllib/FixMyStreet/App/Controller/Admin/ExorDefects.pm230
-rw-r--r--perllib/FixMyStreet/App/Controller/Admin/ResponsePriorities.pm6
-rw-r--r--perllib/FixMyStreet/App/Controller/Around.pm8
-rw-r--r--perllib/FixMyStreet/App/Controller/Auth.pm54
-rw-r--r--perllib/FixMyStreet/App/Controller/Dashboard.pm4
-rw-r--r--perllib/FixMyStreet/App/Controller/Moderate.pm9
-rw-r--r--perllib/FixMyStreet/App/Controller/My.pm81
-rw-r--r--perllib/FixMyStreet/App/Controller/Offline.pm47
-rw-r--r--perllib/FixMyStreet/App/Controller/Open311.pm61
-rwxr-xr-xperllib/FixMyStreet/App/Controller/Questionnaire.pm9
-rw-r--r--perllib/FixMyStreet/App/Controller/Report.pm45
-rw-r--r--perllib/FixMyStreet/App/Controller/Report/New.pm17
-rw-r--r--perllib/FixMyStreet/App/Controller/Reports.pm64
-rw-r--r--perllib/FixMyStreet/App/Controller/Root.pm21
-rw-r--r--perllib/FixMyStreet/App/Controller/Tokens.pm1
17 files changed, 723 insertions, 140 deletions
diff --git a/perllib/FixMyStreet/App/Controller/Admin.pm b/perllib/FixMyStreet/App/Controller/Admin.pm
index bbdf449aa..1f3307710 100644
--- a/perllib/FixMyStreet/App/Controller/Admin.pm
+++ b/perllib/FixMyStreet/App/Controller/Admin.pm
@@ -7,7 +7,7 @@ BEGIN { extends 'Catalyst::Controller'; }
use Path::Class;
use POSIX qw(strftime strcoll);
use Digest::SHA qw(sha1_hex);
-use mySociety::EmailUtil qw(is_valid_email);
+use mySociety::EmailUtil qw(is_valid_email is_valid_email_list);
use mySociety::ArrayUtils;
use DateTime::Format::Strptime;
use List::Util 'first';
@@ -78,7 +78,7 @@ sub index : Path : Args(0) {
$c->forward('stats_by_state');
my @unsent = $c->cobrand->problems->search( {
- state => [ 'confirmed' ],
+ state => [ FixMyStreet::DB::Result::Problem::open_states() ],
whensent => undef,
bodies_str => { '!=', undef },
} )->all;
@@ -357,10 +357,11 @@ sub update_contacts : Private {
}
);
- my $email = $self->trim( $c->get_param('email') );
+ my $email = $c->get_param('email');
+ $email =~ s/\s+//g;
my $send_method = $c->get_param('send_method') || $contact->send_method || $contact->body->send_method || "";
unless ( $send_method eq 'Open311' ) {
- $errors{email} = _('Please enter a valid email') unless is_valid_email($email) || $email eq 'REFUSED';
+ $errors{email} = _('Please enter a valid email') unless is_valid_email_list($email) || $email eq 'REFUSED';
}
$contact->email( $email );
@@ -732,7 +733,7 @@ sub report_edit : Path('report_edit') : Args(1) {
}
}
- $c->stash->{categories} = $c->forward('categories_for_point');
+ $c->forward('categories_for_point');
if ( $c->cobrand->moniker eq 'zurich' ) {
my $done = $c->cobrand->admin_report_edit();
@@ -789,11 +790,9 @@ sub report_edit : Path('report_edit') : Args(1) {
$c->forward( '/admin/report_edit_category', [ $problem ] );
- if ( $c->get_param('email') ne $problem->user->email ) {
- my $user = $c->model('DB::User')->find_or_create(
- { email => $c->get_param('email') }
- );
-
+ my $email = lc $c->get_param('email');
+ if ( $email ne $problem->user->email ) {
+ my $user = $c->model('DB::User')->find_or_create({ email => $email });
$user->insert unless $user->in_storage;
$problem->user( $user );
}
@@ -910,7 +909,8 @@ sub categories_for_point : Private {
# Remove the "Pick a category" option
shift @{$c->stash->{category_options}} if @{$c->stash->{category_options}};
- return $c->stash->{category_options};
+ $c->stash->{categories} = $c->stash->{category_options};
+ $c->stash->{categories_hash} = { map { $_ => 1 } @{$c->stash->{category_options}} };
}
sub templates : Path('templates') : Args(0) {
@@ -978,6 +978,7 @@ sub template_edit : Path('templates') : Args(2) {
} else {
$template->title( $c->get_param('title') );
$template->text( $c->get_param('text') );
+ $template->state( $c->get_param('state') );
$template->auto_response( $c->get_param('auto_response') ? 1 : 0 );
$template->update_or_insert;
@@ -1005,10 +1006,9 @@ sub load_template_body : Private {
my ($self, $c, $body_id) = @_;
my $zurich_user = $c->user->from_body && $c->cobrand->moniker eq 'zurich';
- my $has_permission = $c->user->has_body_permission_to('template_edit') &&
- $c->user->from_body->id eq $body_id;
+ my $has_permission = $c->user->has_body_permission_to('template_edit', $body_id);
- unless ( $c->user->is_superuser || $zurich_user || $has_permission ) {
+ unless ( $zurich_user || $has_permission ) {
$c->detach( '/page_error_404_not_found', [] );
}
@@ -1117,8 +1117,9 @@ sub update_edit : Path('update_edit') : Args(1) {
# $update->name can be null which makes ne unhappy
my $name = $update->name || '';
+ my $email = lc $c->get_param('email');
if ( $c->get_param('name') ne $name
- || $c->get_param('email') ne $update->user->email
+ || $email ne $update->user->email
|| $c->get_param('anonymous') ne $update->anonymous
|| $c->get_param('text') ne $update->text ) {
$edited = 1;
@@ -1138,11 +1139,8 @@ sub update_edit : Path('update_edit') : Args(1) {
$update->anonymous( $c->get_param('anonymous') );
$update->state( $new_state );
- if ( $c->get_param('email') ne $update->user->email ) {
- my $user =
- $c->model('DB::User')
- ->find_or_create( { email => $c->get_param('email') } );
-
+ if ( $email ne $update->user->email ) {
+ my $user = $c->model('DB::User')->find_or_create({ email => $email });
$user->insert unless $user->in_storage;
$update->user($user);
}
@@ -1207,7 +1205,7 @@ sub user_add : Path('user_edit') : Args(0) {
my $user = $c->model('DB::User')->find_or_create( {
name => $c->get_param('name'),
- email => $c->get_param('email'),
+ email => lc $c->get_param('email'),
phone => $c->get_param('phone') || undef,
from_body => $c->get_param('body') || undef,
flagged => $c->get_param('flagged') || 0,
@@ -1217,13 +1215,13 @@ sub user_add : Path('user_edit') : Args(0) {
key => 'users_email_key'
} );
$c->stash->{user} = $user;
+ $c->forward('user_cobrand_extra_fields');
+ $user->update;
$c->forward( 'log_edit', [ $user->id, 'user', 'edit' ] );
- $c->stash->{status_message} =
- '<p><em>' . _('Updated!') . '</em></p>';
-
- return 1;
+ $c->flash->{status_message} = _("Updated!");
+ $c->res->redirect( $c->uri_for( 'user_edit', $user->id ) );
}
sub user_edit : Path('user_edit') : Args(1) {
@@ -1234,7 +1232,7 @@ sub user_edit : Path('user_edit') : Args(1) {
my $user = $c->cobrand->users->find( { id => $id } );
$c->detach( '/page_error_404_not_found', [] ) unless $user;
- unless ( $c->user->is_superuser || $c->user->has_body_permission_to('user_edit') || $c->cobrand->moniker eq 'zurich' ) {
+ unless ( $c->user->has_body_permission_to('user_edit') || $c->cobrand->moniker eq 'zurich' ) {
$c->detach('/page_error_403_access_denied', []);
}
@@ -1247,12 +1245,18 @@ sub user_edit : Path('user_edit') : Args(1) {
$c->forward('fetch_all_bodies');
$c->forward('fetch_body_areas', [ $user->from_body ]) if $user->from_body;
+ if ( defined $c->flash->{status_message} ) {
+ $c->stash->{status_message} =
+ '<p><em>' . $c->flash->{status_message} . '</em></p>';
+ }
+
if ( $c->get_param('submit') ) {
$c->forward('/auth/check_csrf_token');
my $edited = 0;
- if ( $user->email ne $c->get_param('email') ||
+ my $email = lc $c->get_param('email');
+ if ( $user->email ne $email ||
$user->name ne $c->get_param('name') ||
($user->phone || "") ne $c->get_param('phone') ||
($user->from_body && $c->get_param('body') && $user->from_body->id ne $c->get_param('body')) ||
@@ -1262,7 +1266,8 @@ sub user_edit : Path('user_edit') : Args(1) {
}
$user->name( $c->get_param('name') );
- $user->email( $c->get_param('email') );
+ my $original_email = $user->email;
+ $user->email( $email );
$user->phone( $c->get_param('phone') ) if $c->get_param('phone');
$user->flagged( $c->get_param('flagged') || 0 );
# Only superusers can grant superuser status
@@ -1278,6 +1283,8 @@ sub user_edit : Path('user_edit') : Args(1) {
$user->from_body( undef );
}
+ $c->forward('user_cobrand_extra_fields');
+
# Has the user's from_body changed since we fetched areas (if we ever did)?
# If so, we need to re-fetch areas so the UI is up to date.
if ( $user->from_body && $user->from_body->id ne $c->stash->{fetched_areas_body_id} ) {
@@ -1361,19 +1368,24 @@ sub user_edit : Path('user_edit') : Args(1) {
return if %{$c->stash->{field_errors}};
my $existing_user = $c->model('DB::User')->search({ email => $user->email, id => { '!=', $user->id } })->first;
- if ($existing_user) {
+ my $existing_user_cobrand = $c->cobrand->users->search({ email => $user->email, id => { '!=', $user->id } })->first;
+ if ($existing_user_cobrand) {
$existing_user->adopt($user);
$c->forward( 'log_edit', [ $id, 'user', 'merge' ] );
$c->res->redirect( $c->uri_for( 'user_edit', $existing_user->id ) );
} else {
+ if ($existing_user) {
+ # Tried to change email to an existing one lacking permission
+ # so make sure it's switched back
+ $user->email($original_email);
+ }
$user->update;
if ($edited) {
$c->forward( 'log_edit', [ $id, 'user', 'edit' ] );
}
+ $c->flash->{status_message} = _("Updated!");
+ $c->res->redirect( $c->uri_for( 'user_edit', $user->id ) );
}
-
- $c->stash->{status_message} =
- '<p><em>' . _('Updated!') . '</em></p>';
}
if ( $user->from_body ) {
@@ -1395,6 +1407,15 @@ sub user_edit : Path('user_edit') : Args(1) {
return 1;
}
+sub user_cobrand_extra_fields : Private {
+ my ( $self, $c ) = @_;
+
+ my @extra_fields = @{ $c->cobrand->call_hook('user_extra_fields') || [] };
+ foreach ( @extra_fields ) {
+ $c->stash->{user}->set_extra_metadata( $_ => $c->get_param("extra[$_]") );
+ }
+}
+
sub flagged : Path('flagged') : Args(0) {
my ( $self, $c ) = @_;
@@ -1465,7 +1486,7 @@ sub stats : Path('stats') : Args(0) {
$selected_body = $c->user->from_body->id;
}
- if ( $c->cobrand->moniker eq 'seesomething' || $c->cobrand->moniker eq 'zurich' ) {
+ if ( $c->cobrand->moniker eq 'zurich' ) {
return $c->cobrand->admin_stats();
}
@@ -1612,7 +1633,7 @@ accordingly
sub ban_user : Private {
my ( $self, $c ) = @_;
- my $email = $c->get_param('email');
+ my $email = lc $c->get_param('email');
return unless $email;
@@ -1639,7 +1660,7 @@ Sets the flag on a user with the given email
sub flag_user : Private {
my ( $self, $c ) = @_;
- my $email = $c->get_param('email');
+ my $email = lc $c->get_param('email');
return unless $email;
@@ -1667,7 +1688,7 @@ Remove the flag on a user with the given email
sub remove_user_flag : Private {
my ( $self, $c ) = @_;
- my $email = $c->get_param('email');
+ my $email = lc $c->get_param('email');
return unless $email;
diff --git a/perllib/FixMyStreet/App/Controller/Admin/DefectTypes.pm b/perllib/FixMyStreet/App/Controller/Admin/DefectTypes.pm
new file mode 100644
index 000000000..bcfeb3dd8
--- /dev/null
+++ b/perllib/FixMyStreet/App/Controller/Admin/DefectTypes.pm
@@ -0,0 +1,113 @@
+package FixMyStreet::App::Controller::Admin::DefectTypes;
+use Moose;
+use namespace::autoclean;
+use mySociety::ArrayUtils;
+
+BEGIN { extends 'Catalyst::Controller'; }
+
+
+sub begin : Private {
+ my ( $self, $c ) = @_;
+
+ $c->forward('/admin/begin');
+}
+
+sub index : Path : Args(0) {
+ my ( $self, $c ) = @_;
+
+ my $user = $c->user;
+
+ if ($user->is_superuser) {
+ $c->forward('/admin/fetch_all_bodies');
+ } elsif ( $user->from_body ) {
+ $c->forward('load_user_body', [ $user->from_body->id ]);
+ $c->res->redirect( $c->uri_for( '', $c->stash->{body}->id ) );
+ } else {
+ $c->detach( '/page_error_404_not_found' );
+ }
+}
+
+sub list : Path : Args(1) {
+ my ($self, $c, $body_id) = @_;
+
+ $c->forward('load_user_body', [ $body_id ]);
+
+ my @defect_types = $c->stash->{body}->defect_types->search(
+ undef,
+ {
+ order_by => 'name'
+ }
+ );
+
+ $c->stash->{defect_types} = \@defect_types;
+}
+
+sub edit : Path : Args(2) {
+ my ( $self, $c, $body_id, $defect_type_id ) = @_;
+
+ $c->forward('load_user_body', [ $body_id ]);
+
+ my $defect_type;
+ if ($defect_type_id eq 'new') {
+ $defect_type = $c->stash->{body}->defect_types->new({});
+ }
+ else {
+ $defect_type = $c->stash->{body}->defect_types->find( $defect_type_id )
+ or $c->detach( '/page_error_404_not_found' );
+ }
+
+ $c->forward('/admin/fetch_contacts');
+ my @contacts = $defect_type->contacts->all;
+ my @live_contacts = $c->stash->{live_contacts}->all;
+ my %active_contacts = map { $_->id => 1 } @contacts;
+ my @all_contacts = map { {
+ id => $_->id,
+ category => $_->category,
+ active => $active_contacts{$_->id},
+ } } @live_contacts;
+ $c->stash->{contacts} = \@all_contacts;
+
+ if ($c->req->method eq 'POST') {
+ $defect_type->name( $c->get_param('name') );
+ $defect_type->description( $c->get_param('description') );
+
+ my @extra_fields = @{ $c->cobrand->call_hook('defect_type_extra_fields') || [] };
+ foreach ( @extra_fields ) {
+ $defect_type->set_extra_metadata( $_ => $c->get_param("extra[$_]") );
+ }
+
+ $defect_type->update_or_insert;
+ my @live_contact_ids = map { $_->id } @live_contacts;
+ my @new_contact_ids = $c->get_param_list('categories');
+ @new_contact_ids = @{ mySociety::ArrayUtils::intersection(\@live_contact_ids, \@new_contact_ids) };
+ $defect_type->contact_defect_types->search({
+ contact_id => { '!=' => \@new_contact_ids },
+ })->delete;
+ foreach my $contact_id (@new_contact_ids) {
+ $defect_type->contact_defect_types->find_or_create({
+ contact_id => $contact_id,
+ });
+ }
+
+ $c->res->redirect( $c->uri_for( '', $c->stash->{body}->id ) );
+ }
+
+ $c->stash->{defect_type} = $defect_type;
+}
+
+sub load_user_body : Private {
+ my ($self, $c, $body_id) = @_;
+
+ my $has_permission = $c->user->has_body_permission_to('defect_type_edit', $body_id);
+
+ unless ( $has_permission ) {
+ $c->detach( '/page_error_404_not_found' );
+ }
+
+ $c->stash->{body} = $c->model('DB::Body')->find($body_id)
+ or $c->detach( '/page_error_404_not_found' );
+}
+
+__PACKAGE__->meta->make_immutable;
+
+1;
diff --git a/perllib/FixMyStreet/App/Controller/Admin/ExorDefects.pm b/perllib/FixMyStreet/App/Controller/Admin/ExorDefects.pm
new file mode 100644
index 000000000..201742c81
--- /dev/null
+++ b/perllib/FixMyStreet/App/Controller/Admin/ExorDefects.pm
@@ -0,0 +1,230 @@
+package FixMyStreet::App::Controller::Admin::ExorDefects;
+use Moose;
+use namespace::autoclean;
+
+use Text::CSV;
+use DateTime;
+use mySociety::Random qw(random_bytes);
+
+BEGIN { extends 'Catalyst::Controller'; }
+
+
+sub begin : Private {
+ my ( $self, $c ) = @_;
+
+ $c->forward('/admin/begin');
+}
+
+sub index : Path : Args(0) {
+ my ( $self, $c ) = @_;
+
+ foreach (qw(error_message start_date end_date user_id)) {
+ if ( defined $c->flash->{$_} ) {
+ $c->stash->{$_} = $c->flash->{$_};
+ }
+ }
+
+ my @inspectors = $c->cobrand->users->search({
+ 'user_body_permissions.permission_type' => 'report_inspect'
+ }, {
+ join => 'user_body_permissions',
+ distinct => 1,
+ }
+ )->all;
+ $c->stash->{inspectors} = \@inspectors;
+
+ # Default start/end date is today
+ my $now = DateTime->now( time_zone =>
+ FixMyStreet->time_zone || FixMyStreet->local_time_zone );
+ $c->stash->{start_date} ||= $now;
+ $c->stash->{end_date} ||= $now;
+
+}
+
+sub download : Path('download') : Args(0) {
+ my ( $self, $c ) = @_;
+
+ if ( !$c->cobrand->can('exor_rdi_link_id') ) {
+ # This only works on the Oxfordshire cobrand currently.
+ $c->detach( '/page_error_404_not_found', [] );
+ }
+
+ my $parser = DateTime::Format::Strptime->new( pattern => '%d/%m/%Y' );
+ my $start_date = $parser-> parse_datetime ( $c->get_param('start_date') );
+ my $end_date = $parser-> parse_datetime ( $c->get_param('end_date') ) ;
+ my $one_day = DateTime::Duration->new( days => 1 );
+
+ my %params = (
+ -and => [
+ state => [ 'action scheduled' ],
+ external_id => { '!=' => undef },
+ 'admin_log_entries.action' => 'inspected',
+ 'admin_log_entries.whenedited' => { '>=', $start_date },
+ 'admin_log_entries.whenedited' => { '<=', $end_date + $one_day },
+ ]
+ );
+
+ my $user;
+ if ( $c->get_param('user_id') ) {
+ my $uid = $c->get_param('user_id');
+ $params{'admin_log_entries.user_id'} = $uid;
+ $user = $c->model('DB::User')->find( { id => $uid } );
+ }
+
+ my $problems = $c->cobrand->problems->search(
+ \%params,
+ {
+ join => 'admin_log_entries',
+ distinct => 1,
+ }
+ );
+
+ if ( !$problems->count ) {
+ if ( defined $user ) {
+ $c->flash->{error_message} = _("No inspections by that inspector in the selected date range.");
+ } else {
+ $c->flash->{error_message} = _("No inspections in the selected date range.");
+ }
+ $c->flash->{start_date} = $start_date;
+ $c->flash->{end_date} = $end_date;
+ $c->flash->{user_id} = $user->id if $user;
+ $c->res->redirect( $c->uri_for( '' ) );
+ }
+
+ # A single RDI file might contain inspections from multiple inspectors, so
+ # we need to group inspections by inspector within G records.
+ my $inspectors = {};
+ my $inspector_initials = {};
+ while ( my $report = $problems->next ) {
+ my $user = $report->inspection_log_entry->user;
+ $inspectors->{$user->id} ||= [];
+ push @{ $inspectors->{$user->id} }, $report;
+ unless ( $inspector_initials->{$user->id} ) {
+ $inspector_initials->{$user->id} = $user->get_extra_metadata('initials');
+ }
+ }
+
+ my $csv = Text::CSV->new({ binary => 1, eol => "" });
+
+ my $p_count = 0;
+ my $link_id = $c->cobrand->exor_rdi_link_id;
+
+ # RDI first line is always the same
+ $csv->combine("1", "1.8", "1.0.0.0", "ENHN", "");
+ my @body = ($csv->string);
+
+ my $i = 0;
+ foreach my $inspector_id (keys %$inspectors) {
+ my $inspections = $inspectors->{$inspector_id};
+ my $initials = $inspector_initials->{$inspector_id};
+
+ $csv->combine(
+ "G", # start of an area/sequence
+ $link_id, # area/link id, fixed value for our purposes
+ "","", # must be empty
+ $initials || "XX", # inspector initials
+ $start_date->strftime("%y%m%d"), # date of inspection yymmdd
+ "0700", # time of inspection hhmm, set to static value for now
+ "D", # inspection variant, should always be D
+ "INS", # inspection type, always INS
+ "N", # Area of the county - north (N) or south (S)
+ "", "", "", "" # empty fields
+ );
+ push @body, $csv->string;
+
+ $csv->combine(
+ "H", # initial inspection type
+ "MC" # minor carriageway (changes depending on activity code)
+ );
+ push @body, $csv->string;
+
+ foreach my $report (@$inspections) {
+ my ($eastings, $northings) = $report->local_coords;
+ my $description = sprintf("%s %s", $report->external_id || "", $report->get_extra_metadata('detailed_information') || "");
+ my $activity_code = $report->defect_type ?
+ $report->defect_type->get_extra_metadata('activity_code')
+ : 'MC';
+ my $traffic_information = $report->get_extra_metadata('traffic_information') ?
+ 'TM ' . $report->get_extra_metadata('traffic_information')
+ : 'TM none';
+
+ $csv->combine(
+ "I", # beginning of defect record
+ $activity_code, # activity code - minor carriageway, also FC (footway)
+ "", # empty field, can also be A (seen on MC) or B (seen on FC)
+ sprintf("%03d", ++$i), # randomised sequence number
+ "${eastings}E ${northings}N", # defect location field, which we don't capture from inspectors
+ $report->inspection_log_entry->whenedited->strftime("%H%M"), # defect time raised
+ "","","","","","","", # empty fields
+ $traffic_information,
+ $description, # defect description
+ );
+ push @body, $csv->string;
+
+ my $defect_type = $report->defect_type ?
+ $report->defect_type->get_extra_metadata('defect_code')
+ : 'SFP2';
+ $csv->combine(
+ "J", # georeferencing record
+ $defect_type, # defect type - SFP2: sweep and fill <1m2, POT2 also seen
+ $report->response_priority ?
+ $report->response_priority->external_id :
+ "2", # priority of defect
+ "","", # empty fields
+ $eastings, # eastings
+ $northings, # northings
+ "","","","","" # empty fields
+ );
+ push @body, $csv->string;
+
+ $csv->combine(
+ "M", # bill of quantities record
+ "resolve", # permanent repair
+ "","", # empty fields
+ "/CMC", # /C + activity code
+ "", "" # empty fields
+ );
+ push @body, $csv->string;
+ }
+
+ # end this group of defects with a P record
+ $csv->combine(
+ "P", # end of area/sequence
+ 0, # always 0
+ 999999, # charging code, always 999999 in OCC
+ );
+ push @body, $csv->string;
+ $p_count++;
+ }
+
+ # end the RDI file with an X record
+ my $record_count = $i;
+ $csv->combine(
+ "X", # end of inspection record
+ $p_count,
+ $p_count,
+ $record_count, # number of I records
+ $record_count, # number of J records
+ 0, 0, 0, # always zero
+ $record_count, # number of M records
+ 0, # always zero
+ $p_count,
+ 0, 0, 0 # error counts, always zero
+ );
+ push @body, $csv->string;
+
+ my $start = $start_date->strftime("%Y%m%d");
+ my $end = $end_date->strftime("%Y%m%d");
+ my $filename = sprintf("exor_defects-%s-%s.rdi", $start, $end);
+ if ( $user ) {
+ my $initials = $user->get_extra_metadata("initials") || "";
+ $filename = sprintf("exor_defects-%s-%s-%s.rdi", $start, $end, $initials);
+ }
+ $c->res->content_type('text/csv; charset=utf-8');
+ $c->res->header('content-disposition' => "attachment; filename=$filename");
+ # The RDI format is very weird CSV - each line must be wrapped in
+ # double quotes.
+ $c->res->body( join "", map { "\"$_\"\r\n" } @body );
+}
+
+1; \ No newline at end of file
diff --git a/perllib/FixMyStreet/App/Controller/Admin/ResponsePriorities.pm b/perllib/FixMyStreet/App/Controller/Admin/ResponsePriorities.pm
index 032e593c6..bae0f71a7 100644
--- a/perllib/FixMyStreet/App/Controller/Admin/ResponsePriorities.pm
+++ b/perllib/FixMyStreet/App/Controller/Admin/ResponsePriorities.pm
@@ -70,6 +70,7 @@ sub edit : Path : Args(2) {
$priority->deleted( $c->get_param('deleted') ? 1 : 0 );
$priority->name( $c->get_param('name') );
$priority->description( $c->get_param('description') );
+ $priority->external_id( $c->get_param('external_id') );
$priority->update_or_insert;
my @live_contact_ids = map { $_->id } @live_contacts;
@@ -92,10 +93,9 @@ sub edit : Path : Args(2) {
sub load_user_body : Private {
my ($self, $c, $body_id) = @_;
- my $has_permission = $c->user->has_body_permission_to('responsepriority_edit') &&
- $c->user->from_body->id eq $body_id;
+ my $has_permission = $c->user->has_body_permission_to('responsepriority_edit', $body_id);
- unless ( $c->user->is_superuser || $has_permission ) {
+ unless ( $has_permission ) {
$c->detach( '/page_error_404_not_found' );
}
diff --git a/perllib/FixMyStreet/App/Controller/Around.pm b/perllib/FixMyStreet/App/Controller/Around.pm
index b4f94bb35..1fe35d0a3 100644
--- a/perllib/FixMyStreet/App/Controller/Around.pm
+++ b/perllib/FixMyStreet/App/Controller/Around.pm
@@ -182,7 +182,7 @@ sub display_location : Private {
my ( $on_map_all, $on_map, $nearby, $distance ) =
FixMyStreet::Map::map_features( $c,
latitude => $latitude, longitude => $longitude,
- interval => $interval, categories => $c->stash->{filter_category},
+ interval => $interval, categories => [ keys %{$c->stash->{filter_category}} ],
states => $c->stash->{filter_problem_states},
order => $c->stash->{sort_order},
);
@@ -264,8 +264,8 @@ sub check_and_stash_category : Private {
my %categories_mapped = map { $_ => 1 } @categories;
my $categories = [ $c->get_param_list('filter_category', 1) ];
- my @valid_categories = grep { $_ && $categories_mapped{$_} } @$categories;
- $c->stash->{filter_category} = \@valid_categories;
+ my %valid_categories = map { $_ => 1 } grep { $_ && $categories_mapped{$_} } @$categories;
+ $c->stash->{filter_category} = \%valid_categories;
}
=head2 /ajax
@@ -291,6 +291,8 @@ sub ajax : Path('/ajax') {
# assume this is not cacheable - may need to be more fine-grained later
$c->res->header( 'Cache_Control' => 'max-age=0' );
+ $c->stash->{page} = 'around'; # Needed by _item.html
+
# how far back should we go?
my $all_pins = $c->get_param('all_pins') ? 1 : undef;
my $interval = $all_pins ? undef : $c->cobrand->on_map_default_max_pin_age;
diff --git a/perllib/FixMyStreet/App/Controller/Auth.pm b/perllib/FixMyStreet/App/Controller/Auth.pm
index 40cd163cf..4efa7abb8 100644
--- a/perllib/FixMyStreet/App/Controller/Auth.pm
+++ b/perllib/FixMyStreet/App/Controller/Auth.pm
@@ -70,6 +70,7 @@ sub sign_in : Private {
my ( $self, $c, $email ) = @_;
$email ||= $c->get_param('email') || '';
+ $email = lc $email;
my $password = $c->get_param('password_sign_in') || '';
my $remember_me = $c->get_param('remember_me') || 0;
@@ -103,7 +104,7 @@ sub sign_in : Private {
Email the user the details they need to sign in. Don't check for an account - if
there isn't one we can create it when they come back with a token (which
-contains the email addresss).
+contains the email address).
=cut
@@ -222,7 +223,7 @@ sub token : Path('/M') : Args(1) {
$c->authenticate( { email => $user->email }, 'no_password' );
# send the user to their page
- $c->detach( 'redirect_on_signin', [ $data->{r} ] );
+ $c->detach( 'redirect_on_signin', [ $data->{r}, $data->{p} ] );
}
=head2 facebook_sign_in
@@ -271,9 +272,8 @@ sub facebook_callback: Path('/auth/Facebook') : Args(0) {
$access_token = $fb->get_access_token(code => $c->get_param('code'));
};
if ($@) {
- ($c->stash->{message} = $@) =~ s/at [^ ]*Auth.pm.*//;
- $c->stash->{template} = 'errors/generic.html';
- $c->detach;
+ (my $message = $@) =~ s/at [^ ]*Auth.pm.*//;
+ $c->detach('/page_error_500_internal_error', [ $message ]);
}
# save this token in session
@@ -339,9 +339,8 @@ sub twitter_callback: Path('/auth/Twitter') : Args(0) {
$twitter->request_access_token(verifier => $verifier);
};
if ($@) {
- ($c->stash->{message} = $@) =~ s/at [^ ]*Auth.pm.*//;
- $c->stash->{template} = 'errors/generic.html';
- $c->detach;
+ (my $message = $@) =~ s/at [^ ]*Auth.pm.*//;
+ $c->detach('/page_error_500_internal_error', [ $message ]);
}
my $info = $twitter->verify_credentials();
@@ -412,13 +411,36 @@ Used after signing in to take the person back to where they were.
sub redirect_on_signin : Private {
- my ( $self, $c, $redirect ) = @_;
- $redirect = 'my' unless $redirect;
- $redirect = 'my' if $redirect =~ /^admin/ && !$c->user->is_superuser;
+ my ( $self, $c, $redirect, $params ) = @_;
+ unless ( $redirect ) {
+ $c->detach('redirect_to_categories') if $c->user->from_body && scalar @{ $c->user->categories };
+ $redirect = 'my';
+ }
+ $redirect = 'my' if $redirect =~ /^admin/ && !$c->cobrand->admin_allow_user($c->user);
if ( $c->cobrand->moniker eq 'zurich' ) {
$redirect = 'admin' if $c->user->from_body;
}
- $c->res->redirect( $c->uri_for( "/$redirect" ) );
+ if (defined $params) {
+ $c->res->redirect( $c->uri_for( "/$redirect", $params ) );
+ } else {
+ $c->res->redirect( $c->uri_for( "/$redirect" ) );
+ }
+}
+
+=head2 redirect_to_categories
+
+Redirects the user to their body's reports page, prefiltered to whatever
+categories this user has been assigned to.
+
+=cut
+
+sub redirect_to_categories : Private {
+ my ( $self, $c ) = @_;
+
+ my $categories = join(',', @{ $c->user->categories });
+ my $body_short = $c->cobrand->short_name( $c->user->from_body );
+
+ $c->res->redirect( $c->uri_for( "/reports/" . $body_short, { filter_category => $categories } ) );
}
=head2 redirect
@@ -518,17 +540,17 @@ sub check_csrf_token : Private {
$token =~ s/ /+/g;
my ($time) = $token =~ /^(\d+)-[0-9a-zA-Z+\/]+$/;
$c->stash->{csrf_time} = $time;
+ my $gen_token = $c->forward('get_csrf_token');
+ delete $c->stash->{csrf_time};
$c->detach('no_csrf_token')
unless $time
&& $time > time() - 3600
- && $token eq $c->forward('get_csrf_token');
- delete $c->stash->{csrf_time};
+ && $token eq $gen_token;
}
sub no_csrf_token : Private {
my ($self, $c) = @_;
- $c->stash->{message} = _('Unknown error');
- $c->stash->{template} = 'errors/generic.html';
+ $c->detach('/page_error_400_bad_request', []);
}
=head2 sign_out
diff --git a/perllib/FixMyStreet/App/Controller/Dashboard.pm b/perllib/FixMyStreet/App/Controller/Dashboard.pm
index 9189b28e5..fbe5a2dc9 100644
--- a/perllib/FixMyStreet/App/Controller/Dashboard.pm
+++ b/perllib/FixMyStreet/App/Controller/Dashboard.pm
@@ -57,9 +57,9 @@ sub example : Local : Args(0) {
}
};
if ($@) {
- $c->stash->{message} = _("There was a problem showing this page. Please try again later.") . ' ' .
+ my $message = _("There was a problem showing this page. Please try again later.") . ' ' .
sprintf(_('The error was: %s'), $@);
- $c->stash->{template} = 'errors/generic.html';
+ $c->detach('/page_error_500_internal_error', [ $message ]);
}
}
diff --git a/perllib/FixMyStreet/App/Controller/Moderate.pm b/perllib/FixMyStreet/App/Controller/Moderate.pm
index 94e6cd62a..74f2e6b31 100644
--- a/perllib/FixMyStreet/App/Controller/Moderate.pm
+++ b/perllib/FixMyStreet/App/Controller/Moderate.pm
@@ -83,6 +83,11 @@ sub moderate_report : Chained('report') : PathPart('') : Args(0) {
$c->detach( 'report_moderate_audit', \@types )
}
+sub moderating_user_name {
+ my $user = shift;
+ return $user->from_body ? $user->from_body->name : 'a FixMyStreet administrator';
+}
+
sub report_moderate_audit : Private {
my ($self, $c, @types) = @_;
@@ -95,7 +100,7 @@ sub report_moderate_audit : Private {
$c->model('DB::AdminLog')->create({
action => 'moderation',
user => $user,
- admin_user => $user->name,
+ admin_user => moderating_user_name($user),
object_id => $problem->id,
object_type => 'problem',
reason => (sprintf '%s (%s)', $reason, $types_csv),
@@ -249,7 +254,7 @@ sub update_moderate_audit : Private {
$c->model('DB::AdminLog')->create({
action => 'moderation',
user => $user,
- admin_user => $user->name,
+ admin_user => moderating_user_name($user),
object_id => $comment->id,
object_type => 'update',
reason => (sprintf '%s (%s)', $reason, $types_csv),
diff --git a/perllib/FixMyStreet/App/Controller/My.pm b/perllib/FixMyStreet/App/Controller/My.pm
index 51f1687ee..77711f807 100644
--- a/perllib/FixMyStreet/App/Controller/My.pm
+++ b/perllib/FixMyStreet/App/Controller/My.pm
@@ -3,6 +3,7 @@ use Moose;
use namespace::autoclean;
use JSON::MaybeXS;
+use List::MoreUtils qw(first_index);
BEGIN { extends 'Catalyst::Controller'; }
@@ -30,8 +31,11 @@ sub begin : Private {
sub my : Path : Args(0) {
my ( $self, $c ) = @_;
+ $c->forward('/auth/get_csrf_token');
+
$c->stash->{problems_rs} = $c->cobrand->problems->search(
{ user_id => $c->user->id });
+ $c->forward('/reports/stash_report_sort', [ 'created-desc' ]);
$c->forward('get_problems');
if ($c->get_param('ajax')) {
$c->detach('/reports/ajax', [ 'my/_problem-list.html' ]);
@@ -43,21 +47,58 @@ sub my : Path : Args(0) {
sub planned : Local : Args(0) {
my ( $self, $c ) = @_;
+ $c->forward('/auth/get_csrf_token');
+
$c->detach('/page_error_403_access_denied', [])
unless $c->user->has_body_permission_to('planned_reports');
$c->stash->{problems_rs} = $c->user->active_planned_reports;
+ $c->forward('planned_reorder');
+ $c->forward('/reports/stash_report_sort', [ 'shortlist' ]);
$c->forward('get_problems');
+ if ($c->get_param('ajax')) {
+ $c->stash->{shortlist} = $c->stash->{sort_key} eq 'shortlist';
+ $c->detach('/reports/ajax', [ 'my/_problem-list.html' ]);
+ }
$c->forward('setup_page_data');
}
+sub planned_reorder : Private {
+ my ($self, $c) = @_;
+
+ my @extra = grep { /^shortlist-(up|down|\d+)$/ } keys %{$c->req->params};
+ return unless @extra;
+ my ($reorder) = $extra[0] =~ /^shortlist-(up|down|\d+)$/;
+
+ my @shortlist = sort by_shortlisted $c->stash->{problems_rs}->all;
+
+ # Find where moving problem ID is
+ my $id = $c->get_param('id') || return;
+ my $curr_index = first_index { $_->id == $id } @shortlist;
+ return unless $curr_index > -1;
+
+ if ($reorder eq 'up' && $curr_index > 0) {
+ @shortlist[$curr_index-1,$curr_index] = @shortlist[$curr_index,$curr_index-1];
+ } elsif ($reorder eq 'down' && $curr_index < @shortlist-1) {
+ @shortlist[$curr_index,$curr_index+1] = @shortlist[$curr_index+1,$curr_index];
+ } elsif ($reorder >= 0 && $reorder <= @shortlist-1) { # Must be an index to move it
+ @shortlist[$curr_index,$reorder] = @shortlist[$reorder,$curr_index];
+ }
+
+ # Store new ordering
+ my $i = 1;
+ foreach (@shortlist) {
+ $_->set_extra_metadata('order', $i++);
+ $_->update;
+ }
+}
+
sub get_problems : Private {
my ($self, $c) = @_;
my $p_page = $c->get_param('p') || 1;
$c->forward( '/reports/stash_report_filter_status' );
- $c->forward('/reports/stash_report_sort', [ 'created-desc' ]);
my $pins = [];
my $problems = [];
@@ -70,12 +111,15 @@ sub get_problems : Private {
my $categories = [ $c->get_param_list('filter_category', 1) ];
if ( @$categories ) {
$params->{category} = $categories;
- $c->stash->{filter_category} = $categories;
+ $c->stash->{filter_category} = { map { $_ => 1 } @$categories };
}
+ my $rows = 50;
+ $rows = 5000 if $c->stash->{sort_key} eq 'shortlist'; # Want all reports
+
my $rs = $c->stash->{problems_rs}->search( $params, {
order_by => $c->stash->{sort_order},
- rows => 50
+ rows => $rows,
} )->include_comment_counts->page( $p_page );
while ( my $problem = $rs->next ) {
@@ -83,6 +127,9 @@ sub get_problems : Private {
push @$pins, $problem->pin_data($c, 'my', private => 1);
push @$problems, $problem;
}
+
+ @$problems = sort by_shortlisted @$problems if $c->stash->{sort_key} eq 'shortlist';
+
$c->stash->{problems_pager} = $rs->pager;
$c->stash->{problems} = $problems;
$c->stash->{pins} = $pins;
@@ -134,27 +181,45 @@ sub planned_change : Path('planned/change') {
my ($self, $c) = @_;
$c->forward('/auth/check_csrf_token');
+ $c->go('planned') if grep { /^shortlist-(up|down|\d+)$/ } keys %{$c->req->params};
+
my $id = $c->get_param('id');
$c->forward( '/report/load_problem_or_display_error', [ $id ] );
- my $change = $c->get_param('change');
+ my $add = $c->get_param('shortlist-add');
+ my $remove = $c->get_param('shortlist-remove');
$c->detach('/page_error_403_access_denied', [])
- unless $change && $change =~ /add|remove/;
+ unless $add || $remove;
- if ($change eq 'add') {
+ if ($add) {
$c->user->add_to_planned_reports($c->stash->{problem});
- } elsif ($change eq 'remove') {
+ } elsif ($remove) {
$c->user->remove_from_planned_reports($c->stash->{problem});
}
if ($c->get_param('ajax')) {
$c->res->content_type('application/json; charset=utf-8');
- $c->res->body(encode_json({ outcome => $change }));
+ $c->res->body(encode_json({ outcome => $add ? 'add' : 'remove' }));
} else {
$c->res->redirect( $c->uri_for_action('report/display', $id) );
}
}
+sub by_shortlisted {
+ my $a_order = $a->get_extra_metadata('order') || 0;
+ my $b_order = $b->get_extra_metadata('order') || 0;
+ if ($a_order && $b_order) {
+ $a_order <=> $b_order;
+ } elsif ($a_order) {
+ -1; # Want non-ordered to come last
+ } elsif ($b_order) {
+ 1; # Want non-ordered to come last
+ } else {
+ # Default to order added to planned reports
+ $a->user_planned_reports->first->id <=> $b->user_planned_reports->first->id;
+ }
+}
+
__PACKAGE__->meta->make_immutable;
1;
diff --git a/perllib/FixMyStreet/App/Controller/Offline.pm b/perllib/FixMyStreet/App/Controller/Offline.pm
new file mode 100644
index 000000000..dceccc81f
--- /dev/null
+++ b/perllib/FixMyStreet/App/Controller/Offline.pm
@@ -0,0 +1,47 @@
+package FixMyStreet::App::Controller::Offline;
+use Moose;
+use namespace::autoclean;
+
+BEGIN { extends 'Catalyst::Controller'; }
+
+=head1 NAME
+
+FixMyStreet::App::Controller::Offline - Catalyst Controller
+
+=head1 DESCRIPTION
+
+Offline pages Catalyst Controller.
+On staging site, appcache only for people who want it.
+
+=head1 METHODS
+
+=cut
+
+sub have_appcache : Private {
+ my ($self, $c) = @_;
+ return $c->user_exists && $c->user->has_body_permission_to('planned_reports')
+ && !FixMyStreet->staging_flag('enable_appcache', 0);
+}
+
+sub manifest : Path("/offline/appcache.manifest") {
+ my ($self, $c) = @_;
+ unless ($c->forward('have_appcache')) {
+ $c->response->status(404);
+ $c->response->body('NOT FOUND');
+ }
+ $c->res->content_type('text/cache-manifest; charset=utf-8');
+ $c->res->header(Cache_Control => 'no-cache, no-store');
+}
+
+sub appcache : Path("/offline/appcache") {
+ my ($self, $c) = @_;
+ $c->detach('/page_error_404_not_found', []) if keys %{$c->req->params};
+ unless ($c->forward('have_appcache')) {
+ $c->response->status(404);
+ $c->response->body('NOT FOUND');
+ }
+}
+
+__PACKAGE__->meta->make_immutable;
+
+1;
diff --git a/perllib/FixMyStreet/App/Controller/Open311.pm b/perllib/FixMyStreet/App/Controller/Open311.pm
index 98e5f42b2..bc08593de 100644
--- a/perllib/FixMyStreet/App/Controller/Open311.pm
+++ b/perllib/FixMyStreet/App/Controller/Open311.pm
@@ -233,44 +233,42 @@ sub output_requests : Private {
my $request =
{
- 'service_request_id' => [ $id ],
- 'title' => [ $problem->title ], # Not in Open311 v2
- 'detail' => [ $problem->detail ], # Not in Open311 v2
- 'description' => [ $problem->title .': ' . $problem->detail ],
- 'lat' => [ $problem->latitude ],
- 'long' => [ $problem->longitude ],
- 'status' => [ $problem->state ],
-# 'status_notes' => [ {} ],
- 'requested_datetime' => [ w3date($problem->confirmed) ],
- 'updated_datetime' => [ w3date($problem->lastupdate) ],
-# 'expected_datetime' => [ {} ],
-# 'address' => [ {} ],
-# 'address_id' => [ {} ],
- 'service_code' => [ $problem->category ],
- 'service_name' => [ $problem->category ],
-# 'service_notice' => [ {} ],
-# 'zipcode' => [ {} ],
- 'interface_used' => [ $problem->service ], # Not in Open311 v2
+ 'service_request_id' => $id,
+ 'title' => $problem->title, # Not in Open311 v2
+ 'detail' => $problem->detail, # Not in Open311 v2
+ 'description' => $problem->title .': ' . $problem->detail,
+ 'lat' => $problem->latitude,
+ 'long' => $problem->longitude,
+ 'status' => $problem->state,
+# 'status_notes' => {},
+ 'requested_datetime' => w3date($problem->confirmed),
+ 'updated_datetime' => w3date($problem->lastupdate),
+# 'expected_datetime' => {},
+# 'address' => {},
+# 'address_id' => {},
+ 'service_code' => $problem->category,
+ 'service_name' => $problem->category,
+# 'service_notice' => {},
+# 'zipcode' => {},
+ 'interface_used' => $problem->service, # Not in Open311 v2
};
if ( $c->cobrand->moniker eq 'zurich' ) {
- $request->{service_notice} = [
- $problem->get_extra_metadata('public_response')
- ];
+ $request->{service_notice} = $problem->get_extra_metadata('public_response');
}
else {
# FIXME Not according to Open311 v2
- $request->{agency_responsible} = $problem->bodies;
+ my @body_names = map { $_->name } values %{$problem->bodies};
+ $request->{agency_responsible} = {'recipient' => [ @body_names ] };
}
if ( !$problem->anonymous ) {
# Not in Open311 v2
- $request->{'requestor_name'} = [ $problem->name ];
+ $request->{'requestor_name'} = $problem->name;
}
if ( $problem->whensent ) {
# Not in Open311 v2
- $request->{'agency_sent_datetime'} =
- [ w3date($problem->whensent) ];
+ $request->{'agency_sent_datetime'} = w3date($problem->whensent);
}
# Extract number of updates
@@ -279,25 +277,18 @@ sub output_requests : Private {
)->count;
if ($updates) {
# Not in Open311 v2
- $request->{'comment_count'} = [ $updates ];
+ $request->{'comment_count'} = $updates;
}
my $display_photos = $c->cobrand->allow_photo_display($problem);
if ($display_photos && $problem->photo) {
my $url = $c->cobrand->base_url();
my $imgurl = $url . $problem->photos->[0]->{url_full};
- $request->{'media_url'} = [ $imgurl ];
+ $request->{'media_url'} = $imgurl;
}
push(@problemlist, $request);
}
- foreach my $request (@problemlist) {
- if ($request->{agency_responsible}) {
- my @body_names = map { $_->name } values %{$request->{agency_responsible}} ;
- $request->{agency_responsible} =
- [ {'recipient' => [ @body_names ] } ];
- }
- }
$c->forward( 'format_output', [ {
'requests' => [ {
'request' => \@problemlist
@@ -432,7 +423,7 @@ sub format_output : Private {
$c->res->body( encode_json($hashref) );
} elsif ('xml' eq $format) {
$c->res->content_type('application/xml; charset=utf-8');
- $c->res->body( XMLout($hashref, RootName => undef) );
+ $c->res->body( XMLout($hashref, RootName => undef, NoAttr => 1 ) );
} else {
$c->detach( 'error', [
sprintf(_('Invalid format %s specified.'), $format)
diff --git a/perllib/FixMyStreet/App/Controller/Questionnaire.pm b/perllib/FixMyStreet/App/Controller/Questionnaire.pm
index 017a552db..1b338732b 100755
--- a/perllib/FixMyStreet/App/Controller/Questionnaire.pm
+++ b/perllib/FixMyStreet/App/Controller/Questionnaire.pm
@@ -36,9 +36,8 @@ sub check_questionnaire : Private {
if ( $questionnaire->whenanswered ) {
my $problem_url = $c->cobrand->base_url_for_report( $problem ) . $problem->url;
my $contact_url = $c->uri_for( "/contact" );
- $c->stash->{message} = sprintf(_("You have already answered this questionnaire. If you have a question, please <a href='%s'>get in touch</a>, or <a href='%s'>view your problem</a>.\n"), $contact_url, $problem_url);
- $c->stash->{template} = 'errors/generic.html';
- $c->detach;
+ my $message = sprintf(_("You have already answered this questionnaire. If you have a question, please <a href='%s'>get in touch</a>, or <a href='%s'>view your problem</a>.\n"), $contact_url, $problem_url);
+ $c->detach('/page_error_400_bad_request', [ $message ]);
}
unless ( $problem->is_visible ) {
@@ -86,8 +85,8 @@ Display couldn't locate problem error message
sub missing_problem : Private {
my ( $self, $c ) = @_;
- $c->stash->{message} = _("I'm afraid we couldn't locate your problem in the database.\n");
- $c->stash->{template} = 'errors/generic.html';
+ my $message = _("I'm afraid we couldn't locate your problem in the database.\n");
+ $c->detach('/page_error_400_bad_request', [ $message ]);
}
sub submit_creator_fixed : Private {
diff --git a/perllib/FixMyStreet/App/Controller/Report.pm b/perllib/FixMyStreet/App/Controller/Report.pm
index 5a1cfbe54..ad2702460 100644
--- a/perllib/FixMyStreet/App/Controller/Report.pm
+++ b/perllib/FixMyStreet/App/Controller/Report.pm
@@ -275,7 +275,8 @@ sub delete :Local :Args(1) {
$p->user->update_reputation(-1);
$c->model('DB::AdminLog')->create( {
- admin_user => $c->user->email,
+ user => $c->user->obj,
+ admin_user => $c->user->from_body->name,
object_type => 'problem',
action => 'state_change',
object_id => $id,
@@ -305,7 +306,7 @@ sub inspect : Private {
my $problem = $c->stash->{problem};
my $permissions = $c->stash->{_permissions};
- $c->stash->{categories} = $c->forward('/admin/categories_for_point');
+ $c->forward('/admin/categories_for_point');
$c->stash->{report_meta} = { map { $_->{name} => $_ } @{ $c->stash->{problem}->get_extra_fields() } };
my %category_body = map { $_->category => $_->body_id } map { $_->contacts->all } values %{$problem->bodies};
@@ -343,12 +344,15 @@ sub inspect : Private {
$problem->set_extra_metadata( $_ => $c->get_param($_) );
}
- if ( $c->get_param('save_inspected') ) {
+ if ( $c->get_param('defect_type') ) {
+ $problem->defect_type($problem->defect_types->find($c->get_param('defect_type')));
+ } else {
+ $problem->defect_type(undef);
+ }
+
+ if ( $c->get_param('include_update') ) {
$update_text = Utils::cleanup_text( $c->get_param('public_update'), { allow_multiline => 1 } );
- if ($update_text) {
- $problem->set_extra_metadata( inspected => 1 );
- $reputation_change = 1;
- } else {
+ if (!$update_text) {
$valid = 0;
$c->stash->{errors} ||= [];
push @{ $c->stash->{errors} }, _('Please provide a public update for this report.');
@@ -374,6 +378,16 @@ sub inspect : Private {
}
if ( $problem->state ne $old_state ) {
$c->forward( '/admin/log_edit', [ $problem->id, 'problem', 'state_change' ] );
+
+ # If the state has been changed by an inspector, consider the
+ # report to be inspected.
+ unless ($problem->get_extra_metadata('inspected')) {
+ $problem->set_extra_metadata( inspected => 1 );
+ $c->forward( '/admin/log_edit', [ $problem->id, 'problem', 'inspected' ] );
+ my $state = $problem->state;
+ $reputation_change = 1 if $c->cobrand->reputation_increment_states->{$state};
+ $reputation_change = -1 if $c->cobrand->reputation_decrement_states->{$state};
+ }
}
}
@@ -408,12 +422,17 @@ sub inspect : Private {
$problem->lastupdate( \'current_timestamp' );
$problem->update;
if ( defined($update_text) ) {
+ my $timestamp = \'current_timestamp';
+ if (my $saved_at = $c->get_param('saved_at')) {
+ $timestamp = DateTime->from_epoch( epoch => $saved_at );
+ }
+ my $name = $c->user->from_body ? $c->user->from_body->name : $c->user->name;
$problem->add_to_comments( {
text => $update_text,
- created => \'current_timestamp',
- confirmed => \'current_timestamp',
+ created => $timestamp,
+ confirmed => $timestamp,
user_id => $c->user->id,
- name => $c->user->from_body->name,
+ name => $name,
state => 'confirmed',
mark_fixed => 0,
anonymous => 0,
@@ -429,6 +448,12 @@ sub inspect : Private {
} else {
$redirect_uri = $c->uri_for( $problem->url );
}
+
+ # Or if inspector, redirect back to shortlist
+ if ($c->user->has_body_permission_to('planned_reports')) {
+ $redirect_uri = $c->uri_for_action('my/planned');
+ }
+
$c->log->debug( "Redirecting to: " . $redirect_uri );
$c->res->redirect( $redirect_uri );
}
diff --git a/perllib/FixMyStreet/App/Controller/Report/New.pm b/perllib/FixMyStreet/App/Controller/Report/New.pm
index e2569d2e9..2a68b170e 100644
--- a/perllib/FixMyStreet/App/Controller/Report/New.pm
+++ b/perllib/FixMyStreet/App/Controller/Report/New.pm
@@ -83,6 +83,14 @@ sub report_new : Path : Args(0) {
$c->forward('initialize_report');
$c->forward('/auth/get_csrf_token');
+ my @shortlist = grep { /^shortlist-(add|remove)-(\d+)$/ } keys %{$c->req->params};
+ if (@shortlist) {
+ my ($cmd, $id) = $shortlist[0] =~ /^shortlist-(add|remove)-(\d+)$/;
+ $c->req->params->{id} = $id;
+ $c->req->params->{"shortlist-$cmd"} = 1;
+ $c->detach('/my/planned_change');
+ }
+
# work out the location for this report and do some checks
# Also show map if we're just updating the filters
return $c->forward('redirect_to_around')
@@ -651,8 +659,7 @@ sub setup_categories_and_bodies : Private {
push @category_options, _('Other') if $seen{_('Other')};
}
- $c->cobrand->munge_category_list(\@category_options, \@contacts, \%category_extras)
- if $c->cobrand->can('munge_category_list');
+ $c->cobrand->call_hook(munge_category_list => \@category_options, \@contacts, \%category_extras);
# put results onto stash for display
$c->stash->{bodies} = \%bodies;
@@ -895,7 +902,7 @@ sub contacts_to_bodies : Private {
if ($c->stash->{unresponsive}{$category} || $c->stash->{unresponsive}{ALL}) {
[];
} else {
- if ( $c->cobrand->can('singleton_bodies_str') && $c->cobrand->singleton_bodies_str ) {
+ if ( $c->cobrand->call_hook('singleton_bodies_str') ) {
# Cobrands like Zurich can only ever have a single body: 'x', because some functionality
# relies on string comparison against bodies_str.
[ $contacts[0]->body ];
@@ -1025,9 +1032,7 @@ sub send_problem_confirm_email : Private {
$template = 'problem-confirm-not-sending.txt' unless $report->bodies_str;
$c->stash->{token_url} = $c->uri_for_email( '/P', $token->token );
- if ($c->cobrand->can('problem_confirm_email_extras')) {
- $c->cobrand->problem_confirm_email_extras($report);
- }
+ $c->cobrand->call_hook(problem_confirm_email_extras => $report);
$c->send_email( $template, {
to => [ $report->name ? [ $report->user->email, $report->name ] : $report->user->email ],
diff --git a/perllib/FixMyStreet/App/Controller/Reports.pm b/perllib/FixMyStreet/App/Controller/Reports.pm
index 813c2052d..ed851f71f 100644
--- a/perllib/FixMyStreet/App/Controller/Reports.pm
+++ b/perllib/FixMyStreet/App/Controller/Reports.pm
@@ -76,13 +76,12 @@ sub index : Path : Args(0) {
$c->stash->{open} = $j->{open};
};
if ($@) {
- $c->stash->{message} = _("There was a problem showing the All Reports page. Please try again later.");
+ my $message = _("There was a problem showing the All Reports page. Please try again later.");
if ($c->config->{STAGING_SITE}) {
- $c->stash->{message} .= '</p><p>Perhaps the bin/update-all-reports script needs running. Use: bin/update-all-reports</p><p>'
+ $message .= '</p><p>Perhaps the bin/update-all-reports script needs running. Use: bin/update-all-reports</p><p>'
. sprintf(_('The error was: %s'), $@);
}
- $c->stash->{template} = 'errors/generic.html';
- return;
+ $c->detach('/page_error_500_internal_error', [ $message ]);
}
# Down here so that error pages aren't cached.
@@ -109,6 +108,8 @@ Show the summary page for a particular ward.
sub ward : Path : Args(2) {
my ( $self, $c, $body, $ward ) = @_;
+ $c->forward('/auth/get_csrf_token');
+
$c->forward( 'body_check', [ $body ] );
$c->forward( 'ward_check', [ $ward ] )
if $ward;
@@ -136,7 +137,7 @@ sub ward : Path : Args(2) {
} )->all;
@categories = map { $_->category } @categories;
$c->stash->{filter_categories} = \@categories;
- $c->stash->{filter_category} = [ $c->get_param_list('filter_category', 1) ];
+ $c->stash->{filter_category} = { map { $_ => 1 } $c->get_param_list('filter_category', 1) };
my $pins = $c->stash->{pins};
@@ -377,6 +378,25 @@ sub load_and_group_problems : Private {
non_public => 0,
state => [ keys %$states ]
};
+ my $filter = {
+ order_by => $c->stash->{sort_order},
+ rows => $c->cobrand->reports_per_page,
+ };
+
+ if (defined $c->stash->{filter_status}{shortlisted}) {
+ $where->{'me.id'} = { '=', \"user_planned_reports.report_id"};
+ $where->{'user_planned_reports.removed'} = undef;
+ $filter->{join} = 'user_planned_reports';
+ } elsif (defined $c->stash->{filter_status}{unshortlisted}) {
+ my $shortlisted_ids = $c->cobrand->problems->search({
+ 'me.id' => { '=', \"user_planned_reports.report_id"},
+ 'user_planned_reports.removed' => undef,
+ }, {
+ join => 'user_planned_reports',
+ columns => ['me.id'],
+ })->as_query;
+ $where->{'me.id'} = { -not_in => $shortlisted_ids };
+ }
my $not_open = [ FixMyStreet::DB::Result::Problem::fixed_states(), FixMyStreet::DB::Result::Problem::closed_states() ];
if ( $type eq 'new' ) {
@@ -410,13 +430,17 @@ sub load_and_group_problems : Private {
$problems = $problems->to_body($c->stash->{body});
}
+ if (my $bbox = $c->get_param('bbox')) {
+ my ($min_lon, $min_lat, $max_lon, $max_lat) = split /,/, $bbox;
+ $where->{latitude} = { '>=', $min_lat, '<', $max_lat };
+ $where->{longitude} = { '>=', $min_lon, '<', $max_lon };
+ }
+
$problems = $problems->search(
$where,
- {
- order_by => $c->stash->{sort_order},
- rows => $c->cobrand->reports_per_page,
- }
+ $filter
)->include_comment_counts->page( $page );
+
$c->stash->{pager} = $problems->pager;
my ( %problems, @pins );
@@ -499,6 +523,19 @@ sub stash_report_filter_status : Private {
%filter_problem_states = %$s;
}
+ if ($status{shortlisted}) {
+ $filter_status{shortlisted} = 1;
+ }
+
+ if ($status{unshortlisted}) {
+ $filter_status{unshortlisted} = 1;
+ }
+
+ if (keys %filter_problem_states == 0) {
+ my $s = FixMyStreet::DB::Result::Problem->open_states();
+ %filter_problem_states = (%filter_problem_states, %$s);
+ }
+
$c->stash->{filter_problem_states} = \%filter_problem_states;
$c->stash->{filter_status} = \%filter_status;
return 1;
@@ -514,13 +551,17 @@ sub stash_report_sort : Private {
);
my $sort = $c->get_param('sort') || $default;
- $sort = $default unless $sort =~ /^((updated|created)-(desc|asc)|comments-desc)$/;
+ $sort = $default unless $sort =~ /^((updated|created)-(desc|asc)|comments-desc|shortlist)$/;
+ $c->stash->{sort_key} = $sort;
+
+ # Going to do this sorting code-side
+ $sort = 'created-desc' if $sort eq 'shortlist';
+
$sort =~ /^(updated|created|comments)-(desc|asc)$/;
my $order_by = $types{$1} || $1;
my $dir = $2;
$order_by = { -desc => $order_by } if $dir eq 'desc';
- $c->stash->{sort_key} = $sort;
$c->stash->{sort_order} = $order_by;
return 1;
}
@@ -572,4 +613,3 @@ Licensed under the Affero GPL.
__PACKAGE__->meta->make_immutable;
1;
-
diff --git a/perllib/FixMyStreet/App/Controller/Root.pm b/perllib/FixMyStreet/App/Controller/Root.pm
index 3d4c6a1ba..4f098dfc3 100644
--- a/perllib/FixMyStreet/App/Controller/Root.pm
+++ b/perllib/FixMyStreet/App/Controller/Root.pm
@@ -58,6 +58,7 @@ sub index : Path : Args(0) {
return;
}
+ $c->forward('/auth/get_csrf_token');
}
=head2 default
@@ -103,9 +104,25 @@ sub page_error_410_gone : Private {
sub page_error_403_access_denied : Private {
my ( $self, $c, $error_msg ) = @_;
+ $c->detach('page_error', [ $error_msg || _("Sorry, you don't have permission to do that."), 403 ]);
+}
+
+sub page_error_400_bad_request : Private {
+ my ( $self, $c, $error_msg ) = @_;
+ $c->forward('/auth/get_csrf_token');
+ $c->detach('page_error', [ $error_msg, 400 ]);
+}
+
+sub page_error_500_internal_error : Private {
+ my ( $self, $c, $error_msg ) = @_;
+ $c->detach('page_error', [ $error_msg, 500 ]);
+}
+
+sub page_error : Private {
+ my ($self, $c, $error_msg, $code) = @_;
$c->stash->{template} = 'errors/generic.html';
- $c->stash->{message} = $error_msg || _("Sorry, you don't have permission to do that.");
- $c->response->status(403);
+ $c->stash->{message} = $error_msg || _('Unknown error');
+ $c->response->status($code);
}
=head2 end
diff --git a/perllib/FixMyStreet/App/Controller/Tokens.pm b/perllib/FixMyStreet/App/Controller/Tokens.pm
index da017c57f..a1b0c57ba 100644
--- a/perllib/FixMyStreet/App/Controller/Tokens.pm
+++ b/perllib/FixMyStreet/App/Controller/Tokens.pm
@@ -348,6 +348,7 @@ sub token_too_old : Private {
my ( $self, $c ) = @_;
$c->stash->{token_not_found} = 1;
$c->stash->{template} = 'auth/token.html';
+ $c->response->status(400);
}
__PACKAGE__->meta->make_immutable;