aboutsummaryrefslogtreecommitdiffstats
path: root/perllib/FixMyStreet/App/Controller/Admin/Bodies.pm
diff options
context:
space:
mode:
Diffstat (limited to 'perllib/FixMyStreet/App/Controller/Admin/Bodies.pm')
-rw-r--r--perllib/FixMyStreet/App/Controller/Admin/Bodies.pm475
1 files changed, 475 insertions, 0 deletions
diff --git a/perllib/FixMyStreet/App/Controller/Admin/Bodies.pm b/perllib/FixMyStreet/App/Controller/Admin/Bodies.pm
new file mode 100644
index 000000000..8d3f0bb0d
--- /dev/null
+++ b/perllib/FixMyStreet/App/Controller/Admin/Bodies.pm
@@ -0,0 +1,475 @@
+package FixMyStreet::App::Controller::Admin::Bodies;
+use Moose;
+use namespace::autoclean;
+
+BEGIN { extends 'Catalyst::Controller'; }
+
+use POSIX qw(strcoll);
+use mySociety::EmailUtil qw(is_valid_email_list);
+use FixMyStreet::SendReport;
+
+=head1 NAME
+
+FixMyStreet::App::Controller::Admin::Bodies - Catalyst Controller
+
+=head1 DESCRIPTION
+
+Admin pages
+
+=head1 METHODS
+
+=cut
+
+sub index : Path : Args(0) {
+ my ( $self, $c ) = @_;
+
+ if (my $body_id = $c->get_param('body')) {
+ return $c->res->redirect( $c->uri_for( 'body', $body_id ) );
+ }
+
+ if (!$c->user->is_superuser && $c->user->from_body && $c->cobrand->moniker ne 'zurich') {
+ return $c->res->redirect( $c->uri_for( 'body', $c->user->from_body->id ) );
+ }
+
+ $c->forward( '/auth/get_csrf_token' );
+
+ my $edit_activity = $c->model('DB::ContactsHistory')->search(
+ undef,
+ {
+ select => [ 'editor', { count => 'contacts_history_id', -as => 'c' } ],
+ as => [ 'editor', 'c' ],
+ group_by => ['editor'],
+ order_by => { -desc => 'c' }
+ }
+ );
+
+ $c->stash->{edit_activity} = $edit_activity;
+
+ $c->forward( '/admin/fetch_languages' );
+ $c->forward( 'fetch_translations' );
+
+ my $posted = $c->get_param('posted') || '';
+ if ( $posted eq 'body' ) {
+ $c->forward('check_for_super_user');
+ $c->forward('/auth/check_csrf_token');
+
+ my $values = $c->forward('body_params');
+ unless ( keys %{$c->stash->{body_errors}} ) {
+ my $body = $c->model('DB::Body')->create( $values->{params} );
+ if ($values->{extras}) {
+ $body->set_extra_metadata( $_ => $values->{extras}->{$_} )
+ for keys %{$values->{extras}};
+ $body->update;
+ }
+ my @area_ids = $c->get_param_list('area_ids');
+ foreach (@area_ids) {
+ $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');
+ }
+ }
+
+ $c->forward( '/admin/fetch_all_bodies' );
+
+ my $contacts = $c->model('DB::Contact')->search(
+ undef,
+ {
+ 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'
+ }
+ );
+
+ my %council_info = map { $_->{body_id} => $_ } $contacts->all;
+
+ $c->stash->{counts} = \%council_info;
+
+ $c->forward( 'body_form_dropdowns' );
+
+ return 1;
+}
+
+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;
+ $c->stash->{body} = $body;
+
+ if ($body->body_areas->first) {
+ my $example_postcode = mySociety::MaPit::call('area/example_postcode', $body->body_areas->first->area_id);
+ if ($example_postcode && ! ref $example_postcode) {
+ $c->stash->{example_pc} = $example_postcode;
+ }
+ }
+}
+
+sub edit : 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( '/admin/fetch_all_bodies' );
+ $c->forward( 'body_form_dropdowns' );
+ $c->forward('/admin/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');
+
+ # don't set this last as fetch_contacts might over-ride it
+ # to display email addresses as text
+ $c->stash->{template} = 'admin/bodies/body.html';
+ $c->forward('/admin/fetch_contacts');
+
+ return 1;
+}
+
+sub category : Chained('body') : PathPart('') {
+ my ( $self, $c, @category ) = @_;
+ my $category = join( '/', @category );
+
+ $c->forward( '/auth/get_csrf_token' );
+
+ 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('/admin/fetch_languages');
+ $c->forward('fetch_translations');
+
+ my $history = $c->model('DB::ContactsHistory')->search(
+ {
+ 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:://; $_ } sort keys %{ FixMyStreet::SendReport->get_senders };
+ $c->stash->{send_methods} = \@methods;
+
+ return 1;
+}
+
+sub body_form_dropdowns : Private {
+ my ( $self, $c ) = @_;
+
+ my $areas;
+ my $whitelist = $c->config->{MAPIT_ID_WHITELIST};
+
+ if ( $whitelist && ref $whitelist eq 'ARRAY' && @$whitelist ) {
+ $areas = mySociety::MaPit::call('areas', $whitelist);
+ } else {
+ $areas = mySociety::MaPit::call('areas', $c->cobrand->area_types);
+ }
+
+ # Some cobrands may want to add extra areas at runtime beyond those
+ # available via MAPIT_WHITELIST or MAPIT_TYPES. This can be used for,
+ # e.g., parish councils on a particular council cobrand.
+ $areas = $c->cobrand->call_hook("add_extra_areas" => $areas) || $areas;
+
+ $c->stash->{areas} = [ sort { strcoll($a->{name}, $b->{name}) } values %$areas ];
+
+ my @methods = map { $_ =~ s/FixMyStreet::SendReport:://; $_ } sort keys %{ FixMyStreet::SendReport->get_senders };
+ $c->stash->{send_methods} = \@methods;
+}
+
+sub check_for_super_user : Private {
+ my ( $self, $c ) = @_;
+
+ my $superuser = $c->user->is_superuser;
+ # Zurich currently has its own way of defining superusers
+ $superuser ||= $c->cobrand->moniker eq 'zurich' && $c->stash->{admin_type} eq 'super';
+
+ unless ( $superuser ) {
+ $c->detach('/page_error_403_access_denied', []);
+ }
+}
+
+sub update_contacts : Private {
+ my ( $self, $c ) = @_;
+
+ my $posted = $c->get_param('posted');
+ my $editor = $c->forward('/admin/get_user');
+
+ if ( $posted eq 'new' ) {
+ $c->forward('/auth/check_csrf_token');
+
+ my %errors;
+
+ my $category = $self->trim( $c->get_param('category') );
+ $errors{category} = _("Please choose a category") unless $category;
+ $errors{note} = _('Please enter a message') unless $c->get_param('note');
+
+ my $contact = $c->model('DB::Contact')->find_or_new(
+ {
+ body_id => $c->stash->{body_id},
+ category => $category,
+ }
+ );
+
+ my $email = $c->get_param('email');
+ $email =~ s/\s+//g;
+ my $send_method = $c->get_param('send_method') || $contact->send_method || $contact->body->send_method || "";
+ unless ( $send_method eq 'Open311' ) {
+ $errors{email} = _('Please enter a valid email') unless is_valid_email_list($email) || $email eq 'REFUSED';
+ }
+
+ $contact->email( $email );
+ $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' );
+ $contact->editor( $editor );
+ $contact->endpoint( $c->get_param('endpoint') );
+ $contact->jurisdiction( $c->get_param('jurisdiction') );
+ $contact->api_key( $c->get_param('api_key') );
+ $contact->send_method( $c->get_param('send_method') );
+
+ # Set flags in extra to the appropriate values
+ if ( $c->get_param('photo_required') ) {
+ $contact->set_extra_metadata_if_undefined( photo_required => 1 );
+ }
+ else {
+ $contact->unset_extra_metadata( 'photo_required' );
+ }
+ if ( $c->get_param('inspection_required') ) {
+ $contact->set_extra_metadata( inspection_required => 1 );
+ }
+ else {
+ $contact->unset_extra_metadata( 'inspection_required' );
+ }
+ if ( $c->get_param('reputation_threshold') ) {
+ $contact->set_extra_metadata( reputation_threshold => int($c->get_param('reputation_threshold')) );
+ }
+ if ( my $group = $c->get_param('group') ) {
+ $contact->set_extra_metadata( group => $group );
+ } else {
+ $contact->unset_extra_metadata( 'group' );
+ }
+
+
+ $c->forward('/admin/update_extra_fields', [ $contact ]);
+ $c->forward('contact_cobrand_extra_fields', [ $contact ]);
+
+ if ( %errors ) {
+ $c->stash->{updated} = _('Please correct the errors below');
+ $c->stash->{contact} = $contact;
+ $c->stash->{errors} = \%errors;
+ } elsif ( $contact->in_storage ) {
+ $c->stash->{updated} = _('Values updated');
+
+ # NB: History is automatically stored by a trigger in the database
+ $contact->update;
+ } else {
+ $c->stash->{updated} = _('New category contact added');
+ $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');
+
+ my @categories = $c->get_param_list('confirmed');
+
+ my $contacts = $c->model('DB::Contact')->search(
+ {
+ body_id => $c->stash->{body_id},
+ category => { -in => \@categories },
+ }
+ );
+
+ $contacts->update(
+ {
+ state => 'confirmed',
+ whenedited => \'current_timestamp',
+ note => 'Confirmed',
+ editor => $editor,
+ }
+ );
+
+ $c->stash->{updated} = _('Values updated');
+ } elsif ( $posted eq 'body' ) {
+ $c->forward('check_for_super_user');
+ $c->forward('/auth/check_csrf_token');
+
+ my $values = $c->forward( 'body_params' );
+ unless ( keys %{$c->stash->{body_errors}} ) {
+ $c->stash->{body}->update( $values->{params} );
+ if ($values->{extras}) {
+ $c->stash->{body}->set_extra_metadata( $_ => $values->{extras}->{$_} )
+ for keys %{$values->{extras}};
+ $c->stash->{body}->update;
+ }
+ my @current = $c->stash->{body}->body_areas->all;
+ my %current = map { $_->area_id => 1 } @current;
+ my @area_ids = $c->get_param_list('area_ids');
+ foreach (@area_ids) {
+ $c->model('DB::BodyArea')->find_or_create( { body => $c->stash->{body}, area_id => $_ } );
+ delete $current{$_};
+ }
+ # 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 body_params : Private {
+ my ( $self, $c ) = @_;
+
+ my @fields = qw/name endpoint jurisdiction api_key send_method external_url/;
+ my %defaults = map { $_ => '' } @fields;
+ %defaults = ( %defaults,
+ send_comments => 0,
+ fetch_problems => 0,
+ convert_latlong => 0,
+ blank_updates_permitted => 0,
+ suppress_alerts => 0,
+ comment_user_id => undef,
+ send_extended_statuses => 0,
+ can_be_devolved => 0,
+ parent => undef,
+ deleted => 0,
+ );
+ my %params = map { $_ => $c->get_param($_) || $defaults{$_} } keys %defaults;
+ $c->forward('check_body_params', [ \%params ]);
+ my @extras = qw/fetch_all_problems/;
+ %defaults = map { $_ => '' } @extras;
+ my %extras = map { $_ => $c->get_param($_) || $defaults{$_} } @extras;
+ return { params => \%params, extras => \%extras };
+}
+
+sub check_body_params : Private {
+ my ( $self, $c, $params ) = @_;
+
+ $c->stash->{body_errors} ||= {};
+
+ unless ($params->{name}) {
+ $c->stash->{body_errors}->{name} = _('Please enter a name for this body');
+ }
+}
+
+sub contact_cobrand_extra_fields : Private {
+ my ( $self, $c, $contact ) = @_;
+
+ my $extra_fields = $c->cobrand->call_hook('contact_extra_fields');
+ foreach ( @$extra_fields ) {
+ $contact->set_extra_metadata( $_ => $c->get_param("extra[$_]") );
+ }
+}
+
+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 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 trim {
+ my $self = shift;
+ my $e = shift;
+ $e =~ s/^\s+//;
+ $e =~ s/\s+$//;
+ return $e;
+}
+
+=head1 AUTHOR
+
+Struan Donald
+
+=head1 LICENSE
+
+This library is free software. You can redistribute it and/or modify
+it under the same terms as Perl itself.
+
+=cut
+
+__PACKAGE__->meta->make_immutable;
+
+1;