aboutsummaryrefslogtreecommitdiffstats
path: root/perllib/FixMyStreet
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
parentdbf56159e44c1560a413022451bf1a1c4cb22a52 (diff)
parenta085b63ce09f87e83b75cda9b9cd08aadfe75d61 (diff)
Merge tag 'v2.0.4' into fiksgatami-dev
Diffstat (limited to 'perllib/FixMyStreet')
-rw-r--r--perllib/FixMyStreet/App.pm25
-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
-rw-r--r--perllib/FixMyStreet/App/Model/EmailSend.pm19
-rw-r--r--perllib/FixMyStreet/App/Model/PhotoSet.pm16
-rw-r--r--perllib/FixMyStreet/App/Response.pm27
-rw-r--r--perllib/FixMyStreet/App/View/Web.pm9
-rw-r--r--perllib/FixMyStreet/Cobrand.pm10
-rw-r--r--perllib/FixMyStreet/Cobrand/Base.pm26
-rw-r--r--perllib/FixMyStreet/Cobrand/Bristol.pm4
-rw-r--r--perllib/FixMyStreet/Cobrand/Bromley.pm33
-rw-r--r--perllib/FixMyStreet/Cobrand/Default.pm58
-rw-r--r--perllib/FixMyStreet/Cobrand/FiksGataMi.pm2
-rw-r--r--perllib/FixMyStreet/Cobrand/FixMyStreet.pm21
-rw-r--r--perllib/FixMyStreet/Cobrand/FixaMinGata.pm2
-rw-r--r--perllib/FixMyStreet/Cobrand/Greenwich.pm15
-rw-r--r--perllib/FixMyStreet/Cobrand/Harrogate.pm290
-rw-r--r--perllib/FixMyStreet/Cobrand/Oxfordshire.pm89
-rw-r--r--perllib/FixMyStreet/Cobrand/SeeSomething.pm135
-rw-r--r--perllib/FixMyStreet/Cobrand/UK.pm10
-rw-r--r--perllib/FixMyStreet/Cobrand/UKCouncils.pm9
-rw-r--r--perllib/FixMyStreet/Cobrand/WestBerkshire.pm16
-rw-r--r--perllib/FixMyStreet/DB/Result/Body.pm25
-rw-r--r--perllib/FixMyStreet/DB/Result/Comment.pm91
-rw-r--r--perllib/FixMyStreet/DB/Result/Contact.pm13
-rw-r--r--perllib/FixMyStreet/DB/Result/ContactDefectType.pm46
-rw-r--r--perllib/FixMyStreet/DB/Result/DefectType.pm66
-rw-r--r--perllib/FixMyStreet/DB/Result/Problem.pm148
-rw-r--r--perllib/FixMyStreet/DB/Result/ResponsePriority.pm6
-rw-r--r--perllib/FixMyStreet/DB/Result/ResponseTemplate.pm6
-rw-r--r--perllib/FixMyStreet/DB/Result/User.pm41
-rw-r--r--perllib/FixMyStreet/DB/ResultSet/DefectType.pm22
-rw-r--r--perllib/FixMyStreet/Email.pm8
-rw-r--r--perllib/FixMyStreet/Email/Sender.pm50
-rw-r--r--perllib/FixMyStreet/EmailSend.pm78
-rw-r--r--perllib/FixMyStreet/EmailSend/Variable.pm17
-rw-r--r--perllib/FixMyStreet/Script/Alerts.pm6
-rw-r--r--perllib/FixMyStreet/Script/ArchiveOldEnquiries.pm141
-rw-r--r--perllib/FixMyStreet/Script/Reports.pm10
-rw-r--r--perllib/FixMyStreet/SendReport/Email.pm22
-rw-r--r--perllib/FixMyStreet/SendReport/Open311.pm86
-rw-r--r--perllib/FixMyStreet/Template.pm2
-rw-r--r--perllib/FixMyStreet/TestMech.pm19
58 files changed, 1684 insertions, 898 deletions
diff --git a/perllib/FixMyStreet/App.pm b/perllib/FixMyStreet/App.pm
index 7809b5f12..35e8c2537 100644
--- a/perllib/FixMyStreet/App.pm
+++ b/perllib/FixMyStreet/App.pm
@@ -4,13 +4,16 @@ use namespace::autoclean;
use Catalyst::Runtime 5.80;
use FixMyStreet;
+use FixMyStreet::App::Response;
use FixMyStreet::Cobrand;
use Memcached;
use FixMyStreet::Map;
use FixMyStreet::Email;
+use FixMyStreet::Email::Sender;
use Utils;
-use Path::Class;
+use Path::Tiny 'path';
+use Try::Tiny;
use URI;
use URI::QueryParam;
@@ -82,6 +85,8 @@ __PACKAGE__->config(
},
);
+__PACKAGE__->response_class('FixMyStreet::App::Response');
+
# Start the application
__PACKAGE__->setup();
@@ -100,6 +105,13 @@ after 'prepare_headers' => sub {
__PACKAGE__->log->disable('debug') #
unless __PACKAGE__->debug;
+# Check upload_dir
+my $cache_dir = path(FixMyStreet->config('UPLOAD_DIR'))->absolute(FixMyStreet->path_to());
+$cache_dir->mkpath;
+unless ( -d $cache_dir && -w $cache_dir ) {
+ warn "\x1b[31mCan't find/write to photo cache directory '$cache_dir'\x1b[0m\n";
+}
+
=head1 NAME
FixMyStreet::App - Catalyst based application
@@ -160,7 +172,7 @@ sub setup_request {
my $cobrand = $c->cobrand;
- $cobrand->add_response_headers if $cobrand->can('add_response_headers');
+ $cobrand->call_hook('add_response_headers');
# append the cobrand templates to the include path
$c->stash->{additional_template_paths} = $cobrand->path_to_web_templates;
@@ -336,8 +348,13 @@ sub send_email {
$data->{_html_images_} = \@inline_images if @inline_images;
my $email = mySociety::Locale::in_gb_locale { FixMyStreet::Email::construct_email($data) };
- my $return = $c->model('EmailSend')->send($email);
- $c->log->error("$return") if !$return;
+
+ try {
+ FixMyStreet::Email::Sender->send($email, { from => $sender });
+ } catch {
+ my $error = $_ || 'unknown error';
+ $c->log->error("$error");
+ };
return $email;
}
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;
diff --git a/perllib/FixMyStreet/App/Model/EmailSend.pm b/perllib/FixMyStreet/App/Model/EmailSend.pm
deleted file mode 100644
index 93751d4a6..000000000
--- a/perllib/FixMyStreet/App/Model/EmailSend.pm
+++ /dev/null
@@ -1,19 +0,0 @@
-package FixMyStreet::App::Model::EmailSend;
-use base 'Catalyst::Model::Factory';
-
-use strict;
-use warnings;
-
-=head1 NAME
-
-FixMyStreet::App::Model::EmailSend
-
-=head1 DESCRIPTION
-
-Catalyst Model wrapper around FixMyStreet::EmailSend
-
-=cut
-
-__PACKAGE__->config(
- class => 'FixMyStreet::EmailSend',
-);
diff --git a/perllib/FixMyStreet/App/Model/PhotoSet.pm b/perllib/FixMyStreet/App/Model/PhotoSet.pm
index 46e1fb630..8fcc1700e 100644
--- a/perllib/FixMyStreet/App/Model/PhotoSet.pm
+++ b/perllib/FixMyStreet/App/Model/PhotoSet.pm
@@ -67,14 +67,7 @@ has upload_dir => (
is => 'ro',
lazy => 1,
default => sub {
- my $self = shift;
- my $cache_dir = path(FixMyStreet->config('UPLOAD_DIR'))->absolute(FixMyStreet->path_to());
- $cache_dir->mkpath;
- unless ( -d $cache_dir && -w $cache_dir ) {
- warn "Can't find/write to photo cache directory '$cache_dir'";
- return;
- }
- $cache_dir;
+ path(FixMyStreet->config('UPLOAD_DIR'))->absolute(FixMyStreet->path_to());
},
);
@@ -191,12 +184,7 @@ has ids => ( # Arrayref of $fileid tuples (always, so post upload/raw data proc
$type ||= 'jpeg';
if ($fileid && length($fileid) == 40) {
my $file = $self->get_file($fileid, $type);
- if ($file->exists) {
- $file->basename;
- } else {
- warn "File $part doesn't exist";
- ();
- }
+ $file->basename;
} else {
# A bad hash, probably a bot spamming with bad data.
();
diff --git a/perllib/FixMyStreet/App/Response.pm b/perllib/FixMyStreet/App/Response.pm
new file mode 100644
index 000000000..16ebf995f
--- /dev/null
+++ b/perllib/FixMyStreet/App/Response.pm
@@ -0,0 +1,27 @@
+# This package exists to try and work around a big bug in Edge:
+# https://developer.microsoft.com/en-us/microsoft-edge/platform/issues/8572187/
+
+package FixMyStreet::App::Response;
+use Moose;
+extends 'Catalyst::Response';
+
+around 'redirect' => sub {
+ my $orig = shift;
+ my $self = shift;
+ my ($location, $status) = @_;
+
+ return $self->$orig() unless @_; # getter
+
+ my $agent = $self->_context->request->user_agent;
+ return $self->$orig(@_) unless $agent =~ /Edge\/14/; # Only care about Edge
+
+ # Instead of a redirect, output HTML that redirects
+ $self->body(<<END
+<meta http-equiv="refresh" content="0; url=$location">
+Please follow this link: <a href="$location">$location</a>
+END
+ );
+ return $location;
+};
+
+1;
diff --git a/perllib/FixMyStreet/App/View/Web.pm b/perllib/FixMyStreet/App/View/Web.pm
index f0bcad0be..496463700 100644
--- a/perllib/FixMyStreet/App/View/Web.pm
+++ b/perllib/FixMyStreet/App/View/Web.pm
@@ -140,21 +140,22 @@ sub escape_js {
my %version_hash;
sub version {
- my ( $self, $c, $file ) = @_;
+ my ( $self, $c, $file, $url ) = @_;
+ $url ||= $file;
_version_get_mtime($file);
if ($version_hash{$file} && $file =~ /\.js$/) {
# See if there's an auto.min.js version and use that instead if there is
(my $file_min = $file) =~ s/\.js$/.auto.min.js/;
_version_get_mtime($file_min);
- $file = $file_min if $version_hash{$file_min} >= $version_hash{$file};
+ $url = $file = $file_min if $version_hash{$file_min} >= $version_hash{$file};
}
my $admin = $self->template->context->stash->{admin} ? FixMyStreet->config('ADMIN_BASE_URL') : '';
- return "$admin$file?$version_hash{$file}";
+ return "$admin$url?$version_hash{$file}";
}
sub _version_get_mtime {
my $file = shift;
- unless ($version_hash{$file} && !FixMyStreet->config('STAGING_SITE')) {
+ unless (defined $version_hash{$file} && !FixMyStreet->config('STAGING_SITE')) {
my $path = FixMyStreet->path_to('web', $file);
$version_hash{$file} = ( stat( $path ) )[9] || 0;
}
diff --git a/perllib/FixMyStreet/Cobrand.pm b/perllib/FixMyStreet/Cobrand.pm
index 9f61635d8..4b9f2bd0b 100644
--- a/perllib/FixMyStreet/Cobrand.pm
+++ b/perllib/FixMyStreet/Cobrand.pm
@@ -153,4 +153,14 @@ sub exists {
return 0;
}
+sub body_handler {
+ my ($class, $areas) = @_;
+
+ foreach my $avail ( $class->available_cobrand_classes ) {
+ my $cobrand = $class->get_class_for_moniker($avail->{moniker})->new({});
+ next unless $cobrand->can('council_id');
+ return $cobrand if $areas->{$cobrand->council_id};
+ }
+}
+
1;
diff --git a/perllib/FixMyStreet/Cobrand/Base.pm b/perllib/FixMyStreet/Cobrand/Base.pm
index 5a9842233..ea2b8f410 100644
--- a/perllib/FixMyStreet/Cobrand/Base.pm
+++ b/perllib/FixMyStreet/Cobrand/Base.pm
@@ -38,6 +38,20 @@ sub moniker {
return $last_part;
}
+=head2 asset_moniker
+
+ $moniker = $cobrand_class->asset_moniker();
+
+Same as moniker, except for the cobrand with the 'fixmystreet' moniker, when it
+returns 'fixmystreet.com', as to avoid confusion that's where its assets are.
+
+=cut
+
+sub asset_moniker {
+ my $self = shift;
+ return $self->moniker eq 'fixmystreet' ? 'fixmystreet.com' : $self->moniker;
+}
+
=head2 is_default
$bool = $cobrand->is_default();
@@ -51,6 +65,18 @@ sub is_default {
return $self->moniker eq 'default';
}
+=head2 call_hook
+
+ $cobrand->call_hook(foo => 1, 2, 3); # calls $cobrand->foo(1, 2, 3) if it exists
+
+=cut
+
+sub call_hook {
+ my ($self, $method_name, @args) = @_;
+ my $method = $self->can($method_name) or return;
+ return $self->$method(@args);
+}
+
# NB: this Base class is for 'meta' features. To add base methods for all cobrands,
# you may want to look at FMS::Cobrand::Default instead!
diff --git a/perllib/FixMyStreet/Cobrand/Bristol.pm b/perllib/FixMyStreet/Cobrand/Bristol.pm
index ecb19b867..fa7f98666 100644
--- a/perllib/FixMyStreet/Cobrand/Bristol.pm
+++ b/perllib/FixMyStreet/Cobrand/Bristol.pm
@@ -40,6 +40,10 @@ sub disambiguate_location {
};
}
+sub get_geocoder {
+ return 'OSM'; # use OSM geocoder
+}
+
sub pin_colour {
my ( $self, $p, $context ) = @_;
return 'grey' if $p->state eq 'not responsible';
diff --git a/perllib/FixMyStreet/Cobrand/Bromley.pm b/perllib/FixMyStreet/Cobrand/Bromley.pm
index 2d0cb86f1..169175947 100644
--- a/perllib/FixMyStreet/Cobrand/Bromley.pm
+++ b/perllib/FixMyStreet/Cobrand/Bromley.pm
@@ -3,6 +3,7 @@ use parent 'FixMyStreet::Cobrand::UKCouncils';
use strict;
use warnings;
+use DateTime::Format::W3CDTF;
sub council_id { return 2482; }
sub council_area { return 'Bromley'; }
@@ -111,5 +112,37 @@ sub title_list {
return ["MR", "MISS", "MRS", "MS", "DR"];
}
+sub open311_config {
+ my ($self, $row, $h, $params) = @_;
+
+ my $extra = $row->get_extra_fields;
+ push @$extra,
+ { name => 'report_url',
+ value => $h->{url} },
+ { name => 'report_title',
+ value => $row->title },
+ { name => 'public_anonymity_required',
+ value => $row->anonymous ? 'TRUE' : 'FALSE' },
+ { name => 'email_alerts_requested',
+ value => 'FALSE' }, # always false as can never request them
+ { name => 'requested_datetime',
+ value => DateTime::Format::W3CDTF->format_datetime($row->confirmed->set_nanosecond(0)) },
+ { name => 'email',
+ value => $row->user->email };
+
+ # make sure we have last_name attribute present in row's extra, so
+ # it is passed correctly to Bromley as attribute[]
+ if ( $row->cobrand ne 'bromley' ) {
+ my ( $firstname, $lastname ) = ( $row->name =~ /(\w+)\.?\s+(.+)/ );
+ push @$extra, { name => 'last_name', value => $lastname };
+ }
+
+ $row->set_extra_fields(@$extra);
+
+ $params->{always_send_latlong} = 0;
+ $params->{send_notpinpointed} = 1;
+ $params->{extended_description} = 0;
+}
+
1;
diff --git a/perllib/FixMyStreet/Cobrand/Default.pm b/perllib/FixMyStreet/Cobrand/Default.pm
index 27111deb2..ac70fff08 100644
--- a/perllib/FixMyStreet/Cobrand/Default.pm
+++ b/perllib/FixMyStreet/Cobrand/Default.pm
@@ -209,14 +209,14 @@ sub base_url { FixMyStreet->config('BASE_URL') }
=head2 base_url_for_report
Return the base url for a report (might be different in a two-tier county, but
-most of the time will be same as base_url). Report may be an object, or a
-hashref.
+most of the time will be same as base_url_with_lang). Report may be an object,
+or a hashref.
=cut
sub base_url_for_report {
my ( $self, $report ) = @_;
- return $self->base_url;
+ return $self->base_url_with_lang;
}
=head2 base_host
@@ -646,27 +646,26 @@ sub admin_pages {
$pages->{config} = [ _('Configuration'), 9];
};
# And some that need special permissions
- if ( $user->is_superuser || $user->has_body_permission_to('category_edit') ) {
+ if ( $user->has_body_permission_to('category_edit') ) {
my $page_title = $user->is_superuser ? _('Bodies') : _('Categories');
$pages->{bodies} = [ $page_title, 1 ];
$pages->{body} = [ undef, undef ];
}
- if ( $user->is_superuser || $user->has_body_permission_to('report_edit') ) {
+ if ( $user->has_body_permission_to('report_edit') ) {
$pages->{reports} = [ _('Reports'), 2 ];
$pages->{report_edit} = [ undef, undef ];
$pages->{update_edit} = [ undef, undef ];
$pages->{abuse_edit} = [ undef, undef ];
}
- if ( $user->is_superuser || $user->has_body_permission_to('template_edit') ) {
+ if ( $user->has_body_permission_to('template_edit') ) {
$pages->{templates} = [ _('Templates'), 3 ];
$pages->{template_edit} = [ undef, undef ];
};
- if ( $user->is_superuser || $user->has_body_permission_to('responsepriority_edit') ) {
+ if ( $user->has_body_permission_to('responsepriority_edit') ) {
$pages->{responsepriorities} = [ _('Priorities'), 4 ];
$pages->{responsepriority_edit} = [ undef, undef ];
};
-
- if ( $user->is_superuser || $user->has_body_permission_to('user_edit') ) {
+ if ( $user->has_body_permission_to('user_edit') ) {
$pages->{users} = [ _('Users'), 6 ];
$pages->{user_edit} = [ undef, undef ];
}
@@ -713,6 +712,7 @@ sub available_permissions {
planned_reports => _("Manage shortlist"),
contribute_as_another_user => _("Create reports/updates on a user's behalf"),
contribute_as_body => _("Create reports/updates as the council"),
+ view_body_contribute_details => _("See user detail for reports created as the council"),
# NB this permission is special in that it can be assigned to users
# without their from_body being set. It's included here for
@@ -873,13 +873,11 @@ sub get_body_sender {
# look up via category
my $contact = $body->contacts->search( { category => $category } )->first;
- if ( $body->can_be_devolved ) {
- if ( $contact->send_method ) {
- return { method => $contact->send_method, config => $contact, contact => $contact };
- } else {
- return { method => $body->send_method, config => $body, contact => $contact };
- }
- } elsif ( $body->send_method ) {
+ if ( $body->can_be_devolved && $contact->send_method ) {
+ return { method => $contact->send_method, config => $contact, contact => $contact };
+ }
+
+ if ( $body->send_method ) {
return { method => $body->send_method, config => $body, contact => $contact };
}
@@ -1187,4 +1185,32 @@ sub category_extra_hidden {
return 0;
}
+=head2 reputation_increment_states/reputation_decrement_states
+
+Get a hashref of states that cause the reporting user's reputation to be
+incremented/decremented, if a report is changed to this state upon inspection.
+
+=cut
+
+sub reputation_increment_states { {} };
+sub reputation_decrement_states { {} };
+
+sub traffic_management_options {
+ return [
+ _("Yes"),
+ _("No"),
+ ];
+}
+
+
+=head2 display_days_ago_threshold
+
+Used to control whether a relative 'n days ago' or absolute date is shown
+for problems/updates. If a problem/update's `days_ago` value is <= this figure,
+the 'n days ago' format is used. By default the absolute date is always used.
+
+=cut
+sub display_days_ago_threshold { 0 }
+
+
1;
diff --git a/perllib/FixMyStreet/Cobrand/FiksGataMi.pm b/perllib/FixMyStreet/Cobrand/FiksGataMi.pm
index 5e2473280..0f8516afc 100644
--- a/perllib/FixMyStreet/Cobrand/FiksGataMi.pm
+++ b/perllib/FixMyStreet/Cobrand/FiksGataMi.pm
@@ -37,7 +37,7 @@ sub pin_colour {
sub area_types {
my $self = shift;
- return $self->next::method() if FixMyStreet->config('STAGING_SITE') && FixMyStreet->config('SKIP_CHECKS_ON_STAGING');
+ return $self->next::method() if FixMyStreet->staging_flag('skip_checks');
[ 'NKO', 'NFY', 'NRA' ];
}
diff --git a/perllib/FixMyStreet/Cobrand/FixMyStreet.pm b/perllib/FixMyStreet/Cobrand/FixMyStreet.pm
index 1fb822893..1052bac0e 100644
--- a/perllib/FixMyStreet/Cobrand/FixMyStreet.pm
+++ b/perllib/FixMyStreet/Cobrand/FixMyStreet.pm
@@ -60,26 +60,5 @@ sub extra_contact_validation {
return %errors;
}
-sub report_form_extras {
- ( { name => 'gender', required => 0 }, { name => 'variant', required => 0 } )
-}
-
-sub ask_gender_question {
- my $self = shift;
-
- return 1 unless $self->{c}->user;
-
- my $reports = $self->{c}->model('DB::Problem')->search({
- user_id => $self->{c}->user->id,
- extra => { like => '%gender%' }
- }, { order_by => { -desc => 'id' } });
-
- while (my $report = $reports->next) {
- my $gender = $report->get_extra_metadata('gender');
- return 0 if $gender =~ /female|male|other|unknown/;
- }
- return 1;
-}
-
1;
diff --git a/perllib/FixMyStreet/Cobrand/FixaMinGata.pm b/perllib/FixMyStreet/Cobrand/FixaMinGata.pm
index 5b78b3fa1..324811008 100644
--- a/perllib/FixMyStreet/Cobrand/FixaMinGata.pm
+++ b/perllib/FixMyStreet/Cobrand/FixaMinGata.pm
@@ -31,7 +31,7 @@ sub disambiguate_location {
sub area_types {
my $self = shift;
- return $self->next::method() if FixMyStreet->config('STAGING_SITE') && FixMyStreet->config('SKIP_CHECKS_ON_STAGING');
+ return $self->next::method() if FixMyStreet->staging_flag('skip_checks');
[ 'KOM' ];
}
diff --git a/perllib/FixMyStreet/Cobrand/Greenwich.pm b/perllib/FixMyStreet/Cobrand/Greenwich.pm
index 7777079a9..700a12782 100644
--- a/perllib/FixMyStreet/Cobrand/Greenwich.pm
+++ b/perllib/FixMyStreet/Cobrand/Greenwich.pm
@@ -55,4 +55,19 @@ sub contact_email {
return join( '@', 'fixmystreet', 'royalgreenwich.gov.uk' );
}
+sub reports_per_page { return 20; }
+
+sub on_map_default_max_pin_age {
+ return '21 days';
+}
+
+sub open311_config {
+ my ($self, $row, $h, $params) = @_;
+
+ my $extra = $row->get_extra_fields;
+ # Greenwich doesn't have category metadata to fill this
+ push @$extra, { name => 'external_id', value => $row->id };
+ $row->set_extra_fields( @$extra );
+}
+
1;
diff --git a/perllib/FixMyStreet/Cobrand/Harrogate.pm b/perllib/FixMyStreet/Cobrand/Harrogate.pm
deleted file mode 100644
index 8f4a6e2ea..000000000
--- a/perllib/FixMyStreet/Cobrand/Harrogate.pm
+++ /dev/null
@@ -1,290 +0,0 @@
-package FixMyStreet::Cobrand::Harrogate;
-use base 'FixMyStreet::Cobrand::UKCouncils';
-
-use strict;
-use warnings;
-use feature 'say';
-
-sub council_id { return 2407; }
-sub council_area { return 'Harrogate'; }
-sub council_name { return 'Harrogate Borough Council'; }
-sub council_url { return 'harrogate'; }
-sub is_two_tier { return 1; } # with North Yorkshire CC 2235
-
-sub base_url {
- my $self = shift;
- return $self->next::method() if FixMyStreet->config('STAGING_SITE');
- return 'http://fix.harrogate.gov.uk';
-}
-
-sub disambiguate_location {
- my $self = shift;
- my $string = shift;
-
- my $town = 'Harrogate';
-
- # as it's the requested example location, try to avoid a disambiguation page
- $town .= ', HG1 1DH' if $string =~ /^\s*king'?s\s+r(?:oa)?d\s*(?:,\s*har\w+\s*)?$/i;
-
- return {
- %{ $self->SUPER::disambiguate_location() },
- town => $town,
- centre => '54.0671557690306,-1.59581319536637',
- span => '0.370193897090822,0.829517054931808',
- bounds => [ 53.8914112467619, -2.00450542308575, 54.2616051438527, -1.17498836815394 ],
- };
-}
-
-sub example_places {
- return ( 'HG1 2SG', "King's Road" );
-}
-
-sub enter_postcode_text {
- my ($self) = @_;
- return 'Enter a Harrogate district postcode, or street name and area';
-}
-
-# increase map zoom level so street names are visible
-sub default_map_zoom { return 3; }
-
-
-=head2 temp_email_to_update, temp_update_contacts
-
-Temporary helper routines to update the extra for potholes (temporary setup
-hack, cargo-culted from ESCC, may in future be superseded either by
-Open311/integration or a better mechanism for manually creating rich contacts).
-
-Can run with a script or command line like:
-
- bin/cron-wrapper perl -MFixMyStreet::App -MFixMyStreet::Cobrand::Harrogate -e \
- 'FixMyStreet::Cobrand::Harrogate->new({c => FixMyStreet::App->new})->temp_update_contacts'
-
-=cut
-
-sub temp_email_to_update {
- return 'CustomerServices@harrogate.gov.uk';
-}
-
-sub temp_update_contacts {
- my $self = shift;
-
- my $contact_rs = $self->{c}->model('DB::Contact');
-
- my $email = $self->temp_email_to_update;
- my $_update = sub {
- my ($category, $field, $category_details) = @_;
- # NB: we're accepting just 1 field, but supply as array [ $field ]
-
- my $contact = $contact_rs->find_or_create(
- {
- body_id => $self->council_id,
- category => $category,
-
- confirmed => 1,
- deleted => 0,
- email => $email,
- editor => 'automated script',
- note => '',
- send_method => '',
- whenedited => \'NOW()',
- %{ $category_details || {} },
- },
- {
- key => 'contacts_body_id_category_idx'
- }
- );
-
- say "Editing category: $category";
-
- my %default = (
- variable => 'true',
- order => '1',
- required => 'no',
- datatype => 'string',
- datatype_description => 'a string',
- );
-
- if ($field->{datatype} || '' eq 'boolean') {
- my $description = $field->{description};
- %default = (
- %default,
- datatype => 'singlevaluelist',
- datatype_description => 'Yes or No',
- values => { value => [
- { key => ['No'], name => ['No'] },
- { key => ['Yes'], name => ['Yes'] },
- ] },
- );
- }
-
- $contact->update({
- # XXX: we're just setting extra with the expected layout,
- # this could be encapsulated more nicely
- extra => { _fields => [ { %default, %$field } ] },
- confirmed => 1,
- deleted => 0,
- editor => 'automated script',
- whenedited => \'NOW()',
- note => 'Edited by script as per requirements Dec 2014',
- });
- };
-
- $_update->( 'Abandoned vehicles', {
- code => 'registration',
- description => 'Vehicle Registration number:',
- });
-
- $_update->( 'Dead animals', {
- code => 'INFO_TEXT',
- variable => 'false',
- description => 'We do not remove small species, e.g. squirrels, rabbits, and small birds.',
- });
-
- $_update->( 'Flyposting', {
- code => 'offensive',
- description => 'Is it offensive?',
- datatype => 'boolean', # mapped onto singlevaluelist
- });
-
- $_update->( 'Flytipping', {
- code => 'size',
- description => 'Size?',
- datatype => 'singlevaluelist',
- values => { value => [
- { key => ['Single Item'], name => ['Single item'] },
- { key => ['Car boot load'], name => ['Car boot load'] },
- { key => ['Small van load'], name => ['Small van load'] },
- { key => ['Transit van load'], name => ['Transit van load'] },
- { key => ['Tipper lorry load'], name => ['Tipper lorry load'] },
- { key => ['Significant load'], name => ['Significant load'] },
- ] },
- });
-
- $_update->( 'Graffiti', {
- code => 'offensive',
- description => 'Is it offensive?',
- datatype => 'boolean', # mapped onto singlevaluelist
- });
-
- $_update->( 'Parks and playgrounds', {
- code => 'dangerous',
- description => 'Is it dangerous or could cause injury?',
- datatype => 'boolean', # mapped onto singlevaluelist
- });
-
- $_update->( 'Trees', {
- code => 'dangerous',
- description => 'Is it dangerous or could cause injury?',
- datatype => 'boolean', # mapped onto singlevaluelist
- });
-
- # also ensure that the following categories are created:
- for my $category (
- 'Car parking',
- 'Dog and litter bins',
- 'Dog fouling',
- 'Other',
- 'Rubbish (refuse and recycling)',
- 'Street cleaning',
- 'Street lighting',
- 'Street nameplates',
- ) {
- say "Creating $category if required";
- my $contact = $contact_rs->find_or_create(
- {
- body_id => $self->council_id,
- category => $category,
- confirmed => 1,
- deleted => 0,
- email => $email,
- editor => 'automated script',
- note => 'Created by script as per requirements Dec 2014',
- send_method => '',
- whenedited => \'NOW()',
- }
- );
- }
-
- my @to_delete = (
- 'Parks/landscapes', # delete in favour of to parks and playgrounds
- 'Public toilets', # as no longer in specs
- );
- say sprintf "Deleting: %s (if present)", join ',' => @to_delete;
- $contact_rs->search({
- body_id => $self->council_id,
- category => \@to_delete,
- deleted => 0
- })->update({
- deleted => 1,
- editor => 'automated script',
- whenedited => \'NOW()',
- note => 'Deleted by script as per requirements Dec 2014',
- });
-}
-
-sub contact_email {
- my $self = shift;
- return join( '@', 'customerservices', 'harrogate.gov.uk' );
-}
-
-sub process_additional_metadata_for_email {
- my ($self, $problem, $h) = @_;
-
- my $additional = '';
- if (my $extra = $problem->get_extra_fields) {
- $additional = join "\n\n", map {
- if ($_->{name} eq 'INFO_TEXT') {
- ();
- }
- else {
- sprintf '%s: %s', $_->{description}, $_->{value};
- }
- } @$extra;
- $additional = "\n\n$additional" if $additional;
- }
-
- $h->{additional_information} = $additional;
-}
-
-sub send_questionnaires {
- return 0;
-}
-
-sub munge_category_list {
- my ($self, $categories_ref, $contacts_ref, $extras_ref) = @_;
-
- # we want to know which contacts *only* belong to NYCC
- # that's because for shared responsibility, we don't expect
- # the user to have to figure out which authority to contact.
-
- # so we start building up the list of both
- my (%harrogate_contacts, %nycc_contacts);
-
- my $harrogate_id = $self->council_id; # XXX: note reference to council_id as body id!
- for my $contact (@$contacts_ref) {
- my $category = $contact->category;
- if ($contact->body_id == $harrogate_id) {
- $harrogate_contacts{$category} = 1;
- }
- else {
- $nycc_contacts{$category}++;
- }
- }
-
- # and then remove any that also have Harrogate involvement
- delete $nycc_contacts{$_} for keys %harrogate_contacts;
-
- # here, we simply *mark* the text with (NYCC) at the end, and
- # the rest will get done in the template with javascript
- my @categories = map {
- $nycc_contacts{$_} ?
- "$_ (NYCC)"
- : $_
- } @$categories_ref;
-
- # replace the entire list with this transformed one
- @$categories_ref = @categories;
-}
-
-1;
-
diff --git a/perllib/FixMyStreet/Cobrand/Oxfordshire.pm b/perllib/FixMyStreet/Cobrand/Oxfordshire.pm
index dca208e98..3e262a700 100644
--- a/perllib/FixMyStreet/Cobrand/Oxfordshire.pm
+++ b/perllib/FixMyStreet/Cobrand/Oxfordshire.pm
@@ -112,6 +112,29 @@ sub pin_colour {
return 'yellow';
}
+sub open311_config {
+ my ($self, $row, $h, $params) = @_;
+
+ my $extra = $row->get_extra_fields;
+ push @$extra, { name => 'external_id', value => $row->id };
+
+ if ($h->{closest_address}) {
+ push @$extra, { name => 'closest_address', value => $h->{closest_address} }
+ }
+ if ( $row->used_map || ( !$row->used_map && !$row->postcode ) ) {
+ push @$extra, { name => 'northing', value => $h->{northing} };
+ push @$extra, { name => 'easting', value => $h->{easting} };
+ }
+ $row->set_extra_fields( @$extra );
+
+ $params->{extended_description} = 'oxfordshire';
+}
+
+sub open311_pre_send {
+ my ($self, $row, $open311) = @_;
+ $open311->endpoints( { requests => 'open311_service_request.cgi' } );
+}
+
sub on_map_default_status { return 'open'; }
sub contact_email {
@@ -121,4 +144,70 @@ sub contact_email {
sub admin_user_domain { 'oxfordshire.gov.uk' }
+sub traffic_management_options {
+ return [
+ "Signs and Cones",
+ "Stop and Go Boards",
+ "High Speed Roads",
+ ];
+}
+
+sub admin_pages {
+ my $self = shift;
+
+ my $user = $self->{c}->user;
+
+ my $pages = $self->next::method();
+
+ # Oxfordshire have a custom admin page for downloading reports in an Exor-
+ # friendly format which anyone with report_instruct permission can use.
+ if ( $user->has_body_permission_to('report_instruct') ) {
+ $pages->{exordefects} = [ ('Download Exor RDI'), 10 ];
+ }
+ if ( $user->has_body_permission_to('defect_type_edit') ) {
+ $pages->{defecttypes} = [ ('Defect Types'), 11 ];
+ $pages->{defecttype_edit} = [ undef, undef ];
+ };
+
+ return $pages;
+}
+
+sub defect_types {
+ {
+ SFP2 => "SFP2: sweep and fill <1m2",
+ POT2 => "POT2",
+ };
+}
+
+sub exor_rdi_link_id { 1989169 }
+sub exor_rdi_link_length { 50 }
+
+sub reputation_increment_states {
+ return {
+ 'action scheduled' => 1,
+ };
+}
+
+sub user_extra_fields {
+ return [ 'initials' ];
+}
+
+sub display_days_ago_threshold { 28 }
+
+sub defect_type_extra_fields {
+ return [
+ 'activity_code',
+ 'defect_code',
+ ];
+};
+
+sub available_permissions {
+ my $self = shift;
+
+ my $perms = $self->next::method();
+ $perms->{Bodies}->{defect_type_edit} = "Add/edit defect types";
+
+ return $perms;
+}
+
1;
diff --git a/perllib/FixMyStreet/Cobrand/SeeSomething.pm b/perllib/FixMyStreet/Cobrand/SeeSomething.pm
deleted file mode 100644
index 4d4dd000e..000000000
--- a/perllib/FixMyStreet/Cobrand/SeeSomething.pm
+++ /dev/null
@@ -1,135 +0,0 @@
-package FixMyStreet::Cobrand::SeeSomething;
-use parent 'FixMyStreet::Cobrand::UKCouncils';
-
-use strict;
-use warnings;
-
-sub council_id { return [ 2520, 2522, 2514, 2546, 2519, 2538, 2535 ]; }
-sub council_area { return 'West Midlands'; }
-sub council_name { return 'See Something Say Something'; }
-sub council_url { return 'seesomething'; }
-sub area_types { [ 'MTD' ] }
-
-sub area_check {
- my ( $self, $params, $context ) = @_;
-
- my $councils = $params->{all_areas};
- my $council_match = grep { $councils->{$_} } @{ $self->council_id };
-
- if ($council_match) {
- return 1;
- }
-
- return ( 0, "That location is not covered by See Something, Say Something" );
-}
-
-sub disambiguate_location {
- my $self = shift;
- my $string = shift;
-
- my $town = 'West Midlands';
-
- return {
- %{ $self->SUPER::disambiguate_location() },
- town => $town,
- centre => '52.4803101685267,-2.2708272758854',
- span => '1.4002794815887,2.06340043925997',
- bounds => [ 51.8259444771676, -3.23554082684068, 53.2262239587563, -1.17214038758071 ],
- };
-}
-
-sub example_places {
- return ( 'WS1 4NH', 'Austin Drive, Coventry' );
-}
-
-sub send_questionnaires {
- return 0;
-}
-
-sub ask_ever_reported {
- return 0;
-}
-
-sub report_sent_confirmation_email { 1; }
-
-sub report_check_for_errors { return (); }
-
-sub never_confirm_reports { 1; }
-
-sub allow_anonymous_reports { 1; }
-
-sub anonymous_account { return { name => 'Anonymous Submission', email => FixMyStreet->config('DO_NOT_REPLY_EMAIL') }; }
-
-sub admin_allow_user {
- my ( $self, $user ) = @_;
- return 1 if ( $user->from_body || $user->is_superuser );
-}
-
-sub admin_pages {
- my $self = shift;
-
- return {
- 'stats' => ['Reports', 0],
- };
-};
-
-sub admin_stats {
- my $self = shift;
- my $c = $self->{c};
-
- my %filters = ();
-
- # XXX The below lookup assumes a body ID === MapIt area ID
- my %councils =
- map {
- my $name = $_->name;
- $name =~ s/(?:Borough|City) Council//;
- ($_->id => $name);
- }
- $c->model('DB::Body')->search({ id => $self->council_id });
-
- $c->stash->{council_details} = \%councils;
-
- if ( !$c->user_exists || !grep { $_ == $c->user->from_body->id } @{ $self->council_id } ) {
- $c->detach( '/page_error_404_not_found' );
- }
-
- if ( $c->get_param('category') ) {
- $filters{category} = $c->get_param('category');
- $c->stash->{category} = $c->get_param('category');
- }
-
- if ( $c->get_param('subcategory') ) {
- $filters{subcategory} = $c->get_param('subcategory');
- $c->stash->{subcategory} = $c->get_param('subcategory');
- }
-
- if ( $c->get_param('service') ) {
- $filters{service} = { -ilike => $c->get_param('service') };
- $c->stash->{service} = $c->get_param('service');
- }
-
- my $page = $c->get_param('p') || 1;
-
- my $p = $c->model('DB::Problem')->search(
- {
- confirmed => { not => undef },
- %filters
- },
- {
- columns => [ qw(
- service category subcategory confirmed bodies_str
- ) ],
- order_by => { -desc=> [ 'confirmed' ] },
- rows => 20,
- }
- )->page( $page );
-
- $c->stash->{reports} = $p;
- $c->stash->{pager} = $p->pager;
-
- return 1;
-}
-
-1;
-
diff --git a/perllib/FixMyStreet/Cobrand/UK.pm b/perllib/FixMyStreet/Cobrand/UK.pm
index 08ecf0b7d..945af48f8 100644
--- a/perllib/FixMyStreet/Cobrand/UK.pm
+++ b/perllib/FixMyStreet/Cobrand/UK.pm
@@ -1,5 +1,6 @@
package FixMyStreet::Cobrand::UK;
use base 'FixMyStreet::Cobrand::Default';
+use strict;
use JSON::MaybeXS;
use mySociety::MaPit;
@@ -354,13 +355,8 @@ sub get_body_handler_for_problem {
my @bodies = values %{$row->bodies};
my %areas = map { %{$_->areas} } @bodies;
- foreach my $avail ( FixMyStreet::Cobrand->available_cobrand_classes ) {
- my $class = FixMyStreet::Cobrand->get_class_for_moniker($avail->{moniker});
- my $cobrand = $class->new({});
- next unless $cobrand->can('council_id');
- return $cobrand if $areas{$cobrand->council_id};
- }
-
+ my $cobrand = FixMyStreet::Cobrand->body_handler(\%areas);
+ return $cobrand if $cobrand;
return ref $self ? $self : $self->new;
}
diff --git a/perllib/FixMyStreet/Cobrand/UKCouncils.pm b/perllib/FixMyStreet/Cobrand/UKCouncils.pm
index c22224307..e0b6b5298 100644
--- a/perllib/FixMyStreet/Cobrand/UKCouncils.pm
+++ b/perllib/FixMyStreet/Cobrand/UKCouncils.pm
@@ -42,13 +42,13 @@ sub restriction {
sub problems_restriction {
my ($self, $rs) = @_;
- return $rs if FixMyStreet->config('STAGING_SITE') && FixMyStreet->config('SKIP_CHECKS_ON_STAGING');
+ return $rs if FixMyStreet->staging_flag('skip_checks');
return $rs->to_body($self->council_id);
}
sub updates_restriction {
my ($self, $rs) = @_;
- return $rs if FixMyStreet->config('STAGING_SITE') && FixMyStreet->config('SKIP_CHECKS_ON_STAGING');
+ return $rs if FixMyStreet->staging_flag('skip_checks');
return $rs->to_body($self->council_id);
}
@@ -76,7 +76,7 @@ sub users_restriction {
my $or_query = [
from_body => $self->council_id,
- id => [ { -in => $problem_user_ids }, { -in => $update_user_ids } ],
+ 'me.id' => [ { -in => $problem_user_ids }, { -in => $update_user_ids } ],
];
if ($self->can('admin_user_domain')) {
my $domain = $self->admin_user_domain;
@@ -105,7 +105,7 @@ sub enter_postcode_text {
sub area_check {
my ( $self, $params, $context ) = @_;
- return 1 if FixMyStreet->config('STAGING_SITE') && FixMyStreet->config('SKIP_CHECKS_ON_STAGING');
+ return 1 if FixMyStreet->staging_flag('skip_checks');
my $councils = $params->{all_areas};
my $council_match = defined $councils->{$self->council_id};
@@ -200,6 +200,7 @@ sub available_permissions {
my $perms = $self->next::method();
$perms->{Problems}->{contribute_as_body} = "Create reports/updates as " . $self->council_name;
+ $perms->{Problems}->{view_body_contribute_details} = "See user detail for reports created as " . $self->council_name;
$perms->{Users}->{user_assign_areas} = "Assign users to areas in " . $self->council_name;
return $perms;
diff --git a/perllib/FixMyStreet/Cobrand/WestBerkshire.pm b/perllib/FixMyStreet/Cobrand/WestBerkshire.pm
new file mode 100644
index 000000000..7e98187bb
--- /dev/null
+++ b/perllib/FixMyStreet/Cobrand/WestBerkshire.pm
@@ -0,0 +1,16 @@
+package FixMyStreet::Cobrand::WestBerkshire;
+use base 'FixMyStreet::Cobrand::UK';
+
+use strict;
+use warnings;
+
+sub council_id { 2619 }
+
+# non standard west berks end points
+sub open311_pre_send {
+ my ($self, $row, $open311) = @_;
+ $open311->endpoints( { services => 'Services', requests => 'Requests' } );
+}
+
+1;
+
diff --git a/perllib/FixMyStreet/DB/Result/Body.pm b/perllib/FixMyStreet/DB/Result/Body.pm
index 037b69352..82015ad2d 100644
--- a/perllib/FixMyStreet/DB/Result/Body.pm
+++ b/perllib/FixMyStreet/DB/Result/Body.pm
@@ -75,6 +75,12 @@ __PACKAGE__->has_many(
{ "foreign.body_id" => "self.id" },
{ cascade_copy => 0, cascade_delete => 0 },
);
+__PACKAGE__->has_many(
+ "defect_types",
+ "FixMyStreet::DB::Result::DefectType",
+ { "foreign.body_id" => "self.id" },
+ { cascade_copy => 0, cascade_delete => 0 },
+);
__PACKAGE__->belongs_to(
"parent",
"FixMyStreet::DB::Result::Body",
@@ -112,8 +118,8 @@ __PACKAGE__->has_many(
);
-# Created by DBIx::Class::Schema::Loader v0.07035 @ 2016-09-06 15:33:04
-# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:ZuzscnLqcx0k512cTZ/kdg
+# Created by DBIx::Class::Schema::Loader v0.07035 @ 2017-02-13 15:11:11
+# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:BOJANVwg3kR/1VjDq0LykA
sub url {
my ( $self, $c, $args ) = @_;
@@ -127,4 +133,19 @@ sub areas {
return \%ids;
}
+=head2 get_cobrand_handler
+
+Get a cobrand object for this body, if there is one.
+
+e.g.
+ * if the problem was sent to Bromley it will return ::Bromley
+ * if the problem was sent to Camden it will return nothing
+
+=cut
+
+sub get_cobrand_handler {
+ my $self = shift;
+ return FixMyStreet::Cobrand->body_handler($self->areas);
+}
+
1;
diff --git a/perllib/FixMyStreet/DB/Result/Comment.pm b/perllib/FixMyStreet/DB/Result/Comment.pm
index f5601639a..cf1ba444d 100644
--- a/perllib/FixMyStreet/DB/Result/Comment.pm
+++ b/perllib/FixMyStreet/DB/Result/Comment.pm
@@ -6,6 +6,7 @@ package FixMyStreet::DB::Result::Comment;
use strict;
use warnings;
+use FixMyStreet::Template;
use base 'DBIx::Class::Core';
__PACKAGE__->load_components("FilterColumn", "InflateColumn::DateTime", "EncodedColumn");
@@ -199,7 +200,7 @@ __PACKAGE__->has_many(
"admin_log_entries",
"FixMyStreet::DB::Result::AdminLog",
{ "foreign.object_id" => "self.id" },
- {
+ {
cascade_copy => 0, cascade_delete => 0,
where => { 'object_type' => 'update' },
}
@@ -223,4 +224,92 @@ __PACKAGE__->might_have(
{ cascade_copy => 0, cascade_delete => 1 },
);
+=head2 meta_line
+
+Returns a string to be used on a report update, describing some of the metadata
+about an update
+
+=cut
+
+sub meta_line {
+ my ( $self, $c ) = @_;
+
+ my $meta = '';
+
+ $c->stash->{last_state} ||= '';
+
+ if ($self->anonymous or !$self->name) {
+ $meta = sprintf( _( 'Posted anonymously at %s' ), Utils::prettify_dt( $self->confirmed ) )
+ } elsif ($self->user->from_body) {
+ my $user_name = FixMyStreet::Template::html_filter($self->user->name);
+ my $body = $self->user->body;
+ if ($body eq 'Bromley Council') {
+ $body = "$body <img src='/cobrands/bromley/favicon.png' alt=''>";
+ } elsif ($body eq 'Royal Borough of Greenwich') {
+ $body = "$body <img src='/cobrands/greenwich/favicon.png' alt=''>";
+ }
+ if ($c->user_exists and $c->user->has_permission_to('view_body_contribute_details', $self->problem->bodies_str_ids)) {
+ $meta = sprintf( _( 'Posted by <strong>%s</strong> (%s) at %s' ), $body, $user_name, Utils::prettify_dt( $self->confirmed ) );
+ } else {
+ $meta = sprintf( _( 'Posted by <strong>%s</strong> at %s' ), $body, Utils::prettify_dt( $self->confirmed ) );
+ }
+ } else {
+ $meta = sprintf( _( 'Posted by %s at %s' ), FixMyStreet::Template::html_filter($self->name), Utils::prettify_dt( $self->confirmed ) )
+ }
+
+ my $update_state = '';
+
+ if ($self->mark_fixed) {
+ $update_state = _( 'marked as fixed' );
+ } elsif ($self->mark_open) {
+ $update_state = _( 'reopened' );
+ } elsif ($self->problem_state) {
+ my $state = $self->problem_state_display;
+
+ if ($state eq 'confirmed') {
+ if ($c->stash->{last_state}) {
+ $update_state = _( 'reopened' )
+ }
+ } elsif ($state eq 'investigating') {
+ $update_state = _( 'marked as investigating' )
+ } elsif ($state eq 'planned') {
+ $update_state = _( 'marked as planned' )
+ } elsif ($state eq 'in progress') {
+ $update_state = _( 'marked as in progress' )
+ } elsif ($state eq 'action scheduled') {
+ $update_state = _( 'marked as action scheduled' )
+ } elsif ($state eq 'closed') {
+ $update_state = _( 'marked as closed' )
+ } elsif ($state eq 'fixed') {
+ $update_state = _( 'marked as fixed' )
+ } elsif ($state eq 'unable to fix') {
+ $update_state = _( 'marked as no further action' )
+ } elsif ($state eq 'not responsible') {
+ $update_state = _( "marked as not the council's responsibility" )
+ } elsif ($state eq 'duplicate') {
+ $update_state = _( 'closed as a duplicate report' )
+ } elsif ($state eq 'internal referral') {
+ $update_state = _( 'marked as an internal referral' )
+ }
+
+ if ($c->cobrand->moniker eq 'bromley' || (
+ $self->problem->bodies_str &&
+ $self->problem->bodies_str eq '2482'
+ )) {
+ if ($state eq 'not responsible') {
+ $update_state = 'marked as third party responsibility'
+ }
+ }
+
+ }
+
+ if ($update_state ne $c->stash->{last_state} and $update_state) {
+ $meta .= ", $update_state";
+ }
+
+ $c->stash->{last_state} = $update_state;
+
+ return $meta;
+};
+
1;
diff --git a/perllib/FixMyStreet/DB/Result/Contact.pm b/perllib/FixMyStreet/DB/Result/Contact.pm
index 0c9a7c0d8..a620b7358 100644
--- a/perllib/FixMyStreet/DB/Result/Contact.pm
+++ b/perllib/FixMyStreet/DB/Result/Contact.pm
@@ -56,6 +56,12 @@ __PACKAGE__->belongs_to(
{ is_deferrable => 0, on_delete => "NO ACTION", on_update => "NO ACTION" },
);
__PACKAGE__->has_many(
+ "contact_defect_types",
+ "FixMyStreet::DB::Result::ContactDefectType",
+ { "foreign.contact_id" => "self.id" },
+ { cascade_copy => 0, cascade_delete => 0 },
+);
+__PACKAGE__->has_many(
"contact_response_priorities",
"FixMyStreet::DB::Result::ContactResponsePriority",
{ "foreign.contact_id" => "self.id" },
@@ -69,8 +75,8 @@ __PACKAGE__->has_many(
);
-# Created by DBIx::Class::Schema::Loader v0.07035 @ 2016-09-06 15:33:04
-# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:ocmQGeFJtO3wmvyx6W+EKQ
+# Created by DBIx::Class::Schema::Loader v0.07035 @ 2017-02-13 15:11:11
+# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:f9VepR/oPyr3z6PUpJ4w2A
__PACKAGE__->load_components("+FixMyStreet::DB::RABXColumn");
__PACKAGE__->rabx_column('extra');
@@ -82,11 +88,12 @@ with 'FixMyStreet::Roles::Extra';
__PACKAGE__->many_to_many( response_templates => 'contact_response_templates', 'response_template' );
__PACKAGE__->many_to_many( response_priorities => 'contact_response_priorities', 'response_priority' );
+__PACKAGE__->many_to_many( defect_types => 'contact_defect_types', 'defect_type' );
sub get_metadata_for_input {
my $self = shift;
my $id_field = $self->id_field;
- my @metadata = grep { $_->{code} !~ /^(easting|northing|$id_field)$/ } @{$self->get_extra_fields};
+ my @metadata = grep { $_->{code} !~ /^(easting|northing|closest_address|$id_field)$/ } @{$self->get_extra_fields};
# Just in case the extra data is in an old parsed format
foreach (@metadata) {
diff --git a/perllib/FixMyStreet/DB/Result/ContactDefectType.pm b/perllib/FixMyStreet/DB/Result/ContactDefectType.pm
new file mode 100644
index 000000000..2199f0b42
--- /dev/null
+++ b/perllib/FixMyStreet/DB/Result/ContactDefectType.pm
@@ -0,0 +1,46 @@
+use utf8;
+package FixMyStreet::DB::Result::ContactDefectType;
+
+# Created by DBIx::Class::Schema::Loader
+# DO NOT MODIFY THE FIRST PART OF THIS FILE
+
+use strict;
+use warnings;
+
+use base 'DBIx::Class::Core';
+__PACKAGE__->load_components("FilterColumn", "InflateColumn::DateTime", "EncodedColumn");
+__PACKAGE__->table("contact_defect_types");
+__PACKAGE__->add_columns(
+ "id",
+ {
+ data_type => "integer",
+ is_auto_increment => 1,
+ is_nullable => 0,
+ sequence => "contact_defect_types_id_seq",
+ },
+ "contact_id",
+ { data_type => "integer", is_foreign_key => 1, is_nullable => 0 },
+ "defect_type_id",
+ { data_type => "integer", is_foreign_key => 1, is_nullable => 0 },
+);
+__PACKAGE__->set_primary_key("id");
+__PACKAGE__->belongs_to(
+ "contact",
+ "FixMyStreet::DB::Result::Contact",
+ { id => "contact_id" },
+ { is_deferrable => 0, on_delete => "NO ACTION", on_update => "NO ACTION" },
+);
+__PACKAGE__->belongs_to(
+ "defect_type",
+ "FixMyStreet::DB::Result::DefectType",
+ { id => "defect_type_id" },
+ { is_deferrable => 0, on_delete => "NO ACTION", on_update => "NO ACTION" },
+);
+
+
+# Created by DBIx::Class::Schema::Loader v0.07035 @ 2017-02-13 15:11:11
+# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:VIczmM0OXXpWgQVpop3SMw
+
+
+# You can replace this text with custom code or comments, and it will be preserved on regeneration
+1;
diff --git a/perllib/FixMyStreet/DB/Result/DefectType.pm b/perllib/FixMyStreet/DB/Result/DefectType.pm
new file mode 100644
index 000000000..a2969f59e
--- /dev/null
+++ b/perllib/FixMyStreet/DB/Result/DefectType.pm
@@ -0,0 +1,66 @@
+use utf8;
+package FixMyStreet::DB::Result::DefectType;
+
+# Created by DBIx::Class::Schema::Loader
+# DO NOT MODIFY THE FIRST PART OF THIS FILE
+
+use strict;
+use warnings;
+
+use base 'DBIx::Class::Core';
+__PACKAGE__->load_components("FilterColumn", "InflateColumn::DateTime", "EncodedColumn");
+__PACKAGE__->table("defect_types");
+__PACKAGE__->add_columns(
+ "id",
+ {
+ data_type => "integer",
+ is_auto_increment => 1,
+ is_nullable => 0,
+ sequence => "defect_types_id_seq",
+ },
+ "body_id",
+ { data_type => "integer", is_foreign_key => 1, is_nullable => 0 },
+ "name",
+ { data_type => "text", is_nullable => 0 },
+ "description",
+ { data_type => "text", is_nullable => 0 },
+ "extra",
+ { data_type => "text", is_nullable => 1 },
+);
+__PACKAGE__->set_primary_key("id");
+__PACKAGE__->add_unique_constraint("defect_types_body_id_name_key", ["body_id", "name"]);
+__PACKAGE__->belongs_to(
+ "body",
+ "FixMyStreet::DB::Result::Body",
+ { id => "body_id" },
+ { is_deferrable => 0, on_delete => "NO ACTION", on_update => "NO ACTION" },
+);
+__PACKAGE__->has_many(
+ "contact_defect_types",
+ "FixMyStreet::DB::Result::ContactDefectType",
+ { "foreign.defect_type_id" => "self.id" },
+ { cascade_copy => 0, cascade_delete => 0 },
+);
+__PACKAGE__->has_many(
+ "problems",
+ "FixMyStreet::DB::Result::Problem",
+ { "foreign.defect_type_id" => "self.id" },
+ { cascade_copy => 0, cascade_delete => 0 },
+);
+
+
+# Created by DBIx::Class::Schema::Loader v0.07035 @ 2017-02-13 15:11:11
+# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:BBLjb/aAoTKJZerdYCeBMQ
+
+__PACKAGE__->many_to_many( contacts => 'contact_defect_types', 'contact' );
+
+__PACKAGE__->load_components("+FixMyStreet::DB::RABXColumn");
+__PACKAGE__->rabx_column('extra');
+
+use Moo;
+use namespace::clean -except => [ 'meta' ];
+
+with 'FixMyStreet::Roles::Extra';
+
+
+1;
diff --git a/perllib/FixMyStreet/DB/Result/Problem.pm b/perllib/FixMyStreet/DB/Result/Problem.pm
index 203e72fae..84db41490 100644
--- a/perllib/FixMyStreet/DB/Result/Problem.pm
+++ b/perllib/FixMyStreet/DB/Result/Problem.pm
@@ -108,6 +108,8 @@ __PACKAGE__->add_columns(
{ data_type => "text", is_nullable => 1 },
"response_priority_id",
{ data_type => "integer", is_foreign_key => 1, is_nullable => 1 },
+ "defect_type_id",
+ { data_type => "integer", is_foreign_key => 1, is_nullable => 1 },
);
__PACKAGE__->set_primary_key("id");
__PACKAGE__->has_many(
@@ -116,6 +118,17 @@ __PACKAGE__->has_many(
{ "foreign.problem_id" => "self.id" },
{ cascade_copy => 0, cascade_delete => 0 },
);
+__PACKAGE__->belongs_to(
+ "defect_type",
+ "FixMyStreet::DB::Result::DefectType",
+ { id => "defect_type_id" },
+ {
+ is_deferrable => 0,
+ join_type => "LEFT",
+ on_delete => "NO ACTION",
+ on_update => "NO ACTION",
+ },
+);
__PACKAGE__->has_many(
"moderation_original_datas",
"FixMyStreet::DB::Result::ModerationOriginalData",
@@ -153,8 +166,8 @@ __PACKAGE__->has_many(
);
-# Created by DBIx::Class::Schema::Loader v0.07035 @ 2016-09-07 11:01:40
-# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:iH9c4VZZN/ONnhN6g89DFw
+# Created by DBIx::Class::Schema::Loader v0.07035 @ 2017-02-13 15:11:11
+# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:8zzWlJX7OQOdvrGxKuZUmg
# Add fake relationship to stored procedure table
__PACKAGE__->has_one(
@@ -182,6 +195,8 @@ use Utils;
use FixMyStreet::Map::FMS;
use LWP::Simple qw($ua);
use RABX;
+use URI;
+use URI::QueryParam;
my $IM = eval {
require Image::Magick;
@@ -511,6 +526,30 @@ sub admin_url {
return $cobrand->admin_base_url . '/report_edit/' . $self->id;
}
+=head2 tokenised_url
+
+Return a url for this problem report that logs a user in
+
+=cut
+
+sub tokenised_url {
+ my ($self, $user, $params) = @_;
+
+ my $token = FixMyStreet::App->model('DB::Token')->create(
+ {
+ scope => 'email_sign_in',
+ data => {
+ id => $self->id,
+ email => $user->email,
+ r => $self->url,
+ p => $params,
+ }
+ }
+ );
+
+ return "/M/". $token->token;
+}
+
=head2 is_open
Returns 1 if the problem is in a open state otherwise 0.
@@ -523,6 +562,16 @@ sub is_open {
return exists $self->open_states->{ $self->state } ? 1 : 0;
}
+=head2 is_in_progress
+
+Sees if the problem is in an open, not 'confirmed' state.
+
+=cut
+
+sub is_in_progress {
+ my $self = shift;
+ return $self->is_open && $self->state ne 'confirmed' ? 1 : 0;
+}
=head2 is_fixed
@@ -587,9 +636,7 @@ sub meta_line {
my $meta = '';
my $category = $problem->category;
- if ($c->cobrand->can('change_category_text')) {
- $category = $c->cobrand->change_category_text($category);
- }
+ $category = $c->cobrand->call_hook(change_category_text => $category) || $category;
if ( $problem->anonymous ) {
if ( $problem->service and $category && $category ne _('Other') ) {
@@ -606,20 +653,28 @@ sub meta_line {
$meta = sprintf( _('Reported anonymously at %s'), $date_time );
}
} else {
+ my $problem_name = $problem->name;
+
+ if ($c->user_exists and
+ $c->user->has_permission_to('view_body_contribute_details', $problem->bodies_str_ids) and
+ $problem->name ne $problem->user->name) {
+ $problem_name = sprintf('%s (%s)', $problem->name, $problem->user->name );
+ }
+
if ( $problem->service and $category && $category ne _('Other') ) {
$meta = sprintf(
_('Reported via %s in the %s category by %s at %s'),
$problem->service, $category,
- $problem->name, $date_time
+ $problem_name, $date_time
);
} elsif ( $problem->service ) {
$meta = sprintf( _('Reported via %s by %s at %s'),
- $problem->service, $problem->name, $date_time );
+ $problem->service, $problem_name, $date_time );
} elsif ( $category and $category ne _('Other') ) {
$meta = sprintf( _('Reported in the %s category by %s at %s'),
- $category, $problem->name, $date_time );
+ $category, $problem_name, $date_time );
} else {
- $meta = sprintf( _('Reported by %s at %s'), $problem->name, $date_time );
+ $meta = sprintf( _('Reported by %s at %s'), $problem_name, $date_time );
}
}
@@ -651,6 +706,34 @@ sub body {
return $body;
}
+
+=head2 time_ago
+ Returns how long ago a problem was reported in an appropriately
+ prettified duration, depending on the duration.
+=cut
+
+sub time_ago {
+ my ( $self, $date ) = @_;
+ $date ||= 'confirmed';
+ my $duration = time() - $self->$date->epoch;
+
+ return Utils::prettify_duration( $duration );
+}
+
+=head2 days_ago
+
+ Returns how many days ago a problem was reported.
+
+=cut
+
+sub days_ago {
+ my ( $self, $date ) = @_;
+ $date ||= 'confirmed';
+ my $now = DateTime->now( time_zone => FixMyStreet->time_zone || FixMyStreet->local_time_zone );
+ my $duration = $now->delta_days($self->$date);
+ return $duration->delta_days;
+}
+
=head2 response_templates
Returns all ResponseTemplates attached to this problem's bodies, in alphabetical
@@ -684,6 +767,18 @@ sub response_priorities {
return $self->result_source->schema->resultset('ResponsePriority')->for_bodies($self->bodies_str_ids, $self->category);
}
+=head2 defect_types
+
+Returns all DefectTypes attached to this problem's category/contact, in
+alphabetical order of name.
+
+=cut
+
+sub defect_types {
+ my $self = shift;
+ return $self->result_source->schema->resultset('DefectType')->for_bodies($self->bodies_str_ids, $self->category);
+}
+
# returns true if the external id is the council's ref, i.e., useful to publish it
# (by way of an example, the Oxfordshire send method returns a useful reference when
# it succeeds, so that is the ref we should show on the problem report page).
@@ -700,17 +795,10 @@ sub can_display_external_id {
sub duration_string {
my ( $problem, $c ) = @_;
- my $body;
- if ( $c->cobrand->can('link_to_council_cobrand') ) {
- $body = $c->cobrand->link_to_council_cobrand($problem);
- } else {
- $body = $problem->body( $c );
- }
- if ( $c->cobrand->can('get_body_handler_for_problem') ) {
- my $handler = $c->cobrand->get_body_handler_for_problem( $problem );
- if ( $handler->can('is_council_with_case_management') && $handler->is_council_with_case_management ) {
- return sprintf(_('Received by %s moments later'), $body);
- }
+ my $body = $c->cobrand->call_hook(link_to_council_cobrand => $problem) || $problem->body($c);
+ my $handler = $c->cobrand->call_hook(get_body_handler_for_problem => $problem);
+ if ( $handler && $handler->call_hook('is_council_with_case_management') ) {
+ return sprintf(_('Received by %s moments later'), $body);
}
return unless $problem->whensent;
return sprintf(_('Sent to %s %s later'), $body,
@@ -1048,4 +1136,24 @@ has duplicates => (
},
);
+has traffic_management_options => (
+ is => 'ro',
+ lazy => 1,
+ default => sub {
+ my $self = shift;
+ my $cobrand = $self->get_cobrand_logged;
+ $cobrand = $cobrand->call_hook(get_body_handler_for_problem => $self) || $cobrand;
+ return $cobrand->traffic_management_options;
+ },
+);
+
+has inspection_log_entry => (
+ is => 'ro',
+ lazy => 1,
+ default => sub {
+ my $self = shift;
+ return $self->admin_log_entries->search({ action => 'inspected' }, { order_by => { -desc => 'whenedited' } })->first;
+ },
+);
+
1;
diff --git a/perllib/FixMyStreet/DB/Result/ResponsePriority.pm b/perllib/FixMyStreet/DB/Result/ResponsePriority.pm
index 6bc8474fa..44635d174 100644
--- a/perllib/FixMyStreet/DB/Result/ResponsePriority.pm
+++ b/perllib/FixMyStreet/DB/Result/ResponsePriority.pm
@@ -26,6 +26,8 @@ __PACKAGE__->add_columns(
{ data_type => "boolean", default_value => \"false", is_nullable => 0 },
"description",
{ data_type => "text", is_nullable => 1 },
+ "external_id",
+ { data_type => "text", is_nullable => 1 },
);
__PACKAGE__->set_primary_key("id");
__PACKAGE__->add_unique_constraint("response_priorities_body_id_name_key", ["body_id", "name"]);
@@ -49,8 +51,8 @@ __PACKAGE__->has_many(
);
-# Created by DBIx::Class::Schema::Loader v0.07035 @ 2016-10-17 16:37:28
-# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:wok3cPA7cPjG4e9lnc1PIg
+# Created by DBIx::Class::Schema::Loader v0.07035 @ 2016-12-14 17:12:09
+# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:glsO0fLK6fNvg4TmW1DMPg
__PACKAGE__->many_to_many( contacts => 'contact_response_priorities', 'contact' );
diff --git a/perllib/FixMyStreet/DB/Result/ResponseTemplate.pm b/perllib/FixMyStreet/DB/Result/ResponseTemplate.pm
index 0d4377dba..5a2029eb1 100644
--- a/perllib/FixMyStreet/DB/Result/ResponseTemplate.pm
+++ b/perllib/FixMyStreet/DB/Result/ResponseTemplate.pm
@@ -33,6 +33,8 @@ __PACKAGE__->add_columns(
},
"auto_response",
{ data_type => "boolean", default_value => \"false", is_nullable => 0 },
+ "state",
+ { data_type => "text", is_nullable => 1 },
);
__PACKAGE__->set_primary_key("id");
__PACKAGE__->add_unique_constraint("response_templates_body_id_title_key", ["body_id", "title"]);
@@ -50,8 +52,8 @@ __PACKAGE__->has_many(
);
-# Created by DBIx::Class::Schema::Loader v0.07035 @ 2016-08-24 11:29:04
-# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:KRm0RHbtrzuxzH0S/UAsdw
+# Created by DBIx::Class::Schema::Loader v0.07035 @ 2016-12-01 15:10:52
+# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:ySPzQpFwJNki8XBjCNiqZQ
__PACKAGE__->many_to_many( contacts => 'contact_response_templates', 'contact' );
diff --git a/perllib/FixMyStreet/DB/Result/User.pm b/perllib/FixMyStreet/DB/Result/User.pm
index f4e5144f8..cf6de9a76 100644
--- a/perllib/FixMyStreet/DB/Result/User.pm
+++ b/perllib/FixMyStreet/DB/Result/User.pm
@@ -248,6 +248,15 @@ sub split_name {
return { first => $first || '', last => $last || '' };
}
+has body_permissions => (
+ is => 'ro',
+ lazy => 1,
+ default => sub {
+ my $self = shift;
+ return [ $self->user_body_permissions->all ];
+ },
+);
+
sub permissions {
my ($self, $c, $body_id) = @_;
@@ -258,9 +267,7 @@ sub permissions {
return unless $self->belongs_to_body($body_id);
- my @permissions = $self->user_body_permissions->search({
- body_id => $self->from_body->id,
- })->all;
+ my @permissions = grep { $_->body_id == $self->from_body->id } @{$self->body_permissions};
return { map { $_->permission_type => 1 } @permissions };
}
@@ -269,33 +276,37 @@ sub has_permission_to {
return 1 if $self->is_superuser;
return 0 if !$body_ids || (ref $body_ids && !@$body_ids);
+ $body_ids = [ $body_ids ] unless ref $body_ids;
+ my %body_ids = map { $_ => 1 } @$body_ids;
- my $permission = $self->user_body_permissions->find({
- permission_type => $permission_type,
- body_id => $body_ids,
- });
- return $permission ? 1 : 0;
+ foreach (@{$self->body_permissions}) {
+ return 1 if $_->permission_type eq $permission_type && $body_ids{$_->body_id};
+ }
+ return 0;
}
=head2 has_body_permission_to
-Checks if the User has a from_body set, and the specified permission on that body.
+Checks if the User has a from_body set, the specified permission on that body,
+and optionally that their from_body is one particular body.
Instead of saying:
- ($user->from_body && $user->has_permission_to('user_edit', $user->from_body->id))
+ ($user->from_body && $user->from_body->id == $body_id && $user->has_permission_to('user_edit', $body_id))
You can just say:
- $user->has_body_permission_to('user_edit')
-
-NB unlike has_permission_to, this doesn't blindly return 1 if the user is a superuser.
+ $user->has_body_permission_to('user_edit', $body_id)
=cut
sub has_body_permission_to {
- my ($self, $permission_type) = @_;
+ my ($self, $permission_type, $body_id) = @_;
+
+ return 1 if $self->is_superuser;
+
return unless $self->from_body;
+ return if $body_id && $self->from_body->id != $body_id;
return $self->has_permission_to($permission_type, $self->from_body->id);
}
@@ -371,6 +382,8 @@ around add_to_planned_reports => sub {
around remove_from_planned_reports => sub {
my ($orig, $self, $report) = @_;
$self->user_planned_reports->active->for_report($report->id)->remove();
+ $report->unset_extra_metadata('order');
+ $report->update;
};
sub active_planned_reports {
diff --git a/perllib/FixMyStreet/DB/ResultSet/DefectType.pm b/perllib/FixMyStreet/DB/ResultSet/DefectType.pm
new file mode 100644
index 000000000..a873ef252
--- /dev/null
+++ b/perllib/FixMyStreet/DB/ResultSet/DefectType.pm
@@ -0,0 +1,22 @@
+package FixMyStreet::DB::ResultSet::DefectType;
+use base 'DBIx::Class::ResultSet';
+
+use strict;
+use warnings;
+
+sub for_bodies {
+ my ($rs, $bodies, $category) = @_;
+ my $attrs = {
+ 'me.body_id' => $bodies,
+ };
+ if ($category) {
+ $attrs->{'contact.category'} = [ $category, undef ];
+ }
+ $rs->search($attrs, {
+ order_by => 'name',
+ join => { 'contact_defect_types' => 'contact' },
+ distinct => 1,
+ });
+}
+
+1;
diff --git a/perllib/FixMyStreet/Email.pm b/perllib/FixMyStreet/Email.pm
index 7d81c9dc5..ea84e3966 100644
--- a/perllib/FixMyStreet/Email.pm
+++ b/perllib/FixMyStreet/Email.pm
@@ -17,7 +17,7 @@ use mySociety::Random qw(random_bytes);
use Utils::Email;
use FixMyStreet;
use FixMyStreet::DB;
-use FixMyStreet::EmailSend;
+use FixMyStreet::Email::Sender;
sub test_dmarc {
my $email = shift;
@@ -77,6 +77,10 @@ sub _render_template {
return $var;
}
+sub unique_verp_id {
+ sprintf('fms-%s@%s', generate_verp_token(@_), FixMyStreet->config('EMAIL_DOMAIN'));
+}
+
sub _unique_id {
sprintf('fms-%s-%s@%s',
time(), unpack('h*', random_bytes(5, 1)),
@@ -183,7 +187,7 @@ sub send_cron {
print $email->as_string;
return 1; # Failure
} else {
- my $result = FixMyStreet::EmailSend->new({ env_from => $env_from })->send($email);
+ my $result = FixMyStreet::Email::Sender->try_to_send($email, { from => $env_from });
return $result ? 0 : 1;
}
}
diff --git a/perllib/FixMyStreet/Email/Sender.pm b/perllib/FixMyStreet/Email/Sender.pm
new file mode 100644
index 000000000..e6148a56c
--- /dev/null
+++ b/perllib/FixMyStreet/Email/Sender.pm
@@ -0,0 +1,50 @@
+package FixMyStreet::Email::Sender;
+
+use parent Email::Sender::Simple;
+use strict;
+use warnings;
+
+use Email::Sender::Util;
+use FixMyStreet;
+
+=head1 NAME
+
+FixMyStreet::Email::Sender
+
+=head1 DESCRIPTION
+
+Subclass of Email::Sender - configuring it correctly according to our config.
+
+If the config value 'SMTP_SMARTHOST' is set then email is routed via SMTP to
+that. Otherwise it is sent using a 'sendmail' like binary on the local system.
+
+And finally if if FixMyStreet->test_mode returns true then emails are not sent
+at all but are stored in memory for the test suite to inspect (using
+Email::Send::Test).
+
+=cut
+
+sub build_default_transport {
+ if ( FixMyStreet->test_mode ) {
+ Email::Sender::Util->easy_transport(Test => {});
+ } elsif ( my $smtp_host = FixMyStreet->config('SMTP_SMARTHOST') ) {
+ my $type = FixMyStreet->config('SMTP_TYPE') || '';
+ my $port = FixMyStreet->config('SMTP_PORT') || '';
+ my $username = FixMyStreet->config('SMTP_USERNAME') || '';
+ my $password = FixMyStreet->config('SMTP_PASSWORD') || '';
+
+ my $ssl = $type eq 'tls' ? 'starttls' : $type eq 'ssl' ? 'ssl' : '';
+ my $args = {
+ host => $smtp_host,
+ ssl => $ssl,
+ sasl_username => $username,
+ sasl_password => $password,
+ };
+ $args->{port} = $port if $port;
+ Email::Sender::Util->easy_transport(SMTP => $args);
+ } else {
+ Email::Sender::Util->easy_transport(Sendmail => {});
+ }
+}
+
+1;
diff --git a/perllib/FixMyStreet/EmailSend.pm b/perllib/FixMyStreet/EmailSend.pm
deleted file mode 100644
index 09f434931..000000000
--- a/perllib/FixMyStreet/EmailSend.pm
+++ /dev/null
@@ -1,78 +0,0 @@
-package FixMyStreet::EmailSend;
-
-use strict;
-use warnings;
-
-BEGIN {
- # Should move away from Email::Send, but until then:
- $Return::Value::NO_CLUCK = 1;
-}
-
-use FixMyStreet;
-use Email::Send;
-
-=head1 NAME
-
-FixMyStreet::EmailSend
-
-=head1 DESCRIPTION
-
-Thin wrapper around Email::Send - configuring it correctly according to our config.
-
-If the config value 'SMTP_SMARTHOST' is set then email is routed via SMTP to
-that. Otherwise it is sent using a 'sendmail' like binary on the local system.
-
-And finally if if FixMyStreet->test_mode returns true then emails are not sent
-at all but are stored in memory for the test suite to inspect (using
-Email::Send::Test).
-
-=cut
-
-my $args = undef;
-
-if ( FixMyStreet->test_mode ) {
- # Email::Send::Test
- $args = { mailer => 'Test', };
-} elsif ( my $smtp_host = FixMyStreet->config('SMTP_SMARTHOST') ) {
- # Email::Send::SMTP
- my $type = FixMyStreet->config('SMTP_TYPE') || '';
- my $port = FixMyStreet->config('SMTP_PORT') || '';
- my $username = FixMyStreet->config('SMTP_USERNAME') || '';
- my $password = FixMyStreet->config('SMTP_PASSWORD') || '';
-
- unless ($port) {
- $port = 25;
- $port = 465 if $type eq 'ssl';
- $port = 587 if $type eq 'tls';
- }
-
- my $mailer_args = [
- Host => $smtp_host,
- Port => $port,
- ];
- push @$mailer_args, ssl => 1 if $type eq 'ssl';
- push @$mailer_args, tls => 1 if $type eq 'tls';
- push @$mailer_args, username => $username, password => $password
- if $username && $password;
- $args = {
- mailer => 'FixMyStreet::EmailSend::Variable',
- mailer_args => $mailer_args,
- };
-} else {
- # Email::Send::Sendmail
- $args = { mailer => 'Sendmail' };
-}
-
-sub new {
- my ($cls, $hash) = @_;
- $hash ||= {};
- my %args = ( %$args, %$hash );
-
- my $sender = delete($args{env_from});
- if ($sender) {
- $args{mailer_args} = [ @{$args{mailer_args}} ] if $args{mailer_args};
- push @{$args{mailer_args}}, env_from => $sender;
- }
-
- return Email::Send->new(\%args);
-}
diff --git a/perllib/FixMyStreet/EmailSend/Variable.pm b/perllib/FixMyStreet/EmailSend/Variable.pm
deleted file mode 100644
index 4ba56dd41..000000000
--- a/perllib/FixMyStreet/EmailSend/Variable.pm
+++ /dev/null
@@ -1,17 +0,0 @@
-package FixMyStreet::EmailSend::Variable;
-use base Email::Send::SMTP;
-use FixMyStreet;
-
-my $sender;
-
-sub send {
- my ($class, $message, %args) = @_;
- $sender = delete($args{env_from}) || FixMyStreet->config('DO_NOT_REPLY_EMAIL');
- $class->SUPER::send($message, %args);
-}
-
-sub get_env_sender {
- $sender;
-}
-
-1;
diff --git a/perllib/FixMyStreet/Script/Alerts.pm b/perllib/FixMyStreet/Script/Alerts.pm
index 65183c09c..1a760a0c1 100644
--- a/perllib/FixMyStreet/Script/Alerts.pm
+++ b/perllib/FixMyStreet/Script/Alerts.pm
@@ -283,11 +283,7 @@ sub _send_aggregated_alert_email(%) {
} );
$data{unsubscribe_url} = $cobrand->base_url( $data{cobrand_data} ) . '/A/' . $token->token;
- my $sender = sprintf('<fms-%s@%s>',
- FixMyStreet::Email::generate_verp_token('alert', $data{alert_id}),
- FixMyStreet->config('EMAIL_DOMAIN')
- );
-
+ my $sender = FixMyStreet::Email::unique_verp_id('alert', $data{alert_id});
my $result = FixMyStreet::Email::send_cron(
$data{schema},
"$data{template}.txt",
diff --git a/perllib/FixMyStreet/Script/ArchiveOldEnquiries.pm b/perllib/FixMyStreet/Script/ArchiveOldEnquiries.pm
new file mode 100644
index 000000000..5d1d45379
--- /dev/null
+++ b/perllib/FixMyStreet/Script/ArchiveOldEnquiries.pm
@@ -0,0 +1,141 @@
+package FixMyStreet::Script::ArchiveOldEnquiries;
+
+use strict;
+use warnings;
+require 5.8.0;
+
+use FixMyStreet;
+use FixMyStreet::App;
+use FixMyStreet::DB;
+use FixMyStreet::Cobrand;
+use FixMyStreet::Map;
+use FixMyStreet::Email;
+
+
+my $opts = {
+ commit => 0,
+ body => '2237',
+ cobrand => 'oxfordshire',
+ closure_cutoff => "2015-01-01 00:00:00",
+ email_cutoff => "2016-01-01 00:00:00",
+};
+
+sub query {
+ return {
+ bodies_str => { 'LIKE', "%".$opts->{body}."%"},
+ -and => [
+ lastupdate => { '<', $opts->{email_cutoff} },
+ lastupdate => { '>', $opts->{closure_cutoff} },
+ ],
+ state => [ FixMyStreet::DB::Result::Problem->open_states() ],
+ };
+}
+
+sub archive {
+ my $params = shift;
+ if ( $params ) {
+ $opts = {
+ %$opts,
+ %$params,
+ };
+ }
+
+ unless ( $opts->{commit} ) {
+ printf "Doing a dry run; emails won't be sent and reports won't be closed.\n";
+ printf "Re-run with --commit to actually archive reports.\n\n";
+ }
+
+ my @user_ids = FixMyStreet::DB->resultset('Problem')->search(query(),
+ {
+ distinct => 1,
+ columns => ['user_id'],
+ rows => $opts->{limit},
+ })->all;
+
+ @user_ids = map { $_->user_id } @user_ids;
+
+ my $users = FixMyStreet::DB->resultset('User')->search({
+ id => \@user_ids
+ });
+
+ my $user_count = $users->count;
+ my $problem_count = FixMyStreet::DB->resultset('Problem')->search(query(),
+ {
+ columns => ['id'],
+ rows => $opts->{limit},
+ })->count;
+
+ printf("%d users will receive closure emails about %d reports which will be closed.\n", $user_count, $problem_count);
+
+ if ( $opts->{commit} ) {
+ my $i = 0;
+ while ( my $user = $users->next ) {
+ printf("%d/%d: User ID %d\n", ++$i, $user_count, $user->id);
+ send_email_and_close($user);
+ }
+ }
+
+ my $problems_to_close = FixMyStreet::DB->resultset('Problem')->search({
+ bodies_str => { 'LIKE', "%".$opts->{body}."%"},
+ lastupdate => { '<', $opts->{closure_cutoff} },
+ state => [ FixMyStreet::DB::Result::Problem->open_states() ],
+ }, {
+ rows => $opts->{limit},
+ });
+
+ printf("Closing %d old reports, without sending emails: ", $problems_to_close->count);
+
+ if ( $opts->{commit} ) {
+ $problems_to_close->update({ state => 'closed', send_questionnaire => 0 });
+ }
+
+ printf("done.\n")
+}
+
+sub send_email_and_close {
+ my ($user) = @_;
+
+ my $problems = $user->problems->search(query(), {
+ order_by => { -desc => 'confirmed' },
+ });
+
+ my @problems = $problems->all;
+
+ return if scalar(@problems) == 0;
+
+ my $cobrand = FixMyStreet::Cobrand->get_class_for_moniker($opts->{cobrand})->new();
+ $cobrand->set_lang_and_domain($problems[0]->lang, 1);
+ FixMyStreet::Map::set_map_class($cobrand->map_type);
+
+ my %h = (
+ reports => [@problems],
+ report_count => scalar(@problems),
+ site_name => $cobrand->moniker,
+ user => $user,
+ cobrand => $cobrand,
+ );
+
+ # Send email
+ printf(" Sending email about %d reports: ", scalar(@problems));
+ my $email_error = FixMyStreet::Email::send_cron(
+ $problems->result_source->schema,
+ 'archive.txt',
+ \%h,
+ {
+ To => [ [ $user->email, $user->name ] ],
+ },
+ undef,
+ undef,
+ $cobrand,
+ $problems[0]->lang,
+ );
+
+ unless ( $email_error ) {
+ printf("done.\n Closing reports: ");
+
+ $problems->update({ state => 'closed', send_questionnaire => 0 });
+ printf("done.\n");
+ } else {
+ printf("error! Not closing reports for this user.\n")
+ }
+}
diff --git a/perllib/FixMyStreet/Script/Reports.pm b/perllib/FixMyStreet/Script/Reports.pm
index 7d614bc30..6057807de 100644
--- a/perllib/FixMyStreet/Script/Reports.pm
+++ b/perllib/FixMyStreet/Script/Reports.pm
@@ -28,7 +28,7 @@ sub send(;$) {
my $base_url = FixMyStreet->config('BASE_URL');
my $site = $site_override || CronFns::site($base_url);
- my $states = [ 'confirmed', 'fixed' ];
+ my $states = [ FixMyStreet::DB::Result::Problem::open_states() ];
$states = [ 'unconfirmed', 'confirmed', 'in progress', 'planned', 'closed', 'investigating' ] if $site eq 'zurich';
my $unsent = $rs->search( {
state => $states,
@@ -112,9 +112,7 @@ sub send(;$) {
$h{user_details} .= sprintf(_('Email: %s'), $row->user->email) . "\n\n";
}
- if ($cobrand->can('process_additional_metadata_for_email')) {
- $cobrand->process_additional_metadata_for_email($row, \%h);
- }
+ $cobrand->call_hook(process_additional_metadata_for_email => $row, \%h);
my $bodies = FixMyStreet::DB->resultset('Body')->search(
{ id => $row->bodies_str_ids },
@@ -211,7 +209,7 @@ sub send(;$) {
. " ]\n\n";
}
- if (FixMyStreet->config('STAGING_SITE') && !FixMyStreet->config('SEND_REPORTS_ON_STAGING')) {
+ if (FixMyStreet->staging_flag('send_reports', 0)) {
# on a staging server send emails to ourselves rather than the bodies
%reporters = map { $_ => $reporters{$_} } grep { /FixMyStreet::SendReport::Email/ } keys %reporters;
unless (%reporters) {
@@ -276,7 +274,7 @@ sub send(;$) {
}
my $sending_errors = '';
my $unsent = $rs->search( {
- state => [ 'confirmed', 'fixed' ],
+ state => [ FixMyStreet::DB::Result::Problem::open_states() ],
whensent => undef,
bodies_str => { '!=', undef },
send_fail_count => { '>', 0 }
diff --git a/perllib/FixMyStreet/SendReport/Email.pm b/perllib/FixMyStreet/SendReport/Email.pm
index 2eab1c754..28f3411d0 100644
--- a/perllib/FixMyStreet/SendReport/Email.pm
+++ b/perllib/FixMyStreet/SendReport/Email.pm
@@ -2,6 +2,7 @@ package FixMyStreet::SendReport::Email;
use Moo;
use FixMyStreet::Email;
+use Utils::Email;
BEGIN { extends 'FixMyStreet::SendReport'; }
@@ -28,13 +29,6 @@ sub build_recipient_list {
$self->unconfirmed_notes->{$body_email}{$row->category} = $note;
}
- my $body_name = $body->name;
- # see something uses council areas but doesn't send to councils so just use a
- # generic name here to minimise confusion
- if ( $row->cobrand eq 'seesomething' ) {
- $body_name = 'See Something, Say Something';
- }
-
my @emails;
# allow multiple emails per contact
if ( $body_email =~ /,/ ) {
@@ -43,7 +37,7 @@ sub build_recipient_list {
@emails = ( $body_email );
}
for my $email ( @emails ) {
- push @{ $self->to }, [ $email, $body_name ];
+ push @{ $self->to }, [ $email, $body->name ];
}
}
@@ -67,7 +61,7 @@ sub send {
my $recips = $self->build_recipient_list( $row, $h );
# on a staging server send emails to ourselves rather than the bodies
- if (FixMyStreet->config('STAGING_SITE') && !FixMyStreet->config('SEND_REPORTS_ON_STAGING') && !FixMyStreet->test_mode) {
+ if (FixMyStreet->staging_flag('send_reports', 0) && !FixMyStreet->test_mode) {
$recips = 1;
@{$self->to} = [ $row->user->email, $self->to->[0][1] || $row->name ];
}
@@ -84,16 +78,14 @@ sub send {
From => $self->send_from( $row ),
};
- $cobrand->munge_sendreport_params($row, $h, $params) if $cobrand->can('munge_sendreport_params');
+ $cobrand->call_hook(munge_sendreport_params => $row, $h, $params);
$params->{Bcc} = $self->bcc if @{$self->bcc};
- my $sender = sprintf('<fms-%s@%s>',
- FixMyStreet::Email::generate_verp_token('report', $row->id),
- FixMyStreet->config('EMAIL_DOMAIN')
- );
+ my $sender = FixMyStreet::Email::unique_verp_id('report', $row->id);
- if (FixMyStreet::Email::test_dmarc($params->{From}[0])) {
+ if (FixMyStreet::Email::test_dmarc($params->{From}[0])
+ || Utils::Email::same_domain($params->{From}, $params->{To})) {
$params->{'Reply-To'} = [ $params->{From} ];
$params->{From} = [ $sender, $params->{From}[1] ];
}
diff --git a/perllib/FixMyStreet/SendReport/Open311.pm b/perllib/FixMyStreet/SendReport/Open311.pm
index ee40f371a..059690612 100644
--- a/perllib/FixMyStreet/SendReport/Open311.pm
+++ b/perllib/FixMyStreet/SendReport/Open311.pm
@@ -5,14 +5,7 @@ use namespace::autoclean;
BEGIN { extends 'FixMyStreet::SendReport'; }
-use DateTime::Format::W3CDTF;
use Open311;
-use Readonly;
-
-Readonly::Scalar my $COUNCIL_ID_OXFORDSHIRE => 2237;
-Readonly::Scalar my $COUNCIL_ID_WARWICKSHIRE => 2243;
-Readonly::Scalar my $COUNCIL_ID_GREENWICH => 2493;
-Readonly::Scalar my $COUNCIL_ID_BROMLEY => 2482;
has open311_test_req_used => (
is => 'rw',
@@ -27,47 +20,18 @@ sub send {
foreach my $body ( @{ $self->bodies } ) {
my $conf = $self->body_config->{ $body->id };
- my $always_send_latlong = 1;
- my $send_notpinpointed = 0;
- my $use_service_as_deviceid = 0;
-
- my $extended_desc = 1;
-
- my $extra = $row->get_extra_fields();
+ my %open311_params = (
+ jurisdiction => $conf->jurisdiction,
+ endpoint => $conf->endpoint,
+ api_key => $conf->api_key,
+ always_send_latlong => 1,
+ send_notpinpointed => 0,
+ use_service_as_deviceid => 0,
+ extended_description => 1,
+ );
- # Extra bromley fields
- if ( $row->bodies_str eq $COUNCIL_ID_BROMLEY ) {
- push @$extra, { name => 'report_url', value => $h->{url} };
- push @$extra, { name => 'report_title', value => $row->title };
- push @$extra, { name => 'public_anonymity_required', value => $row->anonymous ? 'TRUE' : 'FALSE' };
- push @$extra, { name => 'email_alerts_requested', value => 'FALSE' }; # always false as can never request them
- push @$extra, { name => 'requested_datetime', value => DateTime::Format::W3CDTF->format_datetime($row->confirmed->set_nanosecond(0)) };
- push @$extra, { name => 'email', value => $row->user->email };
- # make sure we have last_name attribute present in row's extra, so
- # it is passed correctly to Bromley as attribute[]
- if ( $row->cobrand ne 'bromley' ) {
- my ( $firstname, $lastname ) = ( $row->name =~ /(\w+)\.?\s+(.+)/ );
- push @$extra, { name => 'last_name', value => $lastname };
- }
- $always_send_latlong = 0;
- $send_notpinpointed = 1;
- $extended_desc = 0;
- } elsif ( $row->bodies_str =~ /\b$COUNCIL_ID_OXFORDSHIRE\b/ ) {
- # Oxfordshire doesn't have category metadata to fill these
- $extended_desc = 'oxfordshire';
- push @$extra, { name => 'external_id', value => $row->id };
- push @$extra, { name => 'closest_address', value => $h->{closest_address} } if $h->{closest_address};
- if ( $row->used_map || ( !$row->used_map && !$row->postcode ) ) {
- push @$extra, { name => 'northing', value => $h->{northing} };
- push @$extra, { name => 'easting', value => $h->{easting} };
- }
- } elsif ( $row->bodies_str =~ /\b$COUNCIL_ID_WARWICKSHIRE\b/ ) {
- $extended_desc = 'warwickshire';
- push @$extra, { name => 'closest_address', value => $h->{closest_address} } if $h->{closest_address};
- } elsif ( $row->bodies_str == $COUNCIL_ID_GREENWICH ) {
- # Greenwich doesn't have category metadata to fill this
- push @$extra, { name => 'external_id', value => $row->id };
- }
+ my $cobrand = $body->get_cobrand_handler || $row->get_cobrand_logged;
+ $cobrand->call_hook(open311_config => $row, $h, \%open311_params);
# Try and fill in some ones that we've been asked for, but not asked the user for
@@ -77,10 +41,14 @@ sub send {
category => $row->category
} );
+ my $extra = $row->get_extra_fields();
+
my $id_field = $contact->id_field;
foreach (@{$contact->get_extra_fields}) {
if ($_->{code} eq $id_field) {
push @$extra, { name => $id_field, value => $row->id };
+ } elsif ($_->{code} eq 'closest_address' && $h->{closest_address}) {
+ push @$extra, { name => $_->{code}, value => $h->{$_->{code}} };
} elsif ($_->{code} =~ /^(easting|northing)$/) {
if ( $row->used_map || ( !$row->used_map && !$row->postcode ) ) {
push @$extra, { name => $_->{code}, value => $h->{$_->{code}} };
@@ -90,15 +58,6 @@ sub send {
$row->set_extra_fields( @$extra ) if @$extra;
- my %open311_params = (
- jurisdiction => $conf->jurisdiction,
- endpoint => $conf->endpoint,
- api_key => $conf->api_key,
- always_send_latlong => $always_send_latlong,
- send_notpinpointed => $send_notpinpointed,
- use_service_as_deviceid => $use_service_as_deviceid,
- extended_description => $extended_desc,
- );
if (FixMyStreet->test_mode) {
my $test_res = HTTP::Response->new();
$test_res->code(200);
@@ -110,20 +69,7 @@ sub send {
my $open311 = Open311->new( %open311_params );
- # non standard west berks end points
- if ( $row->bodies_str =~ /2619/ ) {
- $open311->endpoints( { services => 'Services', requests => 'Requests' } );
- }
-
- # non-standard Oxfordshire endpoint (because it's just a script, not a full Open311 service)
- if ( $row->bodies_str =~ /$COUNCIL_ID_OXFORDSHIRE/ ) {
- $open311->endpoints( { requests => 'open311_service_request.cgi' } );
- }
-
- # required to get round issues with CRM constraints
- if ( $row->bodies_str =~ /2218/ ) {
- $row->user->name( $row->user->id . ' ' . $row->user->name );
- }
+ $cobrand->call_hook(open311_pre_send => $row, $open311);
my $resp = $open311->send_service_request( $row, $h, $contact->email );
if (FixMyStreet->test_mode) {
diff --git a/perllib/FixMyStreet/Template.pm b/perllib/FixMyStreet/Template.pm
index f41d11b69..4a9cffecb 100644
--- a/perllib/FixMyStreet/Template.pm
+++ b/perllib/FixMyStreet/Template.pm
@@ -62,7 +62,7 @@ sub loc : Fn {
=head2 nget
- [% nget( 'singular', 'plural', $number ) %]
+ [% nget( 'singular', 'plural', $number ) %]
Use first or second string depending on the number.
diff --git a/perllib/FixMyStreet/TestMech.pm b/perllib/FixMyStreet/TestMech.pm
index 122a5d0c9..166ba116f 100644
--- a/perllib/FixMyStreet/TestMech.pm
+++ b/perllib/FixMyStreet/TestMech.pm
@@ -10,10 +10,11 @@ BEGIN {
}
use Test::WWW::Mechanize::Catalyst 'FixMyStreet::App';
+use t::Mock::MapIt;
use Test::More;
use Web::Scraper;
use Carp;
-use Email::Send::Test;
+use FixMyStreet::Email::Sender;
use JSON::MaybeXS;
=head1 NAME
@@ -182,7 +183,7 @@ Clear the email queue.
sub clear_emails_ok {
my $mech = shift;
- Email::Send::Test->clear;
+ FixMyStreet::Email::Sender->default_transport->clear_deliveries;
$mech->builder->ok( 1, 'cleared email queue' );
return 1;
}
@@ -199,7 +200,7 @@ sub email_count_is {
my $mech = shift;
my $number = shift || 0;
- $mech->builder->is_num( scalar( Email::Send::Test->emails ),
+ $mech->builder->is_num( scalar( FixMyStreet::Email::Sender->default_transport->delivery_count ),
$number, "checking for $number email(s) in the queue" );
}
@@ -215,7 +216,8 @@ In list context returns all the emails (or none).
sub get_email {
my $mech = shift;
- my @emails = Email::Send::Test->emails;
+ my @emails = FixMyStreet::Email::Sender->default_transport->deliveries;
+ @emails = map { $_->{email}->object } @emails;
return @emails if wantarray;
@@ -610,6 +612,7 @@ sub delete_body {
my $body = shift;
$mech->delete_problems_for_body($body->id);
+ $mech->delete_defect_type($_) for $body->defect_types;
$mech->delete_contact($_) for $body->contacts;
$mech->delete_user($_) for $body->users;
$_->delete for $body->response_templates;
@@ -641,6 +644,14 @@ sub delete_problems_for_body {
}
}
+sub delete_defect_type {
+ my $mech = shift;
+ my $defect_type = shift;
+
+ $defect_type->contact_defect_types->delete_all;
+ $defect_type->delete;
+}
+
sub create_contact_ok {
my $self = shift;
my %contact_params = (