aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorStruan Donald <struan@exo.org.uk>2018-03-15 16:12:58 +0000
committerStruan Donald <struan@exo.org.uk>2018-03-15 16:12:58 +0000
commit9d9352ca6bbd757b30796d3cebd42f3ba06f6ddc (patch)
treee587f810b1adf59d6c947e1e887b341cb3e635e5
parentd5fed14044a348d85061202265dd09e4ce1b776b (diff)
parent8c23490f465fad0089d77ee362c566d066798d09 (diff)
Merge branch 'rutland-integration'
-rw-r--r--CHANGELOG.md6
-rwxr-xr-xbin/fetch-reports25
-rwxr-xr-xbin/update-schema2
-rw-r--r--db/downgrade_0057---0056.sql5
-rw-r--r--db/downgrade_0058---0057.sql5
-rw-r--r--db/schema.sql2
-rw-r--r--db/schema_0057-fetch-problems.sql5
-rw-r--r--db/schema_0058-blank-updates-permitted.sql5
-rw-r--r--perllib/FixMyStreet/App/Controller/Admin.pm2
-rw-r--r--perllib/FixMyStreet/App/Controller/Report/New.pm4
-rw-r--r--perllib/FixMyStreet/Cobrand/Default.pm2
-rw-r--r--perllib/FixMyStreet/Cobrand/Rutland.pm46
-rw-r--r--perllib/FixMyStreet/Cobrand/UK.pm3
-rw-r--r--perllib/FixMyStreet/DB/Result/Body.pm8
-rw-r--r--perllib/FixMyStreet/Script/Reports.pm2
-rw-r--r--perllib/FixMyStreet/SendReport/Open311.pm1
-rw-r--r--perllib/Open311.pm50
-rw-r--r--perllib/Open311/GetServiceRequestUpdates.pm36
-rw-r--r--perllib/Open311/GetServiceRequests.pm168
-rw-r--r--perllib/Open311/GetUpdates.pm2
-rw-r--r--perllib/Open311/PopulateServiceList.pm2
-rw-r--r--t/cobrand/rutland.t60
-rw-r--r--t/open311/getservicerequests.t301
-rw-r--r--t/open311/getservicerequestupdates.t48
-rw-r--r--t/open311/populate-service-list.t90
-rw-r--r--t/sendreport/open311.t81
-rw-r--r--templates/email/rutland/_email_color_overrides.html21
-rw-r--r--templates/web/base/admin/open311-form-fields.html26
-rwxr-xr-x[-rw-r--r--]templates/web/base/footer.html0
-rw-r--r--templates/web/base/report/new/category_extras_fields.html2
-rwxr-xr-xtemplates/web/rutland/front/footer-marketing.html8
-rwxr-xr-xtemplates/web/rutland/site-name.html1
-rwxr-xr-xweb/cobrands/rutland/RCCLogo.gifbin0 -> 74768 bytes
-rwxr-xr-xweb/cobrands/rutland/_colours.scss34
-rwxr-xr-xweb/cobrands/rutland/base.scss16
-rw-r--r--web/cobrands/rutland/images/email-logo.gifbin0 -> 3606 bytes
-rwxr-xr-xweb/cobrands/rutland/layout.scss15
37 files changed, 1043 insertions, 41 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md
index fdfad3359..20a7ce037 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,6 +1,12 @@
## Releases
* Unreleased
+ - New features
+ - Fetch problems over Open311 #1986
+ - Option to send multiple photos over Open311 #1986
+ - Allow Open311 service definitions to include automated
+ attributes #1986
+ - Optionally supress blank Open311 update errors #1986
- Front end improvements:
- Improve questionnaire process. #1939 #1998
- Increase size of "sub map links" (hide pins, permalink, etc) #2003
diff --git a/bin/fetch-reports b/bin/fetch-reports
new file mode 100755
index 000000000..665b4aff0
--- /dev/null
+++ b/bin/fetch-reports
@@ -0,0 +1,25 @@
+#!/usr/bin/env perl
+#
+# This script utilises the Open311 extension explained at
+# https://github.com/mysociety/FixMyStreet/wiki/Open311-FMS---Proposed-differences-to-Open311
+# to fetch updates on service requests.
+
+use strict;
+use warnings;
+require 5.8.0;
+
+BEGIN {
+ use File::Basename qw(dirname);
+ use File::Spec;
+ my $d = dirname(File::Spec->rel2abs($0));
+ require "$d/../setenv.pl";
+}
+
+use CronFns;
+my ($verbose, $nomail) = CronFns::options();
+
+use Open311::GetServiceRequests;
+
+my $reports = Open311::GetServiceRequests->new( verbose => $verbose );
+
+$reports->fetch;
diff --git a/bin/update-schema b/bin/update-schema
index fea316bd6..9660837c6 100755
--- a/bin/update-schema
+++ b/bin/update-schema
@@ -212,6 +212,8 @@ else {
# (assuming schema change files are never half-applied, which should be the case)
sub get_db_version {
return 'EMPTY' if ! table_exists('problem');
+ return '0058' if column_exists('body', 'blank_updates_permitted');
+ return '0057' if column_exists('body', 'fetch_problems');
return '0056' if column_exists('users', 'email_verified');
return '0055' if column_exists('response_priorities', 'is_default');
return '0054' if table_exists('state');
diff --git a/db/downgrade_0057---0056.sql b/db/downgrade_0057---0056.sql
new file mode 100644
index 000000000..a87488e41
--- /dev/null
+++ b/db/downgrade_0057---0056.sql
@@ -0,0 +1,5 @@
+BEGIN;
+
+ALTER TABLE body DROP fetch_problems;
+
+COMMIT;
diff --git a/db/downgrade_0058---0057.sql b/db/downgrade_0058---0057.sql
new file mode 100644
index 000000000..1ce8f527c
--- /dev/null
+++ b/db/downgrade_0058---0057.sql
@@ -0,0 +1,5 @@
+BEGIN;
+
+ALTER TABLE body DROP blank_updates_permitted;
+
+COMMIT;
diff --git a/db/schema.sql b/db/schema.sql
index f2197dc52..739090480 100644
--- a/db/schema.sql
+++ b/db/schema.sql
@@ -54,6 +54,8 @@ create table body (
suppress_alerts boolean not null default 'f',
can_be_devolved boolean not null default 'f',
send_extended_statuses boolean not null default 'f',
+ fetch_problems boolean not null default 'f',
+ blank_updates_permitted boolean not null default 'f',
deleted boolean not null default 'f'
);
diff --git a/db/schema_0057-fetch-problems.sql b/db/schema_0057-fetch-problems.sql
new file mode 100644
index 000000000..7419eb032
--- /dev/null
+++ b/db/schema_0057-fetch-problems.sql
@@ -0,0 +1,5 @@
+BEGIN;
+
+ALTER TABLE body ADD fetch_problems boolean default 'f' not null;
+
+COMMIT;
diff --git a/db/schema_0058-blank-updates-permitted.sql b/db/schema_0058-blank-updates-permitted.sql
new file mode 100644
index 000000000..8b6710bc0
--- /dev/null
+++ b/db/schema_0058-blank-updates-permitted.sql
@@ -0,0 +1,5 @@
+BEGIN;
+
+ALTER TABLE body ADD blank_updates_permitted boolean default 'f' not null;
+
+COMMIT;
diff --git a/perllib/FixMyStreet/App/Controller/Admin.pm b/perllib/FixMyStreet/App/Controller/Admin.pm
index 5e69d5bf3..c12fdf9b9 100644
--- a/perllib/FixMyStreet/App/Controller/Admin.pm
+++ b/perllib/FixMyStreet/App/Controller/Admin.pm
@@ -445,6 +445,8 @@ sub body_params : Private {
my %defaults = map { $_ => '' } @fields;
%defaults = ( %defaults,
send_comments => 0,
+ fetch_problems => 0,
+ blank_updates_permitted => 0,
suppress_alerts => 0,
comment_user_id => undef,
send_extended_statuses => 0,
diff --git a/perllib/FixMyStreet/App/Controller/Report/New.pm b/perllib/FixMyStreet/App/Controller/Report/New.pm
index f7334c115..e4e82f091 100644
--- a/perllib/FixMyStreet/App/Controller/Report/New.pm
+++ b/perllib/FixMyStreet/App/Controller/Report/New.pm
@@ -1003,7 +1003,7 @@ sub set_report_extras : Private {
foreach my $contact (@$contacts) {
my $metas = $contact->get_metadata_for_input;
foreach my $field ( @$metas ) {
- if ( lc( $field->{required} ) eq 'true' && !$c->cobrand->category_extra_hidden($field->{code})) {
+ if ( lc( $field->{required} ) eq 'true' && !$c->cobrand->category_extra_hidden($field)) {
unless ( $c->get_param($param_prefix . $field->{code}) ) {
$c->stash->{field_errors}->{ $field->{code} } = _('This information is required');
}
@@ -1020,7 +1020,7 @@ sub set_report_extras : Private {
my $metas = $extra_fields->get_extra_fields;
$param_prefix = "extra[" . $extra_fields->id . "]";
foreach my $field ( @$metas ) {
- if ( lc( $field->{required} ) eq 'true' && !$c->cobrand->category_extra_hidden($field->{code})) {
+ if ( lc( $field->{required} ) eq 'true' && !$c->cobrand->category_extra_hidden($field)) {
unless ( $c->get_param($param_prefix . $field->{code}) ) {
$c->stash->{field_errors}->{ $field->{code} } = _('This information is required');
}
diff --git a/perllib/FixMyStreet/Cobrand/Default.pm b/perllib/FixMyStreet/Cobrand/Default.pm
index 03a6c1a83..8c4d8be53 100644
--- a/perllib/FixMyStreet/Cobrand/Default.pm
+++ b/perllib/FixMyStreet/Cobrand/Default.pm
@@ -1178,7 +1178,7 @@ Return true if an Open311 service attribute should be a hidden field.
sub category_extra_hidden {
my ($self, $meta) = @_;
- return 0;
+ return 0;
}
=item reputation_increment_states/reputation_decrement_states
diff --git a/perllib/FixMyStreet/Cobrand/Rutland.pm b/perllib/FixMyStreet/Cobrand/Rutland.pm
new file mode 100644
index 000000000..c0f9dd197
--- /dev/null
+++ b/perllib/FixMyStreet/Cobrand/Rutland.pm
@@ -0,0 +1,46 @@
+package FixMyStreet::Cobrand::Rutland;
+use base 'FixMyStreet::Cobrand::UKCouncils';
+
+use strict;
+use warnings;
+
+sub council_area_id { return 2600; }
+sub council_area { return 'Rutland'; }
+sub council_name { return 'Rutland County Council'; }
+sub council_url { return 'rutland'; }
+
+sub open311_config {
+ my ($self, $row, $h, $params) = @_;
+
+ my $extra = $row->get_extra_fields;
+ push @$extra, { name => 'external_id', value => $row->id };
+ push @$extra, { name => 'title', value => $row->title };
+ push @$extra, { name => 'description', value => $row->detail };
+
+ if ($h->{closest_address}) {
+ push @$extra, { name => 'closest_address', value => $h->{closest_address} }
+ }
+ $row->set_extra_fields( @$extra );
+
+ $params->{multi_photos} = 1;
+}
+
+sub example_places {
+ return ( 'LE15 6HP', 'High Street', 'Oakham' );
+}
+
+sub disambiguate_location {
+ my $self = shift;
+ my $string = shift;
+
+ return {
+ bounds => [52.524755166940075, -0.8217480325342802, 52.7597945702699, -0.4283542728893742]
+ };
+}
+
+sub pin_colour {
+ my ( $self, $p, $context ) = @_;
+ return 'green' if $p->is_fixed || $p->is_closed;
+ return 'yellow';
+}
+1;
diff --git a/perllib/FixMyStreet/Cobrand/UK.pm b/perllib/FixMyStreet/Cobrand/UK.pm
index e1f5e565f..0ecb6a7c6 100644
--- a/perllib/FixMyStreet/Cobrand/UK.pm
+++ b/perllib/FixMyStreet/Cobrand/UK.pm
@@ -396,7 +396,8 @@ sub lookup_by_ref_regex {
sub category_extra_hidden {
my ($self, $meta) = @_;
- return 1 if $meta eq 'usrn' || $meta eq 'asset_id';
+ return 1 if $meta->{code} eq 'usrn' || $meta->{code} eq 'asset_id';
+ return 1 if $meta->{automated} eq 'hidden_field';
return 0;
}
diff --git a/perllib/FixMyStreet/DB/Result/Body.pm b/perllib/FixMyStreet/DB/Result/Body.pm
index 07bea276c..a9df1aeb7 100644
--- a/perllib/FixMyStreet/DB/Result/Body.pm
+++ b/perllib/FixMyStreet/DB/Result/Body.pm
@@ -44,6 +44,10 @@ __PACKAGE__->add_columns(
{ data_type => "boolean", default_value => \"false", is_nullable => 0 },
"external_url",
{ data_type => "text", is_nullable => 1 },
+ "fetch_problems",
+ { data_type => "boolean", default_value => \"false", is_nullable => 0 },
+ "blank_updates_permitted",
+ { data_type => "boolean", default_value => \"false", is_nullable => 0 },
);
__PACKAGE__->set_primary_key("id");
__PACKAGE__->has_many(
@@ -118,8 +122,8 @@ __PACKAGE__->has_many(
);
-# Created by DBIx::Class::Schema::Loader v0.07035 @ 2017-02-13 15:11:11
-# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:BOJANVwg3kR/1VjDq0LykA
+# Created by DBIx::Class::Schema::Loader v0.07035 @ 2018-03-01 12:27:28
+# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:dzqgZI1wkGDPS2PfJgDEIg
use Moo;
use namespace::clean;
diff --git a/perllib/FixMyStreet/Script/Reports.pm b/perllib/FixMyStreet/Script/Reports.pm
index aa6b64752..b8c3d6d0d 100644
--- a/perllib/FixMyStreet/Script/Reports.pm
+++ b/perllib/FixMyStreet/Script/Reports.pm
@@ -88,6 +88,8 @@ sub send(;$) {
if ($row->photo) {
$h{has_photo} = _("This web page also contains a photo of the problem, provided by the user.") . "\n\n";
$h{image_url} = $email_base_url . $row->photos->[0]->{url_full};
+ my @all_images = map { $email_base_url . $_->{url_full} } @{ $row->photos };
+ $h{all_image_urls} = \@all_images;
} else {
$h{has_photo} = '';
$h{image_url} = '';
diff --git a/perllib/FixMyStreet/SendReport/Open311.pm b/perllib/FixMyStreet/SendReport/Open311.pm
index ecda0bca1..98637dd1f 100644
--- a/perllib/FixMyStreet/SendReport/Open311.pm
+++ b/perllib/FixMyStreet/SendReport/Open311.pm
@@ -28,6 +28,7 @@ sub send {
send_notpinpointed => 0,
use_service_as_deviceid => 0,
extended_description => 1,
+ multi_photos => 0,
);
my $cobrand = $body->get_cobrand_handler || $row->get_cobrand_logged;
diff --git a/perllib/Open311.pm b/perllib/Open311.pm
index 44af0b134..577de31ea 100644
--- a/perllib/Open311.pm
+++ b/perllib/Open311.pm
@@ -32,6 +32,7 @@ has use_service_as_deviceid => ( is => 'ro', isa => Bool, default => 0 );
has use_extended_updates => ( is => 'ro', isa => Bool, default => 0 );
has extended_statuses => ( is => 'ro', isa => Bool, default => 0 );
has always_send_email => ( is => 'ro', isa => Bool, default => 0 );
+has multi_photos => ( is => 'ro', isa => Bool, default => 0 );
before [
qw/get_service_list get_service_meta_info get_service_requests get_service_request_updates
@@ -163,7 +164,11 @@ sub _populate_service_request_params {
}
if ( $extra->{image_url} ) {
- $params->{media_url} = $extra->{image_url};
+ if ( $self->multi_photos ) {
+ $params->{media_url} = $extra->{all_image_urls};
+ } else {
+ $params->{media_url} = $extra->{image_url};
+ }
}
if ( $self->use_service_as_deviceid && $problem->service ) {
@@ -213,14 +218,20 @@ sub _generate_service_request_description {
sub get_service_requests {
my $self = shift;
- my $report_ids = shift;
+ my $args = shift;
my $params = {};
- if ( $report_ids ) {
- $params->{service_request_id} = join ',', @$report_ids;
+ if ( $args->{report_ids} ) {
+ $params->{service_request_id} = join ',', @{$args->{report_ids}};
+ delete $args->{report_ids};
}
+ $params = {
+ %$params,
+ %$args
+ };
+
my $service_request_xml = $self->_get( $self->endpoints->{requests}, $params || undef );
return $self->_get_xml_object( $service_request_xml );
}
@@ -291,6 +302,37 @@ sub post_service_request_update {
return 0;
}
+sub add_media {
+ my ($self, $url, $object) = @_;
+
+ my $ua = LWP::UserAgent->new;
+ my $res = $ua->get($url);
+ if ( $res->is_success && $res->content_type eq 'image/jpeg' ) {
+ my $photoset = FixMyStreet::App::Model::PhotoSet->new({
+ data_items => [ $res->decoded_content ],
+ });
+ $object->photo($photoset->data);
+ }
+}
+
+sub map_state {
+ my $self = shift;
+ my $incoming_state = shift;
+
+ $incoming_state = lc($incoming_state);
+ $incoming_state =~ s/_/ /g;
+
+ my %state_map = (
+ fixed => 'fixed - council',
+ 'not councils responsibility' => 'not responsible',
+ 'no further action' => 'unable to fix',
+ open => 'confirmed',
+ closed => 'fixed - council',
+ );
+
+ return $state_map{$incoming_state} || $incoming_state;
+}
+
sub _populate_service_request_update_params {
my $self = shift;
my $comment = shift;
diff --git a/perllib/Open311/GetServiceRequestUpdates.pm b/perllib/Open311/GetServiceRequestUpdates.pm
index 2620b176a..f2a319f15 100644
--- a/perllib/Open311/GetServiceRequestUpdates.pm
+++ b/perllib/Open311/GetServiceRequestUpdates.pm
@@ -12,6 +12,7 @@ has end_date => ( is => 'ro', default => sub { undef } );
has suppress_alerts => ( is => 'rw', default => 0 );
has verbose => ( is => 'ro', default => 0 );
has schema => ( is =>'ro', lazy => 1, default => sub { FixMyStreet::DB->schema->connect } );
+has blank_updates_permitted => ( is => 'rw', default => 0 );
Readonly::Scalar my $AREA_ID_BROMLEY => 2482;
Readonly::Scalar my $AREA_ID_OXFORDSHIRE => 2237;
@@ -49,6 +50,7 @@ sub fetch {
}
$self->suppress_alerts( $body->suppress_alerts );
+ $self->blank_updates_permitted( $body->blank_updates_permitted );
$self->system_user( $body->comment_user );
$self->update_comments( $o, $body );
}
@@ -107,7 +109,7 @@ sub update_comments {
my $c = $p->comments->search( { external_id => $request->{update_id} } );
if ( !$c->first ) {
- my $state = $self->map_state( $request->{status} );
+ my $state = $open311->map_state( $request->{status} );
my $comment = $self->schema->resultset('Comment')->new(
{
problem => $p,
@@ -124,16 +126,8 @@ sub update_comments {
}
);
- if ($request->{media_url}) {
- my $ua = LWP::UserAgent->new;
- my $res = $ua->get($request->{media_url});
- if ( $res->is_success && $res->content_type eq 'image/jpeg' ) {
- my $photoset = FixMyStreet::App::Model::PhotoSet->new({
- data_items => [ $res->decoded_content ],
- });
- $comment->photo($photoset->data);
- }
- }
+ $open311->add_media($request->{media_url}, $comment)
+ if $request->{media_url};
# if the comment is older than the last update
# do not change the status of the problem as it's
@@ -191,26 +185,10 @@ sub comment_text_for_request {
return $template->text;
}
+ return "" if $self->blank_updates_permitted;
+
print STDERR "Couldn't determine update text for $request->{update_id} (report " . $problem->id . ")\n";
return "";
}
-sub map_state {
- my $self = shift;
- my $incoming_state = shift;
-
- $incoming_state = lc($incoming_state);
- $incoming_state =~ s/_/ /g;
-
- my %state_map = (
- fixed => 'fixed - council',
- 'not councils responsibility' => 'not responsible',
- 'no further action' => 'unable to fix',
- open => 'confirmed',
- closed => 'fixed - council'
- );
-
- return $state_map{$incoming_state} || $incoming_state;
-}
-
1;
diff --git a/perllib/Open311/GetServiceRequests.pm b/perllib/Open311/GetServiceRequests.pm
new file mode 100644
index 000000000..2a82c64a1
--- /dev/null
+++ b/perllib/Open311/GetServiceRequests.pm
@@ -0,0 +1,168 @@
+package Open311::GetServiceRequests;
+
+use Moo;
+use Open311;
+use FixMyStreet::DB;
+use FixMyStreet::App::Model::PhotoSet;
+use DateTime::Format::W3CDTF;
+
+has system_user => ( is => 'rw' );
+has start_date => ( is => 'ro', default => sub { undef } );
+has end_date => ( is => 'ro', default => sub { undef } );
+has verbose => ( is => 'ro', default => 0 );
+has schema => ( is =>'ro', lazy => 1, default => sub { FixMyStreet::DB->schema->connect } );
+
+sub fetch {
+ my $self = shift;
+
+ my $bodies = $self->schema->resultset('Body')->search(
+ {
+ send_method => 'Open311',
+ fetch_problems => 1,
+ comment_user_id => { '!=', undef },
+ endpoint => { '!=', '' },
+ }
+ );
+
+ while ( my $body = $bodies->next ) {
+
+ my $o = Open311->new(
+ endpoint => $body->endpoint,
+ api_key => $body->api_key,
+ jurisdiction => $body->jurisdiction,
+ );
+
+ $self->system_user( $body->comment_user );
+ $self->create_problems( $o, $body );
+ }
+}
+
+sub create_problems {
+ my ( $self, $open311, $body ) = @_;
+
+ my $args = {};
+
+ if ( $self->start_date || $self->end_date ) {
+ return 0 unless $self->start_date && $self->end_date;
+
+
+ $args->{start_date} = DateTime::Format::W3CDTF->format_datetime( $self->start_date );
+ $args->{end_date} = DateTime::Format::W3CDTF->format_datetime( $self->end_date );
+ }
+
+ my $requests = $open311->get_service_requests( $args );
+
+ unless ( $open311->success ) {
+ warn "Failed to fetch ServiceRequest Updates for " . $body->name . ":\n" . $open311->error
+ if $self->verbose;
+ return 0;
+ }
+
+ my $contacts = $self->schema->resultset('Contact')
+ ->active
+ ->search( { body_id => $body->id } );
+
+ for my $request (@{$requests->{request}}) {
+ # no point importing if we can't put it on the map
+ unless ($request->{service_request_id} && $request->{lat} && $request->{long}) {
+ warn "Not creating request '$request->{description}' for @{[$body->name]} as missing one of id, lat or long"
+ if $self->verbose;
+ next;
+ }
+ my $request_id = $request->{service_request_id};
+
+ my %params;
+ $params{generation} = mySociety::Config::get('MAPIT_GENERATION')
+ if mySociety::Config::get('MAPIT_GENERATION');
+
+ my ($latitude, $longitude) = ( $request->{lat}, $request->{long} );
+ my $all_areas =
+ mySociety::MaPit::call( 'point',
+ "4326/$longitude,$latitude", %params );
+
+ # skip if it doesn't look like it's for this body
+ my @areas = grep { $all_areas->{$_->area_id} } $body->body_areas;
+ unless (@areas) {
+ warn "Not creating request id $request_id for @{[$body->name]} as outside body area"
+ if $self->verbose;
+ next;
+ }
+
+ my $updated_time = eval {
+ DateTime::Format::W3CDTF->parse_datetime(
+ $request->{updated_datetime} || ""
+ )->set_time_zone(
+ FixMyStreet->time_zone || FixMyStreet->local_time_zone
+ );
+ };
+ if ($@) {
+ warn "Not creating problem $request_id for @{[$body->name]}, bad update time"
+ if $self->verbose;
+ next;
+ }
+
+ my $updated = DateTime::Format::W3CDTF->format_datetime(
+ $updated_time->clone->set_time_zone('UTC')
+ );
+ if ($args->{start_date} && $args->{end_date} && ($updated lt $args->{start_date} || $updated gt $args->{end_date}) ) {
+ warn "Problem id $request_id for @{[$body->name]} has an invalid time, not creating"
+ if $self->verbose;
+ next;
+ }
+
+ my $created_time = eval {
+ DateTime::Format::W3CDTF->parse_datetime(
+ $request->{requested_datetime} || ""
+ )->set_time_zone(
+ FixMyStreet->time_zone || FixMyStreet->local_time_zone
+ );
+ };
+ $created_time = $updated_time if $@;
+
+ my $problems;
+ my $criteria = {
+ external_id => $request_id,
+ };
+ $problems = $self->schema->resultset('Problem')->to_body($body)->search( $criteria );
+
+ my @contacts = grep { $request->{service_code} eq $_->category } $contacts->all;
+ my $contact = $contacts[0] ? $contacts[0]->category : 'Other';
+
+ my $state = $open311->map_state($request->{status});
+
+ unless (my $p = $problems->first) {
+ my $problem = $self->schema->resultset('Problem')->new(
+ {
+ user => $self->system_user,
+ external_id => $request_id,
+ detail => $request->{description},
+ title => $request->{title} || $request->{service_name} . ' problem',
+ anonymous => 0,
+ name => $self->system_user->name,
+ confirmed => $created_time,
+ created => $created_time,
+ lastupdate => $updated_time,
+ whensent => $created_time,
+ state => $state,
+ postcode => '',
+ used_map => 1,
+ latitude => $request->{lat},
+ longitude => $request->{long},
+ areas => ',' . $body->id . ',',
+ bodies_str => $body->id,
+ send_method_used => 'Open311',
+ category => $contact,
+ }
+ );
+
+ $open311->add_media($request->{media_url}, $problem)
+ if $request->{media_url};
+
+ $problem->insert();
+ }
+ }
+
+ return 1;
+}
+
+1;
diff --git a/perllib/Open311/GetUpdates.pm b/perllib/Open311/GetUpdates.pm
index 1b1e339e3..f62acf4a8 100644
--- a/perllib/Open311/GetUpdates.pm
+++ b/perllib/Open311/GetUpdates.pm
@@ -41,7 +41,7 @@ sub get_updates {
sub update_reports {
my ( $self, $report_ids, $open311, $body ) = @_;
- my $service_requests = $open311->get_service_requests( $report_ids );
+ my $service_requests = $open311->get_service_requests( { report_ids => $report_ids } );
my $requests = $service_requests->{request};
for my $request (@$requests) {
diff --git a/perllib/Open311/PopulateServiceList.pm b/perllib/Open311/PopulateServiceList.pm
index 30d888eb4..af89e3169 100644
--- a/perllib/Open311/PopulateServiceList.pm
+++ b/perllib/Open311/PopulateServiceList.pm
@@ -267,6 +267,8 @@ sub _add_meta_to_contact {
@meta = grep { ! $ignore{ $_->{ code } } } @meta;
}
+ @meta = grep { !defined $_->{automated} || $_->{ automated } eq 'hidden_field' } @meta;
+
$contact->set_extra_fields(@meta);
$contact->update;
}
diff --git a/t/cobrand/rutland.t b/t/cobrand/rutland.t
new file mode 100644
index 000000000..8943e64fc
--- /dev/null
+++ b/t/cobrand/rutland.t
@@ -0,0 +1,60 @@
+use CGI::Simple;
+use FixMyStreet::TestMech;
+use FixMyStreet::Script::Reports;
+my $mech = FixMyStreet::TestMech->new;
+
+# Create test data
+my $user = $mech->create_user_ok( 'rutland@example.com' );
+my $body = $mech->create_body_ok( 2482, 'Rutland County Council');
+my $contact = $mech->create_contact_ok(
+ body_id => $body->id,
+ category => 'Other',
+ email => 'LIGHT',
+);
+$contact->update;
+
+my @reports = $mech->create_problems_for_body( 1, $body->id, 'Test', {
+ cobrand => 'rutland',
+ user => $user,
+});
+my $report = $reports[0];
+
+for my $update ('in progress', 'unable to fix') {
+ FixMyStreet::DB->resultset('Comment')->find_or_create( {
+ problem_state => $update,
+ problem_id => $report->id,
+ user_id => $user->id,
+ name => 'User',
+ mark_fixed => 'f',
+ text => "This update marks it as $update",
+ state => 'confirmed',
+ confirmed => 'now()',
+ anonymous => 'f',
+ } );
+}
+
+subtest 'testing special Open311 behaviour', sub {
+ $report->set_extra_fields();
+ $report->update;
+ $body->update( { send_method => 'Open311', endpoint => 'http://rutland.endpoint.example.com', jurisdiction => 'FMS', api_key => 'test', send_comments => 1 } );
+ my $test_data;
+ FixMyStreet::override_config {
+ STAGING_FLAGS => { send_reports => 1 },
+ ALLOWED_COBRANDS => [ 'fixmystreet', 'rutland' ],
+ }, sub {
+ $test_data = FixMyStreet::Script::Reports::send();
+ };
+ $report->discard_changes;
+ ok $report->whensent, 'Report marked as sent';
+ is $report->send_method_used, 'Open311', 'Report sent via Open311';
+ is $report->external_id, 248, 'Report has right external ID';
+
+ my $req = $test_data->{test_req_used};
+ my $c = CGI::Simple->new($req->content);
+ is $c->param('attribute[title]'), $report->title, 'Request had title';
+ is $c->param('attribute[description]'), $report->detail, 'Request had description';
+ is $c->param('attribute[external_id]'), $report->id, 'Request had correct ID';
+ is $c->param('jurisdiction_id'), 'FMS', 'Request had correct jurisdiction';
+};
+
+done_testing();
diff --git a/t/open311/getservicerequests.t b/t/open311/getservicerequests.t
new file mode 100644
index 000000000..878c178ef
--- /dev/null
+++ b/t/open311/getservicerequests.t
@@ -0,0 +1,301 @@
+#!/usr/bin/env perl
+
+use FixMyStreet::TestMech;
+
+use_ok( 'Open311' );
+use_ok( 'Open311::GetServiceRequests' );
+use DateTime;
+use DateTime::Format::W3CDTF;
+
+my $mech = FixMyStreet::TestMech->new;
+
+my $user = $mech->create_user_ok('system_user@example.com', name => 'test users');
+my $body = $mech->create_body_ok(2482, 'Bromley');
+my $contact = $mech->create_contact_ok( body_id => $body->id, category => 'sidewalks', email => 'sidewalks@example.com' );
+
+my $dtf = DateTime::Format::W3CDTF->new;
+
+my $requests_xml = qq{<?xml version="1.0" encoding="utf-8"?>
+<service_requests>
+<request>
+<service_request_id>638344</service_request_id>
+<status>open</status>
+<status_notes>This is a note.</status_notes>
+<service_name>Sidewalk and Curb Issues</service_name>
+<service_code>sidewalks</service_code>
+<description>This is a sidewalk problem</description>
+<agency_responsible></agency_responsible>
+<service_notice></service_notice>
+<requested_datetime>2010-04-14T06:37:38-08:00</requested_datetime>
+<updated_datetime>2010-04-14T06:37:38-08:00</updated_datetime>
+<expected_datetime>2010-04-15T06:37:38-08:00</expected_datetime>
+<lat>51.4021</lat>
+<long>0.01578</long>
+</request>
+<request>
+<service_request_id>638345</service_request_id>
+<status>investigating</status>
+<status_notes>This is a for a different issue.</status_notes>
+<service_name>Not Sidewalk and Curb Issues</service_name>
+<service_code>not_sidewalks</service_code>
+<description>This is a problem</description>
+<agency_responsible></agency_responsible>
+<service_notice></service_notice>
+<requested_datetime>2010-04-15T06:37:38-08:00</requested_datetime>
+<updated_datetime>2010-04-15T06:37:38-08:00</updated_datetime>
+<expected_datetime>2010-04-15T06:37:38-08:00</expected_datetime>
+<lat>51.4021</lat>
+<long>0.01578</long>
+</request>
+</service_requests>
+};
+
+my $o = Open311->new(
+ jurisdiction => 'mysociety',
+ endpoint => 'http://example.com',
+ test_mode => 1,
+ test_get_returns => { 'requests.xml' => $requests_xml }
+);
+
+subtest 'basic parsing checks' => sub {
+ my $update = Open311::GetServiceRequests->new( system_user => $user );
+ FixMyStreet::override_config {
+ MAPIT_URL => 'http://mapit.uk/',
+ }, sub {
+ $update->create_problems( $o, $body );
+ };
+
+ my $p1_date = $dtf->parse_datetime('2010-04-14T06:37:38-08:00')
+ ->set_time_zone(
+ FixMyStreet->time_zone || FixMyStreet->local_time_zone
+ );
+
+ my $p = FixMyStreet::DB->resultset('Problem')->search(
+ { external_id => 638344 }
+ )->first;
+
+ ok $p, 'Found problem';
+ is $p->detail, 'This is a sidewalk problem', 'correct problem description';
+ is $p->created, $p1_date, 'Problem has correct creation date';
+ is $p->confirmed, $p1_date, 'Problem has correct confirmed date';
+ is $p->whensent, $p1_date, 'Problem has whensent set';
+ is $p->state, 'confirmed', 'correct problem state';
+ is $p->user->id, $user->id, 'user set to system user';
+ is $p->category, 'sidewalks', 'correct problem category';
+
+ my $p2 = FixMyStreet::DB->resultset('Problem')->search( { external_id => 638345 } )->first;
+ ok $p2, 'second problem found';
+ ok $p2->whensent, 'second problem marked sent';
+ is $p2->state, 'investigating', 'second problem correct state';
+ is $p2->category, 'Other', 'category falls back to Other';
+};
+
+subtest 'check problems not re-created' => sub {
+ my $update = Open311::GetServiceRequests->new( system_user => $user );
+ FixMyStreet::override_config {
+ MAPIT_URL => 'http://mapit.uk/',
+ }, sub {
+ $update->create_problems( $o, $body );
+ };
+
+ my $count = FixMyStreet::DB->resultset('Problem')->count;
+
+ FixMyStreet::override_config {
+ MAPIT_URL => 'http://mapit.uk/',
+ }, sub {
+ $update->create_problems( $o, $body );
+ };
+
+ my $after_count = FixMyStreet::DB->resultset('Problem')->count;
+
+ is $count, $after_count, "problems not re-created";
+};
+
+for my $test (
+ {
+ desc => 'problem with no id is not created',
+ detail => 'This is a problem with no service_code',
+ subs => { id => '', desc => 'This is a problem with service code' },
+ },
+ {
+ desc => 'problem with no lat is not created',
+ detail => 'This is a problem with no lat',
+ subs => { lat => '', desc => 'This is a problem with no lat' },
+ },
+ {
+ desc => 'problem with no long is not created',
+ detail => 'This is a problem with no long',
+ subs => { long => '', desc => 'This is a problem with no long' },
+ },
+ {
+ desc => 'problem with bad lat/long is not created',
+ detail => 'This is a problem with bad lat/long',
+ subs => { lat => '51', long => 0.1, desc => 'This is a problem with bad lat/long' },
+ },
+) {
+ subtest $test->{desc} => sub {
+ my $xml = prepare_xml( $test->{subs} );
+ my $o = Open311->new(
+ jurisdiction => 'mysociety',
+ endpoint => 'http://example.com',
+ test_mode => 1,
+ test_get_returns => { 'requests.xml' => $xml}
+ );
+
+ my $count = FixMyStreet::DB->resultset('Problem')->count;
+ my $update = Open311::GetServiceRequests->new( system_user => $user );
+ FixMyStreet::override_config {
+ MAPIT_URL => 'http://mapit.uk/',
+ }, sub {
+ $update->create_problems( $o, $body );
+ };
+ my $after_count = FixMyStreet::DB->resultset('Problem')->count;
+
+ warn $count;
+ is $count, $after_count, "problems not created";
+
+ my $with_text = FixMyStreet::DB->resultset('Problem')->search( {
+ detail => $test->{detail}
+ } )->count;
+
+ is $with_text, 0, 'no matching problem created';
+ };
+}
+
+my $date = DateTime->new(
+ year => 2010,
+ month => 4,
+ day => 14,
+ hour => 6,
+ minute => 37
+);
+
+for my $test (
+ {
+ start_date => '1',
+ end_date => '',
+ desc => 'do not process if only a start_date',
+ subs => {},
+ },
+ {
+ start_date => '',
+ end_date => '1',
+ desc => 'do not process if only an end_date',
+ subs => {},
+ },
+) {
+ subtest $test->{desc} => sub {
+ my $xml = prepare_xml( $test->{subs} );
+ my $o = Open311->new(
+ jurisdiction => 'mysociety',
+ endpoint => 'http://example.com',
+ test_mode => 1,
+ test_get_returns => { 'requests.xml' => $xml}
+ );
+
+ my $update = Open311::GetServiceRequests->new(
+ start_date => $test->{start_date},
+ end_date => $test->{end_date},
+ system_user => $user,
+ );
+ my $ret = $update->create_problems( $o, $body );
+
+ is $ret, 0, 'failed correctly'
+ };
+}
+
+$date = DateTime->new(
+ year => 2010,
+ month => 4,
+ day => 14,
+ hour => 6,
+ minute => 37
+);
+
+for my $test (
+ {
+ start_date => $date->clone->add(hours => -2),
+ end_date => $date->clone->add(hours => -1),
+ desc => 'do not process if update time after end_date',
+ subs => {},
+ },
+ {
+ start_date => $date->clone->add(hours => 2),
+ end_date => $date->clone->add(hours => 4),
+ desc => 'do not process if update time before start_date',
+ subs => {},
+ },
+ {
+ start_date => $date->clone->add(hours => -2),
+ end_date => $date->clone->add(hours => 4),
+ desc => 'do not process if update time is bad',
+ subs => { update_time => '2010/12/12' },
+ },
+) {
+ subtest $test->{desc} => sub {
+ my $xml = prepare_xml( $test->{subs} );
+ my $o = Open311->new(
+ jurisdiction => 'mysociety',
+ endpoint => 'http://example.com',
+ test_mode => 1,
+ test_get_returns => { 'requests.xml' => $xml}
+ );
+
+ my $update = Open311::GetServiceRequests->new(
+ start_date => $test->{start_date},
+ end_date => $test->{end_date},
+ system_user => $user,
+ );
+ my $count = FixMyStreet::DB->resultset('Problem')->count;
+ FixMyStreet::override_config {
+ MAPIT_URL => 'http://mapit.uk/',
+ }, sub {
+ $update->create_problems( $o, $body );
+ };
+ my $after = FixMyStreet::DB->resultset('Problem')->count;
+
+ is $count, $after, 'problem not added';
+ };
+}
+
+sub prepare_xml {
+ my $replacements = shift;
+
+ my %defaults = (
+ desc => 'this is a problem',
+ lat => 51.4021,
+ long => 0.01578,
+ id => 123456,
+ update_time => '2010-04-14T06:37:38-08:00',
+ %$replacements
+ );
+
+ my $xml = qq[<?xml version="1.0" encoding="utf-8"?>
+<service_requests>
+<request>
+<service_request_id>XXX_ID</service_request_id>
+<status>open</status>
+<status_notes></status_notes>
+<service_name>Sidewalk and Curb Issues</service_name>
+<service_code>sidewalks</service_code>
+<description>XXX_DESC</description>
+<agency_responsible></agency_responsible>
+<service_notice></service_notice>
+<requested_datetime>2010-04-14T06:37:38-08:00</requested_datetime>
+<updated_datetime>XXX_UPDATE_TIME</updated_datetime>
+<expected_datetime>2010-04-15T06:37:38-08:00</expected_datetime>
+<lat>XXX_LAT</lat>
+<long>XXX_LONG</long>
+</request>
+</service_requests>
+];
+
+ for my $key (keys %defaults) {
+ my $string = 'XXX_' . uc $key;
+ $xml =~ s/$string/$defaults{$key}/;
+ }
+
+ return $xml;
+}
+
+done_testing();
diff --git a/t/open311/getservicerequestupdates.t b/t/open311/getservicerequestupdates.t
index da427e505..a53354685 100644
--- a/t/open311/getservicerequestupdates.t
+++ b/t/open311/getservicerequestupdates.t
@@ -1,6 +1,7 @@
#!/usr/bin/env perl
use FixMyStreet::Test;
+use Test::Output;
use CGI::Simple;
use LWP::Protocol::PSGI;
use t::Mock::Static;
@@ -782,6 +783,53 @@ foreach my $test ( {
}
}
+foreach my $test ( {
+ desc => 'normally blank text produces a warning',
+ num_alerts => 1,
+ blank_updates_permitted => 0,
+ },
+ {
+ desc => 'no warning if blank updates permitted',
+ num_alerts => 1,
+ blank_updates_permitted => 1,
+ },
+) {
+ subtest $test->{desc} => sub {
+ my $requests_xml = qq{<?xml version="1.0" encoding="utf-8"?>
+ <service_requests_updates>
+ <request_update>
+ <update_id>638344</update_id>
+ <service_request_id>@{[ $problem->external_id ]}</service_request_id>
+ <status>closed</status>
+ <description></description>
+ <updated_datetime>UPDATED_DATETIME</updated_datetime>
+ </request_update>
+ </service_requests_updates>
+ };
+
+ $problem->state( 'confirmed' );
+ $problem->lastupdate( $dt->clone->subtract( hours => 3 ) );
+ $problem->update;
+
+ $requests_xml =~ s/UPDATED_DATETIME/$dt/;
+
+ my $o = Open311->new( jurisdiction => 'mysociety', endpoint => 'http://example.com', test_mode => 1, test_get_returns => { 'servicerequestupdates.xml' => $requests_xml } );
+
+ my $update = Open311::GetServiceRequestUpdates->new(
+ system_user => $user,
+ blank_updates_permitted => $test->{blank_updates_permitted},
+ );
+
+ if ( $test->{blank_updates_permitted} ) {
+ stderr_is { $update->update_comments( $o, $bodies{2482} ) } '', 'No error message'
+ } else {
+ stderr_like { $update->update_comments( $o, $bodies{2482} ) } qr/Couldn't determine update text for/, 'Error message displayed'
+ }
+ $problem->discard_changes;
+ $problem->comments->delete;
+ }
+}
+
done_testing();
sub setup_xml {
diff --git a/t/open311/populate-service-list.t b/t/open311/populate-service-list.t
index 7d4f491c6..149fb4b2a 100644
--- a/t/open311/populate-service-list.t
+++ b/t/open311/populate-service-list.t
@@ -646,6 +646,96 @@ subtest 'check bromely skip code' => sub {
is_deeply $contact->get_extra_fields, $extra, 'all meta data saved for non bromley';
};
+subtest 'check automated meta skip code' => sub {
+ my $processor = Open311::PopulateServiceList->new();
+
+ my $meta_xml = '<?xml version="1.0" encoding="utf-8"?>
+<service_definition>
+ <service_code>100</service_code>
+ <attributes>
+ <attribute>
+ <variable>true</variable>
+ <code>type</code>
+ <datatype>string</datatype>
+ <required>true</required>
+ <datatype_description>Type of bin</datatype_description>
+ <order>1</order>
+ <description>Type of bin</description>
+ </attribute>
+ <attribute>
+ <automated>server_set</automated>
+ <variable>true</variable>
+ <code>title</code>
+ <datatype>string</datatype>
+ <required>true</required>
+ <datatype_description>Type of bin</datatype_description>
+ <order>1</order>
+ <description>Type of bin</description>
+ </attribute>
+ <attribute>
+ <automated>hidden_field</automated>
+ <variable>true</variable>
+ <code>asset_id</code>
+ <datatype>string</datatype>
+ <required>true</required>
+ <datatype_description>Id of bin</datatype_description>
+ <order>1</order>
+ <description>Id of bin</description>
+ </attribute>
+ </attributes>
+</service_definition>
+ ';
+
+ my $contact = FixMyStreet::DB->resultset('Contact')->find_or_create(
+ {
+ body_id => 1,
+ email => '001',
+ category => 'Bins left out 24x7',
+ state => 'confirmed',
+ editor => $0,
+ whenedited => \'current_timestamp',
+ note => 'test contact',
+ }
+ );
+
+ my $o = Open311->new(
+ jurisdiction => 'mysociety',
+ endpoint => 'http://example.com',
+ test_mode => 1,
+ test_get_returns => { 'services/100.xml' => $meta_xml }
+ );
+
+ $processor->_current_open311( $o );
+ $processor->_current_body( $body );
+ $processor->_current_service( { service_code => 100 } );
+
+ $processor->_add_meta_to_contact( $contact );
+
+ my $extra = [ {
+ variable => 'true',
+ code => 'type',
+ datatype => 'string',
+ required => 'true',
+ datatype_description => 'Type of bin',
+ order => 1,
+ description => 'Type of bin'
+ },
+ {
+ automated => 'hidden_field',
+ variable => 'true',
+ code => 'asset_id',
+ datatype => 'string',
+ required => 'true',
+ datatype_description => 'Id of bin',
+ order => 1,
+ description => 'Id of bin'
+ } ];
+
+ $contact->discard_changes;
+
+ is_deeply $contact->get_extra_fields, $extra, 'only hidden automated meta data saved';
+};
+
sub get_standard_xml {
return qq{<?xml version="1.0" encoding="utf-8"?>
<services>
diff --git a/t/sendreport/open311.t b/t/sendreport/open311.t
index 1eb5535aa..23096aaac 100644
--- a/t/sendreport/open311.t
+++ b/t/sendreport/open311.t
@@ -1,4 +1,16 @@
+package FixMyStreet::Cobrand::Tester;
+
+use parent 'FixMyStreet::Cobrand::FixMyStreet';
+
+sub open311_config {
+ my ($self, $row, $h, $params) = @_;
+ $params->{multi_photos} = 1;
+}
+
+package main;
+
use CGI::Simple;
+use Path::Tiny;
use FixMyStreet::Script::Reports;
use FixMyStreet::TestMech;
my $mech = FixMyStreet::TestMech->new;
@@ -41,4 +53,73 @@ subtest 'testing Open311 behaviour', sub {
is $c->param('jurisdiction_id'), 'FMS', 'Request had correct jurisdiction';
};
+my ($photo_report) = $mech->create_problems_for_body( 1, $body->id, 'Test', {
+ cobrand => 'fixmystreet',
+ category => 'Potholes',
+ user => $user,
+});
+my $sample_file = path(__FILE__)->parent->parent->child("app/controller/sample.jpg");
+my $UPLOAD_DIR = File::Temp->newdir();
+my @files = map { $_ x 40 . ".jpeg" } (1..3);
+$sample_file->copy(path($UPLOAD_DIR, $_)) for @files;
+$photo_report->photo(join(',', @files));
+$photo_report->update;
+
+subtest 'test report with multiple photos only sends one', sub {
+ $body->update( { send_method => 'Open311', endpoint => 'http://endpoint.example.com', jurisdiction => 'FMS', api_key => 'test' } );
+ my $test_data;
+
+ FixMyStreet::override_config {
+ STAGING_FLAGS => { send_reports => 1 },
+ ALLOWED_COBRANDS => [ 'fixmystreet' ],
+ }, sub {
+ $test_data = FixMyStreet::Script::Reports::send();
+ };
+ $photo_report->discard_changes;
+ ok $photo_report->whensent, 'Report marked as sent';
+ is $photo_report->send_method_used, 'Open311', 'Report sent via Open311';
+ is $photo_report->external_id, 248, 'Report has right external ID';
+
+ my $req = $test_data->{test_req_used};
+ my $c = CGI::Simple->new($req->content);
+ is $c->param('attribute[easting]'), 529025, 'Request had easting';
+ is $c->param('attribute[northing]'), 179716, 'Request had northing';
+ is $c->param('attribute[fixmystreet_id]'), $photo_report->id, 'Request had correct ID';
+ is $c->param('jurisdiction_id'), 'FMS', 'Request had correct jurisdiction';
+ my @media = $c->param('media_url');
+ is_deeply \@media, [
+ 'http://www.example.org/photo/' . $photo_report->id .'.0.full.jpeg?11111111'
+ ], 'One photo in media_url';
+};
+
+$photo_report->whensent(undef);
+$photo_report->cobrand('tester');
+$photo_report->send_method_used('');
+$photo_report->update();
+
+subtest 'test sending multiple photos', sub {
+ $body->update( { send_method => 'Open311', endpoint => 'http://endpoint.example.com', jurisdiction => 'FMS', api_key => 'test' } );
+ my $test_data;
+
+ FixMyStreet::override_config {
+ STAGING_FLAGS => { send_reports => 1 },
+ ALLOWED_COBRANDS => [ 'tester' ],
+ }, sub {
+ $test_data = FixMyStreet::Script::Reports::send();
+ };
+ $photo_report->discard_changes;
+ ok $photo_report->whensent, 'Report marked as sent';
+ is $photo_report->send_method_used, 'Open311', 'Report sent via Open311';
+ is $photo_report->external_id, 248, 'Report has right external ID';
+
+ my $req = $test_data->{test_req_used};
+ my $c = CGI::Simple->new($req->content);
+ my @media = $c->param('media_url');
+ is_deeply \@media, [
+ 'http://www.example.org/photo/' . $photo_report->id .'.0.full.jpeg?11111111',
+ 'http://www.example.org/photo/' . $photo_report->id .'.1.full.jpeg?22222222',
+ 'http://www.example.org/photo/' . $photo_report->id .'.2.full.jpeg?33333333'
+ ], 'Multiple photos in media_url';
+};
+
done_testing();
diff --git a/templates/email/rutland/_email_color_overrides.html b/templates/email/rutland/_email_color_overrides.html
new file mode 100644
index 000000000..12ec97bc3
--- /dev/null
+++ b/templates/email/rutland/_email_color_overrides.html
@@ -0,0 +1,21 @@
+[%
+
+color_rutland_dark_green = '#265123'
+color_rutland_mid_green = '#A7B980'
+color_rutland_pale_green = '#DCE6C9'
+color_rutland_dark_grey = '#3C3C3C'
+
+body_font_family = "'PT Sans', Verdana, sans-serif"
+
+header_background_color = color_rutland_mid_green
+header_text_color = color_black
+
+secondary_column_background_color = color_rutland_pale_green
+
+button_background_color = color_rutland_dark_green
+button_text_color = color_white
+
+logo_width = "150" # pixel measurement, but without 'px' suffix
+logo_height = "77" # pixel measurement, but without 'px' suffix
+
+%]
diff --git a/templates/web/base/admin/open311-form-fields.html b/templates/web/base/admin/open311-form-fields.html
index d1067205c..6e6eb96f0 100644
--- a/templates/web/base/admin/open311-form-fields.html
+++ b/templates/web/base/admin/open311-form-fields.html
@@ -49,6 +49,17 @@
</p>
[% IF show_body_fields %]
+ <div class="admin-hint">
+ <p>
+ [% loc(
+ "Enabling this will suppress the error message that is normally emitted when an update has no description"
+ ) %]
+ </p>
+ </div>
+ <p>
+ <input type="checkbox" id="blank_updates_permitted" name="blank_updates_permitted"[% ' checked' IF object.blank_updates_permitted %]>
+ <label for="blank_updates_permitted" class="inline">[% loc('Permit blank updates') %]</label>
+ </p>
[%# These fields aren't shown for contacts %]
<div class="admin-hint">
<p>
@@ -68,6 +79,21 @@
<div class="admin-hint">
<p>
[% loc(
+ "Enable <strong>Open311 problem-fetching</strong> if you want to display reports created at
+ the endpoint to FixMyStreet. If you're not sure, you probably do not, so leave this unchecked.
+ For more information, see
+ <a href='https://www.mysociety.org/2013/02/20/open311-extended/' class='admin-offsite-link'>this article</a>."
+ ) %]
+ </p>
+ </div>
+ <p>
+ <input type="checkbox" id="fetch_problems" name="fetch_problems"[% ' checked' IF object.fetch_problems %]>
+ <label for="fetch_problems" class="inline">[% loc('Use Open311 problem fetching') %]</label>
+ </p>
+
+ <div class="admin-hint">
+ <p>
+ [% loc(
"If you've enabled Open311 update-sending above, you must identify which
FixMyStreet <strong>user</strong> will be attributed as the creator of those updates
when they are shown on the site. Enter the ID (number) of that user."
diff --git a/templates/web/base/footer.html b/templates/web/base/footer.html
index e2bdbb01a..e2bdbb01a 100644..100755
--- a/templates/web/base/footer.html
+++ b/templates/web/base/footer.html
diff --git a/templates/web/base/report/new/category_extras_fields.html b/templates/web/base/report/new/category_extras_fields.html
index 6ed6391c7..cf5cc37b4 100644
--- a/templates/web/base/report/new/category_extras_fields.html
+++ b/templates/web/base/report/new/category_extras_fields.html
@@ -1,7 +1,7 @@
[%- FOR meta IN metas %]
[%- meta_name = meta.code -%]
- [% IF c.cobrand.category_extra_hidden(meta_name) AND NOT show_hidden %]
+ [% IF c.cobrand.category_extra_hidden(meta) AND NOT show_hidden %]
<input type="hidden" value="[% report_meta.$meta_name.value | html %]" name="[% cat_prefix %][% meta_name %]" id="[% cat_prefix %]form_[% meta_name %]">
diff --git a/templates/web/rutland/front/footer-marketing.html b/templates/web/rutland/front/footer-marketing.html
new file mode 100755
index 000000000..7d998eba6
--- /dev/null
+++ b/templates/web/rutland/front/footer-marketing.html
@@ -0,0 +1,8 @@
+ <div class="tablewrapper bordered footer-marketing">
+ <p>
+ [% loc('') %]
+ </p>
+ <p>
+ [% loc('Powered by <a class="platform-logo" href="http://fixmystreet.org/">FixMyStreet Platform</a>') %]
+ </p>
+ </div>
diff --git a/templates/web/rutland/site-name.html b/templates/web/rutland/site-name.html
new file mode 100755
index 000000000..62188c84a
--- /dev/null
+++ b/templates/web/rutland/site-name.html
@@ -0,0 +1 @@
+Rutland County Council FixMyStreet
diff --git a/web/cobrands/rutland/RCCLogo.gif b/web/cobrands/rutland/RCCLogo.gif
new file mode 100755
index 000000000..aeacf01f6
--- /dev/null
+++ b/web/cobrands/rutland/RCCLogo.gif
Binary files differ
diff --git a/web/cobrands/rutland/_colours.scss b/web/cobrands/rutland/_colours.scss
new file mode 100755
index 000000000..c3666ca17
--- /dev/null
+++ b/web/cobrands/rutland/_colours.scss
@@ -0,0 +1,34 @@
+/* LAYOUT */
+
+// If you are wanting a right-to-left layout, uncomment the following line.
+// $direction: right;
+
+/* COLOURS */
+
+$orange: #ff9900;
+$bluey: #6688ff;
+$RCCGreen: #a7b980;
+$RCCGreen_dark: #265123;
+$RCCbg: #F1F1F1;
+
+$primary: $RCCGreen;
+$primary_b: #000000;
+$primary_text: #222222;
+
+$base_bg: $RCCbg;
+$base_fg: #000;
+
+$map_nav_bg: $RCCbg;
+$nav_fg: #000;
+$nav_fg_hover: $primary;
+
+// Colour used for front page 'how to report a problem' steps
+$col_big_numbers: #ccc;
+
+$col_click_map: $RCCGreen_dark;
+
+$col_fixed_label: #00BD08;
+$col_fixed_label_dark: #4B8304;
+
+//$image-sprite: '/cobrands/rutland/RCCLogo.gif';
+
diff --git a/web/cobrands/rutland/base.scss b/web/cobrands/rutland/base.scss
new file mode 100755
index 000000000..4837e970a
--- /dev/null
+++ b/web/cobrands/rutland/base.scss
@@ -0,0 +1,16 @@
+@import "../sass/h5bp";
+@import "./_colours";
+@import "../sass/mixins";
+
+@import "../sass/base";
+
+
+#site-logo {
+ background: url("/cobrands/rutland/RCCLogo.gif");
+ background-size: contain;
+ height: 50px;
+ width: 110px;
+}
+
+
+
diff --git a/web/cobrands/rutland/images/email-logo.gif b/web/cobrands/rutland/images/email-logo.gif
new file mode 100644
index 000000000..bab9d2eef
--- /dev/null
+++ b/web/cobrands/rutland/images/email-logo.gif
Binary files differ
diff --git a/web/cobrands/rutland/layout.scss b/web/cobrands/rutland/layout.scss
new file mode 100755
index 000000000..eb0447be2
--- /dev/null
+++ b/web/cobrands/rutland/layout.scss
@@ -0,0 +1,15 @@
+@import "_colours";
+@import "../sass/layout";
+
+
+body.frontpage #site-header {
+ height: 10em;
+}
+
+
+body.frontpage #site-logo {
+ background: url("/cobrands/rutland/RCCLogo.gif");
+ background-size: contain;
+ height: 100px;
+ width: 220px;
+}