diff options
Diffstat (limited to 'perllib/FixMyStreet')
-rw-r--r-- | perllib/FixMyStreet/App/Controller/Waste.pm | 131 | ||||
-rw-r--r-- | perllib/FixMyStreet/App/Form/Field/Postcode.pm | 50 | ||||
-rw-r--r-- | perllib/FixMyStreet/App/Form/Waste/UPRN.pm | 37 | ||||
-rw-r--r-- | perllib/FixMyStreet/Cobrand/Bromley.pm | 137 | ||||
-rw-r--r-- | perllib/FixMyStreet/Template.pm | 9 |
5 files changed, 295 insertions, 69 deletions
diff --git a/perllib/FixMyStreet/App/Controller/Waste.pm b/perllib/FixMyStreet/App/Controller/Waste.pm index 3cdd9fd42..6319b5f22 100644 --- a/perllib/FixMyStreet/App/Controller/Waste.pm +++ b/perllib/FixMyStreet/App/Controller/Waste.pm @@ -5,86 +5,83 @@ use namespace::autoclean; BEGIN { extends 'Catalyst::Controller' } use utf8; -use Open311::GetServiceRequestUpdates; +use FixMyStreet::App::Form::Waste::UPRN; -sub receive_echo_event_notification : Path('/waste/echo') : Args(0) { - my ($self, $c) = @_; - $c->stash->{format} = 'xml'; - $c->response->header(Content_Type => 'application/soap+xml'); +sub auto : Private { + my ( $self, $c ) = @_; + my $cobrand_check = $c->cobrand->feature('waste'); + $c->detach( '/page_error_404_not_found' ) if !$cobrand_check; + return 1; +} - require SOAP::Lite; +sub index : Path : Args(0) { + my ( $self, $c ) = @_; + + if (my $uprn = $c->get_param('address')) { + $c->detach('redirect_to_uprn', [ $uprn ]); + } + + $c->stash->{title} = 'What is your address?'; + my $form = FixMyStreet::App::Form::Waste::UPRN->new( cobrand => $c->cobrand ); + $form->process( params => $c->req->body_params ); + if ($form->validated) { + my $addresses = $form->value->{postcode}; + $form = address_list_form($addresses); + } + $c->stash->{form} = $form; +} - $c->detach('soap_error', [ 'Invalid method', 405 ]) unless $c->req->method eq 'POST'; +sub address_list_form { + my $addresses = shift; + HTML::FormHandler->new( + field_list => [ + address => { + required => 1, + type => 'Select', + widget => 'RadioGroup', + label => 'Select an address', + tags => { last_differs => 1, small => 1 }, + options => $addresses, + }, + go => { + type => 'Submit', + value => 'Continue', + element_attr => { class => 'govuk-button' }, + }, + ], + ); +} - my $echo = $c->cobrand->feature('echo'); - $c->detach('soap_error', [ 'Missing config', 500 ]) unless $echo; +sub redirect_to_uprn : Private { + my ($self, $c, $uprn) = @_; + my $uri = '/waste/uprn/' . $uprn; + $c->res->redirect($uri); + $c->detach; +} - # Make sure we log entire request for debugging - $c->detach('soap_error', [ 'Missing body' ]) unless $c->req->body; - my $soap = join('', $c->req->body->getlines); - $c->log->info($soap); +sub uprn : Chained('/') : PathPart('waste/uprn') : CaptureArgs(1) { + my ($self, $c, $uprn) = @_; - my $body = $c->cobrand->body; - $c->detach('soap_error', [ 'Bad jurisdiction' ]) unless $body; + if ($uprn eq 'missing') { + $c->stash->{template} = 'waste/missing.html'; + $c->detach; + } - my $env = SOAP::Deserializer->deserialize($soap); + $c->forward('/auth/get_csrf_token'); - my $header = $env->header; - $c->detach('soap_error', [ 'Missing SOAP header' ]) unless $header; - my $action = $header->{Action}; - $c->detach('soap_error', [ 'Incorrect Action' ]) unless $action && $action eq $echo->{receive_action}; - $header = $header->{Security}; - $c->detach('soap_error', [ 'Missing Security header' ]) unless $header; - my $token = $header->{UsernameToken}; - $c->detach('soap_error', [ 'Authentication failed' ]) - unless $token && $token->{Username} eq $echo->{receive_username} && $token->{Password} eq $echo->{receive_password}; + my $property = $c->stash->{property} = $c->cobrand->call_hook(look_up_property => $uprn); + $c->detach( '/page_error_404_not_found', [] ) unless $property; - # Still want to say it is okay, even if we did nothing with it - $c->forward('soap_ok'); -} + $c->stash->{uprn} = $uprn; + $c->stash->{latitude} = $property->{latitude}; + $c->stash->{longitude} = $property->{longitude}; -sub soap_error : Private { - my ($self, $c, $comment, $code) = @_; - $code ||= 400; - $c->response->status($code); - my $type = $code == 500 ? 'Server' : 'Client'; - $c->response->body(SOAP::Serializer->fault($type, "Bad request: $comment", soap_header())); + $c->stash->{service_data} = $c->cobrand->call_hook(bin_services_for_address => $property) || []; + $c->stash->{services} = { map { $_->{service_id} => $_ } @{$c->stash->{service_data}} }; } -sub soap_ok : Private { +sub bin_days : Chained('uprn') : PathPart('') : Args(0) { my ($self, $c) = @_; - $c->response->status(200); - my $method = SOAP::Data->name("NotifyEventUpdatedResponse")->attr({ - xmlns => "http://www.twistedfish.com/xmlns/echo/api/v1" - }); - $c->response->body(SOAP::Serializer->envelope(method => $method, soap_header())); -} - -sub soap_header { - my $attr = "http://www.twistedfish.com/xmlns/echo/api/v1"; - my $action = "NotifyEventUpdatedResponse"; - my $header = SOAP::Header->name("Action")->attr({ - xmlns => 'http://www.w3.org/2005/08/addressing', - 'soap:mustUnderstand' => 1, - })->value("$attr/ReceiverService/$action"); - - my $dt = DateTime->now(); - my $dt2 = $dt->clone->add(minutes => 5); - my $w3c = DateTime::Format::W3CDTF->new; - my $header2 = SOAP::Header->name("Security")->attr({ - 'soap:mustUnderstand' => 'true', - 'xmlns' => 'http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd' - })->value( - \SOAP::Header->name( - "Timestamp" => \SOAP::Header->value( - SOAP::Header->name('Created', $w3c->format_datetime($dt)), - SOAP::Header->name('Expires', $w3c->format_datetime($dt2)), - ) - )->attr({ - xmlns => "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd", - }) - ); - return ($header, $header2); } __PACKAGE__->meta->make_immutable; diff --git a/perllib/FixMyStreet/App/Form/Field/Postcode.pm b/perllib/FixMyStreet/App/Form/Field/Postcode.pm new file mode 100644 index 000000000..093ae66a3 --- /dev/null +++ b/perllib/FixMyStreet/App/Form/Field/Postcode.pm @@ -0,0 +1,50 @@ +package FixMyStreet::App::Form::Field::Postcode; + +use HTML::FormHandler::Moose; +extends 'HTML::FormHandler::Field::Text'; + +use mySociety::PostcodeUtil; + +apply( + [ + { + transform => sub { + my ( $value, $field ) = @_; + $value =~ s/[^A-Z0-9]//i; + return mySociety::PostcodeUtil::canonicalise_postcode($value); + } + }, + { + check => sub { mySociety::PostcodeUtil::is_valid_postcode(shift) }, + message => 'Sorry, we did not recognise that postcode.', + } + ] +); + + +__PACKAGE__->meta->make_immutable; +use namespace::autoclean; + +1; + +__END__ + +=pod + +=encoding UTF-8 + +=head1 NAME + +FixMyStreet::App::Form::Field::Postcode - validates postcode using mySociety::PostcodeUtil + +=head1 DESCRIPTION + +Validates that the input looks like a postcode using L<mySociety::PostcodeUtil>. +Widget type is 'text'. + +=head1 DEPENDENCIES + +L<mySociety::PostcodeUtil> + +=cut + diff --git a/perllib/FixMyStreet/App/Form/Waste/UPRN.pm b/perllib/FixMyStreet/App/Form/Waste/UPRN.pm new file mode 100644 index 000000000..d0ac7b3cb --- /dev/null +++ b/perllib/FixMyStreet/App/Form/Waste/UPRN.pm @@ -0,0 +1,37 @@ +package FixMyStreet::App::Form::Waste::UPRN; + +use utf8; +use HTML::FormHandler::Moose; +extends 'HTML::FormHandler'; + +use mySociety::PostcodeUtil qw(is_valid_postcode); + +has '+field_name_space' => ( default => 'FixMyStreet::App::Form::Field' ); + +has cobrand => ( is => 'ro' ); + +has_field postcode => ( + required => 1, + type => 'Postcode', + validate_method => sub { + my $self = shift; + return if $self->has_errors; # Called even if already failed + my $data = $self->form->cobrand->bin_addresses_for_postcode($self->value); + if (!@$data) { + $self->add_error('Sorry, we did not find any results for that postcode'); + } + push @$data, { value => 'missing', label => 'I can’t find my address' }; + $self->value($data); + }, + tags => { autofocus => 1 }, +); + +has_field go => ( + type => 'Submit', + value => 'Go', + element_attr => { class => 'govuk-button' }, +); + +__PACKAGE__->meta->make_immutable; + +1; diff --git a/perllib/FixMyStreet/Cobrand/Bromley.pm b/perllib/FixMyStreet/Cobrand/Bromley.pm index c8da01226..e4e509f77 100644 --- a/perllib/FixMyStreet/Cobrand/Bromley.pm +++ b/perllib/FixMyStreet/Cobrand/Bromley.pm @@ -6,6 +6,8 @@ use warnings; use utf8; use DateTime::Format::W3CDTF; use DateTime::Format::Flexible; +use Integrations::Echo; +use Sort::Key::Natural qw(natkeysort_inplace); use Try::Tiny; use FixMyStreet::DateRange; @@ -376,5 +378,138 @@ sub munge_load_and_group_problems { } } -1; +sub bin_addresses_for_postcode { + my $self = shift; + my $pc = shift; + + my $echo = $self->feature('echo'); + $echo = Integrations::Echo->new(%$echo); + my $points = $echo->FindPoints($pc); + my $data = [ map { { + value => $_->{SharedRef}{Value}{anyType}, + label => FixMyStreet::Template::title($_->{Description}), + } } @$points ]; + natkeysort_inplace { $_->{label} } @$data; + return $data; +} + +sub look_up_property { + my $self = shift; + my $uprn = shift; + + my $echo = $self->feature('echo'); + $echo = Integrations::Echo->new(%$echo); + my $result = $echo->GetPointAddress($uprn); + return { + id => $result->{Id}, + uprn => $uprn, + address => FixMyStreet::Template::title($result->{Description}), + latitude => $result->{Coordinates}{GeoPoint}{Latitude}, + longitude => $result->{Coordinates}{GeoPoint}{Longitude}, + }; +} + +my %irregulars = ( 1 => 'st', 2 => 'nd', 3 => 'rd', 11 => 'th', 12 => 'th', 13 => 'th'); +sub ordinal { + my $n = shift; + $irregulars{$n % 100} || $irregulars{$n % 10} || 'th'; +} + +sub construct_bin_date { + my $str = shift; + return unless $str; + my $offset = ($str->{OffsetMinutes} || 0) * 60; + my $zone = DateTime::TimeZone->offset_as_string($offset); + my $date = DateTime::Format::W3CDTF->parse_datetime($str->{DateTime}); + $date->set_time_zone($zone); + return $date; +} + +sub bin_services_for_address { + my $self = shift; + my $property = shift; + + my %service_name_override = ( + 531 => 'Non-Recyclable Refuse', + 532 => 'Non-Recyclable Refuse', + 533 => 'Non-Recyclable Refuse', + 535 => 'Mixed Recycling (Cans, Plastics & Glass)', + 536 => 'Mixed Recycling (Cans, Plastics & Glass)', + 537 => 'Paper & Cardboard', + 541 => 'Paper & Cardboard', + 542 => 'Food Waste', + 544 => 'Food Waste', + 545 => 'Garden Waste', + ); + + my $echo = $self->feature('echo'); + $echo = Integrations::Echo->new(%$echo); + my $result = $echo->GetServiceUnitsForObject($property->{uprn}); + return [] unless @$result; + + my @out; + foreach (@$result) { + next unless $_->{ServiceTasks}; + + my $servicetask = $_->{ServiceTasks}{ServiceTask}; + my $schedules = _parse_schedules($servicetask); + + next unless $schedules->{next} or $schedules->{last}; + + my $row = { + id => $_->{Id}, + service_id => $_->{ServiceId}, + service_name => $service_name_override{$_->{ServiceId}} || $_->{ServiceName}, + service_task_id => $servicetask->{Id}, + service_task_name => $servicetask->{TaskTypeName}, + service_task_type_id => $servicetask->{TaskTypeId}, + schedule => $servicetask->{ScheduleDescription}, + last => $schedules->{last}, + next => $schedules->{next}, + }; + push @out, $row; + } + return \@out; +} + +sub _parse_schedules { + my $servicetask = shift; + my $schedules = $servicetask->{ServiceTaskSchedules}{ServiceTaskSchedule}; + $schedules = [ $schedules ] unless ref $schedules eq 'ARRAY'; + + my $today = DateTime->now->set_time_zone(FixMyStreet->local_time_zone)->strftime("%F"); + my ($min_next, $max_last, $next_changed); + foreach my $schedule (@$schedules) { + my $end_date = construct_bin_date($schedule->{EndDate})->strftime("%F"); + next if $end_date lt $today; + + my $next = $schedule->{NextInstance}; + my $d = construct_bin_date($next->{CurrentScheduledDate}); + if ($d && (!$min_next || $d < $min_next->{date})) { + $next_changed = $next->{CurrentScheduledDate}{DateTime} ne $next->{OriginalScheduledDate}{DateTime}; + $min_next = { + date => $d, + ordinal => ordinal($d->day), + changed => $next_changed, + }; + } + + my $last = $schedule->{LastInstance}; + $d = construct_bin_date($last->{CurrentScheduledDate}); + if ($d && (!$max_last || $d > $max_last->{date})) { + $max_last = { + date => $d, + ordinal => ordinal($d->day), + }; + } + } + + return { + next => $min_next, + last => $max_last, + }; +} + + +1; diff --git a/perllib/FixMyStreet/Template.pm b/perllib/FixMyStreet/Template.pm index afb977ac9..275089a35 100644 --- a/perllib/FixMyStreet/Template.pm +++ b/perllib/FixMyStreet/Template.pm @@ -161,7 +161,6 @@ sub sanitize { return $text; } - =head2 email_sanitize_text Intended for use in the _email_comment_list.txt template to allow HTML @@ -291,4 +290,12 @@ sub _space_slash { return $t; } +sub title : Filter { + my $text = shift; + $text =~ s{(\w[\w']*)}{\u\L$1}g; + # Postcode special handling + $text =~ s{(\w?\w\d[\d\w]?\s*\d\w\w)}{\U$1}g; + return $text; +} + 1; |