package FixMyStreet::App::Controller::Reports; use Moose; use namespace::autoclean; use Problems; use POSIX qw(strcoll); # use FixMyStreet::Alert; use mySociety::MaPit; use mySociety::VotingArea; BEGIN { extends 'Catalyst::Controller'; } =head1 NAME FixMyStreet::App::Controller::Reports - Catalyst Controller =head1 DESCRIPTION Catalyst Controller. =head1 METHODS =cut =head2 index Show the summary page of all reports. =cut sub index : Path : Args(0) { my ( $self, $c ) = @_; $c->response->header('Cache-Control' => 'max-age=3600'); # Fetch all areas of the types we're interested in my @area_types = $c->cobrand->area_types; my $areas_info = mySociety::MaPit::call('areas', \@area_types, min_generation => $c->cobrand->area_min_generation ); # For each area, add its link and perhaps alter its name if we need to for # places with the same name. foreach (values %$areas_info) { $_->{url} = $c->uri_for( '/reports/' . $c->cobrand->short_name( $_, $areas_info ) ); if ($_->{parent_area} && $_->{url} =~ /,|%2C/) { $_->{name} .= ', ' . $areas_info->{$_->{parent_area}}{name}; } } $c->stash->{areas_info} = $areas_info; my @keys = sort { strcoll($areas_info->{$a}{name}, $areas_info->{$b}{name}) } keys %$areas_info; $c->stash->{areas_info_sorted} = [ map { $areas_info->{$_} } @keys ]; $c->forward( 'load_problems' ); $c->forward( 'group_problems' ); } =head2 index Show the summary page for a particular council. =cut sub council : Path : Args(1) { my ( $self, $c, $council ) = @_; $c->forward( 'council_check', [ $council ] ); $c->forward( 'load_parent' ); $c->forward( 'load_problems' ); $c->forward( 'group_problems' ); $c->forward( 'sort_problems' ); $c->stash->{rss_url} = '/rss/reports/' . $c->cobrand->short_name( $c->stash->{council}, $c->stash->{areas_info} ); } =head2 index Show the summary page for a particular ward. =cut sub ward : Path : Args(2) { my ( $self, $c, $council, $ward ) = @_; $c->forward( 'council_check', [ $council ] ); $c->forward( 'ward_check', [ $ward ] ); $c->forward( 'load_parent' ); $c->forward( 'load_problems' ); $c->forward( 'group_problems' ); $c->forward( 'sort_problems' ); $c->stash->{rss_url} = '/rss/reports/' . $c->cobrand->short_name( $c->stash->{council}, $c->stash->{areas_info} ) . '/' . $c->cobrand->short_name( $c->stash->{ward} ); } #sub rss_ward : RegEx('/rss/(reports|area)') : Args(2) { # my ( $self, $c, $council, $ward ) = @_; # # $c->stash->{q_council} = $council; # $c->stash->{q_ward} = $ward; # #} # #sub rss_council : RegEx('/rss/(reports|area)') : Args(1) { # my ( $self, $c, $council ) = @_; # $c->stash->{rss} = $c->req->{rss}; #} =head2 council_check This action checks the council name (or code) given in a URI exists, is valid and so on. If it is, it stores the Area in the stash, otherwise it redirects to the all reports page. =cut sub council_check : Private { my ( $self, $c, $q_council ) = @_; $q_council =~ s/\+/ /g; # Manual misspelling redirect if ($q_council =~ /^rhondda cynon taff$/i) { my $url = $c->uri_for( '/reports/rhondda+cynon+taf' ); $c->res->redirect( $url ); $c->detach(); } # Check cobrand specific incantations - e.g. ONS codes for UK, # Oslo/ kommunes sharing a name in Norway return if $c->cobrand->reports_council_check( $c, $q_council ); # If we're passed an ID number (don't think this is used anywhere, it # certainly shouldn't be), just look that up on MaPit and redirect if ($q_council =~ /^\d+$/) { my $council = mySociety::MaPit::call('area', $q_council); $c->detach( 'redirect_index') if $council->{error}; $c->stash->{council} = $council; $c->detach( 'redirect_council' ); } # We must now have a string to check my @area_types = $c->cobrand->area_types; my $areas = mySociety::MaPit::call( 'areas', $q_council, type => \@area_types, min_generation => $c->cobrand->area_min_generation ); if (keys %$areas == 1) { ($c->stash->{council}) = values %$areas; return; } else { foreach (keys %$areas) { if ($areas->{$_}->{name} eq $q_council || $areas->{$_}->{name} =~ /^\Q$q_council\E (Borough|City|District|County) Council$/) { $c->stash->{council} = $areas->{$_}; return; } } } # No result, bad council name. $c->detach( 'redirect_index' ); } =head2 ward_check This action checks the ward name from a URI exists and is part of the right parent, already found with council_check. It either stores the ward Area if okay, or redirects to the council page if bad. This is currently only used in the UK, hence the use of mySociety::VotingArea. =cut sub ward_check : Private { my ( $self, $c, $ward ) = @_; $ward =~ s/\+/ /g; my $council = $c->stash->{council}; my $qw = mySociety::MaPit::call('areas', $ward, type => $mySociety::VotingArea::council_child_types, min_generation => $c->cobrand->area_min_generation ); foreach my $id (sort keys %$qw) { if ($qw->{$id}->{parent_area} == $council->{id}) { $c->stash->{ward} = $qw->{$id}; return; } } # Given a false ward name $c->detach( 'redirect_council' ); } sub load_parent : Private { my ( $self, $c ) = @_; my $council = $c->stash->{council}; my $areas_info; if ($council->{parent_area}) { $c->stash->{areas_info} = mySociety::MaPit::call('areas', [ $council->{id}, $council->{parent_area} ]) } else { $c->stash->{areas_info} = { $council->{id} => $council }; } } sub load_problems : Private { my ( $self, $c ) = @_; my $where = { state => [ 'confirmed', 'fixed' ], %{ Problems::site_restriction() } }; if ($c->stash->{ward}) { $where->{areas} = { 'like', '%' . $c->stash->{ward}->{id} . '%' }; # FIXME Check this is secure } elsif ($c->stash->{council}) { $where->{areas} = { 'like', '%' . $c->stash->{council}->{id} . '%' }; } my $current_timestamp = Problems::current_timestamp(); my $problems = $c->model('DB::Problem')->search( $where, { columns => [ 'id', 'title', 'detail', 'council', 'state', 'areas', { duration => { extract => "epoch from $current_timestamp-lastupdate" } }, { age => { extract => "epoch from $current_timestamp-confirmed" } }, ], order_by => { -desc => 'id' }, } ); $c->stash->{problems} = [ $problems->all ]; return 1; } sub group_problems : Private { my ( $self, $c ) = @_; my ( %fixed, %open ); my $re_councils = join('|', keys %{$c->stash->{areas_info}}); foreach my $row (@{$c->stash->{problems}}) { if (!$row->council) { # Problem was not sent to any council, add to possible councils while ($row->areas =~ /,($re_councils)(?=,)/g) { add_row($row, $1, \%fixed, \%open); } } else { # Add to councils it was sent to foreach ($row->councils) { next if $c->stash->{council} && $_ != $c->stash->{council}->{id}; add_row($row, $_, \%fixed, \%open); } } } $c->stash->{fixed} = \%fixed; $c->stash->{open} = \%open; return 1; } sub sort_problems : Private { my ( $self, $c ) = @_; my $id = $c->stash->{council}->{id}; my $fixed = $c->stash->{fixed}; my $open = $c->stash->{open}; foreach (qw/new old/) { $c->stash->{fixed}{$id}{$_} = [ sort { $a->{duration} <=> $b->{duration} } @{$fixed->{$id}{$_}} ] if $fixed->{$id}{$_}; } foreach (qw/new older unknown/) { $c->stash->{open}{$id}{$_} = [ sort { $a->{age} <=> $b->{age} } @{$open->{$id}{$_}} ] if $open->{$id}{$_}; } } sub redirect_index : Private { my ( $self, $c ) = @_; my $url = '/reports'; $c->res->redirect( $c->uri_for($url) ); } sub redirect_council : Private { my ( $self, $c ) = @_; my $url = ''; $url .= "/rss" if $c->stash->{rss}; $url .= '/reports'; $url .= '/' . $c->cobrand->short_name( $c->stash->{council} ); $c->res->redirect( $c->uri_for($url) ); } sub redirect_ward : Private { my ( $self, $c ) = @_; my $url = ''; $url .= "/rss" if $c->stash->{rss}; $url .= '/reports'; $url .= '/' . $c->cobrand->short_name( $c->stash->{council} ); $url .= '/' . $c->cobrand->short_name( $c->stash->{ward} ); $c->res->redirect( $c->uri_for($url) ); } sub add_row { my ($row, $council, $fixed, $open) = @_; my $fourweeks = 4*7*24*60*60; my $duration = ($row->get_column('duration') > 2 * $fourweeks) ? 'old' : 'new'; my $type = ($row->get_column('duration') > 2 * $fourweeks) ? 'unknown' : ($row->get_column('age') > $fourweeks ? 'older' : 'new'); # Fixed problems are either old or new push @{$fixed->{$council}{$duration}}, $row if $row->state eq 'fixed'; # Open problems are either unknown, older, or new push @{$open->{$council}{$type}}, $row if $row->state eq 'confirmed'; } =head1 AUTHOR Matthew Somerville =head1 LICENSE Copyright (c) 2011 UK Citizens Online Democracy. All rights reserved. Licensed under the Affero GPL. =cut __PACKAGE__->meta->make_immutable; 1; # # RSS - reports for sent reports, area for all problems in area # if ($rss && $council) { # my $url = Page::short_name($council); # $url .= '/' . Page::short_name($ward) if $ward; # if ($rss eq 'area' && $area_type ne 'DIS' && $area_type ne 'CTY') { # # Two possibilites are the same for one-tier councils, so redirect one to the other # print $q->redirect($base_url . '/rss/reports/' . $url); # return; # } # my $type = 'council_problems'; # Problems sent to a council # my (@params, %title_params); # $title_params{COUNCIL} = $area_name; # push @params, $council->{id} if $rss eq 'reports'; # push @params, $ward ? $ward->{id} : $council->{id}; # if ($ward && $rss eq 'reports') { # $type = 'ward_problems'; # Problems sent to a council, restricted to a ward # $title_params{WARD} = $q_ward; # } elsif ($rss eq 'area') { # $title_params{NAME} = $ward ? $q_ward : $q_council; # $type = 'area_problems'; # Problems within an area # } # print $q->header( -type => 'application/xml; charset=utf-8' ); # my $xsl = Cobrand::feed_xsl($cobrand); # my $out = FixMyStreet::Alert::generate_rss($type, $xsl, "/$url", \@params, \%title_params, $cobrand, $q); # $out =~ s/matthew.fixmystreet/emptyhomes.matthew.fixmystreet/g if $q->{site} eq 'emptyhomes'; # print $out; # return; # }