aboutsummaryrefslogtreecommitdiffstats
path: root/perllib/FixMyStreet/Cobrand
diff options
context:
space:
mode:
Diffstat (limited to 'perllib/FixMyStreet/Cobrand')
-rw-r--r--perllib/FixMyStreet/Cobrand/BathNES.pm68
-rw-r--r--perllib/FixMyStreet/Cobrand/Bexley.pm81
-rw-r--r--perllib/FixMyStreet/Cobrand/Bristol.pm7
-rw-r--r--perllib/FixMyStreet/Cobrand/Bromley.pm657
-rw-r--r--perllib/FixMyStreet/Cobrand/Buckinghamshire.pm28
-rw-r--r--perllib/FixMyStreet/Cobrand/CheshireEast.pm37
-rw-r--r--perllib/FixMyStreet/Cobrand/Default.pm54
-rw-r--r--perllib/FixMyStreet/Cobrand/EastSussex.pm5
-rw-r--r--perllib/FixMyStreet/Cobrand/FixMyStreet.pm96
-rw-r--r--perllib/FixMyStreet/Cobrand/Greenwich.pm4
-rw-r--r--perllib/FixMyStreet/Cobrand/Hackney.pm207
-rw-r--r--perllib/FixMyStreet/Cobrand/HighwaysEngland.pm9
-rw-r--r--perllib/FixMyStreet/Cobrand/Hounslow.pm86
-rw-r--r--perllib/FixMyStreet/Cobrand/IsleOfWight.pm31
-rw-r--r--perllib/FixMyStreet/Cobrand/Lincolnshire.pm7
-rw-r--r--perllib/FixMyStreet/Cobrand/Northamptonshire.pm11
-rw-r--r--perllib/FixMyStreet/Cobrand/Oxfordshire.pm91
-rw-r--r--perllib/FixMyStreet/Cobrand/Peterborough.pm61
-rw-r--r--perllib/FixMyStreet/Cobrand/Rutland.pm4
-rw-r--r--perllib/FixMyStreet/Cobrand/TfL.pm68
-rw-r--r--perllib/FixMyStreet/Cobrand/UK.pm92
-rw-r--r--perllib/FixMyStreet/Cobrand/UKCouncils.pm28
-rw-r--r--perllib/FixMyStreet/Cobrand/Westminster.pm8
-rw-r--r--perllib/FixMyStreet/Cobrand/Zurich.pm178
24 files changed, 1457 insertions, 461 deletions
diff --git a/perllib/FixMyStreet/Cobrand/BathNES.pm b/perllib/FixMyStreet/Cobrand/BathNES.pm
index 06095734b..e8e2c5427 100644
--- a/perllib/FixMyStreet/Cobrand/BathNES.pm
+++ b/perllib/FixMyStreet/Cobrand/BathNES.pm
@@ -90,14 +90,6 @@ sub send_questionnaires { 0 }
sub default_map_zoom { 3 }
-sub category_extra_hidden {
- my ($self, $meta) = @_;
- my $code = $meta->{code};
- # These two are used in the non-Open311 'Street light fault' category.
- return 1 if $code eq 'unitid' || $code eq 'asset_details';
- return $self->SUPER::category_extra_hidden($meta);
-}
-
sub available_permissions {
my $self = shift;
@@ -171,9 +163,8 @@ sub categories_restriction {
# Do a manual prefetch of all staff users for contributed_by lookup
sub _dashboard_user_lookup {
my $self = shift;
- my $c = $self->{c};
- my @user_ids = $c->model('DB::User')->search(
+ my @user_ids = FixMyStreet::DB->resultset('User')->search(
{ from_body => { '!=' => undef } },
{ columns => [ 'id', 'email' ] })->all;
@@ -182,23 +173,22 @@ sub _dashboard_user_lookup {
}
sub dashboard_export_updates_add_columns {
- my $self = shift;
- my $c = $self->{c};
+ my ($self, $csv) = @_;
- return unless $c->user->has_body_permission_to('export_extra_columns');
+ return unless $csv->user->has_body_permission_to('export_extra_columns');
- push @{$c->stash->{csv}->{headers}}, "Staff User";
- push @{$c->stash->{csv}->{headers}}, "User Email";
- push @{$c->stash->{csv}->{columns}}, "staff_user";
- push @{$c->stash->{csv}->{columns}}, "user_email";
+ $csv->add_csv_columns(
+ staff_user => 'Staff User',
+ user_email => 'User Email',
+ );
- $c->stash->{csv}->{objects} = $c->stash->{csv}->{objects}->search(undef, {
+ $csv->objects_attrs({
'+columns' => ['user.email'],
join => 'user',
});
my $user_lookup = $self->_dashboard_user_lookup;
- $c->stash->{csv}->{extra_data} = sub {
+ $csv->csv_extra_data(sub {
my $report = shift;
my $staff_user = '';
@@ -210,38 +200,28 @@ sub dashboard_export_updates_add_columns {
user_email => $report->user->email || '',
staff_user => $staff_user,
};
- };
+ });
}
sub dashboard_export_problems_add_columns {
- my $self = shift;
- my $c = $self->{c};
-
- return unless $c->user->has_body_permission_to('export_extra_columns');
-
- $c->stash->{csv}->{headers} = [
- @{ $c->stash->{csv}->{headers} },
- "User Email",
- "User Phone",
- "Staff User",
- "Attribute Data",
- ];
-
- $c->stash->{csv}->{columns} = [
- @{ $c->stash->{csv}->{columns} },
- "user_email",
- "user_phone",
- "staff_user",
- "attribute_data",
- ];
-
- $c->stash->{csv}->{objects} = $c->stash->{csv}->{objects}->search(undef, {
+ my ($self, $csv) = @_;
+
+ return unless $csv->user->has_body_permission_to('export_extra_columns');
+
+ $csv->add_csv_columns(
+ user_email => 'User Email',
+ user_phone => 'User Phone',
+ staff_user => 'Staff User',
+ attribute_data => "Attribute Data",
+ );
+
+ $csv->objects_attrs({
'+columns' => ['user.email', 'user.phone'],
join => 'user',
});
my $user_lookup = $self->_dashboard_user_lookup;
- $c->stash->{csv}->{extra_data} = sub {
+ $csv->csv_extra_data(sub {
my $report = shift;
my $staff_user = '';
@@ -255,7 +235,7 @@ sub dashboard_export_problems_add_columns {
staff_user => $staff_user,
attribute_data => $attribute_data,
};
- };
+ });
}
1;
diff --git a/perllib/FixMyStreet/Cobrand/Bexley.pm b/perllib/FixMyStreet/Cobrand/Bexley.pm
index 481926e72..063a225b7 100644
--- a/perllib/FixMyStreet/Cobrand/Bexley.pm
+++ b/perllib/FixMyStreet/Cobrand/Bexley.pm
@@ -3,10 +3,6 @@ use parent 'FixMyStreet::Cobrand::Whitelabel';
use strict;
use warnings;
-use Encode;
-use JSON::MaybeXS;
-use LWP::Simple qw($ua);
-use Path::Tiny;
use Time::Piece;
sub council_area_id { 2494 }
@@ -54,7 +50,7 @@ sub open311_munge_update_params {
$params->{service_request_id_ext} = $comment->problem->id;
- my $contact = $comment->problem->category_row;
+ my $contact = $comment->problem->contact;
$params->{service_code} = $contact->email;
}
@@ -88,8 +84,8 @@ sub open311_config {
$params->{multi_photos} = 1;
}
-sub open311_extra_data {
- my ($self, $row, $h, $extra, $contact) = @_;
+sub open311_extra_data_include {
+ my ($self, $row, $h, $contact) = @_;
my $open311_only;
if ($contact->email =~ /^Confirm/) {
@@ -103,7 +99,7 @@ sub open311_extra_data {
if (!$row->get_extra_field_value('site_code')) {
if (my $ref = $self->lookup_site_code($row, 'NSG_REF')) {
- push @$extra, { name => 'site_code', value => $ref, description => 'Site code' };
+ $row->update_extra_field({ name => 'site_code', value => $ref, description => 'Site code' });
}
}
} elsif ($contact->email =~ /^Uniform/) {
@@ -112,7 +108,7 @@ sub open311_extra_data {
# WFS service at the point we're sending the report over Open311.
if (!$row->get_extra_field_value('uprn')) {
if (my $ref = $self->lookup_site_code($row, 'UPRN')) {
- push @$extra, { name => 'uprn', description => 'UPRN', value => $ref };
+ $row->update_extra_field({ name => 'uprn', description => 'UPRN', value => $ref });
}
}
} else { # Symology
@@ -121,7 +117,7 @@ sub open311_extra_data {
# WFS service at the point we're sending the report over Open311.
if (!$row->get_extra_field_value('NSGRef')) {
if (my $ref = $self->lookup_site_code($row, 'NSG_REF')) {
- push @$extra, { name => 'NSGRef', description => 'NSG Ref', value => $ref };
+ $row->update_extra_field({ name => 'NSGRef', description => 'NSG Ref', value => $ref });
}
}
}
@@ -202,9 +198,6 @@ sub open311_post_send {
$self->open311_config($row, $h, {}, $contact); # Populate NSGRef again if needed
- my $extra_data = join "; ", map { "$_->{description}: $_->{value}" } @{$row->get_extra_fields};
- $h->{additional_information} = $extra_data;
-
$sender->send($row, $h);
}
@@ -216,71 +209,21 @@ sub email_list {
return @to;
}
-sub dashboard_export_problems_add_columns {
- my $self = shift;
- my $c = $self->{c};
-
- my %groups;
- if ($c->stash->{body}) {
- %groups = FixMyStreet::DB->resultset('Contact')->search({
- body_id => $c->stash->{body}->id,
- })->group_lookup;
- }
-
- splice @{$c->stash->{csv}->{headers}}, 5, 0, 'Subcategory';
- splice @{$c->stash->{csv}->{columns}}, 5, 0, 'subcategory';
-
- $c->stash->{csv}->{extra_data} = sub {
- my $report = shift;
-
- if ($groups{$report->category}) {
- return {
- category => $groups{$report->category},
- subcategory => $report->category,
- };
- }
- return {};
- };
-}
-
sub _is_out_of_hours {
my $time = localtime;
return 1 if $time->hour > 16 || ($time->hour == 16 && $time->min >= 45);
return 1 if $time->hour < 8;
return 1 if $time->wday == 1 || $time->wday == 7;
- return 1 if _is_bank_holiday();
+ return 1 if FixMyStreet::Cobrand::UK::is_public_holiday();
return 0;
}
-sub _is_bank_holiday {
- my $json = _get_bank_holiday_json();
- my $today = localtime->date;
- for my $event (@{$json->{'england-and-wales'}{events}}) {
- if ($event->{date} eq $today) {
- return 1;
- }
- }
-}
+sub update_anonymous_message {
+ my ($self, $update) = @_;
+ my $t = Utils::prettify_dt( $update->confirmed );
-sub _get_bank_holiday_json {
- my $file = 'bank-holidays.json';
- my $cache_file = path(FixMyStreet->path_to("../data/$file"));
- my $js;
- if (-s $cache_file && -M $cache_file <= 7 && !FixMyStreet->config('STAGING_SITE')) {
- # uncoverable statement
- $js = $cache_file->slurp_utf8;
- } else {
- $ua->timeout(5);
- $js = LWP::Simple::get("https://www.gov.uk/$file");
- # uncoverable branch false
- $js = decode_utf8($js) if !utf8::is_utf8($js);
- if ($js && !FixMyStreet->config('STAGING_SITE')) {
- # uncoverable statement
- $cache_file->spew_utf8($js);
- }
- }
- $js = JSON->new->decode($js) if $js;
- return $js;
+ my $staff = $update->user->from_body || $update->get_extra_metadata('is_body_user') || $update->get_extra_metadata('is_superuser');
+ return sprintf('Posted anonymously by a non-staff user at %s', $t) if !$staff;
}
1;
diff --git a/perllib/FixMyStreet/Cobrand/Bristol.pm b/perllib/FixMyStreet/Cobrand/Bristol.pm
index 6e3160c89..5e70c9456 100644
--- a/perllib/FixMyStreet/Cobrand/Bristol.pm
+++ b/perllib/FixMyStreet/Cobrand/Bristol.pm
@@ -52,7 +52,10 @@ sub categories_restriction {
# Email categories with a devolved send_method, so can identify Open311
# categories as those which have a blank send_method.
# Also Highways England categories have a blank send_method.
- return $rs->search( { 'me.send_method' => undef } );
+ return $rs->search( { -or => [
+ 'me.send_method' => undef, # Open311 categories
+ 'me.send_method' => '', # Open311 categories that have been edited in the admin
+ ] } );
}
sub open311_config {
@@ -68,8 +71,10 @@ sub open311_contact_meta_override {
$service->{group} = [];
my %server_set = (easting => 1, northing => 1);
+ my %hidden_field = (usrn => 1, asset_id => 1);
foreach (@$meta) {
$_->{automated} = 'server_set' if $server_set{$_->{code}};
+ $_->{automated} = 'hidden_field' if $hidden_field{$_->{code}};
}
}
diff --git a/perllib/FixMyStreet/Cobrand/Bromley.pm b/perllib/FixMyStreet/Cobrand/Bromley.pm
index 8f82817a8..cd923c19d 100644
--- a/perllib/FixMyStreet/Cobrand/Bromley.pm
+++ b/perllib/FixMyStreet/Cobrand/Bromley.pm
@@ -6,8 +6,17 @@ use warnings;
use utf8;
use DateTime::Format::W3CDTF;
use DateTime::Format::Flexible;
+use File::Temp;
+use Integrations::Echo;
+use JSON::MaybeXS;
+use Parallel::ForkManager;
+use Sort::Key::Natural qw(natkeysort_inplace);
+use Storable;
use Try::Tiny;
use FixMyStreet::DateRange;
+use FixMyStreet::WorkingDays;
+use Open311::GetServiceRequestUpdates;
+use Memcached;
sub council_area_id { return 2482; }
sub council_area { return 'Bromley'; }
@@ -171,11 +180,12 @@ sub open311_config {
$params->{extended_description} = 0;
}
-sub open311_extra_data {
- my ($self, $row, $h, $extra) = @_;
+sub open311_extra_data_include {
+ my ($self, $row, $h) = @_;
my $title = $row->title;
+ my $extra = $row->get_extra_fields;
foreach (@$extra) {
next unless $_->{value};
$title .= ' | ID: ' . $_->{value} if $_->{name} eq 'feature_id';
@@ -207,7 +217,11 @@ sub open311_extra_data {
push @$open311_only, { name => 'fms_extra_title', value => $row->user->title };
}
- return ($open311_only, [ 'feature_id', 'prow_reference' ]);
+ return $open311_only;
+}
+
+sub open311_extra_data_exclude {
+ [ 'feature_id', 'prow_reference' ]
}
sub open311_config_updates {
@@ -215,7 +229,7 @@ sub open311_config_updates {
$params->{endpoints} = {
service_request_updates => 'update.xml',
update => 'update.xml'
- };
+ } if $params->{endpoint} =~ /bromley.gov.uk/;
}
sub open311_pre_send {
@@ -228,6 +242,11 @@ sub open311_pre_send {
}
}
+sub open311_pre_send_updates {
+ my ($self, $row) = @_;
+ return $self->open311_pre_send($row);
+}
+
sub open311_munge_update_params {
my ($self, $params, $comment, $body) = @_;
delete $params->{update_id};
@@ -317,6 +336,8 @@ sub add_admin_subcategories {
my $c = $self->{c};
my $user = $c->stash->{user};
+ return $c->stash->{contacts} unless $user; # e.g. admin templates, not user
+
my @subcategories = @{$user->get_extra_metadata('subcategories') || []};
my %active_contacts = map { $_ => 1 } @subcategories;
@@ -328,7 +349,7 @@ sub add_admin_subcategories {
foreach (@{$subcats{$_->{id}}}) {
push @new_contacts, {
id => $_->{key},
- category => ("&nbsp;" x 4) . $_->{name},
+ category => (" " x 4) . $_->{name}, # nbsp
active => $active_contacts{$_->{key}},
};
}
@@ -344,25 +365,637 @@ sub munge_load_and_group_problems {
return unless $c->action eq 'dashboard/heatmap';
# Bromley subcategory stuff
- if (!$where->{category}) {
+ if (!$where->{'me.category'}) {
my $cats = $c->user->categories;
my $subcats = $c->user->get_extra_metadata('subcategories') || [];
- $where->{category} = [ @$cats, @$subcats ] if @$cats || @$subcats;
+ $where->{'me.category'} = [ @$cats, @$subcats ] if @$cats || @$subcats;
}
my %subcats = $self->subcategories;
my $subcat;
- my %chosen = map { $_ => 1 } @{$where->{category} || []};
+ my %chosen = map { $_ => 1 } @{$where->{'me.category'} || []};
my @subcat = grep { $chosen{$_} } map { $_->{key} } map { @$_ } values %subcats;
if (@subcat) {
my %chosen = map { $_ => 1 } @subcat;
$where->{'-or'} = {
- category => [ grep { !$chosen{$_} } @{$where->{category}} ],
- subcategory => \@subcat,
+ 'me.category' => [ grep { !$chosen{$_} } @{$where->{'me.category'}} ],
+ 'me.subcategory' => \@subcat,
};
- delete $where->{category};
+ delete $where->{'me.category'};
}
}
-1;
+# We want to send confirmation emails only for Waste reports
+sub report_sent_confirmation_email {
+ my ($self, $report) = @_;
+ my $contact = $report->contact or return;
+ return 'id' if grep { $_ eq 'Waste' } @{$report->contact->groups};
+ return '';
+}
+
+sub munge_around_category_where {
+ my ($self, $where) = @_;
+ $where->{extra} = [ undef, { -not_like => '%Waste%' } ];
+}
+
+sub munge_reports_category_list {
+ my ($self, $categories) = @_;
+ @$categories = grep { grep { $_ ne 'Waste' } @{$_->groups} } @$categories;
+}
+
+sub munge_report_new_contacts {
+ my ($self, $categories) = @_;
+
+ return if $self->{c}->action =~ /^waste/;
+
+ @$categories = grep { grep { $_ ne 'Waste' } @{$_->groups} } @$categories;
+ $self->SUPER::munge_report_new_contacts($categories);
+}
+
+sub updates_disallowed {
+ my $self = shift;
+ my ($problem) = @_;
+
+ # No updates on waste reports
+ return 'waste' if $problem->cobrand_data eq 'waste';
+
+ return $self->next::method(@_);
+}
+
+sub bin_addresses_for_postcode {
+ my $self = shift;
+ my $pc = shift;
+
+ my $echo = $self->feature('echo');
+ $echo = Integrations::Echo->new(%$echo);
+ my $points = $echo->FindPoints($pc);
+ my $data = [ map { {
+ value => $_->{Id},
+ label => FixMyStreet::Template::title($_->{Description}),
+ } } @$points ];
+ natkeysort_inplace { $_->{label} } @$data;
+ return $data;
+}
+
+sub look_up_property {
+ my $self = shift;
+ my $id = shift;
+
+ my $cfg = $self->feature('echo');
+ if ($cfg->{max_per_day}) {
+ my $today = DateTime->today->set_time_zone(FixMyStreet->local_time_zone)->ymd;
+ my $ip = $self->{c}->req->address;
+ my $key = FixMyStreet->test_mode ? "bromley-test" : "bromley-$ip-$today";
+ my $count = Memcached::increment($key, 86400) || 0;
+ $self->{c}->detach('/page_error_403_access_denied', []) if $count > $cfg->{max_per_day};
+ }
+
+ my $calls = $self->call_api(
+ GetPointAddress => [ $id ],
+ GetServiceUnitsForObject => [ $id ],
+ GetEventsForObject => [ 'PointAddress', $id ],
+ );
+
+ $self->{api_serviceunits} = $calls->{"GetServiceUnitsForObject $id"};
+ $self->{api_events} = $calls->{"GetEventsForObject PointAddress $id"};
+ my $result = $calls->{"GetPointAddress $id"};
+ return {
+ id => $result->{Id},
+ uprn => $result->{SharedRef}{Value}{anyType},
+ address => FixMyStreet::Template::title($result->{Description}),
+ latitude => $result->{Coordinates}{GeoPoint}{Latitude},
+ longitude => $result->{Coordinates}{GeoPoint}{Longitude},
+ };
+}
+
+my %irregulars = ( 1 => 'st', 2 => 'nd', 3 => 'rd', 11 => 'th', 12 => 'th', 13 => 'th');
+sub ordinal {
+ my $n = shift;
+ $irregulars{$n % 100} || $irregulars{$n % 10} || 'th';
+}
+
+sub construct_bin_date {
+ my $str = shift;
+ return unless $str;
+ my $offset = ($str->{OffsetMinutes} || 0) * 60;
+ my $zone = DateTime::TimeZone->offset_as_string($offset);
+ my $date = DateTime::Format::W3CDTF->parse_datetime($str->{DateTime});
+ $date->set_time_zone($zone);
+ return $date;
+}
+
+sub image_for_service {
+ my ($self, $service_id) = @_;
+ my $base = '/cobrands/bromley/images/container-images';
+ my $images = {
+ 531 => "$base/refuse-black-sack",
+ 532 => "$base/refuse-black-sack",
+ 533 => "$base/large-communal-black",
+ 535 => "$base/kerbside-green-box-mix",
+ 536 => "$base/small-communal-mix",
+ 537 => "$base/kerbside-black-box-paper",
+ 541 => "$base/small-communal-paper",
+ 542 => "$base/food-green-caddy",
+ 544 => "$base/food-communal",
+ 545 => "$base/garden-waste-bin",
+ };
+ return $images->{$service_id};
+}
+
+sub bin_services_for_address {
+ my $self = shift;
+ my $property = shift;
+
+ my %service_name_override = (
+ 531 => 'Non-Recyclable Refuse',
+ 532 => 'Non-Recyclable Refuse',
+ 533 => 'Non-Recyclable Refuse',
+ 535 => 'Mixed Recycling (Cans, Plastics & Glass)',
+ 536 => 'Mixed Recycling (Cans, Plastics & Glass)',
+ 537 => 'Paper & Cardboard',
+ 541 => 'Paper & Cardboard',
+ 542 => 'Food Waste',
+ 544 => 'Food Waste',
+ 545 => 'Garden Waste',
+ );
+
+ $self->{c}->stash->{containers} = {
+ 1 => 'Green Box (Plastic)',
+ 2 => 'Wheeled Bin (Plastic)',
+ 12 => 'Black Box (Paper)',
+ 13 => 'Wheeled Bin (Paper)',
+ 9 => 'Kitchen Caddy',
+ 10 => 'Outside Food Waste Container',
+ 45 => 'Wheeled Bin (Food)',
+ };
+ my %service_to_containers = (
+ 535 => [ 1 ],
+ 536 => [ 2 ],
+ 537 => [ 12 ],
+ 541 => [ 13 ],
+ 542 => [ 9, 10 ],
+ 544 => [ 45 ],
+ );
+ my %request_allowed = map { $_ => 1 } keys %service_to_containers;
+ my %quantity_max = (
+ 535 => 6,
+ 536 => 4,
+ 537 => 6,
+ 541 => 4,
+ 542 => 6,
+ 544 => 4,
+ );
+
+ my $result = $self->{api_serviceunits};
+ return [] unless @$result;
+
+ my $events = $self->{api_events};
+ my $open = $self->_parse_open_events($events);
+
+ my @to_fetch;
+ my %schedules;
+ my @task_refs;
+ foreach (@$result) {
+ next unless $_->{ServiceTasks};
+
+ my $servicetask = $_->{ServiceTasks}{ServiceTask};
+ my $schedules = _parse_schedules($servicetask);
+
+ next unless $schedules->{next} or $schedules->{last};
+ $schedules{$_->{Id}} = $schedules;
+ push @to_fetch, GetEventsForObject => [ ServiceUnit => $_->{Id} ];
+ push @task_refs, $schedules->{last}{ref} if $schedules->{last};
+ }
+ push @to_fetch, GetTasks => \@task_refs if @task_refs;
+
+ my $calls = $self->call_api(@to_fetch);
+
+ my @out;
+ my %task_ref_to_row;
+ foreach (@$result) {
+ next unless $schedules{$_->{Id}};
+ my $schedules = $schedules{$_->{Id}};
+ my $servicetask = $_->{ServiceTasks}{ServiceTask};
+
+ my $events = $calls->{"GetEventsForObject ServiceUnit $_->{Id}"};
+ my $open_unit = $self->_parse_open_events($events);
+
+ my $containers = $service_to_containers{$_->{ServiceId}};
+ my ($open_request) = grep { $_ } map { $open->{request}->{$_} } @$containers;
+ my $row = {
+ id => $_->{Id},
+ service_id => $_->{ServiceId},
+ service_name => $service_name_override{$_->{ServiceId}} || $_->{ServiceName},
+ report_open => $open->{missed}->{$_->{ServiceId}} || $open_unit->{missed}->{$_->{ServiceId}},
+ request_allowed => $request_allowed{$_->{ServiceId}},
+ request_open => $open_request,
+ request_containers => $containers,
+ request_max => $quantity_max{$_->{ServiceId}},
+ enquiry_open_events => $open->{enquiry},
+ service_task_id => $servicetask->{Id},
+ service_task_name => $servicetask->{TaskTypeName},
+ service_task_type_id => $servicetask->{TaskTypeId},
+ schedule => $servicetask->{ScheduleDescription},
+ last => $schedules->{last},
+ next => $schedules->{next},
+ };
+ if ($row->{last}) {
+ my $ref = join(',', @{$row->{last}{ref}});
+ $task_ref_to_row{$ref} = $row;
+ }
+ push @out, $row;
+ }
+ if (%task_ref_to_row) {
+ my $tasks = $calls->{GetTasks};
+ my $now = DateTime->now->set_time_zone(FixMyStreet->local_time_zone);
+ foreach (@$tasks) {
+ my $ref = join(',', @{$_->{Ref}{Value}{anyType}});
+ my $completed = construct_bin_date($_->{CompletedDate});
+ my $state = $_->{State}{Name} || '';
+ my $task_type_id = $_->{TaskTypeId} || '';
+
+ my $orig_resolution = $_->{Resolution}{Name} || '';
+ my $resolution = $orig_resolution;
+ my $resolution_id = $_->{Resolution}{Ref}{Value}{anyType};
+ if ($resolution_id) {
+ my $template = FixMyStreet::DB->resultset('ResponseTemplate')->search({
+ 'me.body_id' => $self->body->id,
+ 'me.external_status_code' => [
+ "$resolution_id,$task_type_id,$state",
+ "$resolution_id,$task_type_id,",
+ "$resolution_id,,$state",
+ "$resolution_id,,",
+ $resolution_id,
+ ],
+ })->first;
+ $resolution = $template->text if $template;
+ }
+
+ my $row = $task_ref_to_row{$ref};
+ $row->{last}{state} = $state;
+ $row->{last}{completed} = $completed;
+ $row->{last}{resolution} = $resolution;
+ $row->{report_allowed} = within_working_days($row->{last}{date}, 2);
+
+ # Special handling if last instance is today
+ if ($row->{last}{date}->ymd eq $now->ymd) {
+ # If it's before 5pm and outstanding, show it as in progress
+ if ($state eq 'Outstanding' && $now->hour < 17) {
+ $row->{next} = $row->{last};
+ $row->{next}{state} = 'In progress';
+ delete $row->{last};
+ }
+ if (!$completed && $now->hour < 17) {
+ $row->{report_allowed} = 0;
+ }
+ }
+
+ # If the task is ended and could not be done, do not allow reporting
+ if ($state eq 'Not Completed' || ($state eq 'Completed' && $orig_resolution eq 'Excess Waste')) {
+ $row->{report_allowed} = 0;
+ $row->{report_locked_out} = 1;
+ }
+ }
+ }
+
+ return \@out;
+}
+
+sub _parse_open_events {
+ my $self = shift;
+ my $events = shift;
+ my $open;
+ foreach (@$events) {
+ next if $_->{ResolvedDate};
+ next if $_->{ResolutionCodeId} && $_->{ResolutionCodeId} != 584; # Out of Stock
+ my $event_type = $_->{EventTypeId};
+ my $service_id = $_->{ServiceId};
+ if ($event_type == 2104) { # Request
+ my $data = $_->{Data}{ExtensibleDatum};
+ my $container;
+ DATA: foreach (@$data) {
+ if ($_->{ChildData}) {
+ foreach (@{$_->{ChildData}{ExtensibleDatum}}) {
+ if ($_->{DatatypeName} eq 'Container Type') {
+ $container = $_->{Value};
+ last DATA;
+ }
+ }
+ }
+ }
+ my $report = $self->problems->search({ external_id => $_->{Guid} })->first;
+ $open->{request}->{$container} = $report ? { report => $report } : 1;
+ } elsif (2095 <= $event_type && $event_type <= 2103) { # Missed collection
+ my $report = $self->problems->search({ external_id => $_->{Guid} })->first;
+ $open->{missed}->{$service_id} = $report ? { report => $report } : 1;
+ } else { # General enquiry of some sort
+ $open->{enquiry}->{$event_type} = 1;
+ }
+ }
+ return $open;
+}
+
+sub _parse_schedules {
+ my $servicetask = shift;
+ return unless $servicetask->{ServiceTaskSchedules};
+ my $schedules = $servicetask->{ServiceTaskSchedules}{ServiceTaskSchedule};
+ $schedules = [ $schedules ] unless ref $schedules eq 'ARRAY';
+
+ my $today = DateTime->now->set_time_zone(FixMyStreet->local_time_zone)->strftime("%F");
+ my ($min_next, $max_last, $next_changed);
+ foreach my $schedule (@$schedules) {
+ my $end_date = construct_bin_date($schedule->{EndDate})->strftime("%F");
+ next if $end_date lt $today;
+
+ my $next = $schedule->{NextInstance};
+ my $d = construct_bin_date($next->{CurrentScheduledDate});
+ if ($d && (!$min_next || $d < $min_next->{date})) {
+ $next_changed = $next->{CurrentScheduledDate}{DateTime} ne $next->{OriginalScheduledDate}{DateTime};
+ $min_next = {
+ date => $d,
+ ordinal => ordinal($d->day),
+ changed => $next_changed,
+ };
+ }
+
+ my $last = $schedule->{LastInstance};
+ $d = construct_bin_date($last->{CurrentScheduledDate});
+ # It is possible the last instance for this schedule has been rescheduled to
+ # be in the future. If so, we should treat it like it is a next instance.
+ if ($d && $d->strftime("%F") gt $today && (!$min_next || $d < $min_next->{date})) {
+ my $last_changed = $last->{CurrentScheduledDate}{DateTime} ne $last->{OriginalScheduledDate}{DateTime};
+ $min_next = {
+ date => $d,
+ ordinal => ordinal($d->day),
+ changed => $last_changed,
+ };
+ } elsif ($d && (!$max_last || $d > $max_last->{date})) {
+ my $last_changed = $last->{CurrentScheduledDate}{DateTime} ne $last->{OriginalScheduledDate}{DateTime};
+ $max_last = {
+ date => $d,
+ ordinal => ordinal($d->day),
+ changed => $last_changed,
+ ref => $last->{Ref}{Value}{anyType},
+ };
+ }
+ }
+
+ return {
+ next => $min_next,
+ last => $max_last,
+ };
+}
+
+sub bin_future_collections {
+ my $self = shift;
+
+ my $services = $self->{c}->stash->{service_data};
+ my @tasks;
+ my %names;
+ foreach (@$services) {
+ push @tasks, $_->{service_task_id};
+ $names{$_->{service_task_id}} = $_->{service_name};
+ }
+
+ my $echo = $self->feature('echo');
+ $echo = Integrations::Echo->new(%$echo);
+ my $result = $echo->GetServiceTaskInstances(@tasks);
+
+ my $events = [];
+ foreach (@$result) {
+ my $task_id = $_->{ServiceTaskRef}{Value}{anyType};
+ my $tasks = Integrations::Echo::force_arrayref($_->{Instances}, 'ScheduledTaskInfo');
+ foreach (@$tasks) {
+ my $dt = construct_bin_date($_->{CurrentScheduledDate});
+ my $summary = $names{$task_id} . ' collection';
+ my $desc = '';
+ push @$events, { date => $dt, summary => $summary, desc => $desc };
+ }
+ }
+ return $events;
+}
+
+=over
+
+=item within_working_days
+
+Given a DateTime object and a number, return true if today is less than or
+equal to that number of working days (excluding weekends and bank holidays)
+after the date.
+
+=cut
+
+sub within_working_days {
+ my ($dt, $days) = @_;
+ my $wd = FixMyStreet::WorkingDays->new(public_holidays => FixMyStreet::Cobrand::UK::public_holidays());
+ $dt = $wd->add_days($dt, $days)->ymd;
+ my $today = DateTime->now->set_time_zone(FixMyStreet->local_time_zone)->ymd;
+ return $today le $dt;
+}
+
+=item waste_fetch_events
+
+Loop through all open waste events to see if there have been any updates
+
+=back
+
+=cut
+
+sub waste_fetch_events {
+ my ($self, $verbose) = @_;
+
+ my $body = $self->body;
+ my @contacts = $body->contacts->search({
+ send_method => 'Open311',
+ endpoint => { '!=', '' },
+ })->all;
+ die "Could not find any devolved contacts\n" unless @contacts;
+
+ my %open311_conf = (
+ endpoint => $contacts[0]->endpoint || '',
+ api_key => $contacts[0]->api_key || '',
+ jurisdiction => $contacts[0]->jurisdiction || '',
+ extended_statuses => $body->send_extended_statuses,
+ );
+ my $cobrand = $body->get_cobrand_handler;
+ $cobrand->call_hook(open311_config_updates => \%open311_conf)
+ if $cobrand;
+ my $open311 = Open311->new(%open311_conf);
+
+ my $updates = Open311::GetServiceRequestUpdates->new(
+ current_open311 => $open311,
+ current_body => $body,
+ system_user => $body->comment_user,
+ suppress_alerts => 0,
+ blank_updates_permitted => $body->blank_updates_permitted,
+ );
+
+ my $echo = $self->feature('echo');
+ $echo = Integrations::Echo->new(%$echo);
+
+ my $cfg = {
+ verbose => $verbose,
+ updates => $updates,
+ echo => $echo,
+ event_types => {},
+ };
+
+ my $reports = $self->problems->search({
+ external_id => { '!=', '' },
+ state => [ FixMyStreet::DB::Result::Problem->open_states() ],
+ category => [ map { $_->category } @contacts ],
+ });
+
+ while (my $report = $reports->next) {
+ print 'Fetching data for report ' . $report->id . "\n" if $verbose;
+
+ my $event = $cfg->{echo}->GetEvent($report->external_id);
+ my $request = $self->construct_waste_open311_update($cfg, $event) or next;
+
+ next if !$request->{status} || $request->{status} eq 'confirmed'; # Still in initial state
+ next unless $self->waste_check_last_update(
+ $cfg, $report, $request->{status}, $request->{external_status_code});
+ my $last_updated = construct_bin_date($event->{LastUpdatedDate});
+ $request->{comment_time} = $last_updated;
+
+ print " Updating report to state $request->{status}, $request->{description} ($request->{external_status_code})\n" if $cfg->{verbose};
+ $cfg->{updates}->process_update($request, $report);
+ }
+}
+
+sub construct_waste_open311_update {
+ my ($self, $cfg, $event) = @_;
+
+ my $event_type = $cfg->{event_types}{$event->{EventTypeId}} ||= $self->waste_get_event_type($cfg, $event->{EventTypeId});
+ my $state_id = $event->{EventStateId};
+ my $resolution_id = $event->{ResolutionCodeId} || '';
+ my $status = $event_type->{states}{$state_id}{state};
+ my $description = $event_type->{resolution}{$resolution_id} || $event_type->{states}{$state_id}{name};
+ return {
+ description => $description,
+ status => $status,
+ update_id => 'waste',
+ external_status_code => "$resolution_id,,",
+ };
+}
+
+sub waste_get_event_type {
+ my ($self, $cfg, $id) = @_;
+
+ my $event_type = $cfg->{echo}->GetEventType($id);
+
+ my $state_map = {
+ New => { New => 'confirmed' },
+ Pending => {
+ Unallocated => 'investigating',
+ 'Allocated to Crew' => 'action scheduled',
+ },
+ Closed => {
+ Completed => 'fixed - council',
+ 'Not Completed' => 'unable to fix',
+ Rejected => 'closed',
+ },
+ };
+
+ my $states = $event_type->{Workflow}->{States}->{State};
+ my $data;
+ foreach (@$states) {
+ my $core = $_->{CoreState}; # New/Pending/Closed
+ my $name = $_->{Name}; # New : Unallocated/Allocated to Crew : Completed/Not Completed/Rejected
+ $data->{states}{$_->{Id}} = {
+ core => $core,
+ name => $name,
+ state => $state_map->{$core}{$name},
+ };
+ my $codes = Integrations::Echo::force_arrayref($_->{ResolutionCodes}, 'StateResolutionCode');
+ foreach (@$codes) {
+ my $name = $_->{Name};
+ my $id = $_->{ResolutionCodeId};
+ $data->{resolution}{$id} = $name;
+ }
+ }
+ return $data;
+}
+
+# We only have the report's current state, no history, so must check current
+# against latest received update to see if state the same, and skip if so
+sub waste_check_last_update {
+ my ($self, $cfg, $report, $status, $resolution_id) = @_;
+
+ my $latest = $report->comments->search(
+ { external_id => 'waste', },
+ { order_by => { -desc => 'id' } }
+ )->first;
+
+ if ($latest) {
+ my $state = $cfg->{updates}->current_open311->map_state($status);
+ my $code = $latest->get_extra_metadata('external_status_code') || '';
+ if ($latest->problem_state eq $state && $code eq $resolution_id) {
+ print " Latest update matches fetched state, skipping\n" if $cfg->{verbose};
+ return;
+ }
+ }
+ return 1;
+}
+
+sub admin_templates_external_status_code_hook {
+ my ($self) = @_;
+ my $c = $self->{c};
+
+ my $res_code = $c->get_param('resolution_code') || '';
+ my $task_type = $c->get_param('task_type') || '';
+ my $task_state = $c->get_param('task_state') || '';
+
+ return "$res_code,$task_type,$task_state";
+}
+
+sub call_api {
+ my $self = shift;
+
+ my $tmp = File::Temp->new;
+ my @cmd = (
+ FixMyStreet->path_to('bin/fixmystreet.com/bromley-echo'),
+ '--out', $tmp,
+ '--calls', encode_json(\@_),
+ );
+
+ # We cannot fork directly under mod_fcgid, so
+ # call an external script that calls back in.
+ my $data;
+ if (FixMyStreet->test_mode) {
+ $data = $self->_parallel_api_calls(@_);
+ } else {
+ system(@cmd);
+ $data = Storable::fd_retrieve($tmp);
+ }
+ return $data;
+}
+
+sub _parallel_api_calls {
+ my $self = shift;
+ my $echo = $self->feature('echo');
+ $echo = Integrations::Echo->new(%$echo);
+
+ my %calls;
+ my $pm = Parallel::ForkManager->new(FixMyStreet->test_mode ? 0 : 10);
+ $pm->run_on_finish(sub {
+ my ($pid, $exit_code, $ident, $exit_signal, $core_dump, $data) = @_;
+ %calls = ( %calls, %$data );
+ });
+
+ while (@_) {
+ my $call = shift;
+ my $args = shift;
+ $pm->start and next;
+ my $result = $echo->$call(@$args);
+ my $key = "$call @$args";
+ $key = $call if $call eq 'GetTasks';
+ $pm->finish(0, { $key => $result });
+ }
+ $pm->wait_all_children;
+
+ return \%calls;
+}
+
+1;
diff --git a/perllib/FixMyStreet/Cobrand/Buckinghamshire.pm b/perllib/FixMyStreet/Cobrand/Buckinghamshire.pm
index 117725273..f901c4e2f 100644
--- a/perllib/FixMyStreet/Cobrand/Buckinghamshire.pm
+++ b/perllib/FixMyStreet/Cobrand/Buckinghamshire.pm
@@ -45,16 +45,7 @@ sub send_questionnaires {
return 0;
}
-sub open311_pre_send {
- my ($self, $row, $open311) = @_;
-
- return unless $row->extra;
- my $extra = $row->get_extra_fields;
- if (@$extra) {
- @$extra = grep { $_->{name} ne 'road-placement' } @$extra;
- $row->set_extra_fields(@$extra);
- }
-}
+sub open311_extra_data_exclude { [ 'road-placement' ] }
sub open311_post_send {
my ($self, $row, $h) = @_;
@@ -103,6 +94,7 @@ sub report_new_munge_before_insert {
my ($self, $report) = @_;
return unless $report->category eq 'Flytipping';
+ return unless $self->{c}->stash->{report}->to_body_named('Buckinghamshire');
my $placement = $self->{c}->get_param('road-placement');
return unless $placement && $placement eq 'off-road';
@@ -132,19 +124,17 @@ sub map_type { 'Buckinghamshire' }
sub default_map_zoom { 3 }
sub _dashboard_export_add_columns {
- my $self = shift;
- my $c = $self->{c};
+ my ($self, $csv) = @_;
- push @{$c->stash->{csv}->{headers}}, "Staff User";
- push @{$c->stash->{csv}->{columns}}, "staff_user";
+ $csv->add_csv_columns( staff_user => 'Staff User' );
# All staff users, for contributed_by lookup
- my @user_ids = $c->model('DB::User')->search(
+ my @user_ids = FixMyStreet::DB->resultset('User')->search(
{ from_body => $self->body->id },
{ columns => [ 'id', 'email', ] })->all;
my %user_lookup = map { $_->id => $_->email } @user_ids;
- $c->stash->{csv}->{extra_data} = sub {
+ $csv->csv_extra_data(sub {
my $report = shift;
my $staff_user = '';
if (my $contributed_by = $report->get_extra_metadata('contributed_by')) {
@@ -153,15 +143,15 @@ sub _dashboard_export_add_columns {
return {
staff_user => $staff_user,
};
- };
+ });
}
sub dashboard_export_updates_add_columns {
- shift->_dashboard_export_add_columns;
+ shift->_dashboard_export_add_columns(@_);
}
sub dashboard_export_problems_add_columns {
- shift->_dashboard_export_add_columns;
+ shift->_dashboard_export_add_columns(@_);
}
# Enable adding/editing of parish councils in the admin
diff --git a/perllib/FixMyStreet/Cobrand/CheshireEast.pm b/perllib/FixMyStreet/Cobrand/CheshireEast.pm
index c5e5107f3..2a0423b7c 100644
--- a/perllib/FixMyStreet/Cobrand/CheshireEast.pm
+++ b/perllib/FixMyStreet/Cobrand/CheshireEast.pm
@@ -5,6 +5,7 @@ use strict;
use warnings;
use Moo;
+with 'FixMyStreet::Roles::ConfirmOpen311';
with 'FixMyStreet::Roles::ConfirmValidation';
sub council_area_id { 21069 }
@@ -56,39 +57,6 @@ sub abuse_reports_only { 1 }
sub send_questionnaires { 0 }
-sub open311_config {
- my ($self, $row, $h, $params) = @_;
-
- $params->{multi_photos} = 1;
-}
-
-sub open311_extra_data {
- my ($self, $row, $h, $extra) = @_;
-
- my $open311_only = [
- { name => 'report_url',
- value => $h->{url} },
- { name => 'title',
- value => $row->title },
- { name => 'description',
- value => $row->detail },
- ];
-
- # Reports made via FMS.com or the app probably won't have a site code
- # value because we don't display the adopted highways layer on those
- # frontends. Instead we'll look up the closest asset from the WFS
- # service at the point we're sending the report over Open311.
- if (!$row->get_extra_field_value('site_code')) {
- if (my $site_code = $self->lookup_site_code($row)) {
- push @$extra,
- { name => 'site_code',
- value => $site_code };
- }
- }
-
- return $open311_only;
-}
-
# TODO These values may not be accurate
sub lookup_site_code_config { {
buffer => 200, # metres
@@ -142,4 +110,7 @@ sub council_rss_alert_options {
return ( \@options, undef );
}
+# Make sure fetched report description isn't shown.
+sub filter_report_description { "" }
+
1;
diff --git a/perllib/FixMyStreet/Cobrand/Default.pm b/perllib/FixMyStreet/Cobrand/Default.pm
index 695487268..e58bceb2a 100644
--- a/perllib/FixMyStreet/Cobrand/Default.pm
+++ b/perllib/FixMyStreet/Cobrand/Default.pm
@@ -523,13 +523,29 @@ sub allow_update_reporting { return 0; }
=item updates_disallowed
Returns a boolean indicating whether updates on a particular report are allowed
-or not. Default behaviour is disallowed if "closed_updates" metadata is set.
+or not. Default behaviour is disallowed if "closed_updates" metadata is set, or
+if the report's category has its "updates_disallowed" flag set.
=cut
sub updates_disallowed {
my ($self, $problem) = @_;
return 1 if $problem->get_extra_metadata('closed_updates');
+ return 1 if $problem->contact && $problem->contact->get_extra_metadata('updates_disallowed');
+ return 0;
+}
+
+=item reopening_disallowed
+
+Returns a boolean indicating whether reopening of a particular report is
+allowed or not. Default behaviour is allowed unless the report's category
+has its reopening_disallowed flag set.
+
+=cut
+
+sub reopening_disallowed {
+ my ($self, $problem) = @_;
+ return 1 if $problem->contact && $problem->contact->get_extra_metadata('reopening_disallowed');
return 0;
}
@@ -941,11 +957,12 @@ Get stats to display on the council reports page
sub get_report_stats { return 0; }
sub get_body_sender {
- my ( $self, $body, $category ) = @_;
+ my ( $self, $body, $problem ) = @_;
# look up via category
+ my $category = $problem->category;
my $contact = $body->contacts->search( { category => $category } )->first;
- if ( $body->can_be_devolved && $contact->send_method ) {
+ if ( $body->can_be_devolved && $contact && $contact->send_method ) {
return { method => $contact->send_method, config => $contact, contact => $contact };
}
@@ -1055,7 +1072,7 @@ sub can_support_problems { return 0; }
=item default_map_zoom
default_map_zoom is used when displaying a map overriding the
-default of max-4 or max-3 depending on population density.
+default that depends on population density.
=cut
@@ -1107,7 +1124,22 @@ pressed in the front end, rather than whenever a username is not provided.
=cut
-sub allow_anonymous_reports { 0; }
+sub allow_anonymous_reports {
+ my ($self, $category_name) = @_;
+
+ $category_name ||= $self->{c}->stash->{category};
+ if ( $category_name && $self->can('body') and $self->body ) {
+ my $category_rs = FixMyStreet::DB->resultset("Contact")->search({
+ body_id => $self->body->id,
+ category => $category_name
+ });
+ if ( my $category = $category_rs->first ) {
+ return 'button' if $category->get_extra_metadata('anonymous_allowed');
+ }
+ }
+
+ return 0;
+}
=item anonymous_account
@@ -1216,15 +1248,13 @@ sub get_geocoder {
sub problem_as_hashref {
my $self = shift;
my $problem = shift;
- my $ctx = shift;
- return $problem->as_hashref( $ctx );
+ return $problem->as_hashref;
}
sub updates_as_hashref {
my $self = shift;
my $problem = shift;
- my $ctx = shift;
return {};
}
@@ -1256,14 +1286,6 @@ sub category_extra_hidden {
return 0;
}
-sub traffic_management_options {
- return [
- _("Yes"),
- _("No"),
- ];
-}
-
-
=item display_days_ago_threshold
Used to control whether a relative 'n days ago' or absolute date is shown
diff --git a/perllib/FixMyStreet/Cobrand/EastSussex.pm b/perllib/FixMyStreet/Cobrand/EastSussex.pm
index e6c2da6c5..b2fd58dc1 100644
--- a/perllib/FixMyStreet/Cobrand/EastSussex.pm
+++ b/perllib/FixMyStreet/Cobrand/EastSussex.pm
@@ -7,11 +7,10 @@ use warnings;
sub council_area_id { return 2224; }
sub open311_extra_data {
- my ($self, $row, $h, $extra, $contact) = @_;
+ my ($self, $row, $h, $contact) = @_;
$h->{es_original_detail} = $row->detail;
- $contact = $row->category_row;
my $fields = $contact->get_extra_fields;
my $text = '';
for my $field ( @$fields ) {
@@ -21,7 +20,7 @@ sub open311_extra_data {
}
}
$row->detail($row->detail . $text);
- return ();
+ return (undef, ['sect_label', 'road_name', 'area_name']);
}
sub open311_post_send {
diff --git a/perllib/FixMyStreet/Cobrand/FixMyStreet.pm b/perllib/FixMyStreet/Cobrand/FixMyStreet.pm
index dfb511f39..ae96924d8 100644
--- a/perllib/FixMyStreet/Cobrand/FixMyStreet.pm
+++ b/perllib/FixMyStreet/Cobrand/FixMyStreet.pm
@@ -35,15 +35,19 @@ sub restriction {
return {};
}
-# FixMyStreet needs to not show TfL reports...
+# FixMyStreet needs to not show TfL reports or Bromley waste reports
sub problems_restriction {
my ($self, $rs) = @_;
my $table = ref $rs eq 'FixMyStreet::DB::ResultSet::Nearby' ? 'problem' : 'me';
- return $rs->search({ "$table.cobrand" => { '!=' => 'tfl' } });
+ return $rs->search({
+ "$table.cobrand" => { '!=' => 'tfl' },
+ "$table.cobrand_data" => { '!=' => 'waste' },
+ });
}
sub problems_sql_restriction {
my $self = shift;
return "AND cobrand != 'tfl'";
+ # Doesn't need Bromley one as all waste reports non-public
}
sub relative_url_for_report {
@@ -54,32 +58,40 @@ sub relative_url_for_report {
sub munge_around_category_where {
my ($self, $where) = @_;
+ my $iow = grep { $_->name eq 'Isle of Wight Council' } @{ $self->{c}->stash->{around_bodies} };
+ if ($iow) {
+ # display all the categories on Isle of Wight at the moment as there's no way to
+ # do the expand bit later as we fetch it using ajax which uses a bounding box so
+ # can't determine the body
+ $where->{send_method} = [ { '!=' => 'Triage' }, undef ];
+ }
+ my $bromley = grep { $_->name eq 'Bromley Council' } @{ $self->{c}->stash->{around_bodies} };
+ if ($bromley) {
+ $where->{extra} = [ undef, { -not_like => '%Waste%' } ];
+ }
+}
+
+sub _iow_category_munge {
+ my ($self, $body, $categories) = @_;
my $user = $self->{c}->user;
- my @iow = grep { $_->name eq 'Isle of Wight Council' } @{ $self->{c}->stash->{around_bodies} };
- return unless @iow;
-
- # display all the categories on Isle of Wight at the moment as there's no way to
- # do the expand bit later as we fetch it using ajax which uses a bounding box so
- # can't determine the body
- $where->{send_method} = [ { '!=' => 'Triage' }, undef ];
- return $where;
+
+ if ( $user && ( $user->is_superuser || $user->belongs_to_body( $body->id ) ) ) {
+ @$categories = grep { !$_->send_method || $_->send_method ne 'Triage' } @$categories;
+ return;
+ }
+
+ @$categories = grep { $_->send_method && $_->send_method eq 'Triage' } @$categories;
}
-sub munge_reports_categories_list {
+sub munge_reports_category_list {
my ($self, $categories) = @_;
my %bodies = map { $_->body->name => $_->body } @$categories;
- if ( $bodies{'Isle of Wight Council'} ) {
- my $user = $self->{c}->user;
- my $b = $bodies{'Isle of Wight Council'};
-
- if ( $user && ( $user->is_superuser || $user->belongs_to_body( $b->id ) ) ) {
- @$categories = grep { !$_->send_method || $_->send_method ne 'Triage' } @$categories;
- return @$categories;
- }
-
- @$categories = grep { $_->send_method && $_->send_method eq 'Triage' } @$categories;
- return @$categories;
+ if ( my $body = $bodies{'Isle of Wight Council'} ) {
+ return $self->_iow_category_munge($body, $categories);
+ }
+ if ( $bodies{'Bromley Council'} ) {
+ @$categories = grep { grep { $_ ne 'Waste' } @{$_->groups} } @$categories;
}
}
@@ -118,16 +130,12 @@ sub munge_report_new_contacts {
my %bodies = map { $_->body->name => $_->body } @$contacts;
- if ( $bodies{'Isle of Wight Council'} ) {
- my $user = $self->{c}->user;
- if ( $user && ( $user->is_superuser || $user->belongs_to_body( $bodies{'Isle of Wight Council'}->id ) ) ) {
- @$contacts = grep { !$_->send_method || $_->send_method ne 'Triage' } @$contacts;
- return;
- }
-
- @$contacts = grep { $_->send_method && $_->send_method eq 'Triage' } @$contacts;
+ if ( my $body = $bodies{'Isle of Wight Council'} ) {
+ return $self->_iow_category_munge($body, $contacts);
+ }
+ if ( $bodies{'Bromley Council'} ) {
+ @$contacts = grep { grep { $_ ne 'Waste' } @{$_->groups} } @$contacts;
}
-
if ( $bodies{'TfL'} ) {
# Presented categories vary if we're on/off a red route
my $tfl = FixMyStreet::Cobrand->get_class_for_moniker( 'tfl' )->new({ c => $self->{c} });
@@ -139,10 +147,10 @@ sub munge_report_new_contacts {
sub munge_load_and_group_problems {
my ($self, $where, $filter) = @_;
- return unless $where->{category} && $self->{c}->stash->{body}->name eq 'Isle of Wight Council';
+ return unless $where->{'me.category'} && $self->{c}->stash->{body}->name eq 'Isle of Wight Council';
my $iow = FixMyStreet::Cobrand->get_class_for_moniker( 'isleofwight' )->new({ c => $self->{c} });
- $where->{category} = $iow->expand_triage_cat_list($where->{category}, $self->{c}->stash->{body});
+ $where->{'me.category'} = $iow->expand_triage_cat_list($where->{'me.category'}, $self->{c}->stash->{body});
}
sub title_list {
@@ -310,6 +318,19 @@ sub updates_disallowed {
return $self->next::method(@_);
}
+sub problem_state_processed {
+ my ($self, $comment) = @_;
+
+ my $state = $comment->problem_state || '';
+ my $code = $comment->get_extra_metadata('external_status_code') || '';
+
+ my ($cfg) = $self->per_body_config('extra_state_mapping', $comment->problem);
+
+ $state = ( $cfg->{$state}->{$code} || $state ) if $cfg->{$state};
+
+ return $state;
+}
+
sub suppress_reporter_alerts {
my $self = shift;
my $c = $self->{c};
@@ -347,4 +368,13 @@ sub manifest {
};
}
+sub report_new_munge_before_insert {
+ my ($self, $report) = @_;
+
+ # Make sure TfL reports are marked safety critical
+ $self->SUPER::report_new_munge_before_insert($report);
+
+ FixMyStreet::Cobrand::Buckinghamshire::report_new_munge_before_insert($self, $report);
+}
+
1;
diff --git a/perllib/FixMyStreet/Cobrand/Greenwich.pm b/perllib/FixMyStreet/Cobrand/Greenwich.pm
index be260d0c0..4cc4e4163 100644
--- a/perllib/FixMyStreet/Cobrand/Greenwich.pm
+++ b/perllib/FixMyStreet/Cobrand/Greenwich.pm
@@ -44,8 +44,8 @@ sub reports_per_page { return 20; }
sub admin_user_domain { 'royalgreenwich.gov.uk' }
-sub open311_extra_data {
- my ($self, $row, $h, $extra) = @_;
+sub open311_extra_data_include {
+ my ($self, $row, $h) = @_;
# Greenwich doesn't have category metadata to fill this
return [
diff --git a/perllib/FixMyStreet/Cobrand/Hackney.pm b/perllib/FixMyStreet/Cobrand/Hackney.pm
new file mode 100644
index 000000000..b8f92f1ea
--- /dev/null
+++ b/perllib/FixMyStreet/Cobrand/Hackney.pm
@@ -0,0 +1,207 @@
+package FixMyStreet::Cobrand::Hackney;
+use parent 'FixMyStreet::Cobrand::Whitelabel';
+
+use strict;
+use warnings;
+use mySociety::EmailUtil qw(is_valid_email is_valid_email_list);
+
+sub council_area_id { return 2508; }
+sub council_area { return 'Hackney'; }
+sub council_name { return 'Hackney Council'; }
+sub council_url { return 'hackney'; }
+sub send_questionnaires { 0 }
+
+sub disambiguate_location {
+ my $self = shift;
+ my $string = shift;
+
+ my $town = 'Hackney';
+
+ # Teale Street is on the boundary with Tower Hamlets and
+ # shows the 'please use fixmystreet.com' message, but Hackney
+ # do provide services on that road.
+ ($string, $town) = ('E2 9AA', '') if $string =~ /^teale\s+st/i;
+
+ return {
+ %{ $self->SUPER::disambiguate_location() },
+ string => $string,
+ town => $town,
+ centre => '51.552267,-0.063316',
+ bounds => [ 51.519814, -0.104511, 51.577784, -0.016527 ],
+ };
+}
+
+sub do_not_reply_email { shift->feature('do_not_reply_email') }
+
+sub verp_email_domain { shift->feature('verp_email_domain') }
+
+sub get_geocoder {
+ return 'OSM'; # default of Bing gives poor results, let's try overriding.
+}
+
+sub geocoder_munge_query_params {
+ my ($self, $params) = @_;
+
+ $params->{addressdetails} = 1;
+}
+
+sub geocoder_munge_results {
+ my ($self, $result) = @_;
+ if (my $a = $result->{address}) {
+ if ($a->{road} && $a->{suburb} && $a->{postcode}) {
+ $result->{display_name} = "$a->{road}, $a->{suburb}, $a->{postcode}";
+ return;
+ }
+ }
+ $result->{display_name} = '' unless $result->{display_name} =~ /Hackney/;
+ $result->{display_name} =~ s/, United Kingdom$//;
+ $result->{display_name} =~ s/, London, Greater London, England//;
+ $result->{display_name} =~ s/, London Borough of Hackney//;
+}
+
+
+sub open311_config {
+ my ($self, $row, $h, $params) = @_;
+
+ $params->{multi_photos} = 1;
+}
+
+sub open311_extra_data {
+ my ($self, $row, $h, $contact) = @_;
+
+ my $open311_only = [
+ { name => 'report_url',
+ value => $h->{url} },
+ { name => 'title',
+ value => $row->title },
+ { name => 'description',
+ value => $row->detail },
+ { name => 'category',
+ value => $row->category },
+ ];
+
+ # Make sure contact 'email' set correctly for Open311
+ if (my $sent_to = $row->get_extra_metadata('sent_to')) {
+ $row->unset_extra_metadata('sent_to');
+ my $code = $sent_to->{$contact->email};
+ $contact->email($code) if $code;
+ }
+
+ return $open311_only;
+}
+
+sub map_type { 'OSM' }
+
+sub default_map_zoom { 6 }
+
+sub admin_user_domain { 'hackney.gov.uk' }
+
+sub social_auth_enabled {
+ my $self = shift;
+
+ return $self->feature('oidc_login') ? 1 : 0;
+}
+
+sub anonymous_account {
+ my $self = shift;
+ return {
+ email => $self->feature('anonymous_account') . '@' . $self->admin_user_domain,
+ name => 'Anonymous user',
+ };
+}
+
+sub open311_skip_existing_contact {
+ my ($self, $contact) = @_;
+
+ # For Hackney we want the 'protected' flag to prevent any changes to this
+ # contact at all.
+ return $contact->get_extra_metadata("open311_protect") ? 1 : 0;
+}
+
+sub open311_filter_contacts_for_deletion {
+ my ($self, $contacts) = @_;
+
+ # Don't delete open311 protected contacts when importing
+ return $contacts->search({
+ extra => { -not_like => '%T15:open311_protect,I1:1%' },
+ });
+}
+
+sub problem_is_within_area_type {
+ my ($self, $problem, $type) = @_;
+ my $layer_map = {
+ park => "greenspaces:hackney_park",
+ estate => "housing:lbh_estate",
+ };
+ my $layer = $layer_map->{$type};
+ return unless $layer;
+
+ my ($x, $y) = $problem->local_coords;
+
+ my $cfg = {
+ url => "https://map.hackney.gov.uk/geoserver/wfs",
+ srsname => "urn:ogc:def:crs:EPSG::27700",
+ typename => $layer,
+ outputformat => "json",
+ filter => "<Filter xmlns:gml=\"http://www.opengis.net/gml\"><Intersects><PropertyName>geom</PropertyName><gml:Point srsName=\"27700\"><gml:coordinates>$x,$y</gml:coordinates></gml:Point></Intersects></Filter>",
+ };
+
+ my $features = $self->_fetch_features($cfg, $x, $y) || [];
+ return scalar @$features ? 1 : 0;
+}
+
+sub get_body_sender {
+ my ( $self, $body, $problem ) = @_;
+
+ my $contact = $body->contacts->search( { category => $problem->category } )->first;
+
+ if (my ($park, $estate, $other) = $self->_split_emails($contact->email)) {
+ my $to = $other;
+ if ($self->problem_is_within_area_type($problem, 'park')) {
+ $to = $park;
+ } elsif ($self->problem_is_within_area_type($problem, 'estate')) {
+ $to = $estate;
+ }
+ $problem->set_extra_metadata(sent_to => { $contact->email => $to });
+ if (is_valid_email($to)) {
+ return { method => 'Email', contact => $contact };
+ }
+ }
+ return $self->SUPER::get_body_sender($body, $problem);
+}
+
+# Translate email address to actual delivery address
+sub munge_sendreport_params {
+ my ($self, $row, $h, $params) = @_;
+
+ my $sent_to = $row->get_extra_metadata('sent_to') or return;
+ $row->unset_extra_metadata('sent_to');
+ for my $recip (@{$params->{To}}) {
+ my ($email, $name) = @$recip;
+ $recip->[0] = $sent_to->{$email} if $sent_to->{$email};
+ }
+}
+
+sub _split_emails {
+ my ($self, $email) = @_;
+
+ my $parts = join '\s*', qw(^ park : (.*?) ; estate : (.*?) ; other : (.*?) $);
+ my $regex = qr/$parts/i;
+
+ if (my ($park, $estate, $other) = $email =~ $regex) {
+ return ($park, $estate, $other);
+ }
+ return ();
+}
+
+sub validate_contact_email {
+ my ( $self, $email ) = @_;
+
+ return 1 if is_valid_email_list($email);
+
+ my @emails = grep { $_ } $self->_split_emails($email);
+ return unless @emails;
+ return 1 if is_valid_email_list(join(",", @emails));
+}
+
+1;
diff --git a/perllib/FixMyStreet/Cobrand/HighwaysEngland.pm b/perllib/FixMyStreet/Cobrand/HighwaysEngland.pm
index ed58eb4f7..c282ac5ea 100644
--- a/perllib/FixMyStreet/Cobrand/HighwaysEngland.pm
+++ b/perllib/FixMyStreet/Cobrand/HighwaysEngland.pm
@@ -29,6 +29,15 @@ sub users_restriction { FixMyStreet::Cobrand::UKCouncils::users_restriction($_[0
sub updates_restriction { FixMyStreet::Cobrand::UKCouncils::updates_restriction($_[0], $_[1]) }
sub base_url { FixMyStreet::Cobrand::UKCouncils::base_url($_[0]) }
+sub munge_problem_list {
+ my ($self, $problem) = @_;
+ $problem->anonymous(1);
+}
+sub munge_update_list {
+ my ($self, $update) = @_;
+ $update->anonymous(1);
+}
+
sub admin_allow_user {
my ( $self, $user ) = @_;
return 1 if $user->is_superuser;
diff --git a/perllib/FixMyStreet/Cobrand/Hounslow.pm b/perllib/FixMyStreet/Cobrand/Hounslow.pm
index 2fc949546..90d3b17dc 100644
--- a/perllib/FixMyStreet/Cobrand/Hounslow.pm
+++ b/perllib/FixMyStreet/Cobrand/Hounslow.pm
@@ -65,8 +65,14 @@ sub categories_restriction {
# Email categories with a devolved send_method, so can identify Open311
# categories as those which have a blank send_method.
return $rs->search({
- 'me.send_method' => undef,
'body.name' => [ 'Hounslow Borough Council', 'Highways England' ],
+ -or => [
+ 'me.send_method' => undef,
+ 'me.category' => { -in => [
+ 'Pavement Overcrowding',
+ 'Streetspace Suggestions and Feedback',
+ ] },
+ ],
});
}
@@ -120,40 +126,25 @@ sub open311_skip_report_fetch {
sub filter_report_description { "" }
sub setup_general_enquiries_stash {
- my $self = shift;
-
- my @bodies = $self->{c}->model('DB::Body')->active->for_areas(( $self->council_area_id ))->all;
- my %bodies = map { $_->id => $_ } @bodies;
- my @contacts #
- = $self->{c} #
- ->model('DB::Contact') #
- ->active
- ->search(
- {
- 'me.body_id' => [ keys %bodies ]
- },
- {
- prefetch => 'body',
- order_by => 'me.category',
- }
- )->all;
- @contacts = grep {
- my $group = $_->get_extra_metadata('group') || '';
- $group eq 'Other' || $group eq 'General Enquiries';
- } @contacts;
- $self->{c}->stash->{bodies} = \%bodies;
- $self->{c}->stash->{bodies_to_list} = \%bodies;
- $self->{c}->stash->{contacts} = \@contacts;
- $self->{c}->stash->{missing_details_bodies} = [];
- $self->{c}->stash->{missing_details_body_names} = [];
-
- $self->{c}->set_param('title', "General Enquiry");
- # Can't use (0, 0) for lat lon so default to the rough location
- # of Hounslow Highways HQ.
- $self->{c}->stash->{latitude} = 51.469;
- $self->{c}->stash->{longitude} = -0.35;
-
- return 1;
+ my $self = shift;
+ my $c = $self->{c};
+
+ $c->set_param('title', "General Enquiry");
+ # Can't use (0, 0) for lat lon so default to the rough location
+ # of Hounslow Highways HQ.
+ $c->stash->{latitude} = 51.469;
+ $c->stash->{longitude} = -0.35;
+
+ $c->stash->{all_areas} = { $self->council_area_id => { id => $self->council_area_id } };
+ $c->forward('/report/new/setup_categories_and_bodies');
+
+ my $contacts = $c->stash->{contacts};
+ @$contacts = grep {
+ my $groups = $_->groups;
+ grep { $_ eq 'Other' || $_ eq 'General Enquiries' } @$groups;
+ } @$contacts;
+
+ return 1;
}
sub abuse_reports_only { 1 }
@@ -171,4 +162,29 @@ sub lookup_site_code_config { {
# their cobrand at all.
sub cut_off_date { '2019-05-06' }
+sub front_stats_data {
+ my ( $self ) = @_;
+
+ my $recency = '1 week';
+ my $shorter_recency = '3 days';
+
+ my $completed = $self->problems->recent_completed();
+ my $updates = $self->problems->number_comments();
+ my $new = $self->problems->recent_new( $recency );
+
+ if ( $new > $completed ) {
+ $recency = $shorter_recency;
+ $new = $self->problems->recent_new( $recency );
+ }
+
+ my $stats = {
+ completed => $completed,
+ updates => $updates,
+ new => $new,
+ recency => $recency,
+ };
+
+ return $stats;
+}
+
1;
diff --git a/perllib/FixMyStreet/Cobrand/IsleOfWight.pm b/perllib/FixMyStreet/Cobrand/IsleOfWight.pm
index db0a20b9c..72555b9e6 100644
--- a/perllib/FixMyStreet/Cobrand/IsleOfWight.pm
+++ b/perllib/FixMyStreet/Cobrand/IsleOfWight.pm
@@ -6,6 +6,7 @@ use warnings;
use Moo;
with 'FixMyStreet::Roles::ConfirmOpen311';
+with 'FixMyStreet::Roles::ConfirmValidation';
sub council_area_id { 2636 }
sub council_area { 'Isle of Wight' }
@@ -63,20 +64,18 @@ sub lookup_site_code_config { {
accept_feature => sub { 1 }
} }
-sub open311_pre_send {
- my ($self, $row, $open311) = @_;
-
- return unless $row->extra;
- my $extra = $row->get_extra_fields;
- if (@$extra) {
- @$extra = grep { $_->{name} ne 'urgent' } @$extra;
- $row->set_extra_fields(@$extra);
- }
-}
+sub open311_extra_data_exclude { [ '^urgent$' ] }
# Make sure fetched report description isn't shown.
sub filter_report_description { "" }
+around 'open311_config' => sub {
+ my ($orig, $self, $row, $h, $params) = @_;
+
+ $params->{upload_files} = 1;
+ $self->$orig($row, $h, $params);
+};
+
sub open311_munge_update_params {
my ($self, $params, $comment, $body) = @_;
@@ -130,19 +129,18 @@ sub munge_around_category_where {
my $b = $self->{c}->model('DB::Body')->for_areas( $self->council_area_id )->first;
if ( $user && ( $user->is_superuser || $user->belongs_to_body( $b->id ) ) ) {
$where->{send_method} = [ { '!=' => 'Triage' }, undef ];
- return $where;
+ return;
}
$where->{'send_method'} = 'Triage';
- return $where;
}
sub munge_load_and_group_problems {
my ($self, $where, $filter) = @_;
- return unless $where->{category};
+ return unless $where->{'me.category'};
- $where->{category} = $self->_expand_triage_cat_list($where->{category});
+ $where->{'me.category'} = $self->_expand_triage_cat_list($where->{'me.category'});
}
sub munge_around_filter_category_list {
@@ -176,10 +174,7 @@ sub expand_triage_cat_list {
my %group_to_category;
while ( my $cat = $all_cats->next ) {
- next unless $cat->get_extra_metadata('group');
- my $groups = $cat->get_extra_metadata('group');
- $groups = ref $groups eq 'ARRAY' ? $groups : [ $groups ];
- for my $group ( @$groups ) {
+ for my $group ( @{$cat->groups} ) {
$group_to_category{$group} //= [];
push @{ $group_to_category{$group} }, $cat->category;
}
diff --git a/perllib/FixMyStreet/Cobrand/Lincolnshire.pm b/perllib/FixMyStreet/Cobrand/Lincolnshire.pm
index ee40bb173..d1fe319e1 100644
--- a/perllib/FixMyStreet/Cobrand/Lincolnshire.pm
+++ b/perllib/FixMyStreet/Cobrand/Lincolnshire.pm
@@ -77,4 +77,11 @@ sub pin_colour {
return 'yellow';
}
+around 'open311_config' => sub {
+ my ($orig, $self, $row, $h, $params) = @_;
+
+ $params->{upload_files} = 1;
+ $self->$orig($row, $h, $params);
+};
+
1;
diff --git a/perllib/FixMyStreet/Cobrand/Northamptonshire.pm b/perllib/FixMyStreet/Cobrand/Northamptonshire.pm
index 3e32b0856..2543f701d 100644
--- a/perllib/FixMyStreet/Cobrand/Northamptonshire.pm
+++ b/perllib/FixMyStreet/Cobrand/Northamptonshire.pm
@@ -91,10 +91,10 @@ sub open311_config {
$params->{multi_photos} = 1;
}
-sub open311_extra_data {
- my ($self, $row, $h, $extra) = @_;
+sub open311_extra_data_include {
+ my ($self, $row, $h) = @_;
- return ([
+ return [
{ name => 'report_url',
value => $h->{url} },
{ name => 'title',
@@ -103,10 +103,9 @@ sub open311_extra_data {
value => $row->detail },
{ name => 'category',
value => $row->category },
- ], [
- 'emergency'
- ]);
+ ];
}
+sub open311_extra_data_exclude { [ 'emergency' ] }
sub open311_get_update_munging {
my ($self, $comment) = @_;
diff --git a/perllib/FixMyStreet/Cobrand/Oxfordshire.pm b/perllib/FixMyStreet/Cobrand/Oxfordshire.pm
index 8ce12a81b..97174e1ce 100644
--- a/perllib/FixMyStreet/Cobrand/Oxfordshire.pm
+++ b/perllib/FixMyStreet/Cobrand/Oxfordshire.pm
@@ -122,8 +122,8 @@ sub open311_config {
$params->{extended_description} = 'oxfordshire';
}
-sub open311_extra_data {
- my ($self, $row, $h, $extra) = @_;
+sub open311_extra_data_include {
+ my ($self, $row, $h) = @_;
return [
{ name => 'external_id', value => $row->id },
@@ -138,6 +138,65 @@ sub open311_config_updates {
$params->{use_customer_reference} = 1;
}
+sub open311_pre_send {
+ my ($self, $row, $open311) = @_;
+
+ $self->{ox_original_detail} = $row->detail;
+
+ if (my $fid = $row->get_extra_field_value('feature_id')) {
+ my $text = "Asset Id: $fid\n\n" . $row->detail;
+ $row->detail($text);
+ }
+}
+
+sub open311_post_send {
+ my ($self, $row, $h, $contact) = @_;
+
+ $row->detail($self->{ox_original_detail});
+}
+
+sub open311_munge_update_params {
+ my ($self, $params, $comment, $body) = @_;
+
+ if ($comment->get_extra_metadata('defect_raised')) {
+ my $p = $comment->problem;
+ my ($e, $n) = $p->local_coords;
+ my $usrn = $p->get_extra_field_value('usrn');
+ if (!$usrn) {
+ my $cfg = {
+ url => 'https://tilma.mysociety.org/mapserver/oxfordshire',
+ typename => "OCCRoads",
+ srsname => 'urn:ogc:def:crs:EPSG::27700',
+ accept_feature => sub { 1 },
+ filter => "<Filter xmlns:gml=\"http://www.opengis.net/gml\"><DWithin><PropertyName>SHAPE_GEOMETRY</PropertyName><gml:Point><gml:coordinates>$e,$n</gml:coordinates></gml:Point><Distance units='m'>20</Distance></DWithin></Filter>",
+ };
+ my $features = $self->_fetch_features($cfg);
+ my $feature = $self->_nearest_feature($cfg, $e, $n, $features);
+ if ($feature) {
+ my $props = $feature->{properties};
+ $usrn = Utils::trim_text($props->{TYPE1_2_USRN});
+ }
+ }
+ $params->{'attribute[usrn]'} = $usrn;
+ $params->{'attribute[raise_defect]'} = 1;
+ $params->{'attribute[easting]'} = $e;
+ $params->{'attribute[northing]'} = $n;
+ my $details = $comment->user->email . ' ';
+ if (my $traffic = $p->get_extra_metadata('traffic_information')) {
+ $details .= 'TM1 ' if $traffic eq 'Signs and Cones';
+ $details .= 'TM2 ' if $traffic eq 'Stop and Go Boards';
+ }
+ (my $type = $p->get_extra_metadata('defect_item_type')) =~ s/ .*//;
+ $details .= $type eq 'Sweep' ? 'S&F' : $type;
+ $details .= ' ' . ($p->get_extra_metadata('detailed_information') || '');
+ $params->{'attribute[extra_details]'} = $details;
+
+ foreach (qw(defect_item_category defect_item_type defect_item_detail defect_location_description)) {
+ $params->{"attribute[$_]"} = $p->get_extra_metadata($_);
+ }
+ }
+}
+
sub should_skip_sending_update {
my ($self, $update ) = @_;
@@ -151,18 +210,20 @@ sub should_skip_sending_update {
return 0;
}
-sub on_map_default_status { return 'open'; }
-sub admin_user_domain { 'oxfordshire.gov.uk' }
+sub report_inspect_update_extra {
+ my ( $self, $problem ) = @_;
-sub traffic_management_options {
- return [
- "Signs and Cones",
- "Stop and Go Boards",
- "High Speed Roads",
- ];
+ foreach (qw(defect_item_category defect_item_type defect_item_detail defect_location_description)) {
+ my $value = $self->{c}->get_param($_);
+ $problem->set_extra_metadata($_ => $value) if $value;
+ }
}
+sub on_map_default_status { return 'open'; }
+
+sub admin_user_domain { 'oxfordshire.gov.uk' }
+
sub admin_pages {
my $self = shift;
@@ -203,13 +264,11 @@ sub available_permissions {
}
sub dashboard_export_problems_add_columns {
- my $self = shift;
- my $c = $self->{c};
+ my ($self, $csv) = @_;
- push @{$c->stash->{csv}->{headers}}, "HIAMS/Exor Ref";
- push @{$c->stash->{csv}->{columns}}, "external_ref";
+ $csv->add_csv_columns( external_ref => 'HIAMS/Exor Ref' );
- $c->stash->{csv}->{extra_data} = sub {
+ $csv->csv_extra_data(sub {
my $report = shift;
# Try and get a HIAMS reference first of all
my $ref = $report->get_extra_metadata('customer_reference');
@@ -222,7 +281,7 @@ sub dashboard_export_problems_add_columns {
return {
external_ref => ( $ref || '' ),
};
- };
+ });
}
1;
diff --git a/perllib/FixMyStreet/Cobrand/Peterborough.pm b/perllib/FixMyStreet/Cobrand/Peterborough.pm
index 0ddaeacb6..b10367cfd 100644
--- a/perllib/FixMyStreet/Cobrand/Peterborough.pm
+++ b/perllib/FixMyStreet/Cobrand/Peterborough.pm
@@ -13,6 +13,7 @@ sub council_area { 'Peterborough' }
sub council_name { 'Peterborough City Council' }
sub council_url { 'peterborough' }
sub map_type { 'MasterMap' }
+sub default_map_zoom { 5 }
sub send_questionnaires { 0 }
@@ -31,6 +32,8 @@ sub disambiguate_location {
sub get_geocoder { 'OSM' }
+sub contact_extra_fields { [ 'display_name' ] }
+
sub geocoder_munge_results {
my ($self, $result) = @_;
$result->{display_name} = '' unless $result->{display_name} =~ /City of Peterborough/;
@@ -40,30 +43,29 @@ sub geocoder_munge_results {
sub admin_user_domain { "peterborough.gov.uk" }
-around open311_extra_data => sub {
- my ($orig, $self, $row, $h, $extra) = @_;
+around open311_extra_data_include => sub {
+ my ($orig, $self, $row, $h) = @_;
- my $open311_only = $self->$orig($row, $h, $extra);
+ my $open311_only = $self->$orig($row, $h);
foreach (@$open311_only) {
if ($_->{name} eq 'description') {
my ($ref) = grep { $_->{name} =~ /pcc-Skanska-csc-ref/i } @{$row->get_extra_fields};
$_->{value} .= "\n\nSkanska CSC ref: $ref->{value}" if $ref;
}
}
+ if ( $row->geocode && $row->contact->email =~ /Bartec/ ) {
+ my $address = $row->geocode->{resourceSets}->[0]->{resources}->[0]->{address};
+ my ($number, $street) = $address->{addressLine} =~ /\s*(\d*)\s*(.*)/;
+ push @$open311_only, (
+ { name => 'postcode', value => $address->{postalCode} },
+ { name => 'house_no', value => $number },
+ { name => 'street', value => $street }
+ );
+ }
return $open311_only;
};
-
# remove categories which are informational only
-sub open311_pre_send {
- my ($self, $row, $open311) = @_;
-
- return unless $row->extra;
- my $extra = $row->get_extra_fields;
- if (@$extra) {
- @$extra = grep { $_->{name} !~ /^(PCC-|emergency$|private_land$)/i } @$extra;
- $row->set_extra_fields(@$extra);
- }
-}
+sub open311_extra_data_exclude { [ '^PCC-', '^emergency$', '^private_land$' ] }
sub lookup_site_code_config { {
buffer => 50, # metres
@@ -85,8 +87,37 @@ sub open311_munge_update_params {
# Send the FMS problem ID with the update.
$params->{service_request_id_ext} = $comment->problem->id;
- my $contact = $comment->problem->category_row;
+ my $contact = $comment->problem->contact;
$params->{service_code} = $contact->email;
}
+around 'open311_config' => sub {
+ my ($orig, $self, $row, $h, $params) = @_;
+
+ $params->{upload_files} = 1;
+ $self->$orig($row, $h, $params);
+};
+
+sub dashboard_export_problems_add_columns {
+ my ($self, $csv) = @_;
+
+ $csv->add_csv_columns(
+ usrn => 'USRN',
+ nearest_address => 'Nearest address',
+ );
+
+ $csv->csv_extra_data(sub {
+ my $report = shift;
+
+ my $address = '';
+ $address = $report->geocode->{resourceSets}->[0]->{resources}->[0]->{name}
+ if $report->geocode;
+
+ return {
+ usrn => $report->get_extra_field_value('site_code'),
+ nearest_address => $address,
+ };
+ });
+}
+
1;
diff --git a/perllib/FixMyStreet/Cobrand/Rutland.pm b/perllib/FixMyStreet/Cobrand/Rutland.pm
index 63a20d893..bc8eff6d2 100644
--- a/perllib/FixMyStreet/Cobrand/Rutland.pm
+++ b/perllib/FixMyStreet/Cobrand/Rutland.pm
@@ -29,8 +29,8 @@ sub open311_config {
$params->{multi_photos} = 1;
}
-sub open311_extra_data {
- my ($self, $row, $h, $extra) = @_;
+sub open311_extra_data_include {
+ my ($self, $row, $h) = @_;
return [
{ name => 'external_id', value => $row->id },
diff --git a/perllib/FixMyStreet/Cobrand/TfL.pm b/perllib/FixMyStreet/Cobrand/TfL.pm
index b98ad1d8b..b04841c39 100644
--- a/perllib/FixMyStreet/Cobrand/TfL.pm
+++ b/perllib/FixMyStreet/Cobrand/TfL.pm
@@ -209,7 +209,7 @@ sub around_nearby_filter {
sub state_groups_inspect {
my $rs = FixMyStreet::DB->resultset("State");
- my @open = grep { $_ !~ /^(planned|action scheduled|for triage)$/ } FixMyStreet::DB::Result::Problem->open_states;
+ my @open = grep { $_ !~ /^(planned|investigating|for triage)$/ } FixMyStreet::DB::Result::Problem->open_states;
my @closed = grep { $_ ne 'closed' } FixMyStreet::DB::Result::Problem->closed_states;
[
[ $rs->display('confirmed'), \@open ],
@@ -242,51 +242,32 @@ sub available_permissions {
}
sub dashboard_export_problems_add_columns {
- my $self = shift;
- my $c = $self->{c};
+ my ($self, $csv) = @_;
- my %groups;
- if ($c->stash->{body}) {
- %groups = FixMyStreet::DB->resultset('Contact')->search({
- body_id => $c->stash->{body}->id,
- })->group_lookup;
- }
+ $csv->modify_csv_header( Ward => 'Borough' );
+
+ $csv->add_csv_columns(
+ agent_responsible => "Agent responsible",
+ safety_critical => "Safety critical",
+ delivered_to => "Delivered to",
+ closure_email_at => "Closure email at",
+ reassigned_at => "Reassigned at",
+ reassigned_by => "Reassigned by",
+ );
+ $csv->splice_csv_column('fixed', action_scheduled => 'Action scheduled');
- splice @{$c->stash->{csv}->{headers}}, 5, 0, 'Subcategory';
- splice @{$c->stash->{csv}->{columns}}, 5, 0, 'subcategory';
-
- $c->stash->{csv}->{headers} = [
- map { $_ eq 'Ward' ? 'Borough' : $_ } @{ $c->stash->{csv}->{headers} },
- "Agent responsible",
- "Safety critical",
- "Delivered to",
- "Closure email at",
- "Reassigned at",
- "Reassigned by",
- ];
-
- $c->stash->{csv}->{columns} = [
- @{ $c->stash->{csv}->{columns} },
- "agent_responsible",
- "safety_critical",
- "delivered_to",
- "closure_email_at",
- "reassigned_at",
- "reassigned_by",
- ];
-
- if ($c->stash->{category}) {
- my ($contact) = grep { $_->category eq $c->stash->{category} } @{$c->stash->{contacts}};
+ if ($csv->category) {
+ my @contacts = $csv->body->contacts->search(undef, { order_by => [ 'category' ] } )->all;
+ my ($contact) = grep { $_->category eq $csv->category } @contacts;
if ($contact) {
foreach (@{$contact->get_metadata_for_storage}) {
next if $_->{code} eq 'safety_critical';
- push @{$c->stash->{csv}->{columns}}, "extra.$_->{code}";
- push @{$c->stash->{csv}->{headers}}, $_->{description};
+ $csv->add_csv_columns( "extra.$_->{code}" => $_->{description} );
}
}
}
- $c->stash->{csv}->{extra_data} = sub {
+ $csv->csv_extra_data(sub {
my $report = shift;
my $agent = $report->shortlisted_user;
@@ -315,8 +296,6 @@ sub dashboard_export_problems_add_columns {
my $fields = {
acknowledged => $report->whensent,
agent_responsible => $agent ? $agent->name : '',
- category => $groups{$report->category},
- subcategory => $report->category,
user_name_display => $user_name_display,
safety_critical => $safety_critical,
delivered_to => join(',', @$delivered_to),
@@ -329,7 +308,7 @@ sub dashboard_export_problems_add_columns {
$fields->{"extra.$_->{name}"} = $_->{value};
}
return $fields;
- };
+ });
}
sub must_have_2fa {
@@ -449,6 +428,13 @@ sub munge_surrounding_london {
# Don't send any TfL categories
%$bodies = map { $_->id => $_ } grep { $_->name ne 'TfL' } values %$bodies;
}
+
+ # Hackney doesn't have any of the council TfL categories so don't show
+ # any Hackney categories on red routes
+ my %bodies = map { $_->name => $_->id } values %$bodies;
+ if ( $bodies{'Hackney Council'} && $self->report_new_is_on_tlrn ) {
+ delete $bodies->{ $bodies{'Hackney Council'} };
+ }
}
sub munge_red_route_categories {
@@ -498,6 +484,7 @@ sub _tlrn_categories { [
"Mobile Crane Operation",
"Other (TfL)",
"Pavement Defect (uneven surface / cracked paving slab)",
+ "Pavement Overcrowding",
"Pothole",
"Pothole (minor)",
"Roadworks",
@@ -505,6 +492,7 @@ sub _tlrn_categories { [
"Single Light out (street light)",
"Standing water",
"Street Light - Equipment damaged, pole leaning",
+ "Streetspace Feedback",
"Unstable hoardings",
"Unstable scaffolding",
"Worn out road markings",
diff --git a/perllib/FixMyStreet/Cobrand/UK.pm b/perllib/FixMyStreet/Cobrand/UK.pm
index a42ff58a6..988458e0f 100644
--- a/perllib/FixMyStreet/Cobrand/UK.pm
+++ b/perllib/FixMyStreet/Cobrand/UK.pm
@@ -2,7 +2,11 @@ package FixMyStreet::Cobrand::UK;
use base 'FixMyStreet::Cobrand::Default';
use strict;
+use Encode;
use JSON::MaybeXS;
+use LWP::UserAgent;
+use Path::Tiny;
+use Time::Piece;
use mySociety::MaPit;
use mySociety::VotingArea;
use Utils;
@@ -397,9 +401,9 @@ sub link_to_council_cobrand {
$handler->moniker ne $self->{c}->cobrand->moniker
) {
my $url = sprintf("%s%s", $handler->base_url, $problem->url);
- return sprintf("<a href='%s'>%s</a>", $url, $problem->body( $self->{c} ));
+ return sprintf("<a href='%s'>%s</a>", $url, $problem->body);
} else {
- return $problem->body( $self->{c} );
+ return $problem->body;
}
}
@@ -407,12 +411,6 @@ sub lookup_by_ref_regex {
return qr/^\s*(\d+)\s*$/;
}
-sub category_extra_hidden {
- my ($self, $meta) = @_;
- return 1 if $meta->{code} eq 'usrn' || $meta->{code} eq 'asset_id';
- return $self->SUPER::category_extra_hidden($meta);
-}
-
sub report_new_munge_before_insert {
my ($self, $report) = @_;
@@ -422,4 +420,82 @@ sub report_new_munge_before_insert {
}
}
+# To use recaptcha, add a RECAPTCHA key to your config, with subkeys secret and
+# site_key, taken from the recaptcha site. This shows it to non-UK IP addresses
+# on alert and report pages.
+
+sub requires_recaptcha {
+ my $self = shift;
+ my $c = $self->{c};
+
+ return 0 if $c->user_exists;
+ return 0 if !FixMyStreet->config('RECAPTCHA');
+ return 0 unless $c->action =~ /^(alert|report|around)/;
+ return 0 if $c->user_country eq 'GB';
+ return 1;
+}
+
+sub check_recaptcha {
+ my $self = shift;
+ my $c = $self->{c};
+
+ return unless $self->requires_recaptcha;
+
+ my $url = 'https://www.google.com/recaptcha/api/siteverify';
+ my $res = LWP::UserAgent->new->post($url, {
+ secret => FixMyStreet->config('RECAPTCHA')->{secret},
+ response => $c->get_param('g-recaptcha-response'),
+ remoteip => $c->req->address,
+ });
+ $res = decode_json($res->content);
+ $c->detach('/page_error_400_bad_request', ['Bad recaptcha'])
+ unless $res->{success};
+}
+
+sub public_holidays {
+ my $nation = shift || 'england-and-wales';
+ my $json = _get_bank_holiday_json();
+ return [ map { $_->{date} } @{$json->{$nation}{events}} ];
+}
+
+sub is_public_holiday {
+ my %args = @_;
+ $args{date} ||= localtime;
+ $args{date} = $args{date}->date;
+ $args{nation} ||= 'england-and-wales';
+ my $json = _get_bank_holiday_json();
+ for my $event (@{$json->{$args{nation}}{events}}) {
+ if ($event->{date} eq $args{date}) {
+ return 1;
+ }
+ }
+}
+
+sub _get_bank_holiday_json {
+ my $file = 'bank-holidays.json';
+ my $cache_file = path(FixMyStreet->path_to("../data/$file"));
+ my $js;
+ if (-s $cache_file && -M $cache_file <= 7 && !FixMyStreet->config('STAGING_SITE')) {
+ # uncoverable statement
+ $js = $cache_file->slurp_utf8;
+ } else {
+ $js = _fetch_url("https://www.gov.uk/$file");
+ # uncoverable branch false
+ $js = decode_utf8($js) if !utf8::is_utf8($js);
+ if ($js && !FixMyStreet->config('STAGING_SITE')) {
+ # uncoverable statement
+ $cache_file->spew_utf8($js);
+ }
+ }
+ $js = JSON->new->decode($js) if $js;
+ return $js;
+}
+
+sub _fetch_url {
+ my $url = shift;
+ my $ua = LWP::UserAgent->new;
+ $ua->timeout(5);
+ $ua->get($url)->content;
+}
+
1;
diff --git a/perllib/FixMyStreet/Cobrand/UKCouncils.pm b/perllib/FixMyStreet/Cobrand/UKCouncils.pm
index 21dd2d455..0e8341d57 100644
--- a/perllib/FixMyStreet/Cobrand/UKCouncils.pm
+++ b/perllib/FixMyStreet/Cobrand/UKCouncils.pm
@@ -270,6 +270,19 @@ sub relative_url_for_report {
return FixMyStreet->config('BASE_URL');
}
+sub problem_state_processed {
+ my ($self, $comment) = @_;
+
+ my $state = $comment->problem_state || '';
+ my $code = $comment->get_extra_metadata('external_status_code') || '';
+
+ my $cfg = $self->feature('extra_state_mapping');
+
+ $state = ( $cfg->{$state}->{$code} || $state ) if $cfg->{$state};
+
+ return $state;
+}
+
sub admin_allow_user {
my ( $self, $user ) = @_;
return 1 if $user->is_superuser;
@@ -329,6 +342,13 @@ sub munge_report_new_contacts {
}
}
+sub open311_extra_data {
+ my $self = shift;
+ my $include = $self->call_hook(open311_extra_data_include => @_);
+ my $exclude = $self->call_hook(open311_extra_data_exclude => @_);
+ push @$exclude, 'sect_label', 'road_name', 'area_name';
+ return ($include, $exclude);
+};
=head2 lookup_site_code
@@ -392,7 +412,7 @@ sub _fetch_features_url {
SRSNAME => $cfg->{srsname},
TYPENAME => $cfg->{typename},
VERSION => "1.1.0",
- outputformat => "geojson",
+ outputformat => $cfg->{outputformat} || "geojson",
$cfg->{filter} ? ( Filter => $cfg->{filter} ) : ( BBOX => $cfg->{bbox} ),
);
@@ -405,7 +425,7 @@ sub _nearest_feature {
# We have a list of features, and we want to find the one closest to the
# report location.
- my $site_code = '';
+ my $chosen = '';
my $nearest;
# We shouldn't receive anything aside from these geometry types, but belt and braces.
@@ -432,14 +452,14 @@ sub _nearest_feature {
for (my $i=0; $i<@$coordinates-1; $i++) {
my $distance = $self->_distanceToLine($x, $y, $coordinates->[$i], $coordinates->[$i+1]);
if ( !defined $nearest || $distance < $nearest ) {
- $site_code = $feature->{properties}->{$cfg->{property}};
+ $chosen = $feature;
$nearest = $distance;
}
}
}
}
- return $site_code;
+ return $cfg->{property} && $chosen ? $chosen->{properties}->{$cfg->{property}} : $chosen;
}
sub contact_name {
diff --git a/perllib/FixMyStreet/Cobrand/Westminster.pm b/perllib/FixMyStreet/Cobrand/Westminster.pm
index c9f31f7f9..e00a7c092 100644
--- a/perllib/FixMyStreet/Cobrand/Westminster.pm
+++ b/perllib/FixMyStreet/Cobrand/Westminster.pm
@@ -78,15 +78,15 @@ sub open311_config {
$h->{account_id} = $id || '0';
}
-sub open311_extra_data {
- my ($self, $row, $h, $extra) = @_;
+sub open311_extra_data_include {
+ my ($self, $row, $h) = @_;
# Reports made via the app probably won't have a USRN because we don't
# display the road layer. Instead we'll look up the closest asset from the
# asset service at the point we're sending the report over Open311.
if (!$row->get_extra_field_value('USRN')) {
if (my $ref = $self->lookup_site_code($row, 'USRN')) {
- push @$extra, { name => 'USRN', value => $ref };
+ $row->update_extra_field({ name => 'USRN', value => $ref });
}
}
@@ -96,7 +96,7 @@ sub open311_extra_data {
my ($uprn_field) = grep { $_->{name} eq 'UPRN' } @$fields;
if ( $uprn_field && !$uprn_field->{value} ) {
if (my $ref = $self->lookup_site_code($row, 'UPRN')) {
- push @$extra, { name => 'UPRN', value => $ref };
+ $row->update_extra_field({ name => 'UPRN', value => $ref });
}
}
diff --git a/perllib/FixMyStreet/Cobrand/Zurich.pm b/perllib/FixMyStreet/Cobrand/Zurich.pm
index 3cf678f9c..c7b9f70ee 100644
--- a/perllib/FixMyStreet/Cobrand/Zurich.pm
+++ b/perllib/FixMyStreet/Cobrand/Zurich.pm
@@ -10,6 +10,8 @@ use DateTime::Format::Pg;
use Try::Tiny;
use FixMyStreet::Geocode::Zurich;
+use FixMyStreet::Template;
+use FixMyStreet::WorkingDays;
use strict;
use warnings;
@@ -131,9 +133,8 @@ sub problem_has_user_response {
sub problem_as_hashref {
my $self = shift;
my $problem = shift;
- my $ctx = shift;
- my $hashref = $problem->as_hashref( $ctx );
+ my $hashref = $problem->as_hashref;
if ( $problem->state eq 'submitted' ) {
for my $var ( qw( photo is_fixed meta ) ) {
@@ -171,7 +172,6 @@ sub problem_as_hashref {
sub updates_as_hashref {
my $self = shift;
my $problem = shift;
- my $ctx = shift;
my $hashref = {};
@@ -179,10 +179,10 @@ sub updates_as_hashref {
$hashref->{update_pp} = $self->prettify_dt( $problem->lastupdate );
if ( $problem->state ne 'external' ) {
- $hashref->{details} = FixMyStreet::App::View::Web::add_links(
+ $hashref->{details} = FixMyStreet::Template::add_links(
$problem->get_extra_metadata('public_response') || '' );
} else {
- $hashref->{details} = sprintf( _('Assigned to %s'), $problem->body($ctx)->name );
+ $hashref->{details} = sprintf( _('Assigned to %s'), $problem->body->name );
}
}
@@ -217,13 +217,13 @@ sub allow_photo_display {
}
sub get_body_sender {
- my ( $self, $body, $category ) = @_;
+ my ( $self, $body, $problem ) = @_;
return { method => 'Zurich' };
}
# Report overdue functions
-my %public_holidays = map { $_ => 1 } (
+my @public_holidays = (
# New Year's Day, Saint Berchtold, Good Friday, Easter Monday,
# Sechseläuten, Labour Day, Ascension Day, Whit Monday,
# Swiss National Holiday, Knabenschiessen, Christmas, St Stephen's Day
@@ -249,53 +249,23 @@ my %public_holidays = map { $_ => 1 } (
'2021-09-13',
);
-sub is_public_holiday {
- my $dt = shift;
- return $public_holidays{$dt->ymd};
-}
-
-sub is_weekend {
- my $dt = shift;
- return $dt->dow > 5;
-}
-
-sub add_days {
- my ( $dt, $days ) = @_;
- $dt = $dt->clone;
- while ( $days > 0 ) {
- $dt->add ( days => 1 );
- next if is_public_holiday($dt) or is_weekend($dt);
- $days--;
- }
- return $dt;
-}
-
-sub sub_days {
- my ( $dt, $days ) = @_;
- $dt = $dt->clone;
- while ( $days > 0 ) {
- $dt->subtract ( days => 1 );
- next if is_public_holiday($dt) or is_weekend($dt);
- $days--;
- }
- return $dt;
-}
-
sub overdue {
my ( $self, $problem ) = @_;
my $w = $problem->created;
return 0 unless $w;
+ my $wd = FixMyStreet::WorkingDays->new( public_holidays => \@public_holidays );
+
# call with previous state
if ( $problem->state eq 'submitted' ) {
# One working day
- $w = add_days( $w, 1 );
+ $w = $wd->add_days( $w, 1 );
return $w < DateTime->now() ? 1 : 0;
} elsif ( $problem->state eq 'confirmed' || $problem->state eq 'in progress' || $problem->state eq 'feedback pending' ) {
# States which affect the subdiv_overdue statistic. TODO: this may no longer be required
# Six working days from creation
- $w = add_days( $w, 6 );
+ $w = $wd->add_days( $w, 6 );
return $w < DateTime->now() ? 1 : 0;
# call with new state
@@ -303,7 +273,7 @@ sub overdue {
# States which affect the closed_overdue statistic
# Five working days from moderation (so 6 from creation)
- $w = add_days( $w, 6 );
+ $w = $wd->add_days( $w, 6 );
return $w < DateTime->now() ? 1 : 0;
}
}
@@ -454,10 +424,21 @@ sub admin_type {
return $type;
}
+sub _admin_index_order {
+ my $self = shift;
+ my $c = $self->{c};
+ my $order = $c->get_param('o') || 'created';
+ my $dir = defined $c->get_param('d') ? $c->get_param('d') : 1;
+ $c->stash->{order} = $order;
+ $c->stash->{dir} = $dir;
+ return $dir ? { -desc => $order } : $order;
+}
+
sub admin {
my $self = shift;
my $c = $self->{c};
my $type = $c->stash->{admin_type};
+ my $internal = $c->get_param('internal');
if ($type eq 'dm') {
$c->stash->{template} = 'admin/index-dm.html';
@@ -466,22 +447,20 @@ sub admin {
my @children = map { $_->id } $body->bodies->all;
my @all = (@children, $body->id);
- my $order = $c->get_param('o') || 'created';
- my $dir = defined $c->get_param('d') ? $c->get_param('d') : 1;
- $c->stash->{order} = $order;
- $c->stash->{dir} = $dir;
- $order = { -desc => $order } if $dir;
+ my $order = $self->_admin_index_order;
- # XXX No multiples or missing bodies
+ # No multiples or missing bodies
$c->stash->{submitted} = $c->cobrand->problems->search({
state => [ 'submitted', 'confirmed' ],
bodies_str => $c->stash->{body}->id,
+ non_public => $internal ? 1 : 0,
}, {
order_by => $order,
});
$c->stash->{approval} = $c->cobrand->problems->search({
state => 'feedback pending',
bodies_str => $c->stash->{body}->id,
+ non_public => $internal ? 1 : 0,
}, {
order_by => $order,
});
@@ -490,6 +469,7 @@ sub admin {
$c->stash->{other} = $c->cobrand->problems->search({
state => { -not_in => [ 'submitted', 'confirmed', 'feedback pending' ] },
bodies_str => \@all,
+ non_public => $internal ? 1 : 0,
}, {
order_by => $order,
})->page( $page );
@@ -499,23 +479,20 @@ sub admin {
$c->stash->{template} = 'admin/index-sdm.html';
my $body = $c->stash->{body};
+ my $order = $self->_admin_index_order;
- my $order = $c->get_param('o') || 'created';
- my $dir = defined $c->get_param('d') ? $c->get_param('d') : 1;
- $c->stash->{order} = $order;
- $c->stash->{dir} = $dir;
- $order = { -desc => $order } if $dir;
-
- # XXX No multiples or missing bodies
+ # No multiples or missing bodies
$c->stash->{reports_new} = $c->cobrand->problems->search( {
state => 'in progress',
bodies_str => $body->id,
+ non_public => $internal ? 1 : 0,
}, {
order_by => $order
} );
$c->stash->{reports_unpublished} = $c->cobrand->problems->search( {
state => 'feedback pending',
bodies_str => $body->parent->id,
+ non_public => $internal ? 1 : 0,
}, {
order_by => $order
} );
@@ -524,6 +501,7 @@ sub admin {
$c->stash->{reports_published} = $c->cobrand->problems->search( {
state => 'fixed - council',
bodies_str => $body->parent->id,
+ non_public => $internal ? 1 : 0,
}, {
order_by => $order
} )->page( $page );
@@ -544,6 +522,18 @@ sub category_options {
$c->stash->{category_options} = \@categories;
}
+sub report_remove_internal_flag {
+ my $self = shift;
+ my $c = $self->{c};
+ my $problem = $c->stash->{problem};
+ $c->forward('/auth/check_csrf_token');
+ $problem->non_public(0);
+ $problem->update;
+ $c->forward('/admin/log_edit', [ $problem->id, 'problem', 'Intern Flag entfernt' ]);
+ # Make sure the problem's time_spent is updated
+ $self->update_admin_log($c, $problem);
+}
+
sub admin_report_edit {
my $self = shift;
my $c = $self->{c};
@@ -623,6 +613,10 @@ sub admin_report_edit {
}
}
+ if ( ($type eq 'super' || $type eq 'dm') && $c->get_param('stop_internal') ) {
+ $self->report_remove_internal_flag;
+ return $self->admin_report_edit_done;
+ }
# Problem updates upon submission
if ( ($type eq 'super' || $type eq 'dm') && $c->get_param('submit') ) {
@@ -863,18 +857,12 @@ sub admin_report_edit {
$c->go('index');
}
- $c->stash->{updates} = [ $c->model('DB::Comment')
- ->search( { problem_id => $problem->id }, { order_by => 'created' } )
- ->all ];
-
- $self->stash_states($problem);
- return 1;
+ return $self->admin_report_edit_done;
}
if ($type eq 'sdm') {
- my $editable = $type eq 'sdm' && $body->id eq $problem->bodies_str;
- $c->stash->{sdm_disabled} = $editable ? '' : 'disabled';
+ my $editable = $body->id eq $problem->bodies_str;
# Has cut-down edit template for adding update and sending back up only
$c->stash->{template} = 'admin/report_edit-sdm.html';
@@ -905,6 +893,8 @@ sub admin_report_edit {
# Make sure the problem's time_spent is updated
$self->update_admin_log($c, $problem);
$c->res->redirect( '/admin/summary' );
+ } elsif ($editable && $c->get_param('stop_internal')) {
+ $self->report_remove_internal_flag;
} elsif ($editable && $c->get_param('submit')) {
$c->forward('/auth/check_csrf_token');
@@ -936,22 +926,25 @@ sub admin_report_edit {
# If they clicked the no more updates button, we're done.
if ($c->get_param('no_more_updates')) {
- $problem->set_extra_metadata( subdiv_overdue => $self->overdue( $problem ) );
- $problem->bodies_str( $body->parent->id );
- $problem->whensent( undef );
- $self->set_problem_state($c, $problem, 'feedback pending');
+ if ($problem->non_public) {
+ $problem->bodies_str( $body->parent->id );
+ $self->set_problem_state($c, $problem, 'fixed - council');
+ } else {
+ $problem->set_extra_metadata( subdiv_overdue => $self->overdue( $problem ) );
+ $problem->bodies_str( $body->parent->id );
+ $problem->whensent( undef );
+ $self->set_problem_state($c, $problem, 'feedback pending');
+ }
$problem->update;
$c->res->redirect( '/admin/summary' );
}
}
- $c->stash->{updates} = [ $c->model('DB::Comment')
- ->search( { problem_id => $problem->id }, { order_by => 'created' } )
- ->all ];
-
- $self->stash_states($problem);
- return 1;
+ $c->stash->{sdm_disabled} = $editable ? '' : 'disabled';
+ $c->stash->{sdm_disabled_internal} = $problem->non_public ? 'disabled' : '';
+ $c->stash->{sdm_disabled_fixed} = $problem->is_fixed ? 'disabled' : '';
+ return $self->admin_report_edit_done;
}
$self->stash_states($problem);
@@ -959,6 +952,19 @@ sub admin_report_edit {
}
+sub admin_report_edit_done {
+ my $self = shift;
+ my $c = $self->{c};
+ my $problem = $c->stash->{problem};
+ $c->stash->{updates} = [ $c->model('DB::Comment')
+ ->search( { problem_id => $problem->id }, { order_by => 'created' } )
+ ->all ];
+
+ $self->stash_states($problem);
+ return 1;
+}
+
+
sub admin_district_lookup {
my ($self, $row) = @_;
FixMyStreet::Geocode::Zurich::admin_district($row->local_coords);
@@ -1053,6 +1059,7 @@ sub _admin_send_email {
my ( $c, $template, $problem ) = @_;
return unless $problem->get_extra_metadata('email_confirmed');
+ return if $problem->non_public;
my $to = $problem->name
? [ $problem->user->email, $problem->name ]
@@ -1240,8 +1247,8 @@ sub admin_stats {
sub export_as_csv {
my ($self, $c, $params) = @_;
- my $csv = $c->stash->{csv} = {
- objects => $c->model('DB::Problem')->search_rs(
+ my $reporting = FixMyStreet::Reporting->new(
+ objects_rs => $c->model('DB::Problem')->search_rs(
$params,
{
join => ['admin_log_entries', 'user'],
@@ -1262,7 +1269,7 @@ sub export_as_csv {
]
}
),
- headers => [
+ csv_headers => [
'Report ID', 'Created', 'Sent to Agency', 'Last Updated',
'E', 'N', 'Category', 'Status', 'Closure Status',
'UserID', 'User email', 'User phone', 'User name',
@@ -1270,7 +1277,7 @@ sub export_as_csv {
'Media URL', 'Interface Used', 'Council Response',
'Strasse', 'Mast-Nr.', 'Haus-Nr.', 'Hydranten-Nr.',
],
- columns => [
+ csv_columns => [
'id', 'created', 'whensent',' lastupdate', 'local_coords_x',
'local_coords_y', 'category', 'state', 'closure_status',
'user_id', 'user_email', 'user_phone', 'user_name',
@@ -1278,11 +1285,11 @@ sub export_as_csv {
'media_url', 'service', 'public_response',
'strasse', 'mast_nr',' haus_nr', 'hydranten_nr',
],
- extra_data => sub {
+ csv_extra_data => sub {
my $report = shift;
my $body_name = "";
- if ( my $external_body = $report->body($c) ) {
+ if ( my $external_body = $report->body ) {
$body_name = $external_body->name || '[Unknown body]';
}
@@ -1325,8 +1332,8 @@ sub export_as_csv {
};
},
filename => 'stats',
- };
- $c->forward('/dashboard/generate_csv');
+ );
+ $reporting->generate_csv_http($c);
}
sub problem_confirm_email_extras {
@@ -1389,4 +1396,13 @@ sub hook_report_filter_status {
} @$status;
}
+# If report is made by a flagged user, mark as non-public
+sub report_new_munge_before_insert {
+ my ($self, $report) = @_;
+
+ if ($report->user->flagged) {
+ $report->non_public(1);
+ }
+}
+
1;