diff options
Diffstat (limited to 'perllib/FixMyStreet/App/Controller')
22 files changed, 855 insertions, 544 deletions
diff --git a/perllib/FixMyStreet/App/Controller/About.pm b/perllib/FixMyStreet/App/Controller/About.pm index 78e548c5f..233da25d3 100755 --- a/perllib/FixMyStreet/App/Controller/About.pm +++ b/perllib/FixMyStreet/App/Controller/About.pm @@ -34,22 +34,24 @@ sub index : Path("/about") : Args(0) { sub find_template : Private { my ( $self, $c, $page ) = @_; - return $found{$page} if !FixMyStreet->config('STAGING_SITE') && exists $found{$page}; - my $lang_code = $c->stash->{lang_code}; + + return $found{$lang_code}{$page} if !FixMyStreet->config('STAGING_SITE') && + exists $found{$lang_code}{$page}; + foreach my $dir_templates (@{$c->stash->{additional_template_paths}}, @{$c->view('Web')->paths}) { foreach my $dir_static (static_dirs($page, $dir_templates)) { foreach my $file ("$page-$lang_code.html", "$page.html") { if (-e "$dir_templates/$dir_static/$file") { - $found{$page} = "$dir_static/$file"; - return $found{$page}; + $found{$lang_code}{$page} = "$dir_static/$file"; + return $found{$lang_code}{$page}; } } } } # Cache that the page does not exist, so we don't look next time - $found{$page} = undef; - return $found{$page}; + $found{$lang_code}{$page} = undef; + return $found{$lang_code}{$page}; } sub static_dirs { diff --git a/perllib/FixMyStreet/App/Controller/Admin.pm b/perllib/FixMyStreet/App/Controller/Admin.pm index 1f3307710..ed40f4565 100644 --- a/perllib/FixMyStreet/App/Controller/Admin.pm +++ b/perllib/FixMyStreet/App/Controller/Admin.pm @@ -8,9 +8,10 @@ use Path::Class; use POSIX qw(strftime strcoll); use Digest::SHA qw(sha1_hex); use mySociety::EmailUtil qw(is_valid_email is_valid_email_list); -use mySociety::ArrayUtils; use DateTime::Format::Strptime; use List::Util 'first'; +use List::MoreUtils 'uniq'; +use mySociety::ArrayUtils; use FixMyStreet::SendReport; @@ -26,7 +27,7 @@ Admin pages =cut -sub begin : Private { +sub auto : Private { my ( $self, $c ) = @_; $c->uri_disposition('relative'); @@ -43,10 +44,6 @@ sub begin : Private { if ( $c->cobrand->moniker eq 'zurich' ) { $c->cobrand->admin_type(); } -} - -sub auto : Private { - my ( $self, $c ) = @_; $c->forward('check_page_allowed'); } @@ -97,11 +94,11 @@ sub index : Path : Args(0) { my $contacts = $c->model('DB::Contact')->summary_count(); my %contact_counts = - map { $_->confirmed => $_->get_column('confirmed_count') } $contacts->all; + map { $_->state => $_->get_column('state_count') } $contacts->all; - $contact_counts{0} ||= 0; - $contact_counts{1} ||= 0; - $contact_counts{total} = $contact_counts{0} + $contact_counts{1}; + $contact_counts{confirmed} ||= 0; + $contact_counts{unconfirmed} ||= 0; + $contact_counts{total} = $contact_counts{confirmed} + $contact_counts{unconfirmed}; $c->stash->{contacts} = \%contact_counts; @@ -243,6 +240,9 @@ sub bodies : Path('bodies') : Args(0) { $c->stash->{edit_activity} = $edit_activity; + $c->forward( 'fetch_languages' ); + $c->forward( 'fetch_translations' ); + my $posted = $c->get_param('posted') || ''; if ( $posted eq 'body' ) { $c->forward('check_for_super_user'); @@ -256,6 +256,9 @@ sub bodies : Path('bodies') : Args(0) { $c->model('DB::BodyArea')->create( { body => $body, area_id => $_ } ); } + $c->stash->{object} = $body; + $c->stash->{translation_col} = 'name'; + $c->forward('update_translations'); $c->stash->{updated} = _('New body added'); } } @@ -265,8 +268,8 @@ sub bodies : Path('bodies') : Args(0) { my $contacts = $c->model('DB::Contact')->search( undef, { - select => [ 'body_id', { count => 'id' }, { count => \'case when deleted then 1 else null end' }, - { count => \'case when confirmed then 1 else null end' } ], + select => [ 'body_id', { count => 'id' }, { count => \'case when state = \'deleted\' then 1 else null end' }, + { count => \'case when state = \'confirmed\' then 1 else null end' } ], as => [qw/body_id c deleted confirmed/], group_by => [ 'body_id' ], result_class => 'DBIx::Class::ResultClass::HashRefInflator' @@ -299,30 +302,6 @@ sub body_form_dropdowns : Private { $c->stash->{send_methods} = \@methods; } -sub body : Path('body') : Args(1) { - my ( $self, $c, $body_id ) = @_; - - $c->stash->{body_id} = $body_id; - - unless ($c->user->has_permission_to('category_edit', $body_id)) { - $c->forward('check_for_super_user'); - } - - $c->forward( '/auth/get_csrf_token' ); - $c->forward( 'lookup_body' ); - $c->forward( 'fetch_all_bodies' ); - $c->forward( 'body_form_dropdowns' ); - - if ( $c->get_param('posted') ) { - $c->log->debug( 'posted' ); - $c->forward('update_contacts'); - } - - $c->forward('fetch_contacts'); - - return 1; -} - sub check_for_super_user : Private { my ( $self, $c ) = @_; @@ -365,8 +344,7 @@ sub update_contacts : Private { } $contact->email( $email ); - $contact->confirmed( $c->get_param('confirmed') ? 1 : 0 ); - $contact->deleted( $c->get_param('deleted') ? 1 : 0 ); + $contact->state( $c->get_param('state') ); $contact->non_public( $c->get_param('non_public') ? 1 : 0 ); $contact->note( $c->get_param('note') ); $contact->whenedited( \'current_timestamp' ); @@ -393,6 +371,8 @@ sub update_contacts : Private { $contact->set_extra_metadata( reputation_threshold => int($c->get_param('reputation_threshold')) ); } + $c->forward('update_extra_fields', [ $contact ]); + if ( %errors ) { $c->stash->{updated} = _('Please correct the errors below'); $c->stash->{contact} = $contact; @@ -407,6 +387,12 @@ sub update_contacts : Private { $contact->insert; } + unless ( %errors ) { + $c->stash->{translation_col} = 'category'; + $c->stash->{object} = $contact; + $c->forward('update_translations'); + } + } elsif ( $posted eq 'update' ) { $c->forward('/auth/check_csrf_token'); @@ -421,7 +407,7 @@ sub update_contacts : Private { $contacts->update( { - confirmed => 1, + state => 'confirmed', whenedited => \'current_timestamp', note => 'Confirmed', editor => $editor, @@ -446,11 +432,43 @@ sub update_contacts : Private { # Remove any others $c->stash->{body}->body_areas->search( { area_id => [ keys %current ] } )->delete; + $c->stash->{translation_col} = 'name'; + $c->stash->{object} = $c->stash->{body}; + $c->forward('update_translations'); + $c->stash->{updated} = _('Values updated'); } } } +sub update_translations : Private { + my ( $self, $c ) = @_; + + foreach my $lang (keys(%{$c->stash->{languages}})) { + my $id = $c->get_param('translation_id_' . $lang); + my $text = $c->get_param('translation_' . $lang); + if ($id) { + my $translation = $c->model('DB::Translation')->find( + { + id => $id, + } + ); + + if ($text) { + $translation->msgstr($text); + $translation->update; + } else { + $translation->delete; + } + } elsif ($text) { + my $col = $c->stash->{translation_col}; + $c->stash->{object}->add_translation_for( + $col, $lang, $text + ); + } + } +} + sub body_params : Private { my ( $self, $c ) = @_; @@ -485,8 +503,8 @@ sub fetch_contacts : Private { my $contacts = $c->stash->{body}->contacts->search(undef, { order_by => [ 'category' ] } ); $c->stash->{contacts} = $contacts; - $c->stash->{live_contacts} = $contacts->search({ deleted => 0 }); - $c->stash->{any_not_confirmed} = $contacts->search({ confirmed => 0 })->count; + $c->stash->{live_contacts} = $contacts->search({ state => { '!=' => 'deleted' } }); + $c->stash->{any_not_confirmed} = $contacts->search({ state => 'unconfirmed' })->count; if ( $c->get_param('text') && $c->get_param('text') eq '1' ) { $c->stash->{template} = 'admin/council_contacts.txt'; @@ -497,10 +515,48 @@ sub fetch_contacts : Private { return 1; } -sub lookup_body : Private { +sub fetch_languages : Private { my ( $self, $c ) = @_; - my $body_id = $c->stash->{body_id}; + my $lang_map = {}; + foreach my $lang (@{$c->cobrand->languages}) { + my ($id, $name, $code) = split(',', $lang); + $lang_map->{$id} = { name => $name, code => $code }; + } + + $c->stash->{languages} = $lang_map; + + return 1; +} + +sub fetch_translations : Private { + my ( $self, $c ) = @_; + + my $translations = {}; + if ($c->get_param('posted')) { + foreach my $lang (keys %{$c->stash->{languages}}) { + if (my $msgstr = $c->get_param('translation_' . $lang)) { + $translations->{$lang} = { msgstr => $msgstr }; + } + if (my $id = $c->get_param('translation_id_' . $lang)) { + $translations->{$lang}->{id} = $id; + } + } + } elsif ($c->stash->{object}) { + my @translations = $c->stash->{object}->translation_for($c->stash->{translation_col})->all; + + foreach my $tx (@translations) { + $translations->{$tx->lang} = { id => $tx->id, msgstr => $tx->msgstr }; + } + } + + $c->stash->{translations} = $translations; +} + +sub body : Chained('/') : PathPart('admin/body') : CaptureArgs(1) { + my ( $self, $c, $body_id ) = @_; + + $c->stash->{body_id} = $body_id; my $body = $c->model('DB::Body')->find($body_id); $c->detach( '/page_error_404_not_found', [] ) unless $body; @@ -512,39 +568,70 @@ sub lookup_body : Private { $c->stash->{example_pc} = $example_postcode; } } +} + +sub edit_body : Chained('body') : PathPart('') : Args(0) { + my ( $self, $c ) = @_; + + unless ($c->user->has_permission_to('category_edit', $c->stash->{body_id})) { + $c->forward('check_for_super_user'); + } + + $c->forward( '/auth/get_csrf_token' ); + $c->forward( 'fetch_all_bodies' ); + $c->forward( 'body_form_dropdowns' ); + $c->forward('fetch_languages'); + if ( $c->get_param('posted') ) { + $c->forward('update_contacts'); + } + + $c->stash->{object} = $c->stash->{body}; + $c->stash->{translation_col} = 'name'; + + # if there's a contact then it's because we're displaying error + # messages about adding a contact so grabbing translations will + # fetch the contact submitted translations. So grab them, stash + # them and then clear posted so we can fetch the body translations + if ($c->stash->{contact}) { + $c->forward('fetch_translations'); + $c->stash->{contact_translations} = $c->stash->{translations}; + } + $c->set_param('posted', ''); + + $c->forward('fetch_translations'); + $c->forward('fetch_contacts'); + + $c->stash->{template} = 'admin/body.html'; return 1; } -# This is for if the category name contains a '/' -sub category_edit_all : Path('body') { - my ( $self, $c, $body_id, @category ) = @_; +sub category : Chained('body') : PathPart('') { + my ( $self, $c, @category ) = @_; my $category = join( '/', @category ); - $c->go( 'category_edit', [ $body_id, $category ] ); -} - -sub category_edit : Path('body') : Args(2) { - my ( $self, $c, $body_id, $category ) = @_; - - $c->stash->{body_id} = $body_id; $c->forward( '/auth/get_csrf_token' ); - $c->forward( 'lookup_body' ); + $c->stash->{template} = 'admin/category_edit.html'; my $contact = $c->stash->{body}->contacts->search( { category => $category } )->first; $c->stash->{contact} = $contact; + $c->stash->{translation_col} = 'category'; + $c->stash->{object} = $c->stash->{contact}; + + $c->forward('fetch_languages'); + $c->forward('fetch_translations'); + my $history = $c->model('DB::ContactsHistory')->search( { - body_id => $body_id, - category => $category + body_id => $c->stash->{body_id}, + category => $c->stash->{contact}->category }, { order_by => ['contacts_history_id'] }, ); $c->stash->{history} = $history; - my @methods = map { $_ =~ s/FixMyStreet::SendReport:://; $_ } keys %{ FixMyStreet::SendReport->get_senders }; $c->stash->{send_methods} = \@methods; @@ -844,10 +931,26 @@ sub report_edit_category : Private { $problem->category($category); my @contacts = grep { $_->category eq $problem->category } @{$c->stash->{contacts}}; my @new_body_ids = map { $_->body_id } @contacts; - # If the report has changed bodies we need to resend it - if (scalar @{mySociety::ArrayUtils::symmetric_diff($problem->bodies_str_ids, \@new_body_ids)}) { + # If the report has changed bodies (and not to a subset!) we need to resend it + my %old_map = map { $_ => 1 } @{$problem->bodies_str_ids}; + if (grep !$old_map{$_}, @new_body_ids) { + $problem->whensent(undef); + } + # If the send methods of the old/new contacts differ we need to resend the report + my @old_contacts = grep { $_->category eq $category_old } @{$c->stash->{contacts}}; + my @new_send_methods = uniq map { + ( $_->body->can_be_devolved && $_->send_method ) ? + $_->send_method : $_->body->send_method; + } @contacts; + my @old_send_methods = map { + ( $_->body->can_be_devolved && $_->send_method ) ? + $_->send_method : $_->body->send_method; + } @old_contacts; + if ( scalar @{ mySociety::ArrayUtils::symmetric_diff(\@old_send_methods, \@new_send_methods) } ) { + $c->log->debug("Report changed, resending"); $problem->whensent(undef); } + $problem->bodies_str(join( ',', @new_body_ids )); $problem->add_to_comments({ text => '*' . sprintf(_('Category changed from ā%sā to ā%sā'), $category_old, $category) . '*', @@ -909,8 +1012,8 @@ sub categories_for_point : Private { # Remove the "Pick a category" option shift @{$c->stash->{category_options}} if @{$c->stash->{category_options}}; - $c->stash->{categories} = $c->stash->{category_options}; - $c->stash->{categories_hash} = { map { $_ => 1 } @{$c->stash->{category_options}} }; + $c->stash->{category_options_copy} = $c->stash->{category_options}; + $c->stash->{categories_hash} = { map { $_->{name} => 1 } @{$c->stash->{category_options}} }; } sub templates : Path('templates') : Args(0) { @@ -966,7 +1069,7 @@ sub template_edit : Path('templates') : Args(2) { my %active_contacts = map { $_->id => 1 } @contacts; my @all_contacts = map { { id => $_->id, - category => $_->category, + category => $_->category_display, active => $active_contacts{$_->id}, } } @live_contacts; $c->stash->{contacts} = \@all_contacts; @@ -1808,7 +1911,7 @@ sub check_page_allowed : Private { sub fetch_all_bodies : Private { my ($self, $c ) = @_; - my @bodies = $c->model('DB::Body')->all; + my @bodies = $c->model('DB::Body')->all_translated; if ( $c->cobrand->moniker eq 'zurich' ) { @bodies = $c->cobrand->admin_fetch_all_bodies( @bodies ); } else { @@ -1840,6 +1943,46 @@ sub fetch_body_areas : Private { $c->stash->{fetched_areas_body_id} = $body->id; } +sub update_extra_fields : Private { + my ($self, $c, $object) = @_; + + my @indices = grep { /^metadata\[\d+\]\.code/ } keys %{ $c->req->params }; + @indices = sort map { /(\d+)/ } @indices; + + my @extra_fields; + foreach my $i (@indices) { + my $meta = {}; + $meta->{code} = $c->get_param("metadata[$i].code"); + next unless $meta->{code}; + $meta->{order} = int $c->get_param("metadata[$i].order"); + $meta->{datatype} = $c->get_param("metadata[$i].datatype"); + my $required = $c->get_param("metadata[$i].required") && $c->get_param("metadata[$i].required") eq 'on'; + $meta->{required} = $required ? 'true' : 'false'; + my $notice = $c->get_param("metadata[$i].notice") && $c->get_param("metadata[$i].notice") eq 'on'; + $meta->{variable} = $notice ? 'false' : 'true'; + $meta->{description} = $c->get_param("metadata[$i].description"); + $meta->{datatype_description} = $c->get_param("metadata[$i].datatype_description"); + + if ( $meta->{datatype} eq "singlevaluelist" ) { + $meta->{values} = []; + my $re = qr{^metadata\[$i\]\.values\[\d+\]\.key}; + my @vindices = grep { /$re/ } keys %{ $c->req->params }; + @vindices = sort map { /values\[(\d+)\]/ } @vindices; + foreach my $j (@vindices) { + my $name = $c->get_param("metadata[$i].values[$j].name"); + my $key = $c->get_param("metadata[$i].values[$j].key"); + push(@{$meta->{values}}, { + name => $name, + key => $key, + }) if $name; + } + } + push @extra_fields, $meta; + } + @extra_fields = sort { $a->{order} <=> $b->{order} } @extra_fields; + $object->set_extra_fields(@extra_fields); +} + sub trim { my $self = shift; my $e = shift; diff --git a/perllib/FixMyStreet/App/Controller/Admin/DefectTypes.pm b/perllib/FixMyStreet/App/Controller/Admin/DefectTypes.pm index bcfeb3dd8..5dab1da2c 100644 --- a/perllib/FixMyStreet/App/Controller/Admin/DefectTypes.pm +++ b/perllib/FixMyStreet/App/Controller/Admin/DefectTypes.pm @@ -6,12 +6,6 @@ use mySociety::ArrayUtils; BEGIN { extends 'Catalyst::Controller'; } -sub begin : Private { - my ( $self, $c ) = @_; - - $c->forward('/admin/begin'); -} - sub index : Path : Args(0) { my ( $self, $c ) = @_; @@ -62,7 +56,7 @@ sub edit : Path : Args(2) { my %active_contacts = map { $_->id => 1 } @contacts; my @all_contacts = map { { id => $_->id, - category => $_->category, + category => $_->category_display, active => $active_contacts{$_->id}, } } @live_contacts; $c->stash->{contacts} = \@all_contacts; diff --git a/perllib/FixMyStreet/App/Controller/Admin/ExorDefects.pm b/perllib/FixMyStreet/App/Controller/Admin/ExorDefects.pm index 201742c81..bdeecc1a3 100644 --- a/perllib/FixMyStreet/App/Controller/Admin/ExorDefects.pm +++ b/perllib/FixMyStreet/App/Controller/Admin/ExorDefects.pm @@ -2,19 +2,13 @@ package FixMyStreet::App::Controller::Admin::ExorDefects; use Moose; use namespace::autoclean; -use Text::CSV; use DateTime; -use mySociety::Random qw(random_bytes); +use Try::Tiny; +use FixMyStreet::Integrations::ExorRDI; BEGIN { extends 'Catalyst::Controller'; } -sub begin : Private { - my ( $self, $c ) = @_; - - $c->forward('/admin/begin'); -} - sub index : Path : Args(0) { my ( $self, $c ) = @_; @@ -54,177 +48,32 @@ sub download : Path('download') : Args(0) { my $end_date = $parser-> parse_datetime ( $c->get_param('end_date') ) ; my $one_day = DateTime::Duration->new( days => 1 ); - my %params = ( - -and => [ - state => [ 'action scheduled' ], - external_id => { '!=' => undef }, - 'admin_log_entries.action' => 'inspected', - 'admin_log_entries.whenedited' => { '>=', $start_date }, - 'admin_log_entries.whenedited' => { '<=', $end_date + $one_day }, - ] - ); - - my $user; - if ( $c->get_param('user_id') ) { - my $uid = $c->get_param('user_id'); - $params{'admin_log_entries.user_id'} = $uid; - $user = $c->model('DB::User')->find( { id => $uid } ); - } - - my $problems = $c->cobrand->problems->search( - \%params, - { - join => 'admin_log_entries', - distinct => 1, - } - ); - - if ( !$problems->count ) { - if ( defined $user ) { + my $params = { + start_date => $start_date, + inspection_date => $start_date, + end_date => $end_date + $one_day, + user => $c->get_param('user_id'), + mark_as_processed => 0, + }; + my $rdi = FixMyStreet::Integrations::ExorRDI->new($params); + + try { + my $out = $rdi->construct; + $c->res->content_type('text/csv; charset=utf-8'); + $c->res->header('content-disposition' => "attachment; filename=" . $rdi->filename); + $c->res->body( $out ); + } catch { + die $_ unless $_ =~ /FixMyStreet::Integrations::ExorRDI::Error/; + if ($params->{user}) { $c->flash->{error_message} = _("No inspections by that inspector in the selected date range."); } else { $c->flash->{error_message} = _("No inspections in the selected date range."); } - $c->flash->{start_date} = $start_date; - $c->flash->{end_date} = $end_date; - $c->flash->{user_id} = $user->id if $user; + $c->flash->{start_date} = $params->{start_date}; + $c->flash->{end_date} = $params->{end_date}; + $c->flash->{user_id} = $params->{user}; $c->res->redirect( $c->uri_for( '' ) ); - } - - # A single RDI file might contain inspections from multiple inspectors, so - # we need to group inspections by inspector within G records. - my $inspectors = {}; - my $inspector_initials = {}; - while ( my $report = $problems->next ) { - my $user = $report->inspection_log_entry->user; - $inspectors->{$user->id} ||= []; - push @{ $inspectors->{$user->id} }, $report; - unless ( $inspector_initials->{$user->id} ) { - $inspector_initials->{$user->id} = $user->get_extra_metadata('initials'); - } - } - - my $csv = Text::CSV->new({ binary => 1, eol => "" }); - - my $p_count = 0; - my $link_id = $c->cobrand->exor_rdi_link_id; - - # RDI first line is always the same - $csv->combine("1", "1.8", "1.0.0.0", "ENHN", ""); - my @body = ($csv->string); - - my $i = 0; - foreach my $inspector_id (keys %$inspectors) { - my $inspections = $inspectors->{$inspector_id}; - my $initials = $inspector_initials->{$inspector_id}; - - $csv->combine( - "G", # start of an area/sequence - $link_id, # area/link id, fixed value for our purposes - "","", # must be empty - $initials || "XX", # inspector initials - $start_date->strftime("%y%m%d"), # date of inspection yymmdd - "0700", # time of inspection hhmm, set to static value for now - "D", # inspection variant, should always be D - "INS", # inspection type, always INS - "N", # Area of the county - north (N) or south (S) - "", "", "", "" # empty fields - ); - push @body, $csv->string; - - $csv->combine( - "H", # initial inspection type - "MC" # minor carriageway (changes depending on activity code) - ); - push @body, $csv->string; - - foreach my $report (@$inspections) { - my ($eastings, $northings) = $report->local_coords; - my $description = sprintf("%s %s", $report->external_id || "", $report->get_extra_metadata('detailed_information') || ""); - my $activity_code = $report->defect_type ? - $report->defect_type->get_extra_metadata('activity_code') - : 'MC'; - my $traffic_information = $report->get_extra_metadata('traffic_information') ? - 'TM ' . $report->get_extra_metadata('traffic_information') - : 'TM none'; - - $csv->combine( - "I", # beginning of defect record - $activity_code, # activity code - minor carriageway, also FC (footway) - "", # empty field, can also be A (seen on MC) or B (seen on FC) - sprintf("%03d", ++$i), # randomised sequence number - "${eastings}E ${northings}N", # defect location field, which we don't capture from inspectors - $report->inspection_log_entry->whenedited->strftime("%H%M"), # defect time raised - "","","","","","","", # empty fields - $traffic_information, - $description, # defect description - ); - push @body, $csv->string; - - my $defect_type = $report->defect_type ? - $report->defect_type->get_extra_metadata('defect_code') - : 'SFP2'; - $csv->combine( - "J", # georeferencing record - $defect_type, # defect type - SFP2: sweep and fill <1m2, POT2 also seen - $report->response_priority ? - $report->response_priority->external_id : - "2", # priority of defect - "","", # empty fields - $eastings, # eastings - $northings, # northings - "","","","","" # empty fields - ); - push @body, $csv->string; - - $csv->combine( - "M", # bill of quantities record - "resolve", # permanent repair - "","", # empty fields - "/CMC", # /C + activity code - "", "" # empty fields - ); - push @body, $csv->string; - } - - # end this group of defects with a P record - $csv->combine( - "P", # end of area/sequence - 0, # always 0 - 999999, # charging code, always 999999 in OCC - ); - push @body, $csv->string; - $p_count++; - } - - # end the RDI file with an X record - my $record_count = $i; - $csv->combine( - "X", # end of inspection record - $p_count, - $p_count, - $record_count, # number of I records - $record_count, # number of J records - 0, 0, 0, # always zero - $record_count, # number of M records - 0, # always zero - $p_count, - 0, 0, 0 # error counts, always zero - ); - push @body, $csv->string; - - my $start = $start_date->strftime("%Y%m%d"); - my $end = $end_date->strftime("%Y%m%d"); - my $filename = sprintf("exor_defects-%s-%s.rdi", $start, $end); - if ( $user ) { - my $initials = $user->get_extra_metadata("initials") || ""; - $filename = sprintf("exor_defects-%s-%s-%s.rdi", $start, $end, $initials); - } - $c->res->content_type('text/csv; charset=utf-8'); - $c->res->header('content-disposition' => "attachment; filename=$filename"); - # The RDI format is very weird CSV - each line must be wrapped in - # double quotes. - $c->res->body( join "", map { "\"$_\"\r\n" } @body ); + }; } -1;
\ No newline at end of file +1; diff --git a/perllib/FixMyStreet/App/Controller/Admin/ReportExtraFields.pm b/perllib/FixMyStreet/App/Controller/Admin/ReportExtraFields.pm new file mode 100644 index 000000000..337fb4bed --- /dev/null +++ b/perllib/FixMyStreet/App/Controller/Admin/ReportExtraFields.pm @@ -0,0 +1,55 @@ +package FixMyStreet::App::Controller::Admin::ReportExtraFields; +use Moose; +use namespace::autoclean; +use List::MoreUtils qw(uniq); + +BEGIN { extends 'Catalyst::Controller'; } + + +sub index : Path : Args(0) { + my ( $self, $c ) = @_; + + my @extras = $c->model('DB::ReportExtraFields')->search( + undef, + { + order_by => 'name' + } + ); + + $c->stash->{extra_fields} = \@extras; +} + +sub edit : Path : Args(1) { + my ( $self, $c, $extra_id ) = @_; + + my $extra; + if ( $extra_id eq 'new' ) { + $extra = $c->model('DB::ReportExtraFields')->new({}); + } else { + $extra = $c->model('DB::ReportExtraFields')->find( $extra_id ) + or $c->detach( '/page_error_404_not_found' ); + } + + if ($c->req->method eq 'POST') { + $c->forward('/auth/check_csrf_token'); + + foreach (qw/name cobrand language/) { + $extra->$_($c->get_param($_)); + } + $c->forward('/admin/update_extra_fields', [ $extra ]); + + $extra->update_or_insert; + } + + $c->forward('/auth/get_csrf_token'); + $c->forward('/admin/fetch_languages'); + + my @cobrands = uniq sort map { $_->{moniker} } FixMyStreet::Cobrand->available_cobrand_classes; + $c->stash->{cobrands} = \@cobrands; + + $c->stash->{extra} = $extra; +} + +__PACKAGE__->meta->make_immutable; + +1; diff --git a/perllib/FixMyStreet/App/Controller/Admin/ResponsePriorities.pm b/perllib/FixMyStreet/App/Controller/Admin/ResponsePriorities.pm index bae0f71a7..2613f6ae0 100644 --- a/perllib/FixMyStreet/App/Controller/Admin/ResponsePriorities.pm +++ b/perllib/FixMyStreet/App/Controller/Admin/ResponsePriorities.pm @@ -5,12 +5,6 @@ use namespace::autoclean; BEGIN { extends 'Catalyst::Controller'; } -sub begin : Private { - my ( $self, $c ) = @_; - - $c->forward('/admin/begin'); -} - sub index : Path : Args(0) { my ( $self, $c ) = @_; @@ -71,6 +65,7 @@ sub edit : Path : Args(2) { $priority->name( $c->get_param('name') ); $priority->description( $c->get_param('description') ); $priority->external_id( $c->get_param('external_id') ); + $priority->is_default( $c->get_param('is_default') ? 1 : 0 ); $priority->update_or_insert; my @live_contact_ids = map { $_->id } @live_contacts; diff --git a/perllib/FixMyStreet/App/Controller/Admin/States.pm b/perllib/FixMyStreet/App/Controller/Admin/States.pm new file mode 100644 index 000000000..938692af0 --- /dev/null +++ b/perllib/FixMyStreet/App/Controller/Admin/States.pm @@ -0,0 +1,96 @@ +package FixMyStreet::App::Controller::Admin::States; +use Moose; +use namespace::autoclean; + +BEGIN { extends 'Catalyst::Controller'; } + +sub index : Path : Args(0) { + my ( $self, $c ) = @_; + + $c->forward('/auth/get_csrf_token'); + $c->forward('/admin/fetch_languages'); + my $rs = $c->model('DB::State'); + + if ($c->req->method eq 'POST') { + $c->forward('/auth/check_csrf_token'); + + $c->forward('process_new') + && $c->forward('delete') + && $c->forward('update'); + + $rs->clear; + } + + $c->stash->{open_states} = $rs->open; + $c->stash->{closed_states} = $rs->closed; + $c->stash->{fixed_states} = $rs->fixed; +} + +sub process_new : Private { + my ($self, $c) = @_; + if ($c->get_param('new_fixed')) { + $c->model('DB::State')->create({ + label => 'fixed', + type => 'fixed', + name => _('Fixed'), + }); + return 0; + } + return 1 unless $c->get_param('new'); + my %params = map { $_ => $c->get_param($_) } qw/label type name/; + $c->model('DB::State')->create(\%params); + return 0; +} + +sub delete : Private { + my ($self, $c) = @_; + + my @params = keys %{ $c->req->params }; + my ($to_delete) = map { /^delete:(.*)/ } grep { /^delete:/ } @params; + if ($to_delete) { + $c->model('DB::State')->search({ label => $to_delete })->delete; + return 0; + } + return 1; +} + +sub update : Private { + my ($self, $c) = @_; + + my $rs = $c->model('DB::State'); + my %db_states = map { $_->label => $_ } @{$rs->states}; + my @params = keys %{ $c->req->params }; + my @states = map { /^type:(.*)/ } grep { /^type:/ } @params; + + foreach my $state (@states) { + # If there is only one language, we still store confirmed/closed + # as translations, as that seems a sensible place to store them. + if ($state eq 'confirmed' or $state eq 'closed') { + if (my $name = $c->get_param("name:$state")) { + my ($lang) = keys %{$c->stash->{languages}}; + $db_states{$state}->add_translation_for('name', $lang, $name); + } + } else { + $db_states{$state}->update({ + type => $c->get_param("type:$state"), + name => $c->get_param("name:$state"), + }); + } + + foreach my $lang (keys(%{$c->stash->{languages}})) { + my $id = $c->get_param("translation_id:$state:$lang"); + my $text = $c->get_param("translation:$state:$lang"); + if ($text) { + $db_states{$state}->add_translation_for('name', $lang, $text); + } elsif ($id) { + $c->model('DB::Translation')->find({ id => $id })->delete; + } + } + } + + return 1; +} + +__PACKAGE__->meta->make_immutable; + +1; diff --git a/perllib/FixMyStreet/App/Controller/Around.pm b/perllib/FixMyStreet/App/Controller/Around.pm index 1fe35d0a3..b872084ff 100644 --- a/perllib/FixMyStreet/App/Controller/Around.pm +++ b/perllib/FixMyStreet/App/Controller/Around.pm @@ -163,52 +163,20 @@ sub display_location : Private { $c->forward('/auth/get_csrf_token'); - # get the lat,lng - my $latitude = $c->stash->{latitude}; - my $longitude = $c->stash->{longitude}; - - # Deal with pin hiding/age - my $all_pins = $c->get_param('all_pins') ? 1 : undef; - $c->stash->{all_pins} = $all_pins; - my $interval = $all_pins ? undef : $c->cobrand->on_map_default_max_pin_age; - - $c->forward( '/reports/stash_report_filter_status' ); - # Check the category to filter by, if any, is valid $c->forward('check_and_stash_category'); - $c->forward( '/reports/stash_report_sort', [ 'created-desc' ]); - - # get the map features - my ( $on_map_all, $on_map, $nearby, $distance ) = - FixMyStreet::Map::map_features( $c, - latitude => $latitude, longitude => $longitude, - interval => $interval, categories => [ keys %{$c->stash->{filter_category}} ], - states => $c->stash->{filter_problem_states}, - order => $c->stash->{sort_order}, - ); - # copy the found reports to the stash - $c->stash->{on_map} = $on_map; - $c->stash->{around_map} = $nearby; - $c->stash->{distance} = $distance; + my $latitude = $c->stash->{latitude}; + my $longitude = $c->stash->{longitude}; - # create a list of all the pins - my @pins; - unless ($c->get_param('no_pins')) { - @pins = map { - # Here we might have a DB::Problem or a DB::Nearby, we always want the problem. - my $p = (ref $_ eq 'FixMyStreet::App::Model::DB::Nearby') ? $_->problem : $_; - $p->pin_data($c, 'around'); - } @$on_map_all, @$nearby; - } + $c->forward('map_features', [ { latitude => $latitude, longitude => $longitude } ] ); - $c->stash->{page} = 'around'; # So the map knows to make clickable pins, update on pan FixMyStreet::Map::display_map( $c, latitude => $latitude, longitude => $longitude, clickable => 1, - pins => \@pins, + pins => $c->stash->{pins}, area => $c->cobrand->areas_on_around, ); @@ -259,7 +227,7 @@ sub check_and_stash_category : Private { distinct => 1 } )->all; - my @categories = map { $_->category } @contacts; + my @categories = map { { name => $_->category, value => $_->category_display } } @contacts; $c->stash->{filter_categories} = \@categories; my %categories_mapped = map { $_ => 1 } @categories; @@ -268,6 +236,44 @@ sub check_and_stash_category : Private { $c->stash->{filter_category} = \%valid_categories; } +sub map_features : Private { + my ($self, $c, $extra) = @_; + + $c->stash->{page} = 'around'; # Needed by _item.html / so the map knows to make clickable pins, update on pan + + $c->forward( '/reports/stash_report_filter_status' ); + $c->forward( '/reports/stash_report_sort', [ 'created-desc' ]); + + # Deal with pin hiding/age + my $all_pins = $c->get_param('all_pins') ? 1 : undef; + $c->stash->{all_pins} = $all_pins; + my $interval = $all_pins ? undef : $c->cobrand->on_map_default_max_pin_age; + + return if $c->get_param('js'); # JS will request the same (or more) data client side + + my ( $on_map_all, $on_map_list, $nearby, $distance ) = + FixMyStreet::Map::map_features( + $c, interval => $interval, %$extra, + categories => [ keys %{$c->stash->{filter_category}} ], + states => $c->stash->{filter_problem_states}, + order => $c->stash->{sort_order}, + ); + + my @pins; + unless ($c->get_param('no_pins')) { + @pins = map { + # Here we might have a DB::Problem or a DB::Result::Nearby, we always want the problem. + my $p = (ref $_ eq 'FixMyStreet::DB::Result::Nearby') ? $_->problem : $_; + $p->pin_data($c, 'around'); + } @$on_map_all, @$nearby; + } + + $c->stash->{pins} = \@pins; + $c->stash->{on_map} = $on_map_list; + $c->stash->{around_map} = $nearby; + $c->stash->{distance} = $distance; +} + =head2 /ajax Handle the ajax calls that the map makes when it is dragged. The info returned @@ -279,8 +285,6 @@ the map. sub ajax : Path('/ajax') { my ( $self, $c ) = @_; - $c->res->content_type('application/json; charset=utf-8'); - my $bbox = $c->get_param('bbox'); unless ($bbox) { $c->res->status(404); @@ -288,52 +292,13 @@ sub ajax : Path('/ajax') { return; } - # assume this is not cacheable - may need to be more fine-grained later - $c->res->header( 'Cache_Control' => 'max-age=0' ); - - $c->stash->{page} = 'around'; # Needed by _item.html - - # how far back should we go? - my $all_pins = $c->get_param('all_pins') ? 1 : undef; - my $interval = $all_pins ? undef : $c->cobrand->on_map_default_max_pin_age; - - $c->forward( '/reports/stash_report_filter_status' ); - $c->forward( '/reports/stash_report_sort', [ 'created-desc' ]); - - # extract the data from the map - my ( $on_map_all, $on_map_list, $nearby, $dist ) = - FixMyStreet::Map::map_features($c, - bbox => $bbox, interval => $interval, - categories => [ $c->get_param_list('filter_category', 1) ], - states => $c->stash->{filter_problem_states}, - order => $c->stash->{sort_order}, - ); - - # create a list of all the pins - my @pins = map { - # Here we might have a DB::Problem or a DB::Nearby, we always want the problem. - my $p = (ref $_ eq 'FixMyStreet::App::Model::DB::Nearby') ? $_->problem : $_; - my $colour = $c->cobrand->pin_colour( $p, 'around' ); - [ $p->latitude, $p->longitude, - $colour, - $p->id, $p->title_safe - ] - } @$on_map_all, @$nearby; - - # render templates to get the html - my $on_map_list_html = $c->render_fragment( - 'around/on_map_list_items.html', - { on_map => $on_map_list, around_map => $nearby } - ); + my %valid_categories = map { $_ => 1 } $c->get_param_list('filter_category', 1); + $c->stash->{filter_category} = \%valid_categories; - # JSON encode the response - my $json = { pins => \@pins }; - $json->{current} = $on_map_list_html if $on_map_list_html; - my $body = encode_json($json); - $c->res->body($body); + $c->forward('map_features', [ { bbox => $bbox } ]); + $c->forward('/reports/ajax', [ 'around/on_map_list_items.html' ]); } - sub location_autocomplete : Path('/ajax/geocode') { my ( $self, $c ) = @_; $c->res->content_type('application/json; charset=utf-8'); diff --git a/perllib/FixMyStreet/App/Controller/Auth.pm b/perllib/FixMyStreet/App/Controller/Auth.pm index 4efa7abb8..825066026 100644 --- a/perllib/FixMyStreet/App/Controller/Auth.pm +++ b/perllib/FixMyStreet/App/Controller/Auth.pm @@ -33,7 +33,7 @@ sub general : Path : Args(0) { my ( $self, $c ) = @_; $c->detach( 'redirect_on_signin', [ $c->get_param('r') ] ) - if $c->user && $c->get_param('r'); + if $c->req->method eq 'GET' && $c->user && $c->get_param('r'); # all done unless we have a form posted to us return unless $c->req->method eq 'POST'; @@ -128,6 +128,18 @@ sub email_sign_in : Private { return; } + # If user registration is disabled then bail out at this point + # if there's not already a user with this email address. + # NB this uses the same template as a successful sign in to stop + # enumeration of valid email addresses. + if ( FixMyStreet->config('SIGNUPS_DISABLED') + && !$c->model('DB::User')->search({ email => $good_email })->count + && !$c->stash->{current_user} # don't break the change email flow + ) { + $c->stash->{template} = 'auth/token.html'; + return; + } + my $user_params = {}; $user_params->{password} = $c->get_param('password_register') if $c->get_param('password_register'); @@ -199,6 +211,10 @@ sub token : Path('/M') : Args(1) { my $user = $c->model('DB::User')->find_or_new({ email => $data->{email} }); + # Bail out if this is a new user and SIGNUPS_DISABLED is set + $c->detach( '/page_error_403_access_denied', [] ) + if FixMyStreet->config('SIGNUPS_DISABLED') && !$user->in_storage && !$data->{old_email}; + if ($data->{old_email}) { # Were logged in as old_email, want to switch to email ($user) if ($user->in_storage) { @@ -244,6 +260,8 @@ sub fb : Private { sub facebook_sign_in : Private { my ( $self, $c ) = @_; + $c->detach( '/page_error_403_access_denied', [] ) if FixMyStreet->config('SIGNUPS_DISABLED'); + my $fb = $c->forward('/auth/fb'); my $url = $fb->get_authorization_url(scope => ['email']); @@ -302,6 +320,8 @@ sub tw : Private { sub twitter_sign_in : Private { my ( $self, $c ) = @_; + $c->detach( '/page_error_403_access_denied', [] ) if FixMyStreet->config('SIGNUPS_DISABLED'); + my $twitter = $c->forward('/auth/tw'); my $url = $twitter->get_authentication_url(callback => $c->uri_for('/auth/Twitter')); diff --git a/perllib/FixMyStreet/App/Controller/Contact.pm b/perllib/FixMyStreet/App/Controller/Contact.pm index b98bdbcc7..f2c3be47c 100644 --- a/perllib/FixMyStreet/App/Controller/Contact.pm +++ b/perllib/FixMyStreet/App/Controller/Contact.pm @@ -54,7 +54,8 @@ sub submit : Path('submit') : Args(0) { && $c->forward('determine_contact_type') && $c->forward('validate') && $c->forward('prepare_params_for_email') - && $c->forward('send_email'); + && $c->forward('send_email') + && $c->forward('redirect_on_success'); } =head2 determine_contact_type @@ -99,7 +100,7 @@ sub determine_contact_type : Private { =head2 validate -Validate the form submission parameters. Sets error messages and redirect +Validate the form submission parameters. Sets error messages and redirect to index page if errors. =cut @@ -168,8 +169,7 @@ sub prepare_params_for_email : Private { if ( $c->stash->{update} ) { - $c->stash->{problem_url} = $base_url . '/report/' . $c->stash->{update}->problem_id - . '#update_' . $c->stash->{update}->id; + $c->stash->{problem_url} = $base_url . $c->stash->{update}->url; $c->stash->{admin_url} = $admin_url . '/update_edit/' . $c->stash->{update}->id; $c->stash->{complaint} = sprintf( "Complaint about update %d on report %d", @@ -258,10 +258,24 @@ sub send_email : Private { $params->{from} = $from; } - $c->send_email('contact.txt', $params); + $c->stash->{success} = $c->send_email('contact.txt', $params); - # above is always succesful :( - $c->stash->{success} = 1; + return 1; +} + +=head2 redirect_on_success + +Redirect to a custom URL if one was provided + +=cut + +sub redirect_on_success : Private { + my ( $self, $c ) = @_; + + if (my $success_url = $c->get_param('success_url')) { + $c->res->redirect($success_url); + $c->detach; + } return 1; } diff --git a/perllib/FixMyStreet/App/Controller/Dashboard.pm b/perllib/FixMyStreet/App/Controller/Dashboard.pm index fbe5a2dc9..f961660c0 100644 --- a/perllib/FixMyStreet/App/Controller/Dashboard.pm +++ b/perllib/FixMyStreet/App/Controller/Dashboard.pm @@ -24,6 +24,8 @@ sub example : Local : Args(0) { my ( $self, $c ) = @_; $c->stash->{template} = 'dashboard/index.html'; + $c->stash->{filter_states} = $c->cobrand->state_groups_inspect; + $c->stash->{children} = {}; for my $i (1..3) { $c->stash->{children}{$i} = { id => $i, name => "Ward $i" }; @@ -93,6 +95,7 @@ sub index : Path : Args(0) { $c->stash->{body} = $body; # Set up the data for the dropdowns + $c->stash->{filter_states} = $c->cobrand->state_groups_inspect; # Just take the first area ID we find my $area_id = $body->body_areas->first->area_id; @@ -145,12 +148,10 @@ sub index : Path : Args(0) { # List of reports underneath summary table $c->stash->{q_state} = $c->get_param('state') || ''; - if ( $c->stash->{q_state} eq 'fixed' ) { + if ( $c->stash->{q_state} eq 'fixed - council' ) { $prob_where->{'me.state'} = [ FixMyStreet::DB::Result::Problem->fixed_states() ]; } elsif ( $c->stash->{q_state} ) { $prob_where->{'me.state'} = $c->stash->{q_state}; - $prob_where->{'me.state'} = { IN => [ 'planned', 'action scheduled' ] } - if $prob_where->{'me.state'} eq 'action scheduled'; } my $params = { %$prob_where, @@ -180,7 +181,7 @@ sub export_as_csv { my ($self, $c, $problems_rs, $body) = @_; require Text::CSV; my $problems = $problems_rs->search( - {}, { prefetch => 'comments' }); + {}, { prefetch => 'comments', order_by => 'me.confirmed' }); my $filename = do { my %where = ( @@ -211,6 +212,9 @@ sub export_as_csv { 'Status', 'Latitude', 'Longitude', 'Nearest Postcode', + 'Ward', + 'Easting', + 'Northing', 'Report URL', ); my @body = ($csv->string); @@ -242,6 +246,13 @@ sub export_as_csv { } } + my $wards = join ', ', + map { $c->stash->{children}->{$_}->{name} } + grep {$c->stash->{children}->{$_} } + split ',', $hashref->{areas}; + + my @local_coords = $report->local_coords; + $csv->combine( @{$hashref}{ 'id', @@ -258,6 +269,9 @@ sub export_as_csv { 'latitude', 'longitude', 'postcode', }, + $wards, + $local_coords[0], + $local_coords[1], (join '', $c->cobrand->base_url_for_report($report), $report->url), ); diff --git a/perllib/FixMyStreet/App/Controller/JSON.pm b/perllib/FixMyStreet/App/Controller/JSON.pm index d3cd33546..762e3c115 100644 --- a/perllib/FixMyStreet/App/Controller/JSON.pm +++ b/perllib/FixMyStreet/App/Controller/JSON.pm @@ -105,10 +105,9 @@ sub problems : Local { foreach my $problem (@problems) { $problem->name( '' ) if $problem->anonymous == 1; $problem->service( 'Web interface' ) if $problem->service eq ''; - my $bodies = $problem->bodies; - if (keys %$bodies) { - my @body_names = map { $_->name } values %$bodies; - $problem->bodies_str( join(' and ', @body_names) ); + my $body_names = $problem->body_names; + if (@$body_names) { + $problem->bodies_str( join(' and ', @$body_names) ); } } diff --git a/perllib/FixMyStreet/App/Controller/Location.pm b/perllib/FixMyStreet/App/Controller/Location.pm index 6a0f2c0ec..c457c8fce 100644 --- a/perllib/FixMyStreet/App/Controller/Location.pm +++ b/perllib/FixMyStreet/App/Controller/Location.pm @@ -31,8 +31,6 @@ sub determine_location_from_coords : Private { my $latitude = $c->get_param('latitude') || $c->get_param('lat'); my $longitude = $c->get_param('longitude') || $c->get_param('lon'); - $c->log->debug($longitude); - $c->log->debug($latitude); if ( defined $latitude && defined $longitude ) { ($c->stash->{latitude}, $c->stash->{longitude}) = diff --git a/perllib/FixMyStreet/App/Controller/Moderate.pm b/perllib/FixMyStreet/App/Controller/Moderate.pm index 74f2e6b31..e2ab16b6b 100644 --- a/perllib/FixMyStreet/App/Controller/Moderate.pm +++ b/perllib/FixMyStreet/App/Controller/Moderate.pm @@ -146,7 +146,7 @@ sub report_moderate_title : Private { my $title = $c->get_param('problem_revert_title') ? $original_title - : $self->diff($original_title, $c->get_param('problem_title')); + : $c->get_param('problem_title'); if ($title ne $old_title) { $original->insert unless $original->in_storage; @@ -167,7 +167,7 @@ sub report_moderate_detail : Private { my $original_detail = $original->detail; my $detail = $c->get_param('problem_revert_detail') ? $original_detail - : $self->diff($original_detail, $c->get_param('problem_detail')); + : $c->get_param('problem_detail'); if ($detail ne $old_detail) { $original->insert unless $original->in_storage; @@ -285,7 +285,7 @@ sub update_moderate_detail : Private { my $original_detail = $original->detail; my $detail = $c->get_param('update_revert_detail') ? $original_detail - : $self->diff($original_detail, $c->get_param('update_detail')); + : $c->get_param('update_detail'); if ($detail ne $old_detail) { $original->insert unless $original->in_storage; @@ -340,29 +340,6 @@ sub return_text : Private { $c->res->body( $text // '' ); } -sub diff { - my ($self, $old, $new) = @_; - - $new =~s/\[\.{3}\]//g; - - my $diff = Algorithm::Diff->new( [ split //, $old ], [ split //, $new ] ); - my $string; - while ($diff->Next) { - my $d = $diff->Diff; - if ($d & 1) { - my $deleted = join '', $diff->Items(1); - unless ($deleted =~/^\s*$/) { - $string .= ' ' if $deleted =~/^ /; - $string .= '[...]'; - $string .= ' ' if $deleted =~/ $/; - } - } - $string .= join '', $diff->Items(2); - } - return $string; -} - - __PACKAGE__->meta->make_immutable; 1; diff --git a/perllib/FixMyStreet/App/Controller/My.pm b/perllib/FixMyStreet/App/Controller/My.pm index 77711f807..5b80a4a08 100644 --- a/perllib/FixMyStreet/App/Controller/My.pm +++ b/perllib/FixMyStreet/App/Controller/My.pm @@ -19,9 +19,10 @@ Catalyst Controller. =cut -sub begin : Private { +sub auto : Private { my ($self, $c) = @_; $c->detach( '/auth/redirect' ) unless $c->user; + return 1; } =head2 index @@ -162,7 +163,7 @@ sub setup_page_data : Private { distinct => 1, order_by => [ 'category' ], } )->all; - @categories = map { $_->category } @categories; + @categories = map { { name => $_->category, value => $_->category_display } } @categories; $c->stash->{filter_categories} = \@categories; $c->stash->{page} = 'my'; @@ -205,6 +206,20 @@ sub planned_change : Path('planned/change') { } } +sub shortlist_multiple : Path('planned/change_multiple') { + my ($self, $c) = @_; + $c->forward('/auth/check_csrf_token'); + + my @ids = $c->get_param_list('ids[]'); + + foreach my $id (@ids) { + $c->forward( '/report/load_problem_or_display_error', [ $id ] ); + $c->user->add_to_planned_reports($c->stash->{problem}); + } + + $c->res->body(encode_json({ outcome => 'add' })); +} + sub by_shortlisted { my $a_order = $a->get_extra_metadata('order') || 0; my $b_order = $b->get_extra_metadata('order') || 0; @@ -220,6 +235,38 @@ sub by_shortlisted { } } +sub anonymize : Path('anonymize') { + my ($self, $c) = @_; + $c->forward('/auth/get_csrf_token'); + + my $object; + if (my $id = $c->get_param('problem')) { + $c->forward( '/report/load_problem_or_display_error', [ $id ] ); + $object = $c->stash->{problem}; + } elsif ($id = $c->get_param('update')) { + $c->stash->{update} = $object = $c->model('DB::Comment')->find({ id => $id }); + $c->detach('/page_error_400_bad_request') unless $object; + } else { + $c->detach('/page_error_404_not_found'); + } + $c->detach('/page_error_400_bad_request') unless $c->user->id == $object->user_id; + $c->detach('/page_error_400_bad_request') if $object->anonymous; + + if ($c->get_param('hide') || $c->get_param('hide_everywhere')) { + $c->detach('/page_error_400_bad_request') unless $c->req->method eq 'POST'; + $c->forward('/auth/check_csrf_token'); + if ($c->get_param('hide')) { + $object->update({ anonymous => 1 }); + $c->flash->{anonymized} = _('Your name has been hidden.'); + } elsif ($c->get_param('hide_everywhere')) { + $c->user->problems->update({anonymous => 1}); + $c->user->comments->update({anonymous => 1}); + $c->flash->{anonymized} = _('Your name has been hidden from all your reports and updates.'); + } + $c->res->redirect($object->url); + } +} + __PACKAGE__->meta->make_immutable; 1; diff --git a/perllib/FixMyStreet/App/Controller/Open311.pm b/perllib/FixMyStreet/App/Controller/Open311.pm index bc08593de..95b29d116 100644 --- a/perllib/FixMyStreet/App/Controller/Open311.pm +++ b/perllib/FixMyStreet/App/Controller/Open311.pm @@ -160,7 +160,7 @@ sub get_services : Private { my $lon = $c->get_param('long') || ''; # Look up categories for this council or councils - my $categories = $c->model('DB::Contact')->not_deleted; + my $categories = $c->model('DB::Contact')->active; if ($lat || $lon) { my $area_types = $c->cobrand->area_types; @@ -241,7 +241,8 @@ sub output_requests : Private { 'long' => $problem->longitude, 'status' => $problem->state, # 'status_notes' => {}, - 'requested_datetime' => w3date($problem->confirmed), + # Zurich has visible unconfirmed reports + 'requested_datetime' => w3date($problem->confirmed || $problem->created), 'updated_datetime' => w3date($problem->lastupdate), # 'expected_datetime' => {}, # 'address' => {}, @@ -258,8 +259,8 @@ sub output_requests : Private { } else { # FIXME Not according to Open311 v2 - my @body_names = map { $_->name } values %{$problem->bodies}; - $request->{agency_responsible} = {'recipient' => [ @body_names ] }; + my $body_names = $problem->body_names; + $request->{agency_responsible} = {'recipient' => $body_names }; } if ( !$problem->anonymous ) { diff --git a/perllib/FixMyStreet/App/Controller/Report.pm b/perllib/FixMyStreet/App/Controller/Report.pm index ad2702460..e37e08698 100644 --- a/perllib/FixMyStreet/App/Controller/Report.pm +++ b/perllib/FixMyStreet/App/Controller/Report.pm @@ -28,14 +28,11 @@ Redirect to homepage unless C<id> parameter in query, in which case redirect to sub index : Path('') : Args(0) { my ( $self, $c ) = @_; - my $id = $c->get_param('id'); - - my $uri = - $id - ? $c->uri_for( '/report', $id ) - : $c->uri_for('/'); - - $c->res->redirect($uri); + if ($c->stash->{homepage_template}) { + $c->stash->{template} = 'index.html'; + } else { + $c->res->redirect('/'); + } } =head2 report_display @@ -124,7 +121,7 @@ sub load_problem_or_display_error : Private { $c->detach( '/page_error_404_not_found', [ _('Unknown problem ID') ] ) unless $c->cobrand->show_unconfirmed_reports ; } - elsif ( $problem->hidden_states->{ $problem->state } or + elsif ( $problem->hidden_states->{ $problem->state } or (($problem->get_extra_metadata('closure_status')||'') eq 'hidden')) { $c->detach( '/page_error_410_gone', @@ -159,7 +156,7 @@ sub load_updates : Private { my $updates = $c->model('DB::Comment')->search( { problem_id => $c->stash->{problem}->id, state => 'confirmed' }, - { order_by => 'confirmed' } + { order_by => [ 'confirmed', 'id' ] } ); my $questionnaires = $c->model('DB::Questionnaire')->search( @@ -181,8 +178,10 @@ sub load_updates : Private { @combined = map { $_->[1] } sort { $a->[0] <=> $b->[0] } @combined; $c->stash->{updates} = \@combined; - if ($c->sessionid && $c->flash->{alert_to_reporter}) { - $c->stash->{alert_to_reporter} = 1; + if ($c->sessionid) { + foreach (qw(alert_to_reporter anonymized)) { + $c->stash->{$_} = $c->flash->{$_} if $c->flash->{$_}; + } } return 1; @@ -201,7 +200,8 @@ sub format_problem_for_display : Private { $c->stash->{add_alert} = 1; } - $c->stash->{extra_name_info} = $problem->bodies_str && $problem->bodies_str eq '2482' ? 1 : 0; + my $first_body = (values %{$problem->bodies})[0]; + $c->stash->{extra_name_info} = $first_body && $first_body->name =~ /Bromley/ ? 1 : 0; $c->forward('generate_map_tags'); @@ -309,38 +309,33 @@ sub inspect : Private { $c->forward('/admin/categories_for_point'); $c->stash->{report_meta} = { map { $_->{name} => $_ } @{ $c->stash->{problem}->get_extra_fields() } }; - my %category_body = map { $_->category => $_->body_id } map { $_->contacts->all } values %{$problem->bodies}; - - my @priorities = $c->model('DB::ResponsePriority')->for_bodies($problem->bodies_str_ids)->all; - my $priorities_by_category = {}; - foreach my $pri (@priorities) { - my $any = 0; - foreach ($pri->contacts->all) { - $any = 1; - push @{$priorities_by_category->{$_->category}}, $pri->id . '=' . URI::Escape::uri_escape_utf8($pri->name); - } - if (!$any) { - foreach (grep { $category_body{$_} == $pri->body_id } @{$c->stash->{categories}}) { - push @{$priorities_by_category->{$_}}, $pri->id . '=' . URI::Escape::uri_escape_utf8($pri->name); - } - } + if ($c->cobrand->can('council_area_id')) { + my $priorities_by_category = FixMyStreet::App->model('DB::ResponsePriority')->by_categories($c->cobrand->council_area_id, @{$c->stash->{contacts}}); + $c->stash->{priorities_by_category} = $priorities_by_category; + my $templates_by_category = FixMyStreet::App->model('DB::ResponseTemplate')->by_categories($c->cobrand->council_area_id, @{$c->stash->{contacts}}); + $c->stash->{templates_by_category} = $templates_by_category; } - foreach (keys %{$priorities_by_category}) { - $priorities_by_category->{$_} = join('&', @{$priorities_by_category->{$_}}); + + if ($c->user->has_body_permission_to('planned_reports')) { + $c->stash->{post_inspect_url} = $c->req->referer; } - $c->stash->{priorities_by_category} = $priorities_by_category; + if ($c->user->has_body_permission_to('report_edit_priority') or + $c->user->has_body_permission_to('report_inspect') + ) { + $c->stash->{has_default_priority} = scalar( grep { $_->is_default } $problem->response_priorities ); + } if ( $c->get_param('save') ) { $c->forward('/auth/check_csrf_token'); my $valid = 1; - my $update_text; + my $update_text = ''; my $reputation_change = 0; my %update_params = (); if ($permissions->{report_inspect}) { - foreach (qw/detailed_information traffic_information duplicate_of/) { + foreach (qw/detailed_information traffic_information/) { $problem->set_extra_metadata( $_ => $c->get_param($_) ); } @@ -375,19 +370,35 @@ sub inspect : Private { } if ( $problem->state ne 'duplicate' ) { $problem->unset_extra_metadata('duplicate_of'); + } elsif (my $duplicate_of = $c->get_param('duplicate_of')) { + $problem->set_duplicate_of($duplicate_of); } + if ( $problem->state ne $old_state ) { $c->forward( '/admin/log_edit', [ $problem->id, 'problem', 'state_change' ] ); - # If the state has been changed by an inspector, consider the - # report to be inspected. - unless ($problem->get_extra_metadata('inspected')) { - $problem->set_extra_metadata( inspected => 1 ); - $c->forward( '/admin/log_edit', [ $problem->id, 'problem', 'inspected' ] ); - my $state = $problem->state; - $reputation_change = 1 if $c->cobrand->reputation_increment_states->{$state}; - $reputation_change = -1 if $c->cobrand->reputation_decrement_states->{$state}; - } + $update_params{problem_state} = $problem->state; + + my $state = $problem->state; + $reputation_change = 1 if $c->cobrand->reputation_increment_states->{$state}; + $reputation_change = -1 if $c->cobrand->reputation_decrement_states->{$state}; + + # If an inspector has changed the state, subscribe them to + # updates + my $options = { + cobrand => $c->cobrand->moniker, + cobrand_data => $problem->cobrand_data, + lang => $problem->lang, + }; + $problem->user->create_alert($problem->id, $options); + } + + # If the state has been changed to action scheduled and they've said + # they want to raise a defect, consider the report to be inspected. + if ($problem->state eq 'action scheduled' && $c->get_param('raise_defect') && !$problem->get_extra_metadata('inspected')) { + $update_params{extra} = { 'defect_raised' => 1 }; + $problem->set_extra_metadata( inspected => 1 ); + $c->forward( '/admin/log_edit', [ $problem->id, 'problem', 'inspected' ] ); } } @@ -421,39 +432,52 @@ sub inspect : Private { } $problem->lastupdate( \'current_timestamp' ); $problem->update; - if ( defined($update_text) ) { - my $timestamp = \'current_timestamp'; - if (my $saved_at = $c->get_param('saved_at')) { - $timestamp = DateTime->from_epoch( epoch => $saved_at ); - } - my $name = $c->user->from_body ? $c->user->from_body->name : $c->user->name; - $problem->add_to_comments( { - text => $update_text, - created => $timestamp, - confirmed => $timestamp, - user_id => $c->user->id, - name => $name, - state => 'confirmed', - mark_fixed => 0, - anonymous => 0, - %update_params, - } ); + my $timestamp = \'current_timestamp'; + if (my $saved_at = $c->get_param('saved_at')) { + $timestamp = DateTime->from_epoch( epoch => $saved_at ); } - # This problem might no longer be visible on the current cobrand, - # if its body has changed (e.g. by virtue of the category changing) - # so redirect to a cobrand where it can be seen if necessary + my $name = $c->user->from_body ? $c->user->from_body->name : $c->user->name; + $problem->add_to_comments( { + text => $update_text, + created => $timestamp, + confirmed => $timestamp, + user_id => $c->user->id, + name => $name, + state => 'confirmed', + mark_fixed => 0, + anonymous => 0, + %update_params, + } ); + my $redirect_uri; - if ( $c->cobrand->is_council && !$c->cobrand->owns_problem($problem) ) { + $problem->discard_changes; + + # If inspector, redirect back to the map view they came from + # with the right filters. If that wasn't set, go to /around at this + # report's location. + # We go here rather than the shortlist because it makes it much + # simpler to inspect many reports in the same location. The + # shortlist is always a single click away, being on the main nav. + if ($c->user->has_body_permission_to('planned_reports')) { + unless ($redirect_uri = $c->get_param("post_inspect_url")) { + my $categories = join(',', @{ $c->user->categories }); + my $params = { + lat => $problem->latitude, + lon => $problem->longitude, + }; + $params->{filter_category} = $categories if $categories; + $params->{js} = 1 if $c->get_param('js'); + $redirect_uri = $c->uri_for( "/around", $params ); + } + } elsif ( $c->cobrand->is_council && !$c->cobrand->owns_problem($problem) ) { + # This problem might no longer be visible on the current cobrand, + # if its body has changed (e.g. by virtue of the category changing) + # so redirect to a cobrand where it can be seen if necessary $redirect_uri = $c->cobrand->base_url_for_report( $problem ) . $problem->url; } else { $redirect_uri = $c->uri_for( $problem->url ); } - # Or if inspector, redirect back to shortlist - if ($c->user->has_body_permission_to('planned_reports')) { - $redirect_uri = $c->uri_for_action('my/planned'); - } - $c->log->debug( "Redirecting to: " . $redirect_uri ); $c->res->redirect( $redirect_uri ); } @@ -476,7 +500,7 @@ sub nearby_json : Private { $c->forward( 'load_problem_or_display_error', [ $id ] ); my $p = $c->stash->{problem}; - my $dist = 1000; + my $dist = 1; my $nearby = $c->model('DB::Nearby')->nearby( $c, $dist, [ $p->id ], 5, $p->latitude, $p->longitude, undef, [ $p->category ], undef @@ -496,7 +520,7 @@ sub nearby_json : Private { ); my $json = { pins => \@pins }; - $json->{current} = $on_map_list_html if $on_map_list_html; + $json->{reports_list} = $on_map_list_html if $on_map_list_html; my $body = encode_json($json); $c->res->content_type('application/json; charset=utf-8'); $c->res->body($body); diff --git a/perllib/FixMyStreet/App/Controller/Report/New.pm b/perllib/FixMyStreet/App/Controller/Report/New.pm index 2a68b170e..f92a5cb22 100644 --- a/perllib/FixMyStreet/App/Controller/Report/New.pm +++ b/perllib/FixMyStreet/App/Controller/Report/New.pm @@ -99,12 +99,14 @@ sub report_new : Path : Args(0) { # create a problem from the submitted details $c->stash->{template} = "report/new/fill_in_details.html"; $c->forward('setup_categories_and_bodies'); + $c->forward('setup_report_extra_fields'); $c->forward('generate_map'); $c->forward('check_for_category'); # deal with the user and report and check both are happy return unless $c->forward('check_form_submitted'); + $c->forward('/auth/check_csrf_token'); $c->forward('process_user'); $c->forward('process_report'); @@ -137,6 +139,7 @@ sub report_new_ajax : Path('mobile') : Args(0) { } $c->forward('setup_categories_and_bodies'); + $c->forward('setup_report_extra_fields'); $c->forward('process_user'); $c->forward('process_report'); $c->forward('/photo/process_photo'); @@ -184,6 +187,7 @@ sub report_form_ajax : Path('ajax') : Args(0) { } $c->forward('setup_categories_and_bodies'); + $c->forward('setup_report_extra_fields'); # render templates to get the html my $category = $c->render_fragment( 'report/new/category.html'); @@ -200,8 +204,10 @@ sub report_form_ajax : Path('ajax') : Args(0) { if ($c->user_exists) { my @bodies = keys %{$c->stash->{bodies}}; my $ca_another_user = $c->user->has_permission_to('contribute_as_another_user', \@bodies); - my $ca_body = $c->user->has_permission_to('contribute_as_body', \@bodies); + my $ca_anonymous_user = $c->user->has_permission_to('contribute_as_anonymous_user', \@bodies); + my $ca_body = $c->user->from_body && $c->user->has_permission_to('contribute_as_body', \@bodies); $contribute_as->{another_user} = $ca_another_user if $ca_another_user; + $contribute_as->{anonymous_user} = $ca_anonymous_user if $ca_anonymous_user; $contribute_as->{body} = $ca_body if $ca_body; } @@ -212,7 +218,6 @@ sub report_form_ajax : Path('ajax') : Args(0) { category => $category, extra_name_info => $extra_name_info, titles_list => $extra_titles_list, - categories => $c->stash->{category_options}, %$contribute_as ? (contribute_as => $contribute_as) : (), $top_message ? (top_message => $top_message) : (), } @@ -233,6 +238,7 @@ sub category_extras_ajax : Path('category_extras') : Args(0) { return 1; } $c->forward('setup_categories_and_bodies'); + $c->forward('setup_report_extra_fields'); $c->forward('check_for_category'); my $category = $c->stash->{category} || ""; @@ -252,6 +258,9 @@ sub category_extras_ajax : Path('category_extras') : Args(0) { if ($c->stash->{unresponsive}->{$category}) { $generate = 1; } + if ($c->stash->{report_extra_fields}) { + $generate = 1; + } if ($generate) { $category_extra = $c->render_fragment('report/new/category_extras.html', $vars); } @@ -493,7 +502,7 @@ Work out what the location of the report should be - either by using lat,lng or a tile click or what's come in from a partial. Returns false if no location could be found. -=cut +=cut sub determine_location : Private { my ( $self, $c ) = @_; @@ -515,7 +524,7 @@ sub determine_location : Private { Detect that the map tiles have been clicked on by looking for the tile parameters. -=cut +=cut sub determine_location_from_tile_click : Private { my ( $self, $c ) = @_; @@ -566,7 +575,7 @@ sub determine_location_from_tile_click : Private { Use latitude and longitude stored in the report - this is probably result of a partial report being loaded. -=cut +=cut sub determine_location_from_report : Private { my ( $self, $c ) = @_; @@ -604,8 +613,8 @@ sub setup_categories_and_bodies : Private { my $contacts # = $c # ->model('DB::Contact') # - ->not_deleted # - ->search( { body_id => [ keys %bodies ] } ); + ->active + ->search( { body_id => [ keys %bodies ] }, { prefetch => 'body' } ); my @contacts = $c->cobrand->categories_restriction($contacts)->all; # variables to populate @@ -632,13 +641,19 @@ sub setup_categories_and_bodies : Private { # keysort does not appear to obey locale so use strcoll (see i18n.t) @contacts = sort { strcoll( $a->category, $b->category ) } @contacts; + # Get defect types for inspectors + if ($c->cobrand->can('council_area_id')) { + my $category_defect_types = FixMyStreet::App->model('DB::DefectType')->by_categories($c->cobrand->council_area_id, @contacts); + $c->stash->{category_defect_types} = $category_defect_types; + } + my %seen; foreach my $contact (@contacts) { $bodies_to_list{ $contact->body_id } = $contact->body; unless ( $seen{$contact->category} ) { - push @category_options, $contact->category; + push @category_options, { name => $contact->category, value => $contact->category_display }; my $metas = $contact->get_metadata_for_input; $category_extras{$contact->category} = $metas if @$metas; @@ -650,13 +665,15 @@ sub setup_categories_and_bodies : Private { $non_public_categories{ $contact->category } = 1 if $contact->non_public; } - $seen{$contact->category} = 1; + $seen{$contact->category} = $contact->category_display; } if (@category_options) { # If there's an Other category present, put it at the bottom - @category_options = ( _('-- Pick a category --'), grep { $_ ne _('Other') } @category_options ); - push @category_options, _('Other') if $seen{_('Other')}; + @category_options = ( + { name => _('-- Pick a category --'), value => _('-- Pick a category --') }, + grep { $_->{name} ne _('Other') } @category_options ); + push @category_options, { name => _('Other'), value => $seen{_('Other')} } if $seen{_('Other')}; } $c->cobrand->call_hook(munge_category_list => \@category_options, \@contacts, \%category_extras); @@ -679,6 +696,15 @@ sub setup_categories_and_bodies : Private { $c->stash->{missing_details_body_names} = \@missing_details_body_names; } +sub setup_report_extra_fields : Private { + my ( $self, $c ) = @_; + + return unless $c->cobrand->allow_report_extra_fields; + + my @extras = $c->model('DB::ReportExtraFields')->for_cobrand($c->cobrand)->for_language($c->stash->{lang_code})->all; + $c->stash->{report_extra_fields} = \@extras; +} + =head2 check_form_submitted $bool = $c->forward('check_form_submitted'); @@ -734,7 +760,8 @@ sub process_user : Private { $user->title( $user_title ) if $user_title; $report->user( $user ); - if ($c->stash->{contributing_as_body} = $user->contributing_as('body', $c, $c->stash->{bodies})) { + if ($c->stash->{contributing_as_body} = $user->contributing_as('body', $c, $c->stash->{bodies}) or + $c->stash->{contributing_as_anonymous_user} = $user->contributing_as('anonymous_user', $c, $c->stash->{bodies})) { $report->name($user->from_body->name); $user->name($user->from_body->name) unless $user->name; $c->stash->{no_reporter_alert} = 1; @@ -814,6 +841,8 @@ sub process_report : Private { # set some simple bool values (note they get inverted) if ($c->stash->{contributing_as_body}) { $report->anonymous(0); + } elsif ($c->stash->{contributing_as_anonymous_user}) { + $report->anonymous(1); } else { $report->anonymous( $params{may_show_name} ? 0 : 1 ); } @@ -933,7 +962,24 @@ sub set_report_extras : Private { } } - $c->cobrand->process_open311_extras( $c, @$contacts[0]->body_id, \@extra ) + foreach my $extra_fields (@{ $c->stash->{report_extra_fields} }) { + my $metas = $extra_fields->get_extra_fields; + $param_prefix = "extra[" . $extra_fields->id . "]"; + foreach my $field ( @$metas ) { + if ( lc( $field->{required} ) eq 'true' && !$c->cobrand->category_extra_hidden($field->{code})) { + unless ( $c->get_param($param_prefix . $field->{code}) ) { + $c->stash->{field_errors}->{ $field->{code} } = _('This information is required'); + } + } + push @extra, { + name => $field->{code}, + description => $field->{description}, + value => $c->get_param($param_prefix . $field->{code}) || '', + }; + } + } + + $c->cobrand->process_open311_extras( $c, @$contacts[0]->body, \@extra ) if ( scalar @$contacts ); if ( @extra ) { @@ -1140,7 +1186,7 @@ sub save_user_and_report : Private { sub created_as_someone_else : Private { my ($self, $c, $bodies) = @_; - return $c->stash->{contributing_as_another_user} || $c->stash->{contributing_as_body}; + return $c->stash->{contributing_as_another_user} || $c->stash->{contributing_as_body} || $c->stash->{contributing_as_anonymous_user}; } =head2 generate_map @@ -1203,9 +1249,14 @@ sub redirect_or_confirm_creation : Private { to => [ [ $report->user->email, $report->name ] ], } ); } - $c->log->info($report->user->id . ' was logged in, showing confirmation page for ' . $report->id); - $c->stash->{created_report} = 'loggedin'; - $c->stash->{template} = 'tokens/confirm_problem.html'; + if ($c->user_exists && $c->user->has_body_permission_to('planned_reports')) { + $c->log->info($report->user->id . ' is an inspector - redirecting straight to report page for ' . $report->id); + $c->res->redirect( '/report/'. $report->id ); + } else { + $c->log->info($report->user->id . ' was logged in, showing confirmation page for ' . $report->id); + $c->stash->{created_report} = 'loggedin'; + $c->stash->{template} = 'tokens/confirm_problem.html'; + } return 1; } diff --git a/perllib/FixMyStreet/App/Controller/Report/Update.pm b/perllib/FixMyStreet/App/Controller/Report/Update.pm index 4c2d92d5e..033f5c017 100644 --- a/perllib/FixMyStreet/App/Controller/Report/Update.pm +++ b/perllib/FixMyStreet/App/Controller/Report/Update.pm @@ -128,7 +128,7 @@ sub process_user : Private { $update->user( $user ); # Just in case, make sure the user will have a name - if ($c->stash->{contributing_as_body}) { + if ($c->stash->{contributing_as_body} or $c->stash->{contributing_as_anonymous_user}) { $user->name($user->from_body->name) unless $user->name; } @@ -277,17 +277,19 @@ sub process_update : Private { $update->mark_open($params{reopen} ? 1 : 0); $c->stash->{contributing_as_body} = $c->user_exists && $c->user->contributing_as('body', $c, $update->problem->bodies_str_ids); + $c->stash->{contributing_as_anonymous_user} = $c->user_exists && $c->user->contributing_as('anonymous_user', $c, $update->problem->bodies_str_ids); if ($c->stash->{contributing_as_body}) { $update->name($c->user->from_body->name); $update->anonymous(0); + } elsif ($c->stash->{contributing_as_anonymous_user}) { + $update->name($c->user->from_body->name); + $update->anonymous(1); } else { $update->name($name); $update->anonymous($c->get_param('may_show_name') ? 0 : 1); } if ( $params{state} ) { - $params{state} = 'fixed - council' - if $params{state} eq 'fixed' && $c->user && $c->user->belongs_to_body( $update->problem->bodies_str ); $update->problem_state( $params{state} ); } else { # we do this so we have a record of the state of the problem at this point @@ -309,7 +311,8 @@ sub process_update : Private { my @extra; # Next function fills this, but we don't need it here. # This is just so that the error checking for these extra fields runs. # TODO Use extra here as it is used on reports. - $c->cobrand->process_open311_extras( $c, $update->problem->bodies_str, \@extra ); + my $body = (values %{$update->problem->bodies})[0]; + $c->cobrand->process_open311_extras( $c, $body, \@extra ); if ( $c->get_param('fms_extra_title') ) { my %extras = (); @@ -344,14 +347,11 @@ sub check_for_errors : Private { my ( $self, $c ) = @_; # they have to be an authority user to update the state - if ( $c->get_param('state') ) { + my $state = $c->get_param('state'); + if ( $state && $state ne $c->stash->{update}->problem->state ) { my $error = 0; $error = 1 unless $c->user && $c->user->belongs_to_body( $c->stash->{update}->problem->bodies_str ); - - my $state = $c->get_param('state'); - $state = 'fixed - council' if $state eq 'fixed'; - $error = 1 unless ( grep { $state eq $_ } ( FixMyStreet::DB::Result::Problem->council_states() ) ); - + $error = 1 unless grep { $state eq $_ } FixMyStreet::DB::Result::Problem->visible_states(); if ( $error ) { $c->stash->{errors} ||= []; push @{ $c->stash->{errors} }, _('There was a problem with your update. Please try again.'); @@ -548,24 +548,17 @@ sub signup_for_alerts : Private { my ( $self, $c ) = @_; my $update = $c->stash->{update}; + my $user = $update->user; + my $problem_id = $update->problem_id; + if ( $c->stash->{add_alert} ) { my $options = { - user => $update->user, - alert_type => 'new_updates', - parameter => $update->problem_id, + cobrand => $update->cobrand, + cobrand_data => $update->cobrand_data, + lang => $update->lang, }; - my $alert = $c->model('DB::Alert')->find($options); - unless ($alert) { - $alert = $c->model('DB::Alert')->create({ - %$options, - cobrand => $update->cobrand, - cobrand_data => $update->cobrand_data, - lang => $update->lang, - }); - } - $alert->confirm(); - - } elsif ( my $alert = $update->user->alert_for_problem($update->problem_id) ) { + $user->create_alert($problem_id, $options); + } elsif ( my $alert = $user->alert_for_problem($problem_id) ) { $alert->disable(); } diff --git a/perllib/FixMyStreet/App/Controller/Reports.pm b/perllib/FixMyStreet/App/Controller/Reports.pm index ed851f71f..8f8205719 100644 --- a/perllib/FixMyStreet/App/Controller/Reports.pm +++ b/perllib/FixMyStreet/App/Controller/Reports.pm @@ -36,6 +36,12 @@ sub index : Path : Args(0) { if ( $c->cobrand->moniker eq 'zurich' ) { $c->forward( 'stash_report_filter_status' ); $c->forward( 'load_and_group_problems' ); + $c->stash->{body} = { id => 0 }; # So template can fetch the list + + if ($c->get_param('ajax')) { + $c->detach('ajax', [ 'reports/_problem-list.html' ]); + } + my $pins = $c->stash->{pins}; $c->stash->{page} = 'reports'; FixMyStreet::Map::display_map( @@ -54,6 +60,15 @@ sub index : Path : Args(0) { $c->detach( 'redirect_body' ); } + if (my $body = $c->get_param('body')) { + $body = $c->model('DB::Body')->find( { id => $body } ); + if ($body) { + $body = $c->cobrand->short_name($body); + $c->res->redirect("/reports/$body"); + $c->detach; + } + } + # Fetch all bodies my @bodies = $c->model('DB::Body')->search({ deleted => 0, @@ -67,15 +82,21 @@ sub index : Path : Args(0) { $c->stash->{bodies} = \@bodies; $c->stash->{any_empty_bodies} = any { $_->get_column('area_count') == 0 } @bodies; - eval { + my $dashboard = eval { + my $data = File::Slurp::read_file( + FixMyStreet->path_to( '../data/all-reports-dashboard.json' )->stringify + ); + $c->stash(decode_json($data)); + return 1; + }; + my $table = eval { my $data = File::Slurp::read_file( FixMyStreet->path_to( '../data/all-reports.json' )->stringify ); - my $j = decode_json($data); - $c->stash->{fixed} = $j->{fixed}; - $c->stash->{open} = $j->{open}; + $c->stash(decode_json($data)); + return 1; }; - if ($@) { + if (!$dashboard && !$table) { my $message = _("There was a problem showing the All Reports page. Please try again later."); if ($c->config->{STAGING_SITE}) { $message .= '</p><p>Perhaps the bin/update-all-reports script needs running. Use: bin/update-all-reports</p><p>' @@ -88,7 +109,7 @@ sub index : Path : Args(0) { $c->response->header('Cache-Control' => 'max-age=3600'); } -=head2 index +=head2 body Show the summary page for a particular body. @@ -99,7 +120,7 @@ sub body : Path : Args(1) { $c->detach( 'ward', [ $body ] ); } -=head2 index +=head2 ward Show the summary page for a particular ward. @@ -135,7 +156,7 @@ sub ward : Path : Args(2) { distinct => 1, order_by => [ 'category' ], } )->all; - @categories = map { $_->category } @categories; + @categories = map { { name => $_->category, value => $_->category_display } } @categories; $c->stash->{filter_categories} = \@categories; $c->stash->{filter_category} = { map { $_ => 1 } $c->get_param_list('filter_category', 1) }; @@ -303,6 +324,19 @@ sub body_check : Private { } } + my @translations = $c->model('DB::Translation')->search( { + tbl => 'body', + col => 'name', + msgstr => $q_body + } )->all; + + if (@translations == 1) { + if ( my $body = $c->model('DB::Body')->find( { id => $translations[0]->object_id } ) ) { + $c->stash->{body} = $body; + return; + } + } + # No result, bad body name. $c->detach( 'redirect_index' ); } @@ -369,8 +403,6 @@ sub load_and_group_problems : Private { $c->forward('stash_report_sort', [ $c->cobrand->reports_ordering ]); my $page = $c->get_param('p') || 1; - # NB: If 't' is specified, it will override 'status'. - my $type = $c->get_param('t') || 'all'; my $category = [ $c->get_param_list('filter_category', 1) ]; my $states = $c->stash->{filter_problem_states}; @@ -382,6 +414,18 @@ sub load_and_group_problems : Private { order_by => $c->stash->{sort_order}, rows => $c->cobrand->reports_per_page, }; + if ($c->user_exists && $c->stash->{body}) { + my $bid = $c->stash->{body}->id; + my $prefetch = []; + if ($c->user->has_permission_to('planned_reports', $bid)) { + push @$prefetch, 'user_planned_reports'; + } + if ($c->user->has_permission_to('report_edit_priority', $bid) || $c->user->has_permission_to('report_inspect', $bid)) { + push @$prefetch, 'response_priority'; + } + $prefetch = $prefetch->[0] if @$prefetch == 1; + $filter->{prefetch} = $prefetch; + } if (defined $c->stash->{filter_status}{shortlisted}) { $where->{'me.id'} = { '=', \"user_planned_reports.report_id"}; @@ -398,25 +442,6 @@ sub load_and_group_problems : Private { $where->{'me.id'} = { -not_in => $shortlisted_ids }; } - my $not_open = [ FixMyStreet::DB::Result::Problem::fixed_states(), FixMyStreet::DB::Result::Problem::closed_states() ]; - if ( $type eq 'new' ) { - $where->{confirmed} = { '>', \"current_timestamp - INTERVAL '4 week'" }; - $where->{state} = { 'IN', [ FixMyStreet::DB::Result::Problem::open_states() ] }; - } elsif ( $type eq 'older' ) { - $where->{confirmed} = { '<', \"current_timestamp - INTERVAL '4 week'" }; - $where->{lastupdate} = { '>', \"current_timestamp - INTERVAL '8 week'" }; - $where->{state} = { 'IN', [ FixMyStreet::DB::Result::Problem::open_states() ] }; - } elsif ( $type eq 'unknown' ) { - $where->{lastupdate} = { '<', \"current_timestamp - INTERVAL '8 week'" }; - $where->{state} = { 'IN', [ FixMyStreet::DB::Result::Problem::open_states() ] }; - } elsif ( $type eq 'fixed' ) { - $where->{lastupdate} = { '>', \"current_timestamp - INTERVAL '8 week'" }; - $where->{state} = $not_open; - } elsif ( $type eq 'older_fixed' ) { - $where->{lastupdate} = { '<', \"current_timestamp - INTERVAL '8 week'" }; - $where->{state} = $not_open; - } - if (@$category) { $where->{category} = $category; } @@ -445,7 +470,6 @@ sub load_and_group_problems : Private { my ( %problems, @pins ); while ( my $problem = $problems->next ) { - $c->log->debug( $problem->cobrand . ', cobrand is ' . $c->cobrand->moniker ); if ( !$c->stash->{body} ) { add_row( $c, $problem, 0, \%problems, \@pins ); next; @@ -531,6 +555,18 @@ sub stash_report_filter_status : Private { $filter_status{unshortlisted} = 1; } + if ($c->user and ($c->user->is_superuser or ( + $c->stash->{body} and $c->user->belongs_to_body($c->stash->{body}->id) + ))) { + $c->stash->{filter_states} = $c->cobrand->state_groups_inspect; + foreach my $state (FixMyStreet::DB::Result::Problem->visible_states()) { + if ($status{$state}) { + $filter_problem_states{$state} = 1; + $filter_status{$state} = 1; + } + } + } + if (keys %filter_problem_states == 0) { my $s = FixMyStreet::DB::Result::Problem->open_states(); %filter_problem_states = (%filter_problem_states, %$s); diff --git a/perllib/FixMyStreet/App/Controller/Root.pm b/perllib/FixMyStreet/App/Controller/Root.pm index 4f098dfc3..7f70623ae 100644 --- a/perllib/FixMyStreet/App/Controller/Root.pm +++ b/perllib/FixMyStreet/App/Controller/Root.pm @@ -16,6 +16,18 @@ FixMyStreet::App::Controller::Root - Root Controller for FixMyStreet::App =head1 METHODS +=head2 begin + +Any pre-flight checking for all requests + +=cut +sub begin : Private { + my ( $self, $c ) = @_; + + $c->forward( 'check_login_required' ); +} + + =head2 auto Set up general things for this instance @@ -58,6 +70,11 @@ sub index : Path : Args(0) { return; } + if ($c->stash->{homepage_template}) { + $c->stash->{template} = $c->stash->{homepage_template}; + $c->detach; + } + $c->forward('/auth/get_csrf_token'); } @@ -125,6 +142,27 @@ sub page_error : Private { $c->response->status($code); } +sub check_login_required : Private { + my ($self, $c) = @_; + + return if $c->user_exists || !FixMyStreet->config('LOGIN_REQUIRED'); + + # Whitelisted URL patterns are allowed without login + my $whitelist = qr{ + ^auth(/|$) + | ^js/translation_strings\.(.*?)\.js + | ^[PACQM]/ # various tokens that log the user in + }x; + return if $c->request->path =~ $whitelist; + + # Blacklisted URLs immediately 404 + # This is primarily to work around a Safari bug where the appcache + # URL is requested in an infinite loop if it returns a 302 redirect. + $c->detach('/page_error_404_not_found', []) if $c->request->path =~ /^offline/; + + $c->detach( '/auth/redirect' ); +} + =head2 end Attempt to render a view, if needed. diff --git a/perllib/FixMyStreet/App/Controller/Rss.pm b/perllib/FixMyStreet/App/Controller/Rss.pm index 28f1aba43..3497ad0e1 100755 --- a/perllib/FixMyStreet/App/Controller/Rss.pm +++ b/perllib/FixMyStreet/App/Controller/Rss.pm @@ -3,6 +3,7 @@ package FixMyStreet::App::Controller::Rss; use Moose; use namespace::autoclean; use POSIX qw(strftime); +use HTML::Entities; use URI::Escape; use XML::RSS; @@ -11,8 +12,7 @@ use FixMyStreet::App::Model::PhotoSet; use FixMyStreet::Gaze; use mySociety::Locale; use mySociety::MaPit; -use mySociety::Sundries qw(ordinal); -use mySociety::Web qw(ent); +use Lingua::EN::Inflect qw(ORD); BEGIN { extends 'Catalyst::Controller'; } @@ -250,7 +250,7 @@ sub add_row : Private { }; $row->{created} = strftime("%e %B", $6, $5, $4, $3, $2-1, $1-1900, -1, -1, 0); $row->{created} =~ s/^\s+//; - $row->{created} =~ s/^(\d+)/ordinal($1)/e if $c->stash->{lang_code} eq 'en-gb'; + $row->{created} =~ s/^(\d+)/ORD($1)/e if $c->stash->{lang_code} eq 'en-gb'; } if ($row->{confirmed}) { $row->{confirmed} =~ /^(\d\d\d\d)-(\d\d)-(\d\d) (\d\d):(\d\d):(\d\d)/; @@ -259,7 +259,7 @@ sub add_row : Private { }; $row->{confirmed} = strftime("%e %B", $6, $5, $4, $3, $2-1, $1-1900, -1, -1, 0); $row->{confirmed} =~ s/^\s+//; - $row->{confirmed} =~ s/^(\d+)/ordinal($1)/e if $c->stash->{lang_code} eq 'en-gb'; + $row->{confirmed} =~ s/^(\d+)/ORD($1)/e if $c->stash->{lang_code} eq 'en-gb'; } (my $title = _($alert_type->item_title)) =~ s/\{\{(.*?)}}/$row->{$1}/g; @@ -270,13 +270,13 @@ sub add_row : Private { my $url = $base_url . $link; my %item = ( - title => ent($title), + title => encode_entities($title), link => $url, guid => $url, - description => ent(ent($desc)) # Yes, double-encoded, really. + description => encode_entities(encode_entities($desc)) # Yes, double-encoded, really. ); $item{pubDate} = $pubDate if $pubDate; - $item{category} = ent($row->{category}) if $row->{category}; + $item{category} = encode_entities($row->{category}) if $row->{category}; if ($c->cobrand->allow_photo_display($row) && $row->{photo}) { # Bit yucky as we don't have full objects here @@ -285,16 +285,16 @@ sub add_row : Private { my ($hash, $format) = split /\./, $first_fn; my $cachebust = substr($hash, 0, 8); my $key = $alert_type->item_table eq 'comment' ? 'c/' : ''; - $item{description} .= ent("\n<br><img src=\"". $base_url . "/photo/$key$row->{id}.0.$format?$cachebust\">"); + $item{description} .= encode_entities("\n<br><img src=\"". $base_url . "/photo/$key$row->{id}.0.$format?$cachebust\">"); } if ( $row->{used_map} ) { - my $address = $c->cobrand->find_closest_address_for_rss( $row->{latitude}, $row->{longitude}, $row ); - $item{description} .= ent("\n<br>$address") if $address; + my $address = $c->cobrand->find_closest_address_for_rss($row); + $item{description} .= encode_entities("\n<br>$address") if $address; } my $recipient_name = $c->cobrand->contact_name; - $item{description} .= ent("\n<br><a href='$url'>" . + $item{description} .= encode_entities("\n<br><a href='$url'>" . sprintf(_("Report on %s"), $recipient_name) . "</a>"); if ($row->{latitude} || $row->{longitude}) { @@ -329,9 +329,9 @@ sub add_parameters : Private { (my $desc = _($alert_type->head_description)) =~ s/\{\{(.*?)}}/$row->{$1}/g; $c->stash->{rss}->channel( - title => ent($title), + title => encode_entities($title), link => $c->uri_for($link) . ($c->stash->{qs} || ''), - description => ent($desc), + description => encode_entities($desc), language => 'en-gb', ); } |