diff options
31 files changed, 717 insertions, 123 deletions
diff --git a/conf/packages b/conf/packages index c3dd5760f..5fa3cf77e 100644 --- a/conf/packages +++ b/conf/packages @@ -1,4 +1,5 @@ jhead +libdatetime-format-w3cdtf-perl libcache-memcached-perl libdbd-pg-perl libdbi-perl diff --git a/perllib/FixMyStreet/App/Controller/Admin.pm b/perllib/FixMyStreet/App/Controller/Admin.pm index 5c04bf2ad..59695db70 100644 --- a/perllib/FixMyStreet/App/Controller/Admin.pm +++ b/perllib/FixMyStreet/App/Controller/Admin.pm @@ -173,7 +173,7 @@ sub questionnaire : Path('questionnaire') : Args(0) { ); - my %questionnaire_counts = map { $_->get_column( 'reported' ) => $_->get_column( 'questionnaire_count' ) } $questionnaires->all; + my %questionnaire_counts = map { ( $_->get_column( 'reported' ) || -1 ) => $_->get_column( 'questionnaire_count' ) } $questionnaires->all; $questionnaire_counts{1} ||= 0; $questionnaire_counts{0} ||= 0; $questionnaire_counts{total} = $questionnaire_counts{0} + $questionnaire_counts{1}; diff --git a/perllib/FixMyStreet/App/Controller/Around.pm b/perllib/FixMyStreet/App/Controller/Around.pm index fcf91123e..b29415566 100644 --- a/perllib/FixMyStreet/App/Controller/Around.pm +++ b/perllib/FixMyStreet/App/Controller/Around.pm @@ -235,14 +235,23 @@ the map. sub ajax : Path('/ajax') { my ( $self, $c ) = @_; + $c->res->content_type('text/javascript; charset=utf-8'); + + unless ( $c->req->param('bbox') ) { + $c->res->status(404); + $c->res->body(''); + return; + } + + # assume this is not cacheable - may need to be more fine-grained later + $c->res->header( 'Cache_Control' => 'max-age=0' ); + # how far back should we go? my $all_pins = $c->req->param('all_pins') ? 1 : undef; my $interval = $all_pins ? undef : $c->cobrand->on_map_default_max_pin_age; # Need to be the class that can handle it - if ($c->req->param('bbox')) { - FixMyStreet::Map::set_map_class( 'OSM' ); - } + FixMyStreet::Map::set_map_class( 'OSM' ); # extract the data from the map my ( $pins, $on_map, $around_map, $dist ) = @@ -268,16 +277,7 @@ sub ajax : Path('/ajax') { } ); - # assume this is not cacheable - may need to be more fine-grained later - $c->res->content_type('text/javascript; charset=utf-8'); - $c->res->header( 'Cache_Control' => 'max-age=0' ); - - if ( $c->req->param('bbox') ) { - $c->res->body($body); - } else { - # The JS needs the surrounding brackets for Tilma - $c->res->body("($body)"); - } + $c->res->body($body); } __PACKAGE__->meta->make_immutable; diff --git a/perllib/FixMyStreet/App/Controller/Contact.pm b/perllib/FixMyStreet/App/Controller/Contact.pm index 88ac4987f..9d7051e2f 100644 --- a/perllib/FixMyStreet/App/Controller/Contact.pm +++ b/perllib/FixMyStreet/App/Controller/Contact.pm @@ -39,6 +39,8 @@ Handle contact us form submission sub submit : Path('submit') : Args(0) { my ( $self, $c ) = @_; + $c->res->redirect( '/contact' ) and return unless $c->req->method eq 'POST'; + return unless $c->forward('setup_request') && $c->forward('determine_contact_type') diff --git a/perllib/FixMyStreet/App/Controller/Location.pm b/perllib/FixMyStreet/App/Controller/Location.pm index 9f8260768..df8a090c2 100644 --- a/perllib/FixMyStreet/App/Controller/Location.pm +++ b/perllib/FixMyStreet/App/Controller/Location.pm @@ -76,12 +76,12 @@ sub determine_location_from_pc : Private { # $error doubles up to return multiple choices by being an array if ( ref($error) eq 'ARRAY' ) { - @$error = map { - decode_utf8($_); - s/, United Kingdom//; - s/, UK//; - $_; - } @$error; + foreach (@$error) { + my $a = decode_utf8($_->{address}); + $a =~ s/, United Kingdom//; + $a =~ s/, UK//; + $_->{address} = $a; + } $c->stash->{possible_location_matches} = $error; return; } diff --git a/perllib/FixMyStreet/App/Controller/My.pm b/perllib/FixMyStreet/App/Controller/My.pm index 19b3ffee0..e6062fe6c 100644 --- a/perllib/FixMyStreet/App/Controller/My.pm +++ b/perllib/FixMyStreet/App/Controller/My.pm @@ -30,8 +30,10 @@ sub my : Path : Args(0) { my $pins = []; my $problems = {}; - my $rs = $c->user->problems->search( undef, - { rows => 50 } )->page( $p_page ); + my $rs = $c->user->problems->search( undef, { + order_by => { -desc => 'confirmed' }, + rows => 50 + } )->page( $p_page ); while ( my $problem = $rs->next ) { push @$pins, { @@ -48,7 +50,10 @@ sub my : Path : Args(0) { $rs = $c->user->comments->search( { state => 'confirmed' }, - { rows => 50 } )->page( $u_page ); + { + order_by => { -desc => 'confirmed' }, + rows => 50 + } )->page( $u_page ); my @updates = $rs->all; $c->stash->{updates} = \@updates; $c->stash->{updates_pager} = $rs->pager; diff --git a/perllib/FixMyStreet/App/Controller/Open311.pm b/perllib/FixMyStreet/App/Controller/Open311.pm new file mode 100644 index 000000000..459ce12c9 --- /dev/null +++ b/perllib/FixMyStreet/App/Controller/Open311.pm @@ -0,0 +1,458 @@ +package FixMyStreet::App::Controller::Open311; + +use utf8; +use Moose; +use namespace::autoclean; + +use JSON; +use XML::Simple; +use DateTime::Format::W3CDTF; + +BEGIN { extends 'Catalyst::Controller'; } + +=head1 NAME + +FixMyStreet::App::Controller::Open311 - Catalyst Controller + +=head1 DESCRIPTION + +Open311 server API + +Open311 server API for Open311 clients + +http://open311.org/ +http://wiki.open311.org/GeoReport_v2 +http://fixmystreet.org.nz/api +http://seeclickfix.com/open311/ + +Issues with Open311 + * no way to specify which languages are understood by the + recipients. some lang=nb,nn setting should be available. + * not obvious how to handle generic requests (ie without lat/lon + values). + * should service IDs be numeric or not? Spec do not say, and all + examples I find use numbers. + * missing way to search for reports near a location using lat/lon + * report attributes lack title field. + * missing way to provide updates information for a request + * should support GeoRSS output as well as json and home made XML + +=head1 METHODS + +=cut + +=head2 index + +Displays some summary information for the requests. + +=cut + +sub index : Path : Args(0) { + my ( $self, $c ) = @_; + # don't need to do anything here - should just pass through. +} + +sub old_uri : Regex('^open311\.cgi') : Args(0) { + my ( $self, $c ) = @_; + ( my $new = $c->req->path ) =~ s/open311.cgi/open311/; + $c->res->redirect( "/$new", 301); +} + +=head2 discovery + +http://search.cpan.org/~bobtfish/Catalyst-Manual-5.8007/lib/Catalyst/Manual/Intro.pod + +=cut + +sub discovery_v2 : LocalRegex('^v2/discovery.(xml|json|html)$') : Args(0) { + my ( $self, $c ) = @_; + $c->stash->{format} = $c->req->captures->[0]; + $c->forward( 'get_discovery' ); +} + +sub services_v2 : LocalRegex('^v2/services.(xml|json|html)$') : Args(0) { + my ( $self, $c ) = @_; + $c->stash->{format} = $c->req->captures->[0]; + $c->forward( 'get_services' ); +} + +sub requests_v2 : LocalRegex('^v2/requests.(xml|json|html|rss)$') : Args(0) { + my ( $self, $c ) = @_; + $c->stash->{format} = $c->req->captures->[0]; + $c->forward( 'get_requests' ); +} + +sub request_v2 : LocalRegex('^v2/requests/(\d+).(xml|json|html)$') : Args(0) { + my ( $self, $c ) = @_; + $c->stash->{id} = $c->req->captures->[0]; + $c->stash->{format} = $c->req->captures->[1]; + $c->forward( 'get_request' ); +} + +sub error : Private { + my ( $self, $c, $error ) = @_; + $c->stash->{error} = "ERROR: $error"; + $c->stash->{template} = 'open311/index.html'; +} + +# Example +# http://sandbox.georeport.org/tools/discovery/discovery.xml +sub get_discovery : Private { + my ( $self, $c ) = @_; + + my $contact_email = $c->config->{CONTACT_EMAIL}; + my $prod_url = 'http://www.fiksgatami.no/open311'; + my $test_url = 'http://fiksgatami-dev.nuug.no/open311'; + my $prod_changeset = '2011-04-08T00:00:00Z'; + my $test_changeset = $prod_changeset; + my $spec_url = 'http://wiki.open311.org/GeoReport_v2'; + my $info = + { + 'contact' => ["Send email to $contact_email."], + 'changeset' => [$prod_changeset], + # XXX rewrite to match + 'key_service' => ["Read access is open to all according to our \u003Ca href='/open_data' target='_blank'\u003Eopen data license\u003C/a\u003E. For write access either: 1. return the 'guid' cookie on each call (unique to each client) or 2. use an api key from a user account which can be generated here: http://seeclickfix.com/register The unversioned url will always point to the latest supported version."], + 'max_requests' => [ $c->config->{RSS_LIMIT} ], + 'endpoints' => [ + { + 'endpoint' => [ + { + 'formats' => [ + {'format' => [ 'text/xml', + 'application/json', + 'text/html' ] + } + ], + 'specification' => [ $spec_url ], + 'changeset' => [ $prod_changeset ], + 'url' => [ $prod_url ], + 'type' => [ 'production' ] + }, + { + 'formats' => [ + { + 'format' => [ 'text/xml', + 'application/json', + 'text/html' ] + } + ], + 'specification' => [ $spec_url ], + 'changeset' => [ $test_changeset ], + 'url' => [ $test_url ], + 'type' => [ 'test' ] + } + ] + } + ] + }; + $c->forward( 'format_output', [ { + 'discovery' => $info + } ] ); +} + +# Example +# http://seeclickfix.com/open311/services.html?lat=32.1562864999991&lng=-110.883806 +sub get_services : Private { + my ( $self, $c ) = @_; + + my $jurisdiction_id = $c->req->param('jurisdiction_id') || ''; + my $lat = $c->req->param('lat') || ''; + my $lon = $c->req->param('long') || ''; + + # Look up categories for this council or councils + my $categories = $c->model('DB::Contact')->not_deleted; + + if ($lat || $lon) { + my @area_types = $c->cobrand->area_types; + my $all_councils = mySociety::MaPit::call('point', + "4326/$lon,$lat", + type => \@area_types); + $categories = $categories->search( { + area_id => [ keys %$all_councils ], + } ); + } + + my @categories = $categories->search( undef, { + columns => [ 'category' ], + distinct => 1, + } )->all; + + my @services; + for my $categoryref ( sort { $a->category cmp $b->category } + @categories) { + my $categoryname = $categoryref->category; + push(@services, + { + # FIXME Open311 v2 seem to require all three, and we + # only have one value. + 'service_name' => [ $categoryname ], + 'description' => [ $categoryname ], + 'service_code' => [ $categoryname ], + 'metadata' => [ 'false' ], + 'type' => [ 'realtime' ], +# 'group' => [ '' ], +# 'keywords' => [ '' ], + } + ); + } + $c->forward( 'format_output', [ { + 'services' => [ { + 'service' => \@services + } ] + } ] ); +} + + +sub output_requests : Private { + my ( $self, $c, $criteria, $limit ) = @_; + $limit = $c->config->{RSS_LIMIT} + unless $limit && $limit <= $c->config->{RSS_LIMIT}; + + my $attr = { + order_by => { -desc => 'confirmed' }, + rows => $limit + }; + + # Look up categories for this council or councils + my $problems = $c->cobrand->problems->search( $criteria, $attr ); + + my %statusmap = ( 'fixed' => 'closed', + 'confirmed' => 'open'); + + my @problemlist; + my @councils; + while ( my $problem = $problems->next ) { + my $id = $problem->id; + + $problem->service( 'Web interface' ) unless $problem->service; + + if ($problem->council) { + (my $council = $problem->council) =~ s/\|.*//g; + my @council_ids = split(/,/, $council); + push(@councils, @council_ids); + $problem->council( \@council_ids ); + } + + $problem->state( $statusmap{$problem->state} ); + + my $request = + { + 'service_request_id' => [ $id ], + 'title' => [ $problem->title ], # Not in Open311 v2 + 'detail' => [ $problem->detail ], # Not in Open311 v2 + 'description' => [ $problem->title .': ' . $problem->detail ], + 'lat' => [ $problem->latitude ], + 'long' => [ $problem->longitude ], + 'status' => [ $problem->state ], +# 'status_notes' => [ {} ], + 'requested_datetime' => [ w3date($problem->confirmed_local) ], + 'updated_datetime' => [ w3date($problem->lastupdate_local) ], +# 'expected_datetime' => [ {} ], +# 'address' => [ {} ], +# 'address_id' => [ {} ], + 'service_code' => [ $problem->category ], + 'service_name' => [ $problem->category ], +# 'service_notice' => [ {} ], + 'agency_responsible' => $problem->council , # FIXME Not according to Open311 v2 +# 'zipcode' => [ {} ], + 'interface_used' => [ $problem->service ], # Not in Open311 v2 + }; + + if ( !$problem->anonymous ) { + # Not in Open311 v2 + $request->{'requestor_name'} = [ $problem->name ]; + } + if ( $problem->whensent ) { + # Not in Open311 v2 + $request->{'agency_sent_datetime'} = + [ w3date($problem->whensent_local) ]; + } + + # Extract number of updates + my $updates = $problem->comments->search( + { state => 'confirmed' }, + )->count; + if ($updates) { + # Not in Open311 v2 + $request->{'comment_count'} = [ $updates ]; + } + + my $display_photos = $c->cobrand->allow_photo_display; + if ($display_photos && $problem->photo) { + my $url = $c->cobrand->base_url(); + my $imgurl = $url . "/photo?id=$id"; + $request->{'media_url'} = [ $imgurl ]; + } + push(@problemlist, $request); + } + my $areas_info = mySociety::MaPit::call('areas', \@councils); + foreach my $request (@problemlist) { + if ($request->{agency_responsible}) { + my @council_names = map { $areas_info->{$_}->{name} } @{$request->{agency_responsible}} ; + $request->{agency_responsible} = + [ {'recipient' => [ @council_names ] } ]; + } + } + $c->forward( 'format_output', [ { + 'requests' => [ { + 'request' => \@problemlist + } ] + } ] ); +} + +sub get_requests : Private { + my ( $self, $c ) = @_; + + $c->forward( 'is_jurisdiction_id_ok' ); + + my $max_requests = $c->req->param('max_requests') || 0; + + # Only provide access to the published reports + my $criteria = { + state => [ 'fixed', 'confirmed' ] + }; + + my %rules = ( + service_request_id => [ '=', 'id' ], + service_code => [ '=', 'category' ], + status => [ '=', 'state' ], + start_date => [ '>=', 'confirmed' ], + end_date => [ '<', 'confirmed' ], + agency_responsible => [ '~', 'council' ], + interface_used => [ '=', 'service' ], + has_photo => [ '=', 'photo' ], + ); + for my $param (keys %rules) { + my $value = $c->req->param($param); + next unless $value; + my $op = $rules{$param}[0]; + my $key = $rules{$param}[1]; + if ( 'status' eq $param ) { + $value = { + 'open' => 'confirmed', + 'closed' => 'fixed' + }->{$value}; + } elsif ( 'agency_responsible' eq $param ) { + my @valuelist; + for my $agency (split(/\|/, $value)) { + unless ($agency =~ m/^(\d+)$/) { + $c->detach( 'error', [ + sprintf(_('Invalid agency_responsible value %s'), + $value) + ] ); + } + my $agencyid = $1; + # FIXME This seem to match the wrong entries + # some times. Not sure when or why + my $re = "(\\y$agencyid\\y|^$agencyid\\y|\\y$agencyid\$)"; + push(@valuelist, $re); + } + $value = \@valuelist; + } elsif ( 'has_photo' eq $param ) { + $value = undef; + $op = '!=' if 'true' eq $value; + $c->detach( 'error', [ + sprintf(_('Incorrect has_photo value "%s"'), + $value) + ] ) + unless 'true' eq $value || 'false' eq $value; + } elsif ( 'interface_used' eq $param ) { + $value = undef if 'Web interface' eq $value; + } + $criteria->{$key} = { $op, $value }; + } + + if ('rss' eq $c->stash->{format}) { + $c->stash->{type} = 'new_problems'; + $c->forward( '/rss/lookup_type' ); + $c->forward( 'rss_query', [ $criteria, $max_requests ] ); + $c->forward( '/rss/generate' ); + } else { + $c->forward( 'output_requests', [ $criteria, $max_requests ] ); + } +} + +sub rss_query : Private { + my ( $self, $c, $criteria, $limit ) = @_; + $limit = $c->config->{RSS_LIMIT} + unless $limit && $limit <= $c->config->{RSS_LIMIT}; + + my $attr = { + result_class => 'DBIx::Class::ResultClass::HashRefInflator', + order_by => { -desc => 'confirmed' }, + rows => $limit + }; + + my $problems = $c->cobrand->problems->search( $criteria, $attr ); + $c->stash->{problems} = $problems; +} + +# Example +# http://seeclickfix.com/open311/requests/1.xml?jurisdiction_id=sfgov.org +sub get_request : Private { + my ( $self, $c ) = @_; + my $format = $c->stash->{format}; + my $id = $c->stash->{id}; + + $c->forward( 'is_jurisdiction_id_ok' ); + + if ('html' eq $format) { + my $base_url = $c->cobrand->base_url(); + $c->res->redirect($base_url . "/report/$id"); + return; + } + + my $criteria = { + state => [ 'fixed', 'confirmed' ], + id => $id, + }; + $c->forward( 'output_requests', [ $criteria ] ); +} + +sub format_output : Private { + my ( $self, $c, $hashref ) = @_; + my $format = $c->stash->{format}; + if ('json' eq $format) { + $c->res->content_type('application/json; charset=utf-8'); + $c->res->body( encode_json($hashref) ); + } elsif ('xml' eq $format) { + $c->res->content_type('application/xml; charset=utf-8'); + $c->res->body( XMLout($hashref, RootName => undef) ); + } else { + $c->detach( 'error', [ + sprintf(_('Invalid format %s specified.'), $format) + ] ); + } +} + +sub is_jurisdiction_id_ok : Private { + my ( $self, $c ) = @_; + unless (my $jurisdiction_id = $c->req->param('jurisdiction_id')) { + $c->detach( 'error', [ _('Missing jurisdiction_id') ] ); + } +} + +# Input: DateTime object +# Output: 2011-04-23T10:28:55+02:00 +# FIXME Need generic solution to find time zone +sub w3date : Private { + my $datestr = shift; + return unless $datestr; + return DateTime::Format::W3CDTF->format_datetime($datestr); +} + +=head1 AUTHOR + +Copyright (c) 2011 Petter Reinholdtsen, some rights reserved. +Email: pere@hungry.com + +=head1 LICENSE + +This library is free software. You can redistribute it and/or modify +it under the GPL v2 or later. + +=cut + +__PACKAGE__->meta->make_immutable; + +1; diff --git a/perllib/FixMyStreet/App/Controller/Questionnaire.pm b/perllib/FixMyStreet/App/Controller/Questionnaire.pm index acb1628cf..a87aff330 100755 --- a/perllib/FixMyStreet/App/Controller/Questionnaire.pm +++ b/perllib/FixMyStreet/App/Controller/Questionnaire.pm @@ -252,7 +252,7 @@ sub process_questionnaire : Private { } # Sent here from email token action. Simply load and display questionnaire. -sub index : Private { +sub show : Private { my ( $self, $c ) = @_; $c->forward( 'check_questionnaire' ); $c->forward( 'display' ); diff --git a/perllib/FixMyStreet/App/Controller/Rss.pm b/perllib/FixMyStreet/App/Controller/Rss.pm index 78793d9c1..45a16a9dd 100755 --- a/perllib/FixMyStreet/App/Controller/Rss.pm +++ b/perllib/FixMyStreet/App/Controller/Rss.pm @@ -151,12 +151,21 @@ sub local_problems_ll : Private { sub output : Private { my ( $self, $c ) = @_; + $c->forward( 'lookup_type' ); + $c->forward( 'query_main' ); + $c->forward( 'generate' ); +} + +sub lookup_type : Private { + my ( $self, $c ) = @_; $c->stash->{alert_type} = $c->model('DB::AlertType')->find( { ref => $c->stash->{type} } ); $c->detach( '/page_error_404_not_found', [ _('Unknown alert type') ] ) unless $c->stash->{alert_type}; +} - $c->forward( 'query_main' ); +sub generate : Private { + my ( $self, $c ) = @_; # Do our own encoding $c->stash->{rss} = new XML::RSS( @@ -170,8 +179,15 @@ sub output : Private { uri => 'http://www.georss.org/georss' ); - while (my $row = $c->stash->{query_main}->fetchrow_hashref) { - $c->forward( 'add_row', [ $row ] ); + my $problems = $c->stash->{problems}; + if ( $problems->can('fetchrow_hashref') ) { + while ( my $row = $problems->fetchrow_hashref ) { + $c->forward( 'add_row', [ $row ] ); + } + } else { + while ( my $row = $problems->next ) { + $c->forward( 'add_row', [ $row ] ); + } } $c->forward( 'add_parameters' ); @@ -210,7 +226,7 @@ sub query_main : Private { } else { $q->execute(); } - $c->stash->{query_main} = $q; + $c->stash->{problems} = $q; } sub add_row : Private { diff --git a/perllib/FixMyStreet/App/Controller/Tokens.pm b/perllib/FixMyStreet/App/Controller/Tokens.pm index 9abef591d..26a1a1459 100644 --- a/perllib/FixMyStreet/App/Controller/Tokens.pm +++ b/perllib/FixMyStreet/App/Controller/Tokens.pm @@ -33,11 +33,17 @@ sub confirm_problem : Path('/P') { # Load the problem my $data = $auth_token->data; - my $problem_id = $data->{id}; + my $problem_id = ref $data ? $data->{id} : $data; my $problem = $c->cobrand->problems->find( { id => $problem_id } ) || $c->detach('token_error'); $c->stash->{problem} = $problem; + if ( $problem->state eq 'unconfirmed' && $auth_token->created < DateTime->now->subtract( months => 1 ) ) { + $c->stash->{template} = 'errors/generic.html'; + $c->stash->{message} = _("I'm afraid we couldn't validate that token, as the report was made too long ago."); + return; + } + # check that this email or domain are not the cause of abuse. If so hide it. if ( $problem->is_from_abuser ) { $problem->update( @@ -47,6 +53,7 @@ sub confirm_problem : Path('/P') { } # We have a problem - confirm it if needed! + my $old_state = $problem->state; $problem->update( { state => 'confirmed', @@ -60,7 +67,7 @@ sub confirm_problem : Path('/P') { $c->forward( '/report/new/create_reporter_alert' ); # log the problem creation user in to the site - if ( $data->{name} || $data->{password} ) { + if ( ref($data) && ( $data->{name} || $data->{password} ) ) { $problem->user->name( $data->{name} ) if $data->{name}; $problem->user->password( $data->{password}, 1 ) if $data->{password}; $problem->user->update; @@ -68,6 +75,11 @@ sub confirm_problem : Path('/P') { $c->authenticate( { email => $problem->user->email }, 'no_password' ); $c->set_session_cookie_expire(0); + if ( $old_state eq 'confirmed' || $old_state eq 'fixed' ) { + my $report_uri = $c->uri_for( '/report', $problem->id ); + $c->res->redirect($report_uri); + } + return 1; } @@ -169,10 +181,6 @@ sub confirm_update : Path('/C') { sub load_questionnaire : Private { my ( $self, $c, $token_code ) = @_; - # Set up error handling - $c->stash->{error_template} = 'errors/generic.html'; - $c->stash->{message} = _("I'm afraid we couldn't validate that token. If you've copied the URL from an email, please check that you copied it exactly.\n"); - my $auth_token = $c->forward( 'load_auth_token', [ $token_code, 'questionnaire' ] ); $c->stash->{id} = $auth_token->data; $c->stash->{token} = $token_code; @@ -191,7 +199,7 @@ sub questionnaire : Path('/Q') : Args(1) { $c->authenticate( { email => $c->stash->{questionnaire}->problem->user->email }, 'no_password' ); $c->set_session_cookie_expire(0); - $c->forward( '/questionnaire/index'); + $c->forward( '/questionnaire/show' ); } =head2 load_auth_token @@ -218,21 +226,26 @@ sub load_auth_token : Private { scope => $scope, token => $token_code, } - ) || $c->detach('token_error'); + ); + + unless ( $token ) { + $c->stash->{template} = 'errors/generic.html'; + $c->stash->{message} = _("I'm afraid we couldn't validate that token. If you've copied the URL from an email, please check that you copied it exactly.\n"); + $c->detach; + } return $token; } =head2 token_error -Display an error page saying that there is something wrong with the token. +Display an error page saying that there is something wrong with the token (our end). =cut sub token_error : Private { my ( $self, $c ) = @_; - $c->stash->{template} = $c->stash->{error_template} || 'tokens/error.html'; - $c->detach; + $c->stash->{template} = 'tokens/error.html'; } __PACKAGE__->meta->make_immutable; diff --git a/perllib/FixMyStreet/Geocode/Bing.pm b/perllib/FixMyStreet/Geocode/Bing.pm index cfeffc856..90d7f98bd 100644 --- a/perllib/FixMyStreet/Geocode/Bing.pm +++ b/perllib/FixMyStreet/Geocode/Bing.pm @@ -57,7 +57,7 @@ sub string { my $address = $_->{name}; next unless $_->{address}->{countryRegion} eq 'United Kingdom'; # FIXME This is UK only ( $latitude, $longitude ) = @{ $_->{point}->{coordinates} }; - push (@$error, $address); + push (@$error, { address => $address, latitude => $latitude, longitude => $longitude }); push (@valid_locations, $_); } return { latitude => $latitude, longitude => $longitude } if scalar @valid_locations == 1; diff --git a/perllib/FixMyStreet/Geocode/Google.pm b/perllib/FixMyStreet/Geocode/Google.pm index c37a750a2..83b36dbcd 100644 --- a/perllib/FixMyStreet/Geocode/Google.pm +++ b/perllib/FixMyStreet/Geocode/Google.pm @@ -75,7 +75,7 @@ sub string { my $address = $_->{address}; next unless $c->cobrand->geocoded_string_check( $address ); ( $longitude, $latitude ) = @{ $_->{Point}->{coordinates} }; - push (@$error, $address); + push (@$error, { address => $address, latitude => $latitude, longitude => $longitude }); push (@valid_locations, $_); } return { latitude => $latitude, longitude => $longitude } if scalar @valid_locations == 1; diff --git a/t/app/controller/admin.t b/t/app/controller/admin.t index 17e6f015e..a7eee4dd3 100644 --- a/t/app/controller/admin.t +++ b/t/app/controller/admin.t @@ -136,7 +136,7 @@ subtest 'check summary counts' => sub { my $host = FixMyStreet->config('BASE_URL'); $mech->get_ok('/admin/council_contacts/2650'); $mech->content_contains('Aberdeen City Council'); -$mech->content_contains('AB15 8RN'); +$mech->content_like(qr{AB\d\d}); $mech->content_contains("$host/around"); subtest 'check contact creation' => sub { diff --git a/templates/web/barnet/header.html b/templates/web/barnet/header.html index 8346ea344..1616198e7 100644 --- a/templates/web/barnet/header.html +++ b/templates/web/barnet/header.html @@ -3,11 +3,8 @@ <head> <link rel="stylesheet" type="text/css" href="/cobrands/barnet/css/basic.css"> - <style type="text/css"> - @import url(/css/core.css); - @import url(/cobrands/barnet/css/layout.css); - </style> - + <link rel="stylesheet" type="text/css" href="/css/core.css"> + <link rel="stylesheet" type="text/css" href="/cobrands/barnet/css/layout.css"> <!-- Preferred style sheet enabled when the page is loaded --> <link rel="stylesheet" title="default" type="text/css" href="/cobrands/barnet/css/light.css"> diff --git a/templates/web/default/alert/choose.html b/templates/web/default/alert/choose.html index ef632e2d1..fad365088 100644 --- a/templates/web/default/alert/choose.html +++ b/templates/web/default/alert/choose.html @@ -6,7 +6,7 @@ <p>[% loc('We found more than one match for that location. We show up to ten matches, please try a different search if yours is not here.') %]</p> <ul class="pc_alternatives"> [% FOREACH match IN possible_location_matches %] - <li><a href="[% choose_target_uri %]?pc=[% match | uri %]">[% match | html %]</a></li> + <li><a href="[% choose_target_uri %]?latitude=[% match.latitude | uri %];longitude=[% match.longitude | uri %]">[% match.address | html %]</a></li> [% END %] </ul> [% END %] diff --git a/templates/web/default/around/around_index.html b/templates/web/default/around/around_index.html index 8c144e469..3e6d96acd 100644 --- a/templates/web/default/around/around_index.html +++ b/templates/web/default/around/around_index.html @@ -33,7 +33,7 @@ <p>[% loc('We found more than one match for that location. We show up to ten matches, please try a different search if yours is not here.') %]</p> <ul class="pc_alternatives"> [% FOREACH match IN possible_location_matches %] - <li><a href="/around?pc=[% match | uri %]">[% match | html %]</a></li> + <li><a href="/around?latitude=[% match.latitude | uri %];longitude=[% match.longitude | uri %]">[% match.address | html %]</a></li> [% END %] </ul> [% END %] diff --git a/templates/web/default/header.html b/templates/web/default/header.html index 431e6db65..8fffbda88 100644 --- a/templates/web/default/header.html +++ b/templates/web/default/header.html @@ -2,13 +2,14 @@ <html lang="[% lang_code %]"> <head> - [% INCLUDE 'common_header_tags.html' %] - - <style type="text/css">@import url("/css/core.css"); @import url("/css/main.css");</style> + <link rel="stylesheet" type="text/css" href="/css/core.css"> + <link rel="stylesheet" type="text/css" href="/css/main.css"> <!--[if LT IE 7]> - <style type="text/css">@import url("/css/ie6.css");</style> + <link rel="stylesheet" type="text/css" href="/css/ie6.css"> <![endif]--> + [% INCLUDE 'common_header_tags.html' %] + [% IF c.req.uri.host == 'osm.fixmystreet.com' %] <link rel="canonical" href="http://www.fixmystreet.com[% c.req.uri.path_query %]"> [% END %] diff --git a/templates/web/default/open311/index.html b/templates/web/default/open311/index.html new file mode 100644 index 000000000..28e8aee3b --- /dev/null +++ b/templates/web/default/open311/index.html @@ -0,0 +1,147 @@ +[% INCLUDE 'header.html', title => 'Open311' %] + +<h1>[% loc('Open311 API for the mySociety FixMyStreet server') %]</h1> + +[% IF error %] +<p>[% tprintf( loc('Note: <strong>%s</strong>'), error ) %]</p> +[% END %] + +<p>[% loc('At the moment only searching for and looking at reports work.') %]</p> +<p>[% loc('This API implementation is work in progress and not yet stabilized. It will change without warnings in the future.') %]</p> + +<ul> +<li><a rel="nofollow" href="http://www.open311.org/">[% loc('Open311 initiative web page') %]</a></li> +<li><a rel="nofollow" href="http://wiki.open311.org/GeoReport_v2">[% loc('Open311 specification') %]</a></li> +</ul> + +<p>[% tprintf( loc('At most %d requests are returned in each query. The returned requests are ordered by requested_datetime, so to get all requests, do several searches with rolling start_date and end_date.'), c.config.RSS_LIMIT ) %]</p> + +<p>[% loc('The following Open311 v2 attributes are returned for each request: service_request_id, description, lat, long, media_url, status, requested_datetime, updated_datetime, service_code and service_name.') %]</p> + +<p>[% loc('In addition, the following attributes that are not part of the Open311 v2 specification are returned: agency_sent_datetime, title (also returned as part of description), interface_used, comment_count, requestor_name (only present if requestor allowed the name to be shown on this site).') %]</p> + +<p>[% loc('The Open311 v2 attribute agency_responsible is used to list the administrations that received the problem report, which is not quite the way the attribute is defined in the Open311 v2 specification.') %]</p> + +<p>[% tprintf( loc('With request searches, it is also possible to search for agency_responsible to limit the requests to those sent to a single administration. The search term is the administration ID provided by <a href="%s">MaPit</a>.'), c.config.MAPIT_URL ) %]</p> + +<p>[% loc('Examples:') %]</p> + +<ul> + +[% jurisdiction_id = 'fiksgatami.no' %] +[% examples = [ + { + url = c.cobrand.base_url _ "/open311/v2/discovery.xml?jurisdiction_id=$jurisdiction_id", + info = 'discovery information', + }, + { + url = c.cobrand.base_url _ "/open311/v2/services.xml?jurisdiction_id=$jurisdiction_id", + info = 'list of services provided', + }, + { + url = c.cobrand.base_url _ "/open311/v2/services.xml?jurisdiction_id=$jurisdiction_id&lat=60&long=11", + info = 'list of services provided for WGS84 coordinate latitude 60 longitude 11', + }, + { + url = c.cobrand.base_url _ "/open311/v2/requests/1.xml?jurisdiction_id=$jurisdiction_id", + info = 'Request number 1', + }, + { + url = c.cobrand.base_url _ "/open311/v2/requests.xml?jurisdiction_id=$jurisdiction_id&status=open&agency_responsible=1601&end_date=2011-03-10", + info = 'All open requests reported before 2011-03-10 to Trondheim (id 1601)', + }, + { + url = c.cobrand.base_url _ "/open311/v2/requests.xml?jurisdiction_id=$jurisdiction_id&status=open&agency_responsible=219|220", + info = 'All open requests in Asker (id 220) and Bærum (id 219)', + }, + { + url = c.cobrand.base_url _ "/open311/v2/requests.xml?jurisdiction_id=$jurisdiction_id&service_code=Vannforsyning", + info = "All requests with the category 'Vannforsyning'", + }, + { + url = c.cobrand.base_url _ "/open311/v2/requests.xml?jurisdiction_id=$jurisdiction_id&status=closed", + info = 'All closed requests', + }, +] %] +[% FOREACH examples %] + <li><a href="[% url %]">[% info %]</a> + [% IF url.match('/requests.xml') %] + [ <a href="http://maps.google.com/?q=[% url.replace('.xml', '.rss') | uri %]">[% loc('GeoRSS on Google Maps') %]</a> ] + [% END %] + <br>[% ent(url) %]</li> +[% END %] + +</ul> + +<h2>Searching</h2> + +<p>The following search parameters can be used:</p> + +<dl> + +<dt>service_request_id</dt> +<dd>Search for numeric ID of specific request. + Using this is identical to asking for a individual request using + the /requests/number.format URL.</dd> +<dt>service_code</dt> +<dd>Search for the given category / service type string.</dd> + +<dt>status</dt> +<dd>Search for open or closed (fixed) requests.</dd> + +<dt>start_date<dt> +<dd>Only return requests with requested_datetime set after or at the + date and time specified. The format is YYYY-MM-DDTHH:MM:SS+TZ:TZ.</dd> + +<dt>end_date<dt> +<dd>Only return requests with requested_datetime set before the date + and time specified. Same format as start_date.</dd> + +<dt>agency_responsible</dt> +<dd>ID of government body receiving the request. Several IDs can be + specified with | as a separator.</dd> + +<dt>interface_used<dt> +<dd>Name / identifier of interface used.</dd> + +<dt>has_photo<dt> +<dd>Search for entries with or without photos. Use value 'true' to +only get requests created with images, and 'false' to get those +created without images.</dd> + +<dt>max_requests</dt> +<dd>Max number of requests to return from the search. If it is larger +than the site specific max_requests value specified in the discovery +call, the value provided is ignored.</dd> + +<dl> + +<p>The search result might look like this:</p> + +<pre>[% " + <requests> + <request> + <agency_responsible> + <recipient>Statens vegvesen region øst</recipient> + <recipient>Oslo</recipient> + </agency_responsible> + <agency_sent_datetime>2011-04-23T10:28:55+02:00</agency_sent_datetime> + <description>Mangler brustein: Det støver veldig på tørre dager. Her burde det vært brustein.</description> + <detail>Det støver veldig på tørre dager. Her burde det vært brustein.</detail> + <interface_used>Web interface</interface_used> + <lat>59.916848</lat> + <long>10.728148</long> + <requested_datetime>2011-04-23T09:32:36+02:00</requested_datetime> + <requestor_name>Petter Reinholdtsen</requestor_name> + <service_code>Annet</service_code> + <service_name>Annet</service_name> + <service_request_id>1</service_request_id> + <status>open</status> + <title>Mangler brustein</title> + <updated_datetime>2011-04-23T10:28:55+02:00</updated_datetime> + </request> + </requests> +" | html %]</pre> + +[% INCLUDE 'footer.html' %] + diff --git a/templates/web/emptyhomes/header.html b/templates/web/emptyhomes/header.html index ca51d8060..017d3a6f5 100644 --- a/templates/web/emptyhomes/header.html +++ b/templates/web/emptyhomes/header.html @@ -2,15 +2,13 @@ <html lang="[% lang_code %]"> <head> -[% INCLUDE 'common_header_tags.html' %] + <link rel="stylesheet" type="text/css" href="/css/core.css"> + <link rel="stylesheet" type="text/css" href="/cobrands/emptyhomes/css.css"> + <!--[if LT IE 7]> + <link rel="stylesheet" type="text/css" href="/css/ie6.css"> + <![endif]--> -<style type="text/css"> -@import "/css/core.css"; -@import "/cobrands/emptyhomes/css.css"; -</style> -<!--[if LT IE 7]> -<style type="text/css">@import url("/css/ie6.css");</style> -<![endif]--> +[% INCLUDE 'common_header_tags.html' %] </head> <body> diff --git a/templates/web/fiksgatami/header.html b/templates/web/fiksgatami/header.html index e0bf5b150..44ebb7ee7 100644 --- a/templates/web/fiksgatami/header.html +++ b/templates/web/fiksgatami/header.html @@ -1,12 +1,11 @@ <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> <html lang="[% lang_code %]"> <head> - [% INCLUDE 'common_header_tags.html' %] - <style type="text/css"> - @import url("/css/core.css"); - @import url("/cobrands/fiksgatami/css.css"); - </style> + <link rel="stylesheet" type="text/css" href="/css/core.css"> + <link rel="stylesheet" type="text/css" href="/cobrands/fiksgatami/css.css"> + + [% INCLUDE 'common_header_tags.html' %] </head> <body> diff --git a/templates/web/lichfielddc/header.html b/templates/web/lichfielddc/header.html index f162ecc97..e134cd6f9 100644 --- a/templates/web/lichfielddc/header.html +++ b/templates/web/lichfielddc/header.html @@ -2,10 +2,8 @@ <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="[% lang_code %]"> <head> -<style type="text/css"> - @import url(/css/core.css); - @import url(/cobrands/lichfielddc/css/layout.css); -</style> + <link rel="stylesheet" type="text/css" href="/css/core.css" /> + <link rel="stylesheet" type="text/css" href="/cobrands/lichfielddc/css/layout.css" /> <link rel="stylesheet" type="text/css" href="http://www.lichfielddc.gov.uk/site/styles/standard_1_.css" media="screen" /> <link rel="stylesheet" type="text/css" href="http://www.lichfielddc.gov.uk/site/styles/generic/style.php" media="screen" /> diff --git a/templates/web/southampton/header.html b/templates/web/southampton/header.html index 5d94d5bdf..049034252 100644 --- a/templates/web/southampton/header.html +++ b/templates/web/southampton/header.html @@ -1,12 +1,13 @@ <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> <head> - [% INCLUDE 'common_header_tags.html' %] - <script type="text/javascript" src="/js/southampton.js"></script> - <link rel="stylesheet" type="text/css" href="/css/core.css" /> <link rel="stylesheet" type="text/css" href="/cobrands/southampton/style.css" /> <link rel="stylesheet" type="text/css" href="/cobrands/southampton/css.css" /> + + [% INCLUDE 'common_header_tags.html' %] + <script type="text/javascript" src="/js/southampton.js"></script> + </head> <body> <ul id="topMenu"> diff --git a/web/cobrands/barnet/css/layout.css b/web/cobrands/barnet/css/layout.css index 1445ff419..1e1bf5d05 100644 --- a/web/cobrands/barnet/css/layout.css +++ b/web/cobrands/barnet/css/layout.css @@ -7,14 +7,6 @@ width: 378px;
height: 378px;
}
-#mysociety #watermark {
- background: url("/i/mojwatermark-378.png");
- height: 84px;
- width: 171px;
- position: absolute;
- bottom: 0;
- right: 0;
-}
#mysociety p#fixed, #mysociety p#unknown {
margin-right: 400px;
diff --git a/web/cobrands/emptyhomes/css.css b/web/cobrands/emptyhomes/css.css index c45e9255f..168eae2de 100644 --- a/web/cobrands/emptyhomes/css.css +++ b/web/cobrands/emptyhomes/css.css @@ -7,14 +7,6 @@ width: 378px; height: 378px; } -#watermark { - background: url("/i/mojwatermark-378.png"); - height: 84px; - width: 171px; - position: absolute; - bottom: 0; - right: 0; -} p#fixed, p#unknown { margin-right: 400px; diff --git a/web/css/core.css b/web/css/core.css index 4dd0a974b..ac29dfe7a 100644 --- a/web/css/core.css +++ b/web/css/core.css @@ -131,13 +131,6 @@ 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; } #mysociety #map_box { float: right; width: 502px; diff --git a/web/css/core.scss b/web/css/core.scss index 4fbc6aaca..4e8d732d5 100644 --- a/web/css/core.scss +++ b/web/css/core.scss @@ -216,15 +216,6 @@ $map_width: 500px; // Map - #watermark { - background: url("/i/mojwatermark6.png"); - height: 113px; - width: 231px; - position: absolute; - bottom: 0; - right: 0; - } - #map_box { float: right; width: $map_width + 2px; diff --git a/web/css/ie6-378.css b/web/css/ie6-378.css deleted file mode 100644 index 83efdb634..000000000 --- a/web/css/ie6-378.css +++ /dev/null @@ -1,4 +0,0 @@ -#mysociety #watermark { - background: none; - filter:progid:DXImageTransform.Microsoft.AlphaImageLoader(src='/i/mojwatermark-378.png',sizingMethod='scale'); -} diff --git a/web/css/ie6.css b/web/css/ie6.css index 3bb91aa31..fd3e26047 100644 --- a/web/css/ie6.css +++ b/web/css/ie6.css @@ -1,8 +1,3 @@ -#mysociety #watermark { - background: none; - filter:progid:DXImageTransform.Microsoft.AlphaImageLoader(src='/i/mojwatermark6.png',sizingMethod='scale'); -} - #logo { display: none; } diff --git a/web/down.default.html b/web/down.default.html index 35d2cf237..10502d89b 100644 --- a/web/down.default.html +++ b/web/down.default.html @@ -2,12 +2,12 @@ <html lang="en-gb"> <head> <title>FixMyStreet</title> - <style type="text/css">@import url("/css/core.css"); @import url("/css/main.css");</style> + <link rel="stylesheet" type="text/css" href="/css/core.css"> + <link rel="stylesheet" type="text/css" href="/css/main.css"> <!--[if LT IE 7]> -<style type="text/css">@import url("/css/ie6.css");</style> + <link rel="stylesheet" type="text/css" href="/css/ie6.css"> <![endif]--> - <!-- RSS --> </head> <body> <h1 id="header">Fix<span id="my">My</span>Street</h1> @@ -21,15 +21,14 @@ <h2 class="v">Navigation</h2> <ul id="navigation"> -<li>team@fixmystreet.com</a></li> +<li>team@fixmystreet.com</li> </ul> -<a href="http://www.mysociety.org/"><img id="logo" src="/i/mysociety-dark.png" alt="View mySociety.org"><span id="logoie"></span></a> - -<p id="footer">Built by <a href="http://www.mysociety.org/">mySociety</a>, -using some <a href="http://github.com/mysociety/fixmystreet">clever</a> <a -href="https://secure.mysociety.org/cvstrac/dir?d=mysociety/services/TilMa">code</a>.</p> +<a href="http://www.mysociety.org/"><img id="logo" width="133" height="26" src="/i/mysociety-dark.png" alt="View mySociety.org"><span id="logoie"></span></a> +<p id="footer">Built by <a href="http://www.mysociety.org/">mySociety</a> + | <a href="http://github.com/mysociety/fixmystreet">Source code</a> +</p> </body> </html> diff --git a/web/i/mojwatermark-378.png b/web/i/mojwatermark-378.png Binary files differdeleted file mode 100644 index 81faeef84..000000000 --- a/web/i/mojwatermark-378.png +++ /dev/null diff --git a/web/i/mojwatermark6.png b/web/i/mojwatermark6.png Binary files differdeleted file mode 100644 index a470e4cbc..000000000 --- a/web/i/mojwatermark6.png +++ /dev/null |