aboutsummaryrefslogtreecommitdiffstats
path: root/perllib
diff options
context:
space:
mode:
Diffstat (limited to 'perllib')
-rw-r--r--perllib/FixMyStreet/App/Controller/Admin.pm37
-rw-r--r--perllib/FixMyStreet/App/Controller/Around.pm2
-rw-r--r--perllib/FixMyStreet/App/Controller/Report/New.pm83
-rw-r--r--perllib/FixMyStreet/App/Controller/Report/Update.pm8
-rw-r--r--perllib/FixMyStreet/App/Controller/Reports.pm15
-rwxr-xr-xperllib/FixMyStreet/App/Controller/Rss.pm1
-rw-r--r--perllib/FixMyStreet/App/Controller/Tokens.pm1
-rw-r--r--perllib/FixMyStreet/Cobrand/Default.pm8
-rw-r--r--perllib/FixMyStreet/Cobrand/Reading.pm108
-rw-r--r--perllib/FixMyStreet/DB/Result/Contact.pm25
-rw-r--r--perllib/FixMyStreet/DB/Result/Open311conf.pm39
-rw-r--r--perllib/FixMyStreet/DB/Result/Problem.pm108
-rw-r--r--perllib/FixMyStreet/Geocode/Bing.pm3
-rw-r--r--perllib/FixMyStreet/Map/FMS.pm10
-rw-r--r--perllib/Open311.pm204
-rw-r--r--perllib/Open311/GetUpdates.pm82
-rw-r--r--perllib/Open311/PopulateServiceList.pm240
17 files changed, 953 insertions, 21 deletions
diff --git a/perllib/FixMyStreet/App/Controller/Admin.pm b/perllib/FixMyStreet/App/Controller/Admin.pm
index 2aaa488d6..a34737844 100644
--- a/perllib/FixMyStreet/App/Controller/Admin.pm
+++ b/perllib/FixMyStreet/App/Controller/Admin.pm
@@ -217,7 +217,10 @@ sub council_list : Path('council_list') : Args(0) {
$c->stash->{edit_activity} = $edit_activity;
- my @area_types = $c->cobrand->area_types;
+ # Not London, as treated separately
+ my @area_types = $c->cobrand->moniker eq 'emptyhomes'
+ ? $c->cobrand->area_types
+ : grep { $_ ne 'LBO' } $c->cobrand->area_types;
my $areas = mySociety::MaPit::call('areas', \@area_types);
my @councils_ids = sort { strcoll($areas->{$a}->{name}, $areas->{$b}->{name}) } keys %$areas;
@@ -331,6 +334,32 @@ sub update_contacts : Private {
);
$c->stash->{updated} = _('Values updated');
+ } elsif ( $posted eq 'open311' ) {
+ $c->forward('check_token');
+
+ my %params = map { $_ => $c->req->param($_) } qw/open311_id endpoint jurisdiction api_key area_id/;
+
+ if ( $params{open311_id} ) {
+ my $conf = $c->model('DB::Open311Conf')->find( { id => $params{open311_id} } );
+
+ $conf->endpoint( $params{endpoint} );
+ $conf->jurisdiction( $params{jurisdiction} );
+ $conf->api_key( $params{api_key} );
+
+ $conf->update();
+
+ $c->stash->{updated} = _('Configuration updated');
+ } else {
+ my $conf = $c->model('DB::Open311Conf')->find_or_new( { area_id => $params{area_id} } );
+
+ $conf->endpoint( $params{endpoint} );
+ $conf->jurisdiction( $params{jurisdiction} );
+ $conf->api_key( $params{api_key} );
+
+ $conf->insert();
+
+ $c->stash->{updated} = _('Configuration updated - contacts will be generated automatically later');
+ }
}
}
@@ -348,6 +377,12 @@ sub display_contacts : Private {
$c->stash->{contacts} = $contacts;
+ my $open311 = $c->model('DB::Open311Conf')->search(
+ { area_id => $area_id }
+ );
+
+ $c->stash->{open311} = $open311;
+
if ( $c->req->param('text') && $c->req->param('text') == 1 ) {
$c->stash->{template} = 'admin/council_contacts.txt';
$c->res->content_type('text/plain; charset=utf-8');
diff --git a/perllib/FixMyStreet/App/Controller/Around.pm b/perllib/FixMyStreet/App/Controller/Around.pm
index 72d6ac62b..148a22368 100644
--- a/perllib/FixMyStreet/App/Controller/Around.pm
+++ b/perllib/FixMyStreet/App/Controller/Around.pm
@@ -182,7 +182,7 @@ sub display_location : Private {
# create a list of all the pins
my @pins;
- unless ($c->req->param('no_pins')) {
+ unless ($c->req->param('no_pins') || $c->cobrand->moniker eq 'emptyhomes') {
@pins = map {
# Here we might have a DB::Problem or a DB::Nearby, we always want the problem.
my $p = (ref $_ eq 'FixMyStreet::App::Model::DB::Nearby') ? $_->problem : $_;
diff --git a/perllib/FixMyStreet/App/Controller/Report/New.pm b/perllib/FixMyStreet/App/Controller/Report/New.pm
index f16bd5393..e982d6a4c 100644
--- a/perllib/FixMyStreet/App/Controller/Report/New.pm
+++ b/perllib/FixMyStreet/App/Controller/Report/New.pm
@@ -15,6 +15,7 @@ use Path::Class;
use Utils;
use mySociety::EmailUtil;
use mySociety::TempFiles;
+use JSON;
=head1 NAME
@@ -113,11 +114,48 @@ sub report_form_ajax : Path('ajax') : Args(0) {
# render templates to get the html
my $category = $c->view('Web')->render( $c, 'report/new/category.html');
my $councils_text = $c->view('Web')->render( $c, 'report/new/councils_text.html');
+ my $has_open311 = keys %{ $c->stash->{category_extras} };
my $body = JSON->new->utf8(1)->encode(
{
- councils_text => $councils_text,
- category => $category,
+ councils_text => $councils_text,
+ category => $category,
+ has_open311 => $has_open311,
+ }
+ );
+
+ $c->res->content_type('application/json; charset=utf-8');
+ $c->res->body($body);
+}
+
+sub category_extras_ajax : Path('category_extras') : Args(0) {
+ my ( $self, $c ) = @_;
+
+ $c->forward('initialize_report');
+ if ( ! $c->forward('determine_location') ) {
+ my $body = JSON->new->utf8(1)->encode(
+ {
+ error => _("Sorry, we could not find that location."),
+ }
+ );
+ $c->res->content_type('application/json; charset=utf-8');
+ $c->res->body($body);
+ return 1;
+ }
+ $c->forward('setup_categories_and_councils');
+
+ my $category_extra = '';
+ if ( $c->stash->{category_extras}->{ $c->req->param('category') } ) {
+ $c->stash->{report_meta} = {};
+ $c->stash->{report} = { category => $c->req->param('category') };
+ $c->stash->{category_extras} = { $c->req->param('category' ) => $c->stash->{category_extras}->{ $c->req->param('category') } };
+
+ $category_extra= $c->view('Web')->render( $c, 'report/new/category_extras.html');
+ }
+
+ my $body = JSON->new->utf8(1)->encode(
+ {
+ category_extra => $category_extra,
}
);
@@ -476,6 +514,7 @@ sub setup_categories_and_councils : Private {
my %area_ids_to_list = (); # Areas with categories assigned
my @category_options = (); # categories to show
my $category_label = undef; # what to call them
+ my %category_extras = (); # extra fields to fill in for open311
# FIXME - implement in cobrand
if ( $c->cobrand->moniker eq 'emptyhomes' ) {
@@ -522,8 +561,12 @@ sub setup_categories_and_councils : Private {
next if $contact->category eq _('Other');
- push @category_options, $contact->category
- unless $seen{$contact->category};
+ unless ( $seen{$contact->category} ) {
+ push @category_options, $contact->category;
+
+ $category_extras{ $contact->category } = $contact->extra
+ if $contact->extra;
+ }
$seen{$contact->category} = 1;
}
@@ -538,6 +581,8 @@ sub setup_categories_and_councils : Private {
$c->stash->{area_ids_to_list} = [ keys %area_ids_to_list ];
$c->stash->{category_label} = $category_label;
$c->stash->{category_options} = \@category_options;
+ $c->stash->{category_extras} = \%category_extras;
+ $c->stash->{category_extras_json} = encode_json \%category_extras;
my @missing_details_councils =
grep { !$area_ids_to_list{$_} } #
@@ -716,6 +761,26 @@ sub process_report : Private {
if $council_string && @{ $c->stash->{missing_details_councils} };
$report->council($council_string);
+ my @extra = ();
+ my $metas = $contacts[0]->extra;
+
+ foreach my $field ( @$metas ) {
+ if ( lc( $field->{required} ) eq 'true' ) {
+ unless ( $c->request->param( $field->{code} ) ) {
+ $c->stash->{field_errors}->{ $field->{code} } = _('This information is required');
+ }
+ }
+ push @extra, {
+ name => $field->{code},
+ description => $field->{description},
+ value => $c->request->param( $field->{code} ) || '',
+ };
+ }
+
+ if ( @extra ) {
+ $c->stash->{report_meta} = \@extra;
+ $report->extra( \@extra );
+ }
} elsif ( @{ $c->stash->{area_ids_to_list} } ) {
# There was an area with categories, but we've not been given one. Bail.
@@ -881,6 +946,16 @@ sub save_user_and_report : Private {
# Save or update the user if appropriate
if ( !$report->user->in_storage ) {
+ # User does not exist.
+ # Store changes in token for when token is validated.
+ $c->stash->{token_data} = {
+ name => $report->user->name,
+ phone => $report->user->phone,
+ password => $report->user->password,
+ };
+ $report->user->name( undef );
+ $report->user->phone( undef );
+ $report->user->password( '', 1 );
$report->user->insert();
}
elsif ( $c->user && $report->user->id == $c->user->id ) {
diff --git a/perllib/FixMyStreet/App/Controller/Report/Update.pm b/perllib/FixMyStreet/App/Controller/Report/Update.pm
index add9d1371..c67ca4d1f 100644
--- a/perllib/FixMyStreet/App/Controller/Report/Update.pm
+++ b/perllib/FixMyStreet/App/Controller/Report/Update.pm
@@ -255,6 +255,14 @@ sub save_update : Private {
my $update = $c->stash->{update};
if ( !$update->user->in_storage ) {
+ # User does not exist.
+ # Store changes in token for when token is validated.
+ $c->stash->{token_data} = {
+ name => $update->user->name,
+ password => $update->user->password,
+ };
+ $update->user->name( undef );
+ $update->user->password( '', 1 );
$update->user->insert;
}
elsif ( $c->user && $c->user->id == $update->user->id ) {
diff --git a/perllib/FixMyStreet/App/Controller/Reports.pm b/perllib/FixMyStreet/App/Controller/Reports.pm
index 93af3393c..0587a627a 100644
--- a/perllib/FixMyStreet/App/Controller/Reports.pm
+++ b/perllib/FixMyStreet/App/Controller/Reports.pm
@@ -110,6 +110,8 @@ sub ward : Path : Args(2) {
$c->stash->{council_url} = '/reports/' . $council_short;
+ $c->stash->{stats} = $c->cobrand->get_report_stats();
+
my $pins = $c->stash->{pins};
$c->stash->{page} = 'reports'; # So the map knows to make clickable pins
@@ -338,7 +340,7 @@ sub load_and_group_problems : Private {
$where,
{
columns => [
- 'id', 'council', 'state', 'areas', 'latitude', 'longitude', 'title',
+ 'id', 'council', 'state', 'areas', 'latitude', 'longitude', 'title', 'cobrand',
{ duration => { extract => "epoch from current_timestamp-lastupdate" } },
{ age => { extract => "epoch from current_timestamp-confirmed" } },
],
@@ -351,9 +353,10 @@ sub load_and_group_problems : Private {
my ( %fixed, %open, @pins );
my $re_councils = join('|', keys %{$c->stash->{areas_info}});
- my @cols = ( 'id', 'council', 'state', 'areas', 'latitude', 'longitude', 'title', 'duration', 'age' );
+ my @cols = ( 'id', 'council', 'state', 'areas', 'latitude', 'longitude', 'title', 'cobrand', 'duration', 'age' );
while ( my @problem = $problems->next ) {
my %problem = zip @cols, @problem;
+ $c->log->debug( $problem{'cobrand'} . ', cobrand is ' . $c->cobrand->moniker );
if ( !$problem{council} ) {
# Problem was not sent to any council, add to possible councils
$problem{councils} = 0;
@@ -372,10 +375,10 @@ sub load_and_group_problems : Private {
}
}
- $c->stash(
- fixed => \%fixed,
- open => \%open,
- pins => \@pins,
+ $c->stash(
+ fixed => \%fixed,
+ open => \%open,
+ pins => \@pins,
);
return 1;
diff --git a/perllib/FixMyStreet/App/Controller/Rss.pm b/perllib/FixMyStreet/App/Controller/Rss.pm
index 23345df65..7fddbed97 100755
--- a/perllib/FixMyStreet/App/Controller/Rss.pm
+++ b/perllib/FixMyStreet/App/Controller/Rss.pm
@@ -151,6 +151,7 @@ sub local_problems_ll : Private {
sub output : Private {
my ( $self, $c ) = @_;
+ $c->detach( '/page_error_404_not_found', [ 'Feed not found' ] ) if $c->cobrand->moniker eq 'emptyhomes';
$c->forward( 'lookup_type' );
$c->forward( 'query_main' );
$c->forward( 'generate' );
diff --git a/perllib/FixMyStreet/App/Controller/Tokens.pm b/perllib/FixMyStreet/App/Controller/Tokens.pm
index 10f994d9f..b974f94e6 100644
--- a/perllib/FixMyStreet/App/Controller/Tokens.pm
+++ b/perllib/FixMyStreet/App/Controller/Tokens.pm
@@ -69,6 +69,7 @@ sub confirm_problem : Path('/P') {
# log the problem creation user in to the site
if ( ref($data) && ( $data->{name} || $data->{password} ) ) {
$problem->user->name( $data->{name} ) if $data->{name};
+ $problem->user->phone( $data->{phone} ) if $data->{phone};
$problem->user->password( $data->{password}, 1 ) if $data->{password};
$problem->user->update;
}
diff --git a/perllib/FixMyStreet/Cobrand/Default.pm b/perllib/FixMyStreet/Cobrand/Default.pm
index 1e87468ac..2900497c4 100644
--- a/perllib/FixMyStreet/Cobrand/Default.pm
+++ b/perllib/FixMyStreet/Cobrand/Default.pm
@@ -960,5 +960,13 @@ to be resized then return 0;
sub default_photo_resize { return 0; }
+=head2 get_report_stats
+
+Get stats to display on the council reports page
+
+=cut
+
+sub get_report_stats { return 0; }
+
1;
diff --git a/perllib/FixMyStreet/Cobrand/Reading.pm b/perllib/FixMyStreet/Cobrand/Reading.pm
new file mode 100644
index 000000000..8e98931fd
--- /dev/null
+++ b/perllib/FixMyStreet/Cobrand/Reading.pm
@@ -0,0 +1,108 @@
+package FixMyStreet::Cobrand::Reading;
+use base 'FixMyStreet::Cobrand::Default';
+
+use strict;
+use warnings;
+
+use Carp;
+use URI::Escape;
+use mySociety::VotingArea;
+
+sub site_restriction {
+ return ( "and council='2596'", 'reading', { council => '2596' } );
+}
+
+sub problems_clause {
+ return { council => '2596' };
+}
+
+sub problems {
+ my $self = shift;
+ return $self->{c}->model('DB::Problem')->search( $self->problems_clause );
+}
+
+sub base_url {
+ my $base_url = mySociety::Config::get('BASE_URL');
+ if ($base_url !~ /reading/) {
+ $base_url =~ s{http://(?!www\.)}{http://reading.}g;
+ $base_url =~ s{http://www\.}{http://reading.}g;
+ }
+ return $base_url;
+}
+
+sub site_title {
+ my ( $self ) = @_;
+ return 'Reading City Council FixMyStreet';
+}
+
+sub enter_postcode_text {
+ my ( $self ) = @_;
+ return 'Enter a Reading postcode, or street name and area';
+}
+
+sub council_check {
+ my ( $self, $params, $context ) = @_;
+
+ my $councils = $params->{all_councils};
+ my $council_match = defined $councils->{2596};
+ if ($council_match) {
+ return 1;
+ }
+ my $url = 'http://www.fixmystreet.com/';
+ $url .= 'alert' if $context eq 'alert';
+ $url .= '?pc=' . URI::Escape::uri_escape_utf8($self->{c}->req->param('pc'))
+ if $self->{c}->req->param('pc');
+ my $error_msg = "That location is not covered by Reading.
+Please visit <a href=\"$url\">the main FixMyStreet site</a>.";
+ return ( 0, $error_msg );
+}
+
+# All reports page only has the one council.
+sub all_councils_report {
+ return 0;
+}
+
+sub disambiguate_location {
+ return {
+ town => 'Reading',
+ centre => '51.452983169803964,-0.98382678731985973',
+ span => '0.0833543573028663,0.124500468843446',
+ bounds => [ '51.409779668156361,-1.0529948144525243', '51.493134025459227,-0.92849434560907829' ],
+ };
+}
+
+sub recent_photos {
+ my ($self, $num, $lat, $lon, $dist) = @_;
+ $num = 2 if $num == 3;
+ return $self->problems->recent_photos( $num, $lat, $lon, $dist );
+}
+
+sub get_report_stats {
+ my $self = shift;
+
+ my ( $cobrand, $main_site ) = ( 0, 0 );
+
+ $self->{c}->log->debug( 'X' x 60 );
+ my $stats = $self->{c}->model('DB::Problem')->search(
+ { confirmed => { '>=', '2011-11-01' } },
+ {
+ select => [ { count => 'id', -as => 'cobrand_count' }, 'cobrand' ],
+ group_by => [qw/cobrand/]
+ }
+ );
+
+ while ( my $stat = $stats->next ) {
+ if ( $stat->cobrand eq $self->moniker ) {
+ $cobrand += $stat->get_column( 'cobrand_count' );
+ } else {
+ $main_site += $stat->get_column( 'cobrand_count' );
+ }
+ }
+
+ return {
+ cobrand => $cobrand,
+ main_site => $main_site,
+ };
+}
+
+1;
diff --git a/perllib/FixMyStreet/DB/Result/Contact.pm b/perllib/FixMyStreet/DB/Result/Contact.pm
index 001fb4ac6..941e4e1bb 100644
--- a/perllib/FixMyStreet/DB/Result/Contact.pm
+++ b/perllib/FixMyStreet/DB/Result/Contact.pm
@@ -34,12 +34,33 @@ __PACKAGE__->add_columns(
{ data_type => "timestamp", is_nullable => 0 },
"note",
{ data_type => "text", is_nullable => 0 },
+ "extra",
+ { data_type => "text", is_nullable => 1 },
);
__PACKAGE__->set_primary_key("id");
__PACKAGE__->add_unique_constraint("contacts_area_id_category_idx", ["area_id", "category"]);
+# Created by DBIx::Class::Schema::Loader v0.07010 @ 2011-08-01 10:07:59
+# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:4y6yRz4rMN66pBpkzfJJhg
-# Created by DBIx::Class::Schema::Loader v0.07010 @ 2011-06-23 15:49:48
-# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:BXGd4uk1ybC5RTKlInTr0w
+__PACKAGE__->filter_column(
+ extra => {
+ filter_from_storage => sub {
+ my $self = shift;
+ my $ser = shift;
+ return undef unless defined $ser;
+ my $h = new IO::String($ser);
+ return RABX::wire_rd($h);
+ },
+ filter_to_storage => sub {
+ my $self = shift;
+ my $data = shift;
+ my $ser = '';
+ my $h = new IO::String($ser);
+ RABX::wire_wr( $data, $h );
+ return $ser;
+ },
+ }
+);
1;
diff --git a/perllib/FixMyStreet/DB/Result/Open311conf.pm b/perllib/FixMyStreet/DB/Result/Open311conf.pm
new file mode 100644
index 000000000..0a5784560
--- /dev/null
+++ b/perllib/FixMyStreet/DB/Result/Open311conf.pm
@@ -0,0 +1,39 @@
+package FixMyStreet::DB::Result::Open311conf;
+
+# Created by DBIx::Class::Schema::Loader
+# DO NOT MODIFY THE FIRST PART OF THIS FILE
+
+use strict;
+use warnings;
+
+use base 'DBIx::Class::Core';
+
+__PACKAGE__->load_components("FilterColumn", "InflateColumn::DateTime", "EncodedColumn");
+__PACKAGE__->table("open311conf");
+__PACKAGE__->add_columns(
+ "id",
+ {
+ data_type => "integer",
+ is_auto_increment => 1,
+ is_nullable => 0,
+ sequence => "open311conf_id_seq",
+ },
+ "area_id",
+ { data_type => "integer", is_nullable => 0 },
+ "endpoint",
+ { data_type => "text", is_nullable => 0 },
+ "jurisdiction",
+ { data_type => "text", is_nullable => 1 },
+ "api_key",
+ { data_type => "text", is_nullable => 1 },
+);
+__PACKAGE__->set_primary_key("id");
+__PACKAGE__->add_unique_constraint("open311conf_area_id_key", ["area_id"]);
+
+
+# Created by DBIx::Class::Schema::Loader v0.07010 @ 2011-07-29 18:09:25
+# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:ryqCpvwjNtQrZm4I3s0hxg
+
+
+# You can replace this text with custom code or comments, and it will be preserved on regeneration
+1;
diff --git a/perllib/FixMyStreet/DB/Result/Problem.pm b/perllib/FixMyStreet/DB/Result/Problem.pm
index 987c92c64..9ff19efb6 100644
--- a/perllib/FixMyStreet/DB/Result/Problem.pm
+++ b/perllib/FixMyStreet/DB/Result/Problem.pm
@@ -78,6 +78,8 @@ __PACKAGE__->add_columns(
{ data_type => "timestamp", is_nullable => 1 },
"send_questionnaire",
{ data_type => "boolean", default_value => \"true", is_nullable => 0 },
+ "extra",
+ { data_type => "text", is_nullable => 1 },
"flagged",
{ data_type => "boolean", default_value => \"false", is_nullable => 0 },
);
@@ -102,8 +104,8 @@ __PACKAGE__->has_many(
);
-# Created by DBIx::Class::Schema::Loader v0.07010 @ 2011-06-23 15:49:48
-# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:3sw/1dqxlTvcWEI/eJTm4w
+# Created by DBIx::Class::Schema::Loader v0.07010 @ 2011-07-29 16:26:23
+# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:ifvx9FOlbui66hPyzNIAPA
# Add fake relationship to stored procedure table
__PACKAGE__->has_one(
@@ -113,11 +115,31 @@ __PACKAGE__->has_one(
{ cascade_copy => 0, cascade_delete => 0 },
);
+__PACKAGE__->filter_column(
+ extra => {
+ filter_from_storage => sub {
+ my $self = shift;
+ my $ser = shift;
+ return undef unless defined $ser;
+ my $h = new IO::String($ser);
+ return RABX::wire_rd($h);
+ },
+ filter_to_storage => sub {
+ my $self = shift;
+ my $data = shift;
+ my $ser = '';
+ my $h = new IO::String($ser);
+ RABX::wire_wr( $data, $h );
+ return $ser;
+ },
+ }
+);
use DateTime::TimeZone;
use Image::Size;
use Moose;
use namespace::clean -except => [ 'meta' ];
use Utils;
+use RABX;
with 'FixMyStreet::Roles::Abuser';
@@ -535,6 +557,88 @@ sub duration_string {
);
}
+=head2 update_from_open311_service_request
+
+ $p->update_from_open311_service_request( $request, $council_details, $system_user );
+
+Updates the problem based on information in the passed in open311 request. If the request
+has an older update time than the problem's lastupdate time then nothing happens.
+
+Otherwise a comment will be created if there is status update text in the open311 request.
+If the open311 request has a state of closed then the problem will be marked as fixed.
+
+NB: a comment will always be created if the problem is being marked as fixed.
+
+Fixed problems will not be re-opened by this method.
+
+=cut
+
+sub update_from_open311_service_request {
+ my ( $self, $request, $council_details, $system_user ) = @_;
+
+ my ( $updated, $status_notes );
+
+ if ( ! ref $request->{updated_datetime} ) {
+ $updated = $request->{updated_datetime};
+ }
+
+ if ( ! ref $request->{status_notes} ) {
+ $status_notes = $request->{status_notes};
+ }
+
+ my $update = FixMyStreet::App->model('DB::Comment')->new(
+ {
+ problem_id => $self->id,
+ state => 'confirmed',
+ created => $updated || \'ms_current_timestamp()',
+ confirmed => \'ms_current_timestamp()',
+ text => $status_notes,
+ mark_open => 0,
+ mark_fixed => 0,
+ user => $system_user,
+ anonymous => 0,
+ name => $council_details->{name},
+ }
+ );
+
+
+ my $w3c = DateTime::Format::W3CDTF->new;
+ my $req_time = $w3c->parse_datetime( $request->{updated_datetime} );
+
+ # set a timezone here as the $req_time will have one and if we don't
+ # use a timezone then the date comparisons are invalid.
+ # of course if local timezone is not the one that went into the data
+ # base then we're also in trouble
+ my $lastupdate = $self->lastupdate;
+ $lastupdate->set_time_zone( DateTime::TimeZone->new( name => 'local' ) );
+
+ # update from open311 is older so skip
+ if ( $req_time < $lastupdate ) {
+ return 0;
+ }
+
+ if ( $request->{status} eq 'closed' ) {
+ if ( $self->state ne 'fixed' ) {
+ $self->state('fixed');
+ $update->mark_fixed(1);
+
+ if ( !$status_notes ) {
+ # FIXME - better text here
+ $status_notes = _('Closed by council');
+ }
+ }
+ }
+
+ if ( $status_notes ) {
+ $update->text( $status_notes );
+ $self->lastupdate( $req_time );
+ $self->update;
+ $update->insert;
+ }
+
+ return 1;
+}
+
# we need the inline_constructor bit as we don't inherit from Moose
__PACKAGE__->meta->make_immutable( inline_constructor => 0 );
diff --git a/perllib/FixMyStreet/Geocode/Bing.pm b/perllib/FixMyStreet/Geocode/Bing.pm
index 4e12a7a7f..856d7061e 100644
--- a/perllib/FixMyStreet/Geocode/Bing.pm
+++ b/perllib/FixMyStreet/Geocode/Bing.pm
@@ -22,6 +22,7 @@ use Digest::MD5 qw(md5_hex);
# may be used to disambiguate the location in cobranded versions of the site.
sub string {
my ( $s, $c, $params ) = @_;
+ $s .= '+' . $params->{town} if $params->{town} and $s !~ /$params->{town}/i;
my $url = "http://dev.virtualearth.net/REST/v1/Locations?q=$s&c=en-GB"; # FIXME nb-NO for Norway
$url .= '&mapView=' . $params->{bounds}[0] . ',' . $params->{bounds}[1]
if $params->{bounds};
@@ -43,7 +44,7 @@ sub string {
if (!$js) {
return { error => _('Sorry, we could not parse that location. Please try again.') };
} elsif ($js =~ /BT\d/) {
- return { error => _("We do not cover Northern Ireland, I'm afraid, as our licence doesn't include any maps for the region.") };
+ return { error => _("We do not currently cover Northern Ireland, I'm afraid.") };
}
$js = JSON->new->utf8->allow_nonref->decode($js);
diff --git a/perllib/FixMyStreet/Map/FMS.pm b/perllib/FixMyStreet/Map/FMS.pm
index d5edac763..24842c861 100644
--- a/perllib/FixMyStreet/Map/FMS.pm
+++ b/perllib/FixMyStreet/Map/FMS.pm
@@ -47,11 +47,13 @@ sub map_tiles {
"http://tilma.mysociety.org/sv/$z/$x/$y.png",
];
} else {
+ my $url = "g=701";
+ $url .= "&productSet=mmOS" if $z > 10;
return [
- "http://ecn.t0.tiles.virtualearth.net/tiles/r" . get_quadkey($x-1, $y-1, $z) . ".png?g=701&productSet=mmOS",
- "http://ecn.t1.tiles.virtualearth.net/tiles/r" . get_quadkey($x, $y-1, $z) . ".png?g=701&productSet=mmOS",
- "http://ecn.t2.tiles.virtualearth.net/tiles/r" . get_quadkey($x-1, $y, $z) . ".png?g=701&productSet=mmOS",
- "http://ecn.t3.tiles.virtualearth.net/tiles/r" . get_quadkey($x, $y, $z) . ".png?g=701&productSet=mmOS",
+ "http://ecn.t0.tiles.virtualearth.net/tiles/r" . get_quadkey($x-1, $y-1, $z) . ".png?$url",
+ "http://ecn.t1.tiles.virtualearth.net/tiles/r" . get_quadkey($x, $y-1, $z) . ".png?$url",
+ "http://ecn.t2.tiles.virtualearth.net/tiles/r" . get_quadkey($x-1, $y, $z) . ".png?$url",
+ "http://ecn.t3.tiles.virtualearth.net/tiles/r" . get_quadkey($x, $y, $z) . ".png?$url",
];
}
}
diff --git a/perllib/Open311.pm b/perllib/Open311.pm
new file mode 100644
index 000000000..f3f642895
--- /dev/null
+++ b/perllib/Open311.pm
@@ -0,0 +1,204 @@
+package Open311;
+
+use URI;
+use Moose;
+use XML::Simple;
+use LWP::Simple;
+use LWP::UserAgent;
+use HTTP::Request::Common qw(POST);
+
+has jurisdiction => ( is => 'ro', isa => 'Str' );;
+has api_key => ( is => 'ro', isa => 'Str' );
+has endpoint => ( is => 'ro', isa => 'Str' );
+has test_mode => ( is => 'ro', isa => 'Bool' );
+has test_uri_used => ( is => 'rw', 'isa' => 'Str' );
+has test_get_returns => ( is => 'rw' );
+has endpoints => ( is => 'rw', default => sub { { services => 'services.xml', requests => 'requests.xml' } } );
+
+sub get_service_list {
+ my $self = shift;
+
+ my $service_list_xml = $self->_get( $self->endpoints->{services} );
+
+ return $self->_get_xml_object( $service_list_xml );
+}
+
+sub get_service_meta_info {
+ my $self = shift;
+ my $service_id = shift;
+
+ my $service_meta_xml = $self->_get( "services/$service_id.xml" );
+ return $self->_get_xml_object( $service_meta_xml );
+}
+
+sub send_service_request {
+ my $self = shift;
+ my $problem = shift;
+ my $extra = shift;
+ my $service_code = shift;
+
+ my $description = <<EOT;
+title: @{[$problem->title()]}
+
+detail: @{[$problem->detail()]}
+
+url: $extra->{url}
+
+Submitted via FixMyStreet
+EOT
+;
+
+ my $params = {
+ lat => $problem->latitude,
+ long => $problem->longitude,
+ email => $problem->user->email,
+ description => $description,
+ service_code => $service_code,
+ };
+
+ if ( $problem->user->phone ) {
+ $params->{ phone } = $problem->user->phone;
+ }
+
+ if ( $extra->{image_url} ) {
+ $params->{media_url} = $extra->{image_url};
+ }
+
+ if ( $problem->extra ) {
+ my $extras = $problem->extra;
+
+ for my $attr ( @$extras ) {
+ my $name = sprintf( 'attribute[%s]', $attr->{name} );
+ $params->{ $name } = $attr->{value};
+ }
+ }
+
+ my $response = $self->_post( $self->endpoints->{requests}, $params );
+
+ if ( $response ) {
+ my $obj = $self->_get_xml_object( $response );
+
+ if ( $obj ) {
+ if ( $obj->{ request }->{ service_request_id } ) {
+ return $obj->{ request }->{ service_request_id };
+ } else {
+ my $token = $obj->{ request }->{ token };
+ if ( $token ) {
+ return $self->get_service_request_id_from_token( $token );
+ }
+ }
+ }
+
+ warn sprintf( "Failed to submit problem %s over Open311, response\n: %s", $problem->id, $response );
+ return 0;
+ }
+}
+
+sub get_service_requests {
+ my $self = shift;
+ my $report_ids = shift;
+
+ my $params = {};
+
+ if ( $report_ids ) {
+ $params->{service_request_id} = join ',', @$report_ids;
+ }
+
+ my $service_request_xml = $self->_get( $self->endpoints->{requests}, $params || undef );
+ return $self->_get_xml_object( $service_request_xml );
+}
+
+sub get_service_request_id_from_token {
+ my $self = shift;
+ my $token = shift;
+
+ my $service_token_xml = $self->_get( "tokens/$token.xml" );
+
+ my $obj = $self->_get_xml_object( $service_token_xml );
+
+ if ( $obj && $obj->{ request }->{ service_request_id } ) {
+ return $obj->{ request }->{ service_request_id };
+ } else {
+ return 0;
+ }
+}
+
+sub _get {
+ my $self = shift;
+ my $path = shift;
+ my $params = shift || {};
+
+ my $uri = URI->new( $self->endpoint );
+
+ $params->{ jurisdiction_id } = $self->jurisdiction;
+ $uri->path( $uri->path . $path );
+ $uri->query_form( $params );
+
+ my $content;
+ if ( $self->test_mode ) {
+ $content = $self->test_get_returns->{ $path };
+ $self->test_uri_used( $uri->as_string );
+ } else {
+ $content = get( $uri->as_string );
+ }
+
+ return $content;
+}
+
+sub _post {
+ my $self = shift;
+ my $path = shift;
+ my $params = shift;
+
+ my $uri = URI->new( $self->endpoint );
+ $uri->path( $uri->path . $path );
+
+ my $req = POST $uri->as_string,
+ [
+ jurisdiction_id => $self->jurisdiction,
+ api_key => $self->api_key,
+ %{ $params }
+ ];
+
+ my $ua = LWP::UserAgent->new();
+ my $res = $ua->request( $req );
+
+ if ( $res->is_success ) {
+ return $res->decoded_content;
+ } else {
+ warn "request failed: " . $res->status_line;
+ warn $self->_process_error( $res->decoded_content );
+ return 0;
+ }
+}
+
+sub _process_error {
+ my $self = shift;
+ my $error = shift;
+
+ my $obj = $self->_get_xml_object( $error );
+
+ my $msg = '';
+ if ( ref $obj && exists $obj->{error} ) {
+ my $errors = $obj->{error};
+ $errors = [ $errors ] if ref $errors ne 'ARRAY';
+ $msg .= sprintf( "%s: %s\n", $_->{code}, $_->{description} ) for @{ $errors };
+ }
+
+ return $msg || 'unknown error';
+}
+
+sub _get_xml_object {
+ my $self = shift;
+ my $xml= shift;
+
+ my $simple = XML::Simple->new();
+ my $obj;
+
+ eval {
+ $obj = $simple ->XMLin( $xml );
+ };
+
+ return $obj;
+}
+1;
diff --git a/perllib/Open311/GetUpdates.pm b/perllib/Open311/GetUpdates.pm
new file mode 100644
index 000000000..5d5291d47
--- /dev/null
+++ b/perllib/Open311/GetUpdates.pm
@@ -0,0 +1,82 @@
+package Open311::GetUpdates;
+
+use Moose;
+use Open311;
+use FixMyStreet::App;
+
+has council_list => ( is => 'ro' );
+has system_user => ( is => 'ro' );
+
+sub get_updates {
+ my $self = shift;
+
+ while ( my $council = $self->council_list->next ) {
+ my $open311 = Open311->new(
+ endpoint => $council->endpoint,
+ jurisdiction => $council->jurisdiction,
+ api_key => $council->api_key
+ );
+
+ my $area_id = $council->area_id;
+
+ my $council_details = mySociety::MaPit::call( 'area', $area_id );
+
+ my $reports = FixMyStreet::App->model('DB::Problem')->search(
+ {
+ council => { like => "\%$area_id\%" },
+ state => { 'IN', [qw/confirmed fixed/] },
+ -and => [
+ external_id => { '!=', undef },
+ external_id => { '!=', '' },
+ ],
+ }
+ );
+
+ my @report_ids = ();
+ while ( my $report = $reports->next ) {
+ push @report_ids, $report->external_id;
+ }
+
+ next unless @report_ids;
+
+ $self->update_reports( \@report_ids, $open311, $council_details );
+ }
+}
+
+sub update_reports {
+ my ( $self, $report_ids, $open311, $council_details ) = @_;
+
+ my $service_requests = $open311->get_service_requests( $report_ids );
+
+ my $requests;
+
+ # XML::Simple is a bit inconsistent in how it structures
+ # things depending on the number of children an element has :(
+ if ( ref $service_requests->{request} eq 'ARRAY' ) {
+ $requests = $service_requests->{request};
+ }
+ else {
+ $requests = [ $service_requests->{request} ];
+ }
+
+ for my $request (@$requests) {
+ # if it's a ref that means it's an empty element
+ # however, if there's no updated date then we can't
+ # tell if it's newer that what we have so we should skip it
+ next if ref $request->{updated_datetime} || ! exists $request->{updated_datetime};
+
+ my $request_id = $request->{service_request_id};
+
+ my $problem =
+ FixMyStreet::App->model('DB::Problem')
+ ->search( { external_id => $request_id, } );
+
+ if (my $p = $problem->first) {
+ $p->update_from_open311_service_request( $request, $council_details, $self->system_user );
+ }
+ }
+
+ return 1;
+}
+
+1;
diff --git a/perllib/Open311/PopulateServiceList.pm b/perllib/Open311/PopulateServiceList.pm
new file mode 100644
index 000000000..cfec9005d
--- /dev/null
+++ b/perllib/Open311/PopulateServiceList.pm
@@ -0,0 +1,240 @@
+package Open311::PopulateServiceList;
+
+use Moose;
+use LWP::Simple;
+use XML::Simple;
+use FixMyStreet::App;
+use Open311;
+
+has council_list => ( is => 'ro' );
+has found_contacts => ( is => 'rw', default => sub { [] } );
+
+has _current_council => ( is => 'rw' );
+has _current_open311 => ( is => 'rw' );
+has _current_service => ( is => 'rw' );
+
+my $council_list = FixMyStreet::App->model('DB::Open311conf');
+
+sub process_councils {
+ my $self = shift;
+
+ while ( my $council = $self->council_list->next ) {
+ next unless $council->endpoint;
+ $self->_current_council( $council );
+ $self->process_council;
+ }
+}
+
+sub process_council {
+ my $self = shift;
+
+ my $open311 = Open311->new(
+ endpoint => $self->_current_council->endpoint,
+ jurisdiction => $self->_current_council->jurisdiction,
+ api_key => $self->_current_council->api_key
+ );
+
+ $self->_current_open311( $open311 );
+ $self->_check_endpoints;
+
+ my $list = $open311->get_service_list;
+ unless ( $list ) {
+ warn "ERROR: no service list found for " . $self->_current_council->area_id . "\n";
+ return;
+ }
+ $self->process_services( $list );
+}
+
+
+
+sub _check_endpoints {
+ my $self = shift;
+
+ # west berks end point not standard
+ if ( $self->_current_council->area_id == 2619 ) {
+ $self->_current_open311->endpoints(
+ {
+ services => 'Services',
+ requests => 'Requests'
+ }
+ );
+ }
+}
+
+
+sub process_services {
+ my $self = shift;
+ my $list = shift;
+
+ $self->found_contacts( [] );
+ foreach my $service ( @{ $list->{service} } ) {
+ $self->_current_service( $service );
+ $self->process_service;
+ }
+ $self->_delete_contacts_not_in_service_list;
+}
+
+sub process_service {
+ my $self = shift;
+
+ my $category = $self->_current_council->area_id == 2218 ?
+ $self->_current_service->{description} :
+ $self->_current_service->{service_name};
+
+ print $self->_current_service->{service_code} . ': ' . $category . "\n";
+ my $contacts = FixMyStreet::App->model( 'DB::Contact')->search(
+ {
+ area_id => $self->_current_council->area_id,
+ -OR => [
+ email => $self->_current_service->{service_code},
+ category => $category,
+ ]
+ }
+ );
+
+ if ( $contacts->count() > 1 ) {
+ printf(
+ "Multiple contacts for service code %s, category %s - Skipping\n",
+ $self->_current_service->{service_code},
+ $category,
+ );
+
+ # best to not mark them as deleted as we don't know what we're doing
+ while ( my $contact = $contacts->next ) {
+ push @{ $self->found_contacts }, $contact->email;
+ }
+
+ return;
+ }
+
+ my $contact = $contacts->first;
+
+ if ( $contact ) {
+ $self->_handle_existing_contact( $contact );
+ } else {
+ $self->_create_contact;
+ }
+}
+
+sub _handle_existing_contact {
+ my ( $self, $contact ) = @_;
+
+ my $service_name = $self->_normalize_service_name;
+
+ print $self->_current_council->area_id . " already has a contact for service code " . $self->_current_service->{service_code} . "\n";
+
+ if ( $contact->deleted || $service_name ne $contact->category || $self->_current_service->{service_code} ne $contact->email ) {
+ eval {
+ $contact->update(
+ {
+ category => $service_name,
+ email => $self->_current_service->{service_code},
+ confirmed => 1,
+ deleted => 0,
+ editor => $0,
+ whenedited => \'ms_current_timestamp()',
+ note => 'automatically undeleted by script',
+ }
+ );
+ };
+
+ if ( $@ ) {
+ warn "Failed to update contact for service code " . $self->_current_service->{service_code} . " for council @{[$self->_current_council->area_id]}: $@\n";
+ return;
+ }
+ }
+
+ push @{ $self->found_contacts }, $self->_current_service->{service_code};
+}
+
+sub _create_contact {
+ my $self = shift;
+
+ my $service_name = $self->_normalize_service_name;
+
+ my $contact;
+ eval {
+ $contact = FixMyStreet::App->model( 'DB::Contact')->create(
+ {
+ email => $self->_current_service->{service_code},
+ area_id => $self->_current_council->area_id,
+ category => $service_name,
+ confirmed => 1,
+ deleted => 0,
+ editor => $0,
+ whenedited => \'ms_current_timestamp()',
+ note => 'created automatically by script',
+ }
+ );
+ };
+
+ if ( $@ ) {
+ warn "Failed to create contact for service code " . $self->_current_service->{service_code} . " for council @{[$self->_current_council->area_id]}: $@\n";
+ return;
+ }
+
+ if ( $contact and lc( $self->_current_service->{metadata} ) eq 'true' ) {
+ $self->_add_meta_to_contact( $contact );
+ }
+
+ if ( $contact ) {
+ push @{ $self->found_contacts }, $self->_current_service->{service_code};
+ print "created contact for service code " . $self->_current_service->{service_code} . " for council @{[$self->_current_council->area_id]}\n";
+ }
+}
+
+sub _add_contact_to_meta {
+ my ( $self, $contact ) = @_;
+
+ print "Fetching meta data for $self->_current_service->{service_code}\n";
+ my $meta_data = $self->_current_open311->get_service_meta_info( $self->_current_service->{service_code} );
+
+ # turn the data into something a bit more friendly to use
+ my @meta =
+ # remove trailing colon as we add this when we display so we don't want 2
+ map { $_->{description} =~ s/:\s*//; $_ }
+ # there is a display order and we only want to sort once
+ sort { $a->{order} <=> $b->{order} }
+ @{ $meta_data->{attributes}->{attribute} };
+
+ $contact->extra( \@meta );
+ $contact->update;
+}
+
+sub _normalize_service_name {
+ my $self = shift;
+
+ # FIXME - at the moment it makes more sense to use the description
+ # for cambridgeshire but need a more flexible way to set this
+ my $service_name = $self->_current_council->area_id == 2218 ?
+ $self->_current_service->{description} :
+ $self->_current_service->{service_name};
+ # remove trailing whitespace as it upsets db queries
+ # to look up contact details when creating problem
+ $service_name =~ s/\s+$//;
+
+ return $service_name;
+}
+
+sub _delete_contacts_not_in_service_list {
+ my $self = shift;
+
+ my $found_contacts = FixMyStreet::App->model( 'DB::Contact')->search(
+ {
+ email => { -not_in => $self->found_contacts },
+ area_id => $self->_current_council->area_id,
+ deleted => 0,
+ }
+ );
+
+ $found_contacts->update(
+ {
+ deleted => 1,
+ editor => $0,
+ whenedited => \'ms_current_timestamp()',
+ note => 'automatically marked as deleted by script'
+ }
+ );
+}
+
+1;