aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rwxr-xr-xbin/open311-populate-service-list112
-rwxr-xr-xbin/send-reports29
-rw-r--r--db/schema.sql13
-rw-r--r--db/schema_0009-add_extra_to_problem.sql6
-rw-r--r--db/schema_0010-add_open311_conf.sql11
-rw-r--r--db/schema_0011-add_extra_to_contacts.sql6
-rw-r--r--perllib/FixMyStreet/App/Controller/Report/New.pm32
-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.pm26
-rw-r--r--perllib/Open311.pm179
-rw-r--r--t/open311.t24
-rw-r--r--templates/web/default/common_header_tags.html6
-rw-r--r--templates/web/default/report/new/fill_in_details.html29
-rw-r--r--templates/web/default/report/new/fill_in_details_text.html3
-rw-r--r--web/css/core.css305
-rw-r--r--web/css/core.scss8
-rw-r--r--web/js/fixmystreet.js26
18 files changed, 680 insertions, 199 deletions
diff --git a/bin/open311-populate-service-list b/bin/open311-populate-service-list
new file mode 100755
index 000000000..2a6733ef1
--- /dev/null
+++ b/bin/open311-populate-service-list
@@ -0,0 +1,112 @@
+#!/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
+ );
+
+ 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;
+
+ # 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->{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->{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/send-reports b/bin/send-reports
index 1af3ba1ea..298eb458d 100755
--- a/bin/send-reports
+++ b/bin/send-reports
@@ -28,6 +28,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');
@@ -136,6 +138,8 @@ 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 ( FixMyStreet::App->model("DB::Open311conf")->find( { area_id => $council } ) ) {
+ $send_web = 'open311';
} else {
my $contact = FixMyStreet::App->model("DB::Contact")->find( {
deleted => 0,
@@ -248,6 +252,31 @@ while (my $row = $unsent->next) {
if (!$nomail) {
$result *= post_london_report( $row, %h );
}
+ } elsif ($send_web eq 'open311') {
+ # FIXME - looking this up twice :(
+ my $conf = FixMyStreet::App->model('DB::Open311conf')->find( { area_id => $row->council } );
+
+ # FIXME - doesn't deal with multiple recipients
+ my $contact = FixMyStreet::App->model("DB::Contact")->find( {
+ deleted => 0,
+ area_id => $row->council,
+ category => $row->category
+ } );
+
+ my $open311 = Open311->new(
+ jurisdiction => $conf->jurisdiction,
+ endpoint => $conf->endpoint,
+ api_key => $conf->api_key,
+ );
+
+ 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 9c5b3d8fd..3ec3c6756 100644
--- a/db/schema.sql
+++ b/db/schema.sql
@@ -79,6 +79,9 @@ create table contacts (
whenedited timestamp not null,
-- what the last change was for: author's notes
note text not null
+
+ -- extra fields required for open311
+ extra text
);
create unique index contacts_area_id_category_idx on contacts(area_id, category);
@@ -176,6 +179,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
);
create index problem_state_latitude_longitude_idx on problem(state, latitude, longitude);
create index problem_user_id_idx on problem ( user_id );
@@ -387,3 +391,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/Report/New.pm b/perllib/FixMyStreet/App/Controller/Report/New.pm
index 346dfb377..d418becb9 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
@@ -449,6 +450,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' ) {
@@ -495,8 +497,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;
}
@@ -511,6 +517,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{$_} } #
@@ -689,6 +697,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 2df26fde3..567ae9388 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 },
);
__PACKAGE__->set_primary_key("id");
__PACKAGE__->has_many(
@@ -100,8 +102,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(
@@ -111,11 +113,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';
diff --git a/perllib/Open311.pm b/perllib/Open311.pm
new file mode 100644
index 000000000..0c8c3fde6
--- /dev/null
+++ b/perllib/Open311.pm
@@ -0,0 +1,179 @@
+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' );
+
+sub get_service_list {
+ my $self = shift;
+
+ my $service_list_xml = $self->_get( 'services.xml' );
+
+ 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( 'requests.xml', $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 $service_request_xml = $self->_get( 'requests.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 );
+ $uri->path( $uri->path . $path );
+ $uri->query_form( jurisdiction_id => $self->jurisdiction );
+
+ my $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/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/templates/web/default/common_header_tags.html b/templates/web/default/common_header_tags.html
index f9048b067..206a0e6d4 100644
--- a/templates/web/default/common_header_tags.html
+++ b/templates/web/default/common_header_tags.html
@@ -4,6 +4,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.html b/templates/web/default/report/new/fill_in_details.html
index 8150ba894..6beb77802 100644
--- a/templates/web/default/report/new/fill_in_details.html
+++ b/templates/web/default/report/new/fill_in_details.html
@@ -102,6 +102,35 @@
<textarea name="detail" id="form_detail" rows="7" cols="26">[% report.detail | html %]</textarea>
</div>
+[%- 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.$foo.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 44c60ed6e..d55ec5826 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.css b/web/css/core.css
index d468f097d..4dd0a974b 100644
--- a/web/css/core.css
+++ b/web/css/core.css
@@ -1,60 +1,48 @@
#mysociety blockquote {
border-left: solid 4px #666666;
- padding-left: 0.5em;
-}
-#mysociety blockquote h2, #mysociety blockquote p {
- margin: 0;
-}
+ padding-left: 0.5em; }
+ #mysociety blockquote h2, #mysociety blockquote p {
+ margin: 0; }
#mysociety dt {
font-weight: bold;
- margin-top: 0.5em;
-}
+ margin-top: 0.5em; }
#mysociety .gone {
color: #666666;
- background-color: #cccccc;
-}
+ background-color: #cccccc; }
#mysociety p.dev-site-notice, #mysociety p.error {
text-align: center;
color: #cc0000;
- font-size: larger;
-}
+ font-size: larger; }
#mysociety ul {
padding: 0 0 0 1.5em;
- margin: 0;
-}
+ margin: 0; }
#mysociety ul.error {
color: #cc0000;
background-color: #ffeeee;
padding-right: 4px;
text-align: left;
- font-size: larger;
-}
+ font-size: larger; }
#mysociety div.form-error {
color: #cc0000;
margin: 5px 1em 5px 1em;
padding: 2px 5px 2px 5px;
float: left;
background-color: #ffeeee;
- text-align: left;
-}
+ text-align: left; }
#mysociety div.form-field {
- clear: both;
-}
+ clear: both; }
#mysociety #advert_thin {
width: 50%;
margin: 1em auto;
text-align: center;
- border-top: dotted 1px #999999;
-}
+ border-top: dotted 1px #999999; }
#mysociety #advert_hfymp {
border-top: dotted 1px #999999;
- text-align: center;
-}
+ text-align: center; }
#mysociety p#expl {
text-align: center;
font-size: 150%;
- margin: 0 2em;
-}
+ margin: 0 2em; }
#mysociety #postcodeForm {
display: table;
_width: 33em;
@@ -64,23 +52,18 @@
padding: 1em;
-moz-border-radius: 1em;
-webkit-border-radius: 1em;
- border-radius: 1em;
-}
-#mysociety #postcodeForm label {
- float: none;
- padding-right: 0;
-}
-#mysociety #postcodeForm #submit {
- font-size: 83%;
-}
+ border-radius: 1em; }
+ #mysociety #postcodeForm label {
+ float: none;
+ padding-right: 0; }
+ #mysociety #postcodeForm #submit {
+ font-size: 83%; }
#mysociety #front_intro {
float: left;
- width: 48%;
-}
-#mysociety #front_intro p {
- clear: both;
- margin-top: 0;
-}
+ width: 48%; }
+ #mysociety #front_intro p {
+ clear: both;
+ margin-top: 0; }
#mysociety #front_stats div {
text-align: center;
width: 5.5em;
@@ -88,139 +71,115 @@
-webkit-border-radius: 0.5em;
border-radius: 0.5em;
float: left;
- margin: 0 1em 1em;
-}
-#mysociety #front_stats div big {
- font-size: 150%;
- display: block;
-}
+ margin: 0 1em 1em; }
+ #mysociety #front_stats div big {
+ font-size: 150%;
+ display: block; }
#mysociety #front_recent {
float: right;
width: 48%;
- margin-bottom: 1em;
-}
+ margin-bottom: 1em; }
#mysociety #front_recent img, #mysociety #alert_recent img {
margin-right: 0.25em;
- margin-bottom: 0.25em;
-}
+ margin-bottom: 0.25em; }
#mysociety #front_recent > h2:first-child, #mysociety #front_intro > h2:first-child {
- margin-top: 0;
-}
+ margin-top: 0; }
#mysociety form {
- margin: 0;
-}
+ margin: 0; }
#mysociety label {
float: left;
text-align: right;
padding-right: 0.5em;
- width: 5em;
-}
+ width: 5em; }
#mysociety fieldset, #mysociety #fieldset {
border: none;
- padding: 0.5em;
-}
-#mysociety fieldset div, #mysociety #fieldset div {
- margin-top: 2px;
- clear: left;
-}
+ padding: 0.5em; }
+ #mysociety fieldset div, #mysociety #fieldset div {
+ margin-top: 2px;
+ clear: left; }
#mysociety legend {
- display: none;
-}
+ display: none; }
#mysociety #fieldset div.checkbox, #mysociety #problem_submit {
- padding-left: 5.5em;
-}
+ padding-left: 5.5em; }
#mysociety #fieldset div.checkbox label, #mysociety label.n {
float: none;
text-align: left;
padding-right: 0;
width: auto;
cursor: pointer;
- cursor: hand;
-}
+ cursor: hand; }
#mysociety #questionnaire label, #mysociety #alerts label {
- float: none;
-}
+ float: none; }
#mysociety .confirmed {
background-color: #ccffcc;
border: solid 2px #009900;
padding: 5px;
- text-align: center;
-}
+ text-align: center; }
#mysociety #form_sign_in_yes {
float: left;
width: 47%;
padding-right: 1%;
border-right: solid 1px #999999;
- margin-bottom: 1em;
-}
+ margin-bottom: 1em; }
#mysociety #form_sign_in_no, #mysociety #fieldset #form_sign_in_no {
float: right;
width: 47%;
padding-left: 1%;
clear: none;
- margin-bottom: 1em;
-}
+ margin-bottom: 1em; }
+#mysociety #category_meta {
+ margin-bottom: 30px; }
+#mysociety #category_meta label {
+ width: 10em; }
#mysociety #watermark {
background: url("/i/mojwatermark6.png");
height: 113px;
width: 231px;
position: absolute;
bottom: 0;
- right: 0;
-}
+ right: 0; }
#mysociety #map_box {
float: right;
width: 502px;
position: relative;
padding-left: 20px;
- background-color: #ffffff;
-}
+ background-color: #ffffff; }
#mysociety p#copyright {
float: right;
text-align: right;
margin: 0 0 1em 0;
- font-size: 78%;
-}
+ font-size: 78%; }
#mysociety #map {
border: solid 1px #000000;
width: 500px;
height: 500px;
overflow: hidden;
position: relative;
- background-color: #f1f1f1;
-}
+ background-color: #f1f1f1; }
#mysociety #drag {
position: absolute;
width: 500px;
height: 500px;
right: 0;
- top: 0;
-}
-#mysociety #drag input, #mysociety #drag img {
- position: absolute;
- border: none;
-}
-#mysociety #drag input {
- cursor: crosshair;
- background-color: #cccccc;
-}
-#mysociety #drag img {
- cursor: move;
-}
-#mysociety #drag img.pin {
- z-index: 100;
- background-color: inherit;
-}
-#mysociety #drag a img.pin {
- cursor: pointer;
- cursor: hand;
-}
+ top: 0; }
+ #mysociety #drag input, #mysociety #drag img {
+ position: absolute;
+ border: none; }
+ #mysociety #drag input {
+ cursor: crosshair;
+ background-color: #cccccc; }
+ #mysociety #drag img {
+ cursor: move; }
+ #mysociety #drag img.pin {
+ z-index: 100;
+ background-color: inherit; }
+ #mysociety #drag a img.pin {
+ cursor: pointer;
+ cursor: hand; }
#mysociety form#mapForm #map {
- cursor: pointer;
-}
+ cursor: pointer; }
#mysociety form#mapForm .olTileImage {
- cursor: crosshair;
-}
+ cursor: crosshair; }
#mysociety #compass {
background-color: #ffffff;
border: solid 1px #000000;
@@ -228,76 +187,60 @@
color: #000000;
position: absolute;
top: 0px;
- left: 0px;
-}
-#mysociety #compass img {
- border: 0;
-}
+ left: 0px; }
+ #mysociety #compass img {
+ border: 0; }
#mysociety #text_map {
margin-top: 0.5em;
margin-bottom: 1em;
- font-size: 110%;
-}
+ font-size: 110%; }
#mysociety #text_no_map {
- margin-top: 0;
-}
+ margin-top: 0; }
#mysociety #sub_map_links {
float: right;
clear: right;
- margin-top: 0;
-}
+ margin-top: 0; }
#mysociety #fixed {
margin: 0 530px 1em 0;
padding: 5px;
text-align: center;
position: relative;
background-color: #ccffcc;
- border: solid 2px #009900;
-}
+ border: solid 2px #009900; }
#mysociety #unknown {
margin: 0 530px 1em 0;
padding: 5px;
text-align: center;
position: relative;
background-color: #ffcccc;
- border: solid 2px #990000;
-}
+ border: solid 2px #990000; }
#mysociety #updates div {
padding: 0 0 0.5em;
margin: 0 0 0.25em;
- border-bottom: dotted 1px #5e552b;
-}
-#mysociety #updates div .problem-update, #mysociety #updates div .update-text {
- padding: 0;
- margin: 0;
- border-bottom: 0;
-}
+ border-bottom: dotted 1px #5e552b; }
+ #mysociety #updates div .problem-update, #mysociety #updates div .update-text {
+ padding: 0;
+ margin: 0;
+ border-bottom: 0; }
#mysociety #updates p {
- margin: 0;
-}
+ margin: 0; }
#mysociety #nearby_lists h2 {
margin-top: 0.5em;
- margin-bottom: 0;
-}
+ margin-bottom: 0; }
#mysociety #nearby_lists li small {
- color: #666666;
-}
+ color: #666666; }
#mysociety #alert_links {
- float: right;
-}
+ float: right; }
#mysociety #alert_links_area {
background-color: #ffeecc;
border: solid 1px #ff9900;
border-width: 1px 0;
padding: 3px 10px;
- margin: 0;
-}
+ margin: 0; }
#mysociety #rss_alert {
- text-decoration: none;
-}
-#mysociety #rss_alert span {
- text-decoration: underline;
-}
+ text-decoration: none; }
+ #mysociety #rss_alert span {
+ text-decoration: underline; }
#mysociety #email_alert_box {
display: none;
position: absolute;
@@ -305,97 +248,73 @@
font-size: 83%;
border: solid 1px #7399C3;
background-color: #eeeeff;
- color: #000000;
-}
+ color: #000000; }
#mysociety #email_alert_box p {
- margin: 0;
-}
+ margin: 0; }
#mysociety .council_sent_info {
- font-size: smaller;
-}
+ font-size: smaller; }
#mysociety #rss_items {
width: 62%;
- float: left;
-}
+ float: left; }
#mysociety #rss_rhs {
border-left: 1px dashed #999;
width: 36%;
float: right;
padding: 0 0 0 0.5em;
- margin: 0 0 1em 0.5em;
-}
+ margin: 0 0 1em 0.5em; }
#mysociety #rss_box {
padding: 10px;
- border: 1px solid #999999;
-}
+ border: 1px solid #999999; }
#mysociety #rss_feed {
list-style-type: none;
- margin-bottom: 2em;
-}
+ margin-bottom: 2em; }
#mysociety #rss_feed li {
- margin-bottom: 1em;
-}
+ margin-bottom: 1em; }
#mysociety #alert_or {
font-style: italic;
font-size: 125%;
- margin: 0;
-}
+ margin: 0; }
#mysociety #rss_list {
float: left;
- width: 47%;
-}
+ width: 47%; }
#mysociety #rss_list ul {
- list-style-type: none;
-}
+ list-style-type: none; }
#mysociety #rss_buttons {
float: right;
width: 35%;
text-align: center;
- margin-bottom: 2em;
-}
+ margin-bottom: 2em; }
#mysociety #rss_local {
margin-left: 1.5em;
- margin-bottom: 0;
-}
+ margin-bottom: 0; }
#mysociety #rss_local_alt {
- margin: 0 0 2em 4em;
-}
+ margin: 0 0 2em 4em; }
#mysociety #alert_photos {
text-align: center;
float: right;
width: 150px;
- margin-left: 0.5em;
-}
+ margin-left: 0.5em; }
#mysociety #alert_photos h2 {
- font-size: 100%;
-}
+ font-size: 100%; }
#mysociety #alert_photos img {
- margin-bottom: 0.25em;
-}
+ margin-bottom: 0.25em; }
#mysociety #col_problems, #mysociety #col_fixed {
float: left;
width: 48%;
- margin-right: 1em;
-}
+ margin-right: 1em; }
#mysociety .contact-details {
font-size: 80%;
- margin-top: 2em;
-}
+ margin-top: 2em; }
.olControlAttribution {
bottom: 3px !important;
- left: 3px;
-}
+ left: 3px; }
@media print {
#mysociety #map_box {
float: none;
- margin: 0 auto;
- }
+ margin: 0 auto; }
#mysociety #mysociety {
- max-width: none;
- }
+ max-width: none; }
#mysociety #side {
- margin-right: 0;
- }
-}
+ margin-right: 0; } }
diff --git a/web/css/core.scss b/web/css/core.scss
index 22bdc3d2e..4fbc6aaca 100644
--- a/web/css/core.scss
+++ b/web/css/core.scss
@@ -206,6 +206,14 @@ $map_width: 500px;
margin-bottom: 1em;
}
+ #category_meta {
+ margin-bottom: 30px;
+ }
+
+ #category_meta label {
+ width: 10em;
+ }
+
// Map
#watermark {
diff --git a/web/js/fixmystreet.js b/web/js/fixmystreet.js
index 4b19dc53e..9fa0f948b 100644
--- a/web/js/fixmystreet.js
+++ b/web/js/fixmystreet.js
@@ -57,4 +57,30 @@ $(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');
+ }
+ }
+ }
+ });
+
});