diff options
25 files changed, 1392 insertions, 8 deletions
diff --git a/bin/open311-populate-service-list b/bin/open311-populate-service-list new file mode 100755 index 000000000..cb22d6dad --- /dev/null +++ b/bin/open311-populate-service-list @@ -0,0 +1,127 @@ +#!/usr/bin/perl + +use strict; +use warnings; +use LWP::Simple; +use XML::Simple; +use FixMyStreet::App; +use Open311; + +use Data::Dumper; + +my $council_list = FixMyStreet::App->model('DB::Open311conf'); + +while ( my $council = $council_list->next ) { + + my $open311 = Open311->new( + endpoint => $council->endpoint, + jurisdiction => $council->jurisdiction, + api_key => $council->api_key + ); + + # west berks end point not standard + if ( $council->area_id == 2619 ) { + $open311->endpoints( + { + services => 'Services', + requests => 'Requests' + } + ); + } + + my $list = $open311->get_service_list; + + my @found_contacts; + + # print Dumper $list; + + foreach my $service ( @{ $list->{service} } ) { + print $service->{service_code} . ': ' . $service->{service_name} . "\n"; + my $contacts = FixMyStreet::App->model( 'DB::Contact')->search( + { + area_id => $council->area_id, + -OR => [ + email => $service->{service_code}, + category => $service->{service_name} + ] + } + ); + + my $contact = $contacts->first; + + # remove trailing whitespace as it upsets db queries + # to look up contact details when creating problem + my $service_name = $service->{service_name}; + $service_name =~ s/\s+$//; + + # FIXME - handle change of service name or service code + if ( $contact ) { + + print $council->area_id . " already has a contact for service code " . $service->{service_code} . "\n"; + push @found_contacts, $service->{service_code}; + + if ( $contact->deleted ) { + $contact->update( + { + category => $service_name, + email => $service->{service_code}, + confirmed => 1, + deleted => 0, + editor => $0, + whenedited => \'ms_current_timestamp()', + note => 'automatically undeleted by script', + } + ); + } + } else { + my $contact = FixMyStreet::App->model( 'DB::Contact')->create( + { + email => $service->{service_code}, + area_id => $council->area_id, + category => $service_name, + confirmed => 1, + deleted => 0, + editor => $0, + whenedited => \'ms_current_timestamp()', + note => 'created automatically by script', + } + ); + + if ( lc( $service->{metadata} ) eq 'true' ) { + print "Fetching meta data for $service->{service_code}\n"; + my $meta_data = $open311->get_service_meta_info( $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; + } + + push @found_contacts, $service->{service_code}; + print "created contact for service code " . $service->{service_code} . " for council @{[$council->area_id]}\n"; + } + } + + my $found_contacts = FixMyStreet::App->model( 'DB::Contact')->search( + { + email => { -not_in => \@found_contacts }, + area_id => $council->area_id, + deleted => 0, + } + ); + + $found_contacts->update( + { + deleted => 1, + editor => $0, + whenedited => \'ms_current_timestamp()', + note => 'automatically marked as deleted by script' + } + ); +} diff --git a/bin/open311-update-reports b/bin/open311-update-reports new file mode 100644 index 000000000..41c9c4546 --- /dev/null +++ b/bin/open311-update-reports @@ -0,0 +1,21 @@ +#!/usr/bin/perl + +use strict; +use warnings; +use Open311::GetUpdates; +use FixMyStreet::App; + +# FIXME - make this configurable and/or better +my $system_user = FixMyStreet::App->model('DB::User')->find_or_create( + { + email => FixMyStreet::App->config->{'CONTACT_EMAIL'}, + name => 'System User', + } +); + +my $council_list = FixMyStreet::App->model('DB::Open311conf'); + +my $update = Open311::GetUpdates->new( + council_list => $council_list, + system_user => $system_user +)->get_updates; diff --git a/bin/send-reports b/bin/send-reports index 9ab6f8274..1ca0b51f0 100755 --- a/bin/send-reports +++ b/bin/send-reports @@ -27,6 +27,8 @@ use mySociety::EmailUtil; use mySociety::MaPit; use mySociety::Web qw(ent); +use Open311; + # Set up site, language etc. my ($verbose, $nomail) = CronFns::options(); my $base_url = mySociety::Config::get('BASE_URL'); @@ -93,7 +95,7 @@ while (my $row = $unsent->next) { $h{closest_address} = $cobrand->find_closest( $h{latitude}, $h{longitude} ); } - my (@to, @recips, $template, $areas_info); + my (@to, @recips, $template, $areas_info, @open311_councils); if ($site eq 'emptyhomes') { my $council = $row->council; @@ -135,6 +137,9 @@ while (my $row = $unsent->next) { $h{category} = 'Customer Services' if $h{category} eq 'Other'; } elsif ($areas_info->{$council}->{type} eq 'LBO') { # London $send_web = 'london'; + } elsif ( my $endpoint = FixMyStreet::App->model("DB::Open311conf")->find( { area_id => $council } ) ) { + push @open311_councils, $endpoint; + $send_web = 'open311'; } else { my $contact = FixMyStreet::App->model("DB::Contact")->find( { deleted => 0, @@ -247,6 +252,38 @@ while (my $row = $unsent->next) { if (!$nomail) { $result *= post_london_report( $row, %h ); } + } elsif ($send_web eq 'open311') { + # FIXME - looking this up twice :( + foreach my $conf ( @open311_councils ) { + # my $conf = FixMyStreet::App->model('DB::Open311conf')->find( { area_id => $row->council } ); + print 'posting to end point for ' . $conf->area_id . "\n"; + + # FIXME - doesn't deal with multiple recipients + my $contact = FixMyStreet::App->model("DB::Contact")->find( { + deleted => 0, + area_id => $conf->area_id, + category => $row->category + } ); + + my $open311 = Open311->new( + jurisdiction => $conf->jurisdiction, + endpoint => $conf->endpoint, + api_key => $conf->api_key, + ); + + # non standard west berks end points + if ( $row->council == 2619 ) { + $open311->endpoints( { services => 'Services', requests => 'Requests' } ); + } + + my $resp = $open311->send_service_request( $row, \%h, $contact->email ); + + if ( $resp ) { + $row->external_id( $resp ); + $result = 0; + } else { + $result = 1; + } } if ($result == mySociety::EmailUtil::EMAIL_SUCCESS) { diff --git a/db/schema.sql b/db/schema.sql index 99cf2832d..fcd137919 100644 --- a/db/schema.sql +++ b/db/schema.sql @@ -78,7 +78,10 @@ create table contacts ( -- time of last change whenedited timestamp not null, -- what the last change was for: author's notes - note text not null + note text not null, + + -- extra fields required for open311 + extra text ); create unique index contacts_area_id_category_idx on contacts(area_id, category); @@ -184,6 +187,7 @@ create table problem ( lastupdate timestamp not null default ms_current_timestamp(), whensent timestamp, send_questionnaire boolean not null default 't', + extra text, -- extra fields required for open311 flagged boolean not null default 'f' ); create index problem_state_latitude_longitude_idx on problem(state, latitude, longitude); @@ -407,3 +411,12 @@ create table admin_log ( whenedited timestamp not null default ms_current_timestamp() ); +-- Record open 311 configuration details + +create table open311conf ( + id serial primary key, + area_id integer not null unique, + endpoint text not null, + jurisdiction text, + api_key text +); diff --git a/db/schema_0009-add_extra_to_problem.sql b/db/schema_0009-add_extra_to_problem.sql new file mode 100644 index 000000000..bac5806c7 --- /dev/null +++ b/db/schema_0009-add_extra_to_problem.sql @@ -0,0 +1,6 @@ +begin; + +ALTER TABLE problem + ADD COLUMN extra TEXT; + +commit; diff --git a/db/schema_0010-add_open311_conf.sql b/db/schema_0010-add_open311_conf.sql new file mode 100644 index 000000000..920272c05 --- /dev/null +++ b/db/schema_0010-add_open311_conf.sql @@ -0,0 +1,11 @@ +begin; + +CREATE TABLE open311conf ( + id SERIAL PRIMARY KEY, + area_id INTEGER NOT NULL unique, + endpoint TEXT NOT NULL, + jurisdiction TEXT, + api_key TEXT +); + +commit; diff --git a/db/schema_0011-add_extra_to_contacts.sql b/db/schema_0011-add_extra_to_contacts.sql new file mode 100644 index 000000000..fd6eae807 --- /dev/null +++ b/db/schema_0011-add_extra_to_contacts.sql @@ -0,0 +1,6 @@ +begin; + +ALTER TABLE contacts + ADD COLUMN extra TEXT; + +commit; diff --git a/perllib/FixMyStreet/App/Controller/Admin.pm b/perllib/FixMyStreet/App/Controller/Admin.pm index 8949e9f4c..4410acdd4 100644 --- a/perllib/FixMyStreet/App/Controller/Admin.pm +++ b/perllib/FixMyStreet/App/Controller/Admin.pm @@ -332,6 +332,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'); + } } } @@ -349,6 +375,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/Report/New.pm b/perllib/FixMyStreet/App/Controller/Report/New.pm index 1e6a3a088..e84a44dd3 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 @@ -476,6 +477,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 +524,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 +544,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 +724,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. diff --git a/perllib/FixMyStreet/DB/Result/Contact.pm b/perllib/FixMyStreet/DB/Result/Contact.pm index 001fb4ac6..779ca9bc2 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"]); +__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; + }, + } +); -# Created by DBIx::Class::Schema::Loader v0.07010 @ 2011-06-23 15:49:48 -# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:BXGd4uk1ybC5RTKlInTr0w +# Created by DBIx::Class::Schema::Loader v0.07010 @ 2011-08-01 10:07:59 +# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:4y6yRz4rMN66pBpkzfJJhg 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/Open311.pm b/perllib/Open311.pm new file mode 100644 index 000000000..e5d57dca3 --- /dev/null +++ b/perllib/Open311.pm @@ -0,0 +1,199 @@ +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 }; + return $self->get_service_request_id_from_token( $token ); + } + } + } +} + +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..5fecaf89c --- /dev/null +++ b/perllib/Open311/GetUpdates.pm @@ -0,0 +1,83 @@ +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) { + warn 'updating problem ' . $p->id; + $p->update_from_open311_service_request( $request, $council_details, $self->system_user ); + } + } + + return 1; +} + +1; diff --git a/t/app/controller/admin.t b/t/app/controller/admin.t index 25e921a4f..beeb6c9c1 100644 --- a/t/app/controller/admin.t +++ b/t/app/controller/admin.t @@ -205,6 +205,59 @@ subtest 'check contact updating' => sub { $mech->content_like(qr{test2\@example.com[^<]*</td>[^<]*<td><strong>Yes}s); }; +my $open311 = + FixMyStreet::App->model('DB::Open311Conf')->search( { area_id => 2650 } ); +$open311->delete if $open311; + +subtest 'check open311 configuring' => sub { + $mech->get_ok('/admin/council_contacts/2650/'); + $mech->content_lacks('Council contacts configured via Open311'); + + $mech->form_number(3); + $mech->submit_form_ok( + { + with_fields => { + api_key => 'api key', + endpoint => 'http://example.com/open311', + jurisdiction => 'mySociety', + } + } + ); + $mech->content_contains('Council contacts configured via Open311'); + $mech->content_contains('Configuration updated - contacts will be generated automatically later'); + + $open311 = + FixMyStreet::App->model('DB::Open311Conf')->search( { area_id => 2650 } ); + + is $open311->count, 1, 'only one configuration'; + my $conf = $open311->first; + is $conf->endpoint, 'http://example.com/open311', 'endpoint configured'; + is $conf->api_key, 'api key', 'api key configured'; + is $conf->jurisdiction, 'mySociety', 'jurisdiction configures'; + + $mech->form_number(3); + $mech->submit_form_ok( + { + with_fields => { + api_key => 'new api key', + endpoint => 'http://example.org/open311', + jurisdiction => 'open311', + } + } + ); + + $mech->content_contains('Configuration updated'); + + $open311 = + FixMyStreet::App->model('DB::Open311Conf')->search( { area_id => 2650 } ); + + is $open311->count, 1, 'only one configuration'; + $conf = $open311->first; + is $conf->endpoint, 'http://example.org/open311', 'endpoint updated'; + is $conf->api_key, 'new api key', 'api key updated'; + is $conf->jurisdiction, 'open311', 'jurisdiction configures'; +}; + subtest 'check text output' => sub { $mech->get_ok('/admin/council_contacts/2650?text=1'); is $mech->content_type, 'text/plain'; diff --git a/t/app/controller/report_new_open311.t b/t/app/controller/report_new_open311.t new file mode 100644 index 000000000..dc3583e6b --- /dev/null +++ b/t/app/controller/report_new_open311.t @@ -0,0 +1,167 @@ +use strict; +use warnings; +use Test::More; + +use FixMyStreet::TestMech; +use Web::Scraper; + +my $mech = FixMyStreet::TestMech->new; + +my $open311Conf = FixMyStreet::App->model('DB::Open311Conf')->find_or_create( { + area_id => 2651, + endpoint => 'http://example.com/open311', + jurisdiction => 'mySociety', + api_key => 'apikey', +} ); + +my %contact_params = ( + confirmed => 1, + deleted => 0, + editor => 'Test', + whenedited => \'current_timestamp', + note => 'Created for test', +); +# Let's make some contacts to send things to! +my $contact1 = FixMyStreet::App->model('DB::Contact')->find_or_create( { + %contact_params, + area_id => 2651, # Edinburgh + category => 'Street lighting', + email => '100', + extra => [ { description => 'Lamppost number', code => 'number', required => 'True' }, + { description => 'Lamppost type', code => 'type', required => 'False', values => + { value => { Yellow => { key => 'modern' }, 'Gas' => { key => 'old' } } } + } + ], +} ); +my $contact2 = FixMyStreet::App->model('DB::Contact')->find_or_create( { + %contact_params, + area_id => 2651, # Edinburgh + category => 'Graffiti Removal', + email => '101', +} ); +ok $contact1, "created test contact 1"; +ok $contact2, "created test contact 2"; + +# test that the various bit of form get filled in and errors correctly +# generated. +foreach my $test ( + { + msg => 'all fields empty', + pc => 'EH99 1SP', + fields => { + title => '', + detail => '', + photo => '', + name => '', + may_show_name => '1', + email => '', + phone => '', + category => 'Street lighting', + password_sign_in => '', + password_register => '', + remember_me => undef, + }, + changes => { + number => '', + type => 'old', + }, + errors => [ + 'Please enter a subject', + 'Please enter some details', + 'This information is required', + 'Please enter your email', + 'Please enter your name', + ], + submit_with => { + title => 'test', + detail => 'test detail', + name => 'Test User', + email => 'testopen311@example.com', + category => 'Street lighting', + number => 27, + }, + extra => [ + { + name => 'number', + value => 27, + description => 'Lamppost number', + }, + { + name => 'type', + value => 'old', + description => 'Lamppost type', + } + ] + }, + ) +{ + subtest "check form errors where $test->{msg}" => sub { + $mech->log_out_ok; + $mech->clear_emails_ok; + + # check that the user does not exist + my $test_email = $test->{submit_with}->{email}; + my $user = FixMyStreet::App->model('DB::User')->find( { email => $test_email } ); + if ( $user ) { + $user->problems->delete; + $user->comments->delete; + $user->delete; + } + + $mech->get_ok('/around'); + + # submit initial pc form + $mech->submit_form_ok( { with_fields => { pc => $test->{pc} } }, + "submit location" ); + is_deeply $mech->form_errors, [], "no errors for pc '$test->{pc}'"; + + # click through to the report page + $mech->follow_link_ok( { text => 'skip this step', }, + "follow 'skip this step' link" ); + + # submit the main form + $mech->submit_form_ok( { with_fields => $test->{fields} }, + "submit form" ); + + # check that we got the errors expected + is_deeply $mech->form_errors, $test->{errors}, "check errors"; + + # check that fields have changed as expected + my $new_values = { + %{ $test->{fields} }, # values added to form + %{ $test->{changes} }, # changes we expect + }; + is_deeply $mech->visible_form_values, $new_values, + "values correctly changed"; + + if ( $test->{fields}->{category} eq 'Street lighting' ) { + my $result = scraper { + process 'div#category_meta div select#form_type option', 'option[]' => '@value'; + } + ->scrape( $mech->response ); + + is_deeply $result->{option}, [ qw/old modern/], 'displayed streetlight type select'; + } + + $new_values = { + %{ $test->{fields} }, + %{ $test->{submit_with} }, + }; + $mech->submit_form_ok( { with_fields => $new_values } ); + + $user = FixMyStreet::App->model('DB::User')->find( { email => $test_email } ); + ok $user, 'created user'; + my $prob = $user->problems->first; + ok $prob, 'problem created'; + + is_deeply $prob->extra, $test->{extra}, 'extra open311 data added to problem'; + + $user->problems->delete; + $user->delete; + }; +} + +$contact1->delete; +$contact2->delete; + +done_testing(); diff --git a/t/app/model/problem.t b/t/app/model/problem.t index 4c6be6a8d..ad42c5fdf 100644 --- a/t/app/model/problem.t +++ b/t/app/model/problem.t @@ -152,6 +152,111 @@ for my $test ( }; } +my $user = FixMyStreet::App->model('DB::User')->find_or_create( + { + email => 'system_user@example.com' + } +); + +$problem->user( $user ); +$problem->created( DateTime->now()->subtract( days => 1 ) ); +$problem->lastupdate( DateTime->now()->subtract( days => 1 ) ); +$problem->anonymous(1); +$problem->insert; + +my $tz_local = DateTime::TimeZone->new( name => 'local' ); + +for my $test ( + { + desc => 'request older than problem ignored', + lastupdate => '', + request => { + updated_datetime => DateTime::Format::W3CDTF->new()->format_datetime( DateTime->now()->set_time_zone( $tz_local )->subtract( days => 2 ) ), + }, + council => { + name => 'Edinburgh City Council', + }, + created => 0, + }, + { + desc => 'request newer than problem created', + lastupdate => '', + request => { + updated_datetime => DateTime::Format::W3CDTF->new()->format_datetime( DateTime->now()->set_time_zone( $tz_local ) ), + status => 'open', + status_notes => 'this is an update from the council', + }, + council => { + name => 'Edinburgh City Council', + }, + created => 1, + state => 'confirmed', + mark_fixed => 0, + mark_open => 0, + }, + { + desc => 'update with state of closed fixes problem', + lastupdate => '', + request => { + updated_datetime => DateTime::Format::W3CDTF->new()->format_datetime( DateTime->now()->set_time_zone( $tz_local ) ), + status => 'closed', + status_notes => 'the council have fixed this', + }, + council => { + name => 'Edinburgh City Council', + }, + created => 1, + state => 'fixed', + mark_fixed => 1, + mark_open => 0, + }, + { + desc => 'update with state of open leaves problem as fixed', + lastupdate => '', + request => { + updated_datetime => DateTime::Format::W3CDTF->new()->format_datetime( DateTime->now()->set_time_zone( $tz_local ) ), + status => 'open', + status_notes => 'the council do not think this is fixed', + }, + council => { + name => 'Edinburgh City Council', + }, + created => 1, + start_state => 'fixed', + state => 'fixed', + mark_fixed => 0, + mark_open => 0, + }, +) { + subtest $test->{desc} => sub { + # makes testing easier; + $problem->comments->delete; + $problem->created( DateTime->now()->subtract( days => 1 ) ); + $problem->lastupdate( DateTime->now()->subtract( days => 1 ) ); + $problem->state( $test->{start_state} || 'confirmed' ); + $problem->update; + my $w3c = DateTime::Format::W3CDTF->new(); + + my $ret = $problem->update_from_open311_service_request( $test->{request}, $test->{council}, $user ); + is $ret, $test->{created}, 'return value'; + + return unless $test->{created}; + + $problem->discard_changes; + is $problem->lastupdate, $w3c->parse_datetime($test->{request}->{updated_datetime}), 'lastupdate time'; + + my $update = $problem->comments->first; + + ok $update, 'updated created'; + + is $problem->state, $test->{state}, 'problem state'; + + is $update->text, $test->{request}->{status_notes}, 'update text'; + is $update->mark_open, $test->{mark_open}, 'update mark_open flag'; + is $update->mark_fixed, $test->{mark_fixed}, 'update mark_fixed flag'; + }; +} + for my $test ( { state => 'partial', @@ -240,4 +345,8 @@ for my $test ( }; } +$problem->comments->delete; +$problem->delete; +$user->delete; + done_testing(); diff --git a/t/open311.t b/t/open311.t new file mode 100644 index 000000000..f7a8cd815 --- /dev/null +++ b/t/open311.t @@ -0,0 +1,24 @@ +#!/usr/bin/env perl + +use strict; +use warnings; +use Test::More tests => 4; + +use FindBin; +use lib "$FindBin::Bin/../perllib"; +use lib "$FindBin::Bin/../commonlib/perllib"; + +use_ok( 'Open311' ); + +my $o = Open311->new(); +ok $o, 'created object'; + +my $err_text = <<EOT +<?xml version="1.0" encoding="utf-8"?><errors><error><code>400</code><description>Service Code cannot be null -- can't proceed with the request.</description></error></errors> +EOT +; + +is $o->_process_error( $err_text ), "400: Service Code cannot be null -- can't proceed with the request.\n", 'error text parsing'; +is $o->_process_error( '503 - service unavailable' ), 'unknown error', 'error text parsing of bad error'; + + diff --git a/t/open311/getupdates.t b/t/open311/getupdates.t new file mode 100644 index 000000000..500ac97d2 --- /dev/null +++ b/t/open311/getupdates.t @@ -0,0 +1,198 @@ +#!/usr/bin/env perl + +use strict; +use warnings; +use Test::More; + +use FindBin; +use lib "$FindBin::Bin/../perllib"; +use lib "$FindBin::Bin/../commonlib/perllib"; + +use_ok( 'Open311::GetUpdates' ); +use_ok( 'Open311' ); + +my $user = FixMyStreet::App->model('DB::User')->find_or_create( + { + email => 'system_user@example.com' + } +); + + +my $updates = Open311::GetUpdates->new( system_user => $user ); +ok $updates, 'created object'; + +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>006</service_code> +<description></description> +<agency_responsible></agency_responsible> +<service_notice></service_notice> +<requested_datetime>2010-04-14T06:37:38-08:00</requested_datetime> +UPDATED_DATETIME +<expected_datetime>2010-04-15T06:37:38-08:00</expected_datetime> +<lat>37.762221815</lat> +<long>-122.4651145</long> +</request> +</service_requests> +}; + +my $problem_rs = FixMyStreet::App->model('DB::Problem'); +my $problem = $problem_rs->new( + { + postcode => 'EH99 1SP', + latitude => 1, + longitude => 1, + areas => 1, + title => '', + detail => '', + used_map => 1, + user_id => 1, + name => '', + state => 'confirmed', + service => '', + cobrand => 'default', + cobrand_data => '', + user => $user, + created => DateTime->now()->subtract( days => 1 ), + lastupdate => DateTime->now()->subtract( days => 1 ), + anonymous => 1, + external_id => 638344, + } +); + +$problem->insert; + +for my $test ( + { + desc => 'element missing', + updated_datetime => '', + comment_count => 0, + }, + { + desc => 'empty element', + updated_datetime => '<updated_datetime />', + comment_count => 0, + }, + { + desc => 'element with no content', + updated_datetime => '<updated_datetime></updated_datetime>', + comment_count => 0, + }, + { + desc => 'element with old content', + updated_datetime => sprintf( '<updated_datetime>%s</updated_datetime>', DateTime->now->subtract( days => 3 ) ), + comment_count => 0, + }, + { + desc => 'element with new content', + updated_datetime => sprintf( '<updated_datetime>%s</updated_datetime>', DateTime->now ), + comment_count => 1, + }, +) { + subtest $test->{desc} => sub { + $problem->comments->delete; + $problem->lastupdate(DateTime->now()->subtract( days => 1 ) ), + $problem->update; + + my $local_requests_xml = $requests_xml; + $local_requests_xml =~ s/UPDATED_DATETIME/$test->{updated_datetime}/; + + my $o = Open311->new( jurisdiction => 'mysociety', endpoint => 'http://example.com', test_mode => 1, test_get_returns => { 'requests.xml' => $local_requests_xml } ); + + ok $updates->update_reports( [ 638344 ], $o, { name => 'Test Council' } ); + is $o->test_uri_used, 'http://example.com/requests.xml?jurisdiction_id=mysociety&service_request_id=638344', 'get url'; + + is $problem->comments->count, $test->{comment_count}, 'added a comment'; + }; +} + +$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>006</service_code> +<description></description> +<agency_responsible></agency_responsible> +<service_notice></service_notice> +<requested_datetime>2010-04-14T06:37:38-08:00</requested_datetime> +<updated_datetime>UPDATED_DATETIME</updated_datetime> +<expected_datetime>2010-04-15T06:37:38-08:00</expected_datetime> +<lat>37.762221815</lat> +<long>-122.4651145</long> +</request> +<request> +<service_request_id>638345</service_request_id> +<status>open</status> +<status_notes>This is a for a different issue.</status_notes> +<service_name>Sidewalk and Curb Issues</service_name> +<service_code>006</service_code> +<description></description> +<agency_responsible></agency_responsible> +<service_notice></service_notice> +<requested_datetime>2010-04-14T06:37:38-08:00</requested_datetime> +<updated_datetime>UPDATED_DATETIME2</updated_datetime> +<expected_datetime>2010-04-15T06:37:38-08:00</expected_datetime> +<lat>37.762221815</lat> +<long>-122.4651145</long> +</request> +</service_requests> +}; + +my $problem2 = $problem_rs->create( + { + postcode => 'EH99 1SP', + latitude => 1, + longitude => 1, + areas => 1, + title => '', + detail => '', + used_map => 1, + user_id => 1, + name => '', + state => 'confirmed', + service => '', + cobrand => 'default', + cobrand_data => '', + user => $user, + created => DateTime->now()->subtract( days => 1 ), + lastupdate => DateTime->now()->subtract( days => 1 ), + anonymous => 1, + external_id => 638345, + } +); + +$problem->comments->delete; +subtest 'update with two requests' => sub { + $problem->comments->delete; + $problem->lastupdate(DateTime->now()->subtract( days => 1 ) ), + + my $date1 = DateTime::Format::W3CDTF->new->format_datetime( DateTime->now() ); + my $date2 = DateTime::Format::W3CDTF->new->format_datetime( DateTime->now->subtract( hours => 1) ); + my $local_requests_xml = $requests_xml; + $local_requests_xml =~ s/UPDATED_DATETIME2/$date2/; + $local_requests_xml =~ s/UPDATED_DATETIME/$date1/; + + my $o = Open311->new( jurisdiction => 'mysociety', endpoint => 'http://example.com', test_mode => 1, test_get_returns => { 'requests.xml' => $local_requests_xml } ); + + ok $updates->update_reports( [ 638344,638345 ], $o, { name => 'Test Council' } ); + is $o->test_uri_used, 'http://example.com/requests.xml?jurisdiction_id=mysociety&service_request_id=638344%2C638345', 'get url'; + + is $problem->comments->count, 1, 'added a comment to first problem'; + is $problem2->comments->count, 1, 'added a comment to second problem'; +}; + +$problem->comments->delete; +$problem->delete; +$user->comments->delete; +$user->problems->delete; +$user->delete; + +done_testing(); diff --git a/templates/web/default/admin/council_contacts.html b/templates/web/default/admin/council_contacts.html index 669f137f9..75d915a29 100644 --- a/templates/web/default/admin/council_contacts.html +++ b/templates/web/default/admin/council_contacts.html @@ -12,6 +12,13 @@ <a href="[% c.uri_for( 'council_contacts', area_id, { text => 1 } ) %]">[% loc('Text only version') %]</a> </p> +[% IF open311.count > 0 %] + <h2> + Council contacts configured via Open311 + </h2> + +[% END %] + <form method="post" action="[% c.uri_for('council_contacts', area_id ) %]" enctype="application/x-www-form-urlencoded" accept-charset="utf-8"> <table cellspacing="0" cellpadding="2" border="1"> @@ -86,4 +93,31 @@ </div> </form> + <h2>[% loc('Configure Open311 integration') %]</h2> + <form method="post" action="[% c.uri_for('council_contacts', area_id ) %]" enctype="application/x-www-form-urlencoded" accept-charset="utf-8"> + [% conf = open311.next %] + <p> + <label for="endpoint">Endpoint</label>: + <input type="text" name="endpoint" id="endpoint" value="[% conf.endpoint %]" size="50"> + </p> + + <p> + <label for="jurisdiction">Jurisdiction</label>: + <input type="text" name="jurisdiction" id="jurisdiction" value="[% conf.jurisdiction %]" size="50"> + </p> + + <p> + <label for="api_key">Api Key</label>: + <input type="text" name="api_key" id="api_key" value="[% conf.api_key %]" size="25"> + </p> + + <p> + <input type="hidden" name="open311_id" value="[% conf.id %]"> + <input type="hidden" name="area_id" value="[% area_id %]"> + <input type="hidden" name="posted" value="open311"> + <input type="hidden" name="token" value="[% token %]"> + <input type="submit" name="Configure Open311" value="[% loc('Configure Open311') %]"> + </p> + </form> + [% INCLUDE 'admin/footer.html' %] diff --git a/templates/web/default/common_header_tags.html b/templates/web/default/common_header_tags.html index 44da6aa9b..e6278847d 100644 --- a/templates/web/default/common_header_tags.html +++ b/templates/web/default/common_header_tags.html @@ -5,6 +5,12 @@ [% map_js %] +[% IF category_extras_json && category_extras_json != '{}' %] +<script type="text/javascript"> + category_extras = [% category_extras_json %]; +</script> +[% END %] + [% IF robots %] <meta name="robots" content="[% robots %]"> [% ELSIF c.config.STAGING_SITE %] diff --git a/templates/web/default/report/new/fill_in_details_form.html b/templates/web/default/report/new/fill_in_details_form.html index 633dc0b11..617bb0fa6 100644 --- a/templates/web/default/report/new/fill_in_details_form.html +++ b/templates/web/default/report/new/fill_in_details_form.html @@ -79,6 +79,35 @@ [% END %] [% END %] +[%- IF category_extras %] +<div id="category_meta"> + [%- IF report_meta %] + [%- category = report.category %] + <h4>Additional Information</h4> + [%- FOR meta IN category_extras.$category %] + [%- meta_name = meta.code -%] + +[% IF field_errors.$meta_name %] + <div class='form-error'>[% field_errors.$meta_name %]</div> +[% END -%] + + <div class="form-field"> + <label for="form_[% meta_name %]">[% meta.description _ ':' %]</label> + [% IF meta.exists('values') %] + <select name="[% meta_name %]" id="form_[% meta_name %]"> + [% FOR option IN meta.values.value.keys %] + <option value="[% meta.values.value.$option.key %]">[% option %]</option> + [% END %] + </select> + [% ELSE %] + <input type="text" value="[% report_meta.$meta_name | html %]" name="[% meta_name %]" id="form_[% meta_name %]"> + [% END %] + </div> + [%- END %] + [%- END %] +</div> +[%- END %] + [% IF c.cobrand.allow_photo_upload %] [% IF field_errors.photo %] <div class='form-error'>[% field_errors.photo %]</div> diff --git a/templates/web/default/report/new/fill_in_details_text.html b/templates/web/default/report/new/fill_in_details_text.html index 9ebb8107b..5d9716915 100644 --- a/templates/web/default/report/new/fill_in_details_text.html +++ b/templates/web/default/report/new/fill_in_details_text.html @@ -4,6 +4,9 @@ to help unless you leave as much detail as you can, so please describe the exact location of the problem (e.g. on a wall), what it is, how long it has been there, a description (and a photo of the problem if you have one), etc.'); + IF category_extras; + ' ' _ loc('Some categories may require additional information.'); + END; ELSE; loc('Please fill in details of the problem below.'); END; diff --git a/web/css/core.scss b/web/css/core.scss index 895a8826b..c1856a2c5 100644 --- a/web/css/core.scss +++ b/web/css/core.scss @@ -217,6 +217,14 @@ $map_width: 500px; margin-bottom: 1em; } + #category_meta { + margin-bottom: 30px; + } + + #category_meta label { + width: 10em; + } + // Map #map_box { diff --git a/web/js/fixmystreet.js b/web/js/fixmystreet.js index 6a11a5832..3d07c61ec 100644 --- a/web/js/fixmystreet.js +++ b/web/js/fixmystreet.js @@ -59,6 +59,32 @@ $(function(){ timer = window.setTimeout(email_alert_close, 2000); }); + $('#form_category').change(function() { + if ( category_extras ) { + $('#category_meta').empty(); + if ( category_extras[this.options[ this.selectedIndex ].text] ) { + var fields = category_extras[this.options[ this.selectedIndex ].text]; + $('<h4>Additional information</h4>').appendTo('#category_meta'); + for ( var i in fields) { + var meta = fields[i]; + var field = '<div class="form-field">'; + field += '<label for="form_' + meta.code + '">' + meta.description + ':</label>'; + if ( meta.values ) { + field += '<select name="' + meta.code + '" id="form_' + meta.code + '">'; + for ( var j in meta.values.value ) { + field += '<option value="' + meta.values.value[j].key + '">' + j + '</option>'; + } + field += '</select>'; + } else { + field += '<input type="text" value="" name="' + meta.code + '" id="form_' + meta.code + '">'; + } + field += '</div>'; + $( field ).appendTo('#category_meta'); + } + } + } + }); + // Geolocation if (geo_position_js.init()) { $('#postcodeForm').append('<p id="geolocate_para">Or <a href="#" id="geolocate_link">locate me automatically</a>').css({ "padding-bottom": "0.5em" }); |