aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--perllib/FixMyStreet/App/Controller/Admin.pm1088
-rw-r--r--perllib/FixMyStreet/App/Controller/Admin/Bodies.pm475
-rw-r--r--perllib/FixMyStreet/App/Controller/Admin/Users.pm684
-rw-r--r--t/app/controller/admin/permissions.t10
-rw-r--r--t/app/controller/admin/users.t38
-rw-r--r--t/cobrand/bathnes.t2
-rw-r--r--t/cobrand/bromley.t2
-rw-r--r--t/cobrand/zurich.t2
-rw-r--r--templates/web/base/admin/bodies/_translations.html (renamed from templates/web/base/admin/_translations.html)0
-rw-r--r--templates/web/base/admin/bodies/body.html (renamed from templates/web/base/admin/body.html)12
-rw-r--r--templates/web/base/admin/bodies/category.html (renamed from templates/web/base/admin/category_edit.html)2
-rw-r--r--templates/web/base/admin/bodies/contact-form.html (renamed from templates/web/base/admin/contact-form.html)6
-rw-r--r--templates/web/base/admin/bodies/edit-league.html (renamed from templates/web/base/admin/edit-league.html)0
-rw-r--r--templates/web/base/admin/bodies/form.html (renamed from templates/web/base/admin/body-form.html)6
-rw-r--r--templates/web/base/admin/bodies/index.html (renamed from templates/web/base/admin/bodies.html)8
-rw-r--r--templates/web/base/admin/bodies/open311-form-fields.html (renamed from templates/web/base/admin/open311-form-fields.html)0
-rw-r--r--templates/web/base/admin/users/alerts.html (renamed from templates/web/base/admin/user-alerts.html)0
-rw-r--r--templates/web/base/admin/users/edit.html (renamed from templates/web/base/admin/user_edit.html)4
-rw-r--r--templates/web/base/admin/users/form.html (renamed from templates/web/base/admin/user-form.html)4
-rw-r--r--templates/web/base/admin/users/import.html (renamed from templates/web/base/admin/user_import.html)6
-rw-r--r--templates/web/base/admin/users/index.html (renamed from templates/web/base/admin/users.html)10
-rw-r--r--templates/web/zurich/admin/bodies/body.html (renamed from templates/web/zurich/admin/body.html)6
-rw-r--r--templates/web/zurich/admin/bodies/contact-form.html (renamed from templates/web/zurich/admin/contact-form.html)2
-rw-r--r--templates/web/zurich/admin/bodies/edit-league.html (renamed from templates/web/zurich/admin/edit-league.html)0
-rw-r--r--templates/web/zurich/admin/bodies/form.html (renamed from templates/web/zurich/admin/body-form.html)2
25 files changed, 1226 insertions, 1143 deletions
diff --git a/perllib/FixMyStreet/App/Controller/Admin.pm b/perllib/FixMyStreet/App/Controller/Admin.pm
index 84651ad07..0c37eeb27 100644
--- a/perllib/FixMyStreet/App/Controller/Admin.pm
+++ b/perllib/FixMyStreet/App/Controller/Admin.pm
@@ -190,318 +190,6 @@ sub timeline : Path( 'timeline' ) : Args(0) {
return 1;
}
-sub bodies : Path('bodies') : 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( '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( '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_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('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('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 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 ) = @_;
-
- 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 fetch_contacts : Private {
my ( $self, $c ) = @_;
@@ -533,125 +221,6 @@ sub fetch_languages : Private {
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 lookup_body : Private {
- 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;
-}
-
-sub body : Chained('/') : PathPart('admin/body') : CaptureArgs(1) {
- my ( $self, $c, $body_id ) = @_;
-
- $c->forward('lookup_body');
- my $body = $c->stash->{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_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');
-
- # don't set this last as fetch_contacts might over-ride it
- # to display email addresses as text
- $c->stash->{template} = 'admin/body.html';
- $c->forward('fetch_contacts');
-
- return 1;
-}
-
-sub category : Chained('body') : PathPart('') {
- my ( $self, $c, @category ) = @_;
- my $category = join( '/', @category );
-
- $c->forward( '/auth/get_csrf_token' );
- $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 => $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 reports : Path('reports') {
my ( $self, $c ) = @_;
@@ -927,15 +496,15 @@ sub report_edit : Path('report_edit') : Args(1) {
$c->forward( 'log_edit', [ $id, 'problem', 'marked sent' ] );
}
elsif ( $c->get_param('flaguser') ) {
- $c->forward('flag_user');
+ $c->forward('users/flag');
$c->stash->{problem}->discard_changes;
}
elsif ( $c->get_param('removeuserflag') ) {
- $c->forward('remove_user_flag');
+ $c->forward('users/flag_remove');
$c->stash->{problem}->discard_changes;
}
elsif ( $c->get_param('banuser') ) {
- $c->forward('ban_user');
+ $c->forward('users/ban');
}
elsif ( $c->get_param('submit') ) {
$c->forward('/auth/check_csrf_token');
@@ -1292,50 +861,6 @@ sub load_template_body : Private {
or $c->detach( '/page_error_404_not_found', [] );
}
-sub users: Path('users') : Args(0) {
- my ( $self, $c ) = @_;
-
- if (my $search = $c->get_param('search')) {
- $search = $self->trim($search);
- $search =~ s/^<(.*)>$/$1/; # In case email wrapped in <...>
- $c->stash->{searched} = $search;
-
- my $isearch = '%' . $search . '%';
- my $search_n = 0;
- $search_n = int($search) if $search =~ /^\d+$/;
-
- my $users = $c->cobrand->users->search(
- {
- -or => [
- email => { ilike => $isearch },
- phone => { ilike => $isearch },
- name => { ilike => $isearch },
- from_body => $search_n,
- ]
- }
- );
- my @users = $users->all;
- $c->stash->{users} = [ @users ];
- $c->forward('add_flags', [ { email => { ilike => $isearch } } ]);
-
- } else {
- $c->forward('/auth/get_csrf_token');
- $c->forward('fetch_all_bodies');
- $c->cobrand->call_hook('admin_user_edit_extra_data');
-
-
- # Admin users by default
- my $users = $c->cobrand->users->search(
- { from_body => { '!=', undef } },
- { order_by => 'name' }
- );
- my @users = $users->all;
- $c->stash->{users} = \@users;
- }
-
- return 1;
-}
-
sub update_edit : Path('update_edit') : Args(1) {
my ( $self, $c, $id ) = @_;
@@ -1356,14 +881,14 @@ sub update_edit : Path('update_edit') : Args(1) {
$c->forward('check_username_for_abuse', [ $update->user ] );
if ( $c->get_param('banuser') ) {
- $c->forward('ban_user');
+ $c->forward('users/ban');
}
elsif ( $c->get_param('flaguser') ) {
- $c->forward('flag_user');
+ $c->forward('users/flag');
$c->stash->{update}->discard_changes;
}
elsif ( $c->get_param('removeuserflag') ) {
- $c->forward('remove_user_flag');
+ $c->forward('users/flag_remove');
$c->stash->{update}->discard_changes;
}
elsif ( $c->get_param('submit') ) {
@@ -1430,382 +955,6 @@ sub update_edit : Path('update_edit') : Args(1) {
return 1;
}
-sub phone_check : Private {
- my ($self, $c, $phone) = @_;
- my $parsed = FixMyStreet::SMS->parse_username($phone);
- if ($parsed->{phone} && $parsed->{may_be_mobile}) {
- return $parsed->{username};
- } elsif ($parsed->{phone}) {
- $c->stash->{field_errors}->{phone} = _('Please enter a mobile number');
- } else {
- $c->stash->{field_errors}->{phone} = _('Please check your phone number is correct');
- }
-}
-
-sub user_add : Path('user_edit') : Args(0) {
- my ( $self, $c ) = @_;
-
- $c->stash->{template} = 'admin/user_edit.html';
- $c->forward('/auth/get_csrf_token');
- $c->forward('fetch_all_bodies');
- $c->cobrand->call_hook('admin_user_edit_extra_data');
-
- return unless $c->get_param('submit');
-
- $c->forward('/auth/check_csrf_token');
-
- $c->stash->{field_errors} = {};
- my $email = lc $c->get_param('email');
- my $phone = $c->get_param('phone');
- my $email_v = $c->get_param('email_verified');
- my $phone_v = $c->get_param('phone_verified');
-
- if ($email && !is_valid_email($email)) {
- $c->stash->{field_errors}->{email} = _('Please enter a valid email');
- }
- unless ($c->get_param('name')) {
- $c->stash->{field_errors}->{name} = _('Please enter a name');
- }
-
- unless ($email || $phone) {
- $c->stash->{field_errors}->{username} = _('Please enter a valid email or phone number');
- }
- if (!$email_v && !$phone_v) {
- $c->stash->{field_errors}->{username} = _('Please verify at least one of email/phone');
- }
-
- if ($phone_v) {
- my $parsed_phone = $c->forward('phone_check', [ $phone ]);
- $phone = $parsed_phone if $parsed_phone;
- }
-
- my $existing_email = $email_v && $c->model('DB::User')->find( { email => $email } );
- my $existing_phone = $phone_v && $c->model('DB::User')->find( { phone => $phone } );
- if ($existing_email || $existing_phone) {
- $c->stash->{field_errors}->{username} = _('User already exists');
- }
-
- return if %{$c->stash->{field_errors}};
-
- my $user = $c->model('DB::User')->create( {
- name => $c->get_param('name'),
- email => $email ? $email : undef,
- email_verified => $email && $email_v ? 1 : 0,
- phone => $phone || undef,
- phone_verified => $phone && $phone_v ? 1 : 0,
- from_body => $c->get_param('body') || undef,
- flagged => $c->get_param('flagged') || 0,
- # Only superusers can create superusers
- is_superuser => ( $c->user->is_superuser && $c->get_param('is_superuser') ) || 0,
- } );
- $c->stash->{user} = $user;
- $c->forward('user_cobrand_extra_fields');
- $user->update;
-
- $c->forward( 'log_edit', [ $user->id, 'user', 'edit' ] );
-
- $c->flash->{status_message} = _("Updated!");
- $c->res->redirect( $c->uri_for( 'user_edit', $user->id ) );
-}
-
-sub user_edit : Path('user_edit') : Args(1) {
- my ( $self, $c, $id ) = @_;
-
- $c->forward('/auth/get_csrf_token');
-
- my $user = $c->cobrand->users->find( { id => $id } );
- $c->detach( '/page_error_404_not_found', [] ) unless $user;
-
- unless ( $c->user->has_body_permission_to('user_edit') || $c->cobrand->moniker eq 'zurich' ) {
- $c->detach('/page_error_403_access_denied', []);
- }
-
- $c->stash->{user} = $user;
- $c->forward( 'check_username_for_abuse', [ $user ] );
-
- if ( $user->from_body && $c->user->has_permission_to('user_manage_permissions', $user->from_body->id) ) {
- $c->stash->{available_permissions} = $c->cobrand->available_permissions;
- }
-
- $c->forward('fetch_all_bodies');
- $c->forward('fetch_body_areas', [ $user->from_body ]) if $user->from_body;
- $c->cobrand->call_hook('admin_user_edit_extra_data');
-
- if ( defined $c->flash->{status_message} ) {
- $c->stash->{status_message} =
- '<p><em>' . $c->flash->{status_message} . '</em></p>';
- }
-
- $c->forward('/auth/check_csrf_token') if $c->get_param('submit');
-
- if ( $c->get_param('submit') and $c->get_param('unban') ) {
- $c->forward('unban_user', [ $user ]);
- } elsif ( $c->get_param('submit') and $c->get_param('logout_everywhere') ) {
- $c->forward('user_logout_everywhere', [ $user ]);
- } elsif ( $c->get_param('submit') and $c->get_param('anon_everywhere') ) {
- $c->forward('user_anon_everywhere', [ $user ]);
- } elsif ( $c->get_param('submit') and $c->get_param('hide_everywhere') ) {
- $c->forward('user_hide_everywhere', [ $user ]);
- } elsif ( $c->get_param('submit') and $c->get_param('remove_account') ) {
- $c->forward('user_remove_account', [ $user ]);
- } elsif ( $c->get_param('submit') and $c->get_param('send_login_email') ) {
- my $email = lc $c->get_param('email');
- my %args = ( email => $email );
- $args{user_id} = $id if $user->email ne $email || !$user->email_verified;
- $c->forward('send_login_email', [ \%args ]);
- } elsif ( $c->get_param('update_alerts') ) {
- $c->forward('update_alerts');
- } elsif ( $c->get_param('submit') ) {
-
- my $edited = 0;
-
- my $name = $c->get_param('name');
- my $email = lc $c->get_param('email');
- my $phone = $c->get_param('phone');
- my $email_v = $c->get_param('email_verified') || 0;
- my $phone_v = $c->get_param('phone_verified') || 0;
-
- $c->stash->{field_errors} = {};
-
- unless ($email || $phone) {
- $c->stash->{field_errors}->{username} = _('Please enter a valid email or phone number');
- }
- if (!$email_v && !$phone_v) {
- $c->stash->{field_errors}->{username} = _('Please verify at least one of email/phone');
- }
- if ($email && !is_valid_email($email)) {
- $c->stash->{field_errors}->{email} = _('Please enter a valid email');
- }
-
- if ($phone_v) {
- my $parsed_phone = $c->forward('phone_check', [ $phone ]);
- $phone = $parsed_phone if $parsed_phone;
- }
-
- unless ($name) {
- $c->stash->{field_errors}->{name} = _('Please enter a name');
- }
-
- my $email_params = { email => $email, email_verified => 1, id => { '!=', $user->id } };
- my $phone_params = { phone => $phone, phone_verified => 1, id => { '!=', $user->id } };
- my $existing_email = $email_v && $c->model('DB::User')->search($email_params)->first;
- my $existing_phone = $phone_v && $c->model('DB::User')->search($phone_params)->first;
- my $existing_user = $existing_email || $existing_phone;
- my $existing_email_cobrand = $email_v && $c->cobrand->users->search($email_params)->first;
- my $existing_phone_cobrand = $phone_v && $c->cobrand->users->search($phone_params)->first;
- my $existing_user_cobrand = $existing_email_cobrand || $existing_phone_cobrand;
- if ($existing_phone_cobrand && $existing_email_cobrand && $existing_email_cobrand->id != $existing_phone_cobrand->id) {
- $c->stash->{field_errors}->{username} = _('User already exists');
- }
-
- return if %{$c->stash->{field_errors}};
-
- if ( ($user->email || "") ne $email ||
- $user->name ne $name ||
- ($user->phone || "") ne $phone ||
- ($user->from_body && $c->get_param('body') && $user->from_body->id ne $c->get_param('body')) ||
- (!$user->from_body && $c->get_param('body'))
- ) {
- $edited = 1;
- }
-
- if ($existing_user_cobrand) {
- $existing_user->adopt($user);
- $c->forward( 'log_edit', [ $id, 'user', 'merge' ] );
- return $c->res->redirect( $c->uri_for( 'user_edit', $existing_user->id ) );
- }
-
- $user->email($email) if !$existing_email;
- $user->phone($phone) if !$existing_phone;
- $user->email_verified( $email_v );
- $user->phone_verified( $phone_v );
- $user->name( $name );
-
- $user->flagged( $c->get_param('flagged') || 0 );
- # Only superusers can grant superuser status
- $user->is_superuser( ( $c->user->is_superuser && $c->get_param('is_superuser') ) || 0 );
- # Superusers can set from_body to any value, but other staff can only
- # set from_body to the same value as their own from_body.
- if ( $c->user->is_superuser || $c->cobrand->moniker eq 'zurich' ) {
- $user->from_body( $c->get_param('body') || undef );
- } elsif ( $c->user->has_body_permission_to('user_assign_body') ) {
- if ($c->get_param('body') && $c->get_param('body') eq $c->user->from_body->id ) {
- $user->from_body( $c->user->from_body );
- } else {
- $user->from_body( undef );
- }
- }
-
- $c->forward('user_cobrand_extra_fields');
-
- # Has the user's from_body changed since we fetched areas (if we ever did)?
- # If so, we need to re-fetch areas so the UI is up to date.
- if ( $user->from_body && $user->from_body->id ne $c->stash->{fetched_areas_body_id} ) {
- $c->forward('fetch_body_areas', [ $user->from_body ]);
- }
-
- if (!$user->from_body) {
- # Non-staff users aren't allowed any permissions or to be in an area
- $user->admin_user_body_permissions->delete;
- $user->area_ids(undef);
- delete $c->stash->{areas};
- delete $c->stash->{fetched_areas_body_id};
- } elsif ($c->stash->{available_permissions}) {
- my @all_permissions = map { keys %$_ } values %{ $c->stash->{available_permissions} };
- my @user_permissions = grep { $c->get_param("permissions[$_]") ? 1 : undef } @all_permissions;
- $user->admin_user_body_permissions->search({
- body_id => $user->from_body->id,
- permission_type => { '!=' => \@user_permissions },
- })->delete;
- foreach my $permission_type (@user_permissions) {
- $user->user_body_permissions->find_or_create({
- body_id => $user->from_body->id,
- permission_type => $permission_type,
- });
- }
- }
-
- if ( $user->from_body && $c->user->has_permission_to('user_assign_areas', $user->from_body->id) ) {
- my %valid_areas = map { $_->{id} => 1 } @{ $c->stash->{areas} };
- my @area_ids = grep { $valid_areas{$_} } $c->get_param_list('area_ids');
- $user->area_ids( @area_ids ? \@area_ids : undef );
- }
-
- # Handle 'trusted' flag(s)
- my @trusted_bodies = $c->get_param_list('trusted_bodies');
- if ( $c->user->is_superuser ) {
- $user->user_body_permissions->search({
- body_id => { '!=' => \@trusted_bodies },
- permission_type => 'trusted',
- })->delete;
- foreach my $body_id (@trusted_bodies) {
- $user->user_body_permissions->find_or_create({
- body_id => $body_id,
- permission_type => 'trusted',
- });
- }
- } elsif ( $c->user->from_body ) {
- my %trusted = map { $_ => 1 } @trusted_bodies;
- my $body_id = $c->user->from_body->id;
- if ( $trusted{$body_id} ) {
- $user->user_body_permissions->find_or_create({
- body_id => $body_id,
- permission_type => 'trusted',
- });
- } else {
- $user->user_body_permissions->search({
- body_id => $body_id,
- permission_type => 'trusted',
- })->delete;
- }
- }
-
- # Update the categories this user operates in
- if ( $user->from_body ) {
- $c->stash->{body} = $user->from_body;
- $c->forward('fetch_contacts');
- my @live_contacts = $c->stash->{live_contacts}->all;
- my @live_contact_ids = map { $_->id } @live_contacts;
- my @new_contact_ids = grep { $c->get_param("contacts[$_]") } @live_contact_ids;
- $user->set_extra_metadata('categories', \@new_contact_ids);
- }
-
- $user->update;
- if ($edited) {
- $c->forward( 'log_edit', [ $id, 'user', 'edit' ] );
- }
- $c->flash->{status_message} = _("Updated!");
- return $c->res->redirect( $c->uri_for( 'user_edit', $user->id ) );
- }
-
- if ( $user->from_body ) {
- unless ( $c->stash->{live_contacts} ) {
- $c->stash->{body} = $user->from_body;
- $c->forward('fetch_contacts');
- }
- my @contacts = @{$user->get_extra_metadata('categories') || []};
- my %active_contacts = map { $_ => 1 } @contacts;
- my @live_contacts = $c->stash->{live_contacts}->all;
- my @all_contacts = map { {
- id => $_->id,
- category => $_->category,
- active => $active_contacts{$_->id},
- } } @live_contacts;
- $c->stash->{contacts} = \@all_contacts;
- }
-
- # this goes after in case we've delete any alerts
- unless ( $c->cobrand->moniker eq 'zurich' ) {
- $c->forward('user_alert_details');
- }
-
- return 1;
-}
-
-sub user_import : Path('user_import') {
- my ( $self, $c, $id ) = @_;
-
- $c->forward('/auth/get_csrf_token');
- return unless $c->user_exists && $c->user->is_superuser;
-
- if ($c->req->method eq 'POST') {
- $c->forward('/auth/check_csrf_token');
- $c->stash->{new_users} = [];
- $c->stash->{existing_users} = [];
-
- my @all_permissions = map { keys %$_ } values %{ $c->cobrand->available_permissions };
- my %available_permissions = map { $_ => 1 } @all_permissions;
-
- my $csv = Text::CSV->new({ binary => 1});
- my $fh = $c->req->upload('csvfile')->fh;
- $csv->getline($fh); # discard the header
- while (my $row = $csv->getline($fh)) {
- my ($name, $email, $from_body, $permissions) = @$row;
- $email = lc Utils::trim_text($email);
- my @permissions = split(/:/, $permissions);
-
- my $user = FixMyStreet::DB->resultset("User")->find_or_new({ email => $email, email_verified => 1 });
- if ($user->in_storage) {
- push @{$c->stash->{existing_users}}, $user;
- next;
- }
-
- $user->name($name);
- $user->from_body($from_body || undef);
- $user->update_or_insert;
-
- my @user_permissions = grep { $available_permissions{$_} } @permissions;
- foreach my $permission_type (@user_permissions) {
- $user->user_body_permissions->find_or_create({
- body_id => $user->from_body->id,
- permission_type => $permission_type,
- });
- }
-
- push @{$c->stash->{new_users}}, $user;
- }
-
- }
-}
-
-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 user_cobrand_extra_fields : Private {
- my ( $self, $c ) = @_;
-
- my @extra_fields = @{ $c->cobrand->call_hook('user_extra_fields') || [] };
- foreach ( @extra_fields ) {
- $c->stash->{user}->set_extra_metadata( $_ => $c->get_param("extra[$_]") );
- }
-}
-
sub add_flags : Private {
my ( $self, $c, $search ) = @_;
@@ -1873,28 +1022,6 @@ sub get_user : Private {
return $user;
}
-sub user_alert_details : Private {
- my ( $self, $c ) = @_;
-
- my @alerts = $c->stash->{user}->alerts({}, { prefetch => 'alert_type' })->all;
- $c->stash->{alerts} = \@alerts;
-
- my @wards;
-
- for my $alert (@alerts) {
- if ($alert->alert_type->ref eq 'ward_problems') {
- push @wards, $alert->parameter2;
- }
- }
-
- if (@wards) {
- $c->stash->{alert_areas} = mySociety::MaPit::call('areas', join(',', @wards) );
- }
-
- my %body_names = map { $_->{id} => $_->{name} } @{ $c->stash->{bodies} };
- $c->stash->{body_names} = \%body_names;
-}
-
=item log_edit
$c->forward( 'log_edit', [ $object_id, $object_type, $action_performed ] );
@@ -1926,209 +1053,6 @@ sub log_edit : Private {
)->insert();
}
-=head2 ban_user
-
-Add the user's email address/phone number to the abuse table if they are not
-already in there and sets status_message accordingly.
-
-=cut
-
-sub ban_user : Private {
- my ( $self, $c ) = @_;
-
- my $user;
- if ($c->stash->{problem}) {
- $user = $c->stash->{problem}->user;
- } elsif ($c->stash->{update}) {
- $user = $c->stash->{update}->user;
- }
- return unless $user;
-
- if ($user->email_verified && $user->email) {
- my $abuse = $c->model('DB::Abuse')->find_or_new({ email => $user->email });
- if ( $abuse->in_storage ) {
- $c->stash->{status_message} = _('User already in abuse list');
- } else {
- $abuse->insert;
- $c->stash->{status_message} = _('User added to abuse list');
- }
- $c->stash->{username_in_abuse} = 1;
- }
- if ($user->phone_verified && $user->phone) {
- my $abuse = $c->model('DB::Abuse')->find_or_new({ email => $user->phone });
- if ( $abuse->in_storage ) {
- $c->stash->{status_message} = _('User already in abuse list');
- } else {
- $abuse->insert;
- $c->stash->{status_message} = _('User added to abuse list');
- }
- $c->stash->{username_in_abuse} = 1;
- }
- return 1;
-}
-
-sub update_alerts : Private {
- my ($self, $c) = @_;
-
- my $changes;
- for my $alert ( $c->stash->{user}->alerts ) {
- my $edit_option = $c->get_param('edit_alert[' . $alert->id . ']');
- next unless $edit_option;
- $changes = 1;
- if ( $edit_option eq 'delete' ) {
- $alert->delete;
- } elsif ( $edit_option eq 'disable' ) {
- $alert->disable;
- } elsif ( $edit_option eq 'enable' ) {
- $alert->confirm;
- }
- }
- $c->flash->{status_message} = _("Updated!") if $changes;
-}
-
-sub user_logout_everywhere : Private {
- my ( $self, $c, $user ) = @_;
- my $sessions = $user->get_extra_metadata('sessions');
- foreach (grep { $_ ne $c->sessionid } @$sessions) {
- $c->delete_session_data("session:$_");
- }
- $c->stash->{status_message} = _('That user has been logged out.');
-}
-
-sub user_anon_everywhere : Private {
- my ( $self, $c, $user ) = @_;
- $user->problems->update({anonymous => 1});
- $user->comments->update({anonymous => 1});
- $c->stash->{status_message} = _('That user has been made anonymous on all reports and updates.');
-}
-
-sub user_hide_everywhere : Private {
- my ( $self, $c, $user ) = @_;
- my $problems = $user->problems->search({ state => { '!=' => 'hidden' } });
- while (my $problem = $problems->next) {
- $problem->get_photoset->delete_cached;
- $problem->update({ state => 'hidden' });
- }
- my $updates = $user->comments->search({ state => { '!=' => 'hidden' } });
- while (my $update = $updates->next) {
- $update->hide;
- }
- $c->stash->{status_message} = _('That user’s reports and updates have been hidden.');
-}
-
-sub send_login_email : Private {
- my ( $self, $c, $args ) = @_;
-
- my $token_data = {
- email => $args->{email},
- };
-
- $token_data->{old_user_id} = $args->{user_id} if $args->{user_id};
- $token_data->{name} = $args->{name} if $args->{name};
-
- my $token_obj = $c->model('DB::Token')->create({
- scope => 'email_sign_in',
- data => $token_data,
- });
-
- $c->stash->{token} = $token_obj->token;
- my $template = 'login.txt';
-
- # do not use relative URIs in the email, obvs.
- $c->uri_disposition('absolute');
- $c->send_email( $template, { to => $args->{email} } );
-
- $c->stash->{status_message} = _('The user has been sent a login email');
-}
-
-# Anonymize and remove name from all problems/updates, disable all alerts.
-# Remove their account's email address, phone number, password, etc.
-sub user_remove_account : Private {
- my ( $self, $c, $user ) = @_;
- $c->forward('user_logout_everywhere', [ $user ]);
- $user->anonymize_account;
- $c->stash->{status_message} = _('That user’s personal details have been removed.');
-}
-
-sub unban_user : Private {
- my ( $self, $c, $user ) = @_;
-
- my @username;
- if ($user->email_verified && $user->email) {
- push @username, $user->email;
- }
- if ($user->phone_verified && $user->phone) {
- push @username, $user->phone;
- }
- if (@username) {
- my $abuse = $c->model('DB::Abuse')->search({ email => \@username });
- if ( $abuse ) {
- $abuse->delete;
- $c->stash->{status_message} = _('user removed from abuse list');
- } else {
- $c->stash->{status_message} = _('user not in abuse list');
- }
- $c->stash->{username_in_abuse} = 0;
- }
-}
-
-=head2 flag_user
-
-Sets the flag on a user
-
-=cut
-
-sub flag_user : Private {
- my ( $self, $c ) = @_;
-
- my $user;
- if ($c->stash->{problem}) {
- $user = $c->stash->{problem}->user;
- } elsif ($c->stash->{update}) {
- $user = $c->stash->{update}->user;
- }
-
- if ( !$user ) {
- $c->stash->{status_message} = _('Could not find user');
- } else {
- $user->flagged(1);
- $user->update;
- $c->stash->{status_message} = _('User flagged');
- }
-
- $c->stash->{user_flagged} = 1;
-
- return 1;
-}
-
-=head2 remove_user_flag
-
-Remove the flag on a user
-
-=cut
-
-sub remove_user_flag : Private {
- my ( $self, $c ) = @_;
-
- my $user;
- if ($c->stash->{problem}) {
- $user = $c->stash->{problem}->user;
- } elsif ($c->stash->{update}) {
- $user = $c->stash->{update}->user;
- }
-
- if ( !$user ) {
- $c->stash->{status_message} = _('Could not find user');
- } else {
- $user->flagged(0);
- $user->update;
- $c->stash->{status_message} = _('User flag removed');
- }
-
- return 1;
-}
-
-
=head2 check_username_for_abuse
$c->forward('check_username_for_abuse', [ $user ] );
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;
diff --git a/perllib/FixMyStreet/App/Controller/Admin/Users.pm b/perllib/FixMyStreet/App/Controller/Admin/Users.pm
new file mode 100644
index 000000000..11a6d9962
--- /dev/null
+++ b/perllib/FixMyStreet/App/Controller/Admin/Users.pm
@@ -0,0 +1,684 @@
+package FixMyStreet::App::Controller::Admin::Users;
+use Moose;
+use namespace::autoclean;
+
+BEGIN { extends 'Catalyst::Controller'; }
+
+use POSIX qw(strcoll);
+use mySociety::EmailUtil qw(is_valid_email);
+use Text::CSV;
+
+use FixMyStreet::SMS;
+use Utils;
+
+=head1 NAME
+
+FixMyStreet::App::Controller::Admin::Users - Catalyst Controller
+
+=head1 DESCRIPTION
+
+Admin pages for editing users
+
+=head1 METHODS
+
+=cut
+
+sub index :Path : Args(0) {
+ my ( $self, $c ) = @_;
+
+ $c->detach('add') if $c->req->method eq 'POST'; # Add a user
+
+ if (my $search = $c->get_param('search')) {
+ $search = $self->trim($search);
+ $search =~ s/^<(.*)>$/$1/; # In case email wrapped in <...>
+ $c->stash->{searched} = $search;
+
+ my $isearch = '%' . $search . '%';
+ my $search_n = 0;
+ $search_n = int($search) if $search =~ /^\d+$/;
+
+ my $users = $c->cobrand->users->search(
+ {
+ -or => [
+ email => { ilike => $isearch },
+ phone => { ilike => $isearch },
+ name => { ilike => $isearch },
+ from_body => $search_n,
+ ]
+ }
+ );
+ my @users = $users->all;
+ $c->stash->{users} = [ @users ];
+ $c->forward('/admin/add_flags', [ { email => { ilike => $isearch } } ]);
+
+ } else {
+ $c->forward('/auth/get_csrf_token');
+ $c->forward('/admin/fetch_all_bodies');
+ $c->cobrand->call_hook('admin_user_edit_extra_data');
+
+
+ # Admin users by default
+ my $users = $c->cobrand->users->search(
+ { from_body => { '!=', undef } },
+ { order_by => 'name' }
+ );
+ my @users = $users->all;
+ $c->stash->{users} = \@users;
+ }
+
+ return 1;
+}
+
+sub add : Local : Args(0) {
+ my ( $self, $c ) = @_;
+
+ $c->stash->{template} = 'admin/users/edit.html';
+ $c->forward('/auth/get_csrf_token');
+ $c->forward('/admin/fetch_all_bodies');
+ $c->cobrand->call_hook('admin_user_edit_extra_data');
+
+ return unless $c->get_param('submit');
+
+ $c->forward('/auth/check_csrf_token');
+
+ $c->stash->{field_errors} = {};
+ my $email = lc $c->get_param('email');
+ my $phone = $c->get_param('phone');
+ my $email_v = $c->get_param('email_verified');
+ my $phone_v = $c->get_param('phone_verified');
+
+ if ($email && !is_valid_email($email)) {
+ $c->stash->{field_errors}->{email} = _('Please enter a valid email');
+ }
+ unless ($c->get_param('name')) {
+ $c->stash->{field_errors}->{name} = _('Please enter a name');
+ }
+
+ unless ($email || $phone) {
+ $c->stash->{field_errors}->{username} = _('Please enter a valid email or phone number');
+ }
+ if (!$email_v && !$phone_v) {
+ $c->stash->{field_errors}->{username} = _('Please verify at least one of email/phone');
+ }
+
+ if ($phone_v) {
+ my $parsed_phone = $c->forward('phone_check', [ $phone ]);
+ $phone = $parsed_phone if $parsed_phone;
+ }
+
+ my $existing_email = $email_v && $c->model('DB::User')->find( { email => $email } );
+ my $existing_phone = $phone_v && $c->model('DB::User')->find( { phone => $phone } );
+ if ($existing_email || $existing_phone) {
+ $c->stash->{field_errors}->{username} = _('User already exists');
+ }
+
+ return if %{$c->stash->{field_errors}};
+
+ my $user = $c->model('DB::User')->create( {
+ name => $c->get_param('name'),
+ email => $email ? $email : undef,
+ email_verified => $email && $email_v ? 1 : 0,
+ phone => $phone || undef,
+ phone_verified => $phone && $phone_v ? 1 : 0,
+ from_body => $c->get_param('body') || undef,
+ flagged => $c->get_param('flagged') || 0,
+ # Only superusers can create superusers
+ is_superuser => ( $c->user->is_superuser && $c->get_param('is_superuser') ) || 0,
+ } );
+ $c->stash->{user} = $user;
+ $c->forward('user_cobrand_extra_fields');
+ $user->update;
+
+ $c->forward( '/admin/log_edit', [ $user->id, 'user', 'edit' ] );
+
+ $c->flash->{status_message} = _("Updated!");
+ $c->res->redirect( $c->uri_for_action( 'admin/users/edit', $user->id ) );
+}
+
+sub edit : Path : Args(1) {
+ my ( $self, $c, $id ) = @_;
+
+ $c->forward('/auth/get_csrf_token');
+
+ my $user = $c->cobrand->users->find( { id => $id } );
+ $c->detach( '/page_error_404_not_found', [] ) unless $user;
+
+ unless ( $c->user->has_body_permission_to('user_edit') || $c->cobrand->moniker eq 'zurich' ) {
+ $c->detach('/page_error_403_access_denied', []);
+ }
+
+ $c->stash->{user} = $user;
+ $c->forward( '/admin/check_username_for_abuse', [ $user ] );
+
+ if ( $user->from_body && $c->user->has_permission_to('user_manage_permissions', $user->from_body->id) ) {
+ $c->stash->{available_permissions} = $c->cobrand->available_permissions;
+ }
+
+ $c->forward('/admin/fetch_all_bodies');
+ $c->forward('/admin/fetch_body_areas', [ $user->from_body ]) if $user->from_body;
+ $c->cobrand->call_hook('admin_user_edit_extra_data');
+
+ if ( defined $c->flash->{status_message} ) {
+ $c->stash->{status_message} =
+ '<p><em>' . $c->flash->{status_message} . '</em></p>';
+ }
+
+ $c->forward('/auth/check_csrf_token') if $c->get_param('submit');
+
+ if ( $c->get_param('submit') and $c->get_param('unban') ) {
+ $c->forward('unban', [ $user ]);
+ } elsif ( $c->get_param('submit') and $c->get_param('logout_everywhere') ) {
+ $c->forward('user_logout_everywhere', [ $user ]);
+ } elsif ( $c->get_param('submit') and $c->get_param('anon_everywhere') ) {
+ $c->forward('user_anon_everywhere', [ $user ]);
+ } elsif ( $c->get_param('submit') and $c->get_param('hide_everywhere') ) {
+ $c->forward('user_hide_everywhere', [ $user ]);
+ } elsif ( $c->get_param('submit') and $c->get_param('remove_account') ) {
+ $c->forward('user_remove_account', [ $user ]);
+ } elsif ( $c->get_param('submit') and $c->get_param('send_login_email') ) {
+ my $email = lc $c->get_param('email');
+ my %args = ( email => $email );
+ $args{user_id} = $id if $user->email ne $email || !$user->email_verified;
+ $c->forward('send_login_email', [ \%args ]);
+ } elsif ( $c->get_param('update_alerts') ) {
+ $c->forward('update_alerts');
+ } elsif ( $c->get_param('submit') ) {
+
+ my $edited = 0;
+
+ my $name = $c->get_param('name');
+ my $email = lc $c->get_param('email');
+ my $phone = $c->get_param('phone');
+ my $email_v = $c->get_param('email_verified') || 0;
+ my $phone_v = $c->get_param('phone_verified') || 0;
+
+ $c->stash->{field_errors} = {};
+
+ unless ($email || $phone) {
+ $c->stash->{field_errors}->{username} = _('Please enter a valid email or phone number');
+ }
+ if (!$email_v && !$phone_v) {
+ $c->stash->{field_errors}->{username} = _('Please verify at least one of email/phone');
+ }
+ if ($email && !is_valid_email($email)) {
+ $c->stash->{field_errors}->{email} = _('Please enter a valid email');
+ }
+
+ if ($phone_v) {
+ my $parsed_phone = $c->forward('phone_check', [ $phone ]);
+ $phone = $parsed_phone if $parsed_phone;
+ }
+
+ unless ($name) {
+ $c->stash->{field_errors}->{name} = _('Please enter a name');
+ }
+
+ my $email_params = { email => $email, email_verified => 1, id => { '!=', $user->id } };
+ my $phone_params = { phone => $phone, phone_verified => 1, id => { '!=', $user->id } };
+ my $existing_email = $email_v && $c->model('DB::User')->search($email_params)->first;
+ my $existing_phone = $phone_v && $c->model('DB::User')->search($phone_params)->first;
+ my $existing_user = $existing_email || $existing_phone;
+ my $existing_email_cobrand = $email_v && $c->cobrand->users->search($email_params)->first;
+ my $existing_phone_cobrand = $phone_v && $c->cobrand->users->search($phone_params)->first;
+ my $existing_user_cobrand = $existing_email_cobrand || $existing_phone_cobrand;
+ if ($existing_phone_cobrand && $existing_email_cobrand && $existing_email_cobrand->id != $existing_phone_cobrand->id) {
+ $c->stash->{field_errors}->{username} = _('User already exists');
+ }
+
+ return if %{$c->stash->{field_errors}};
+
+ if ( ($user->email || "") ne $email ||
+ $user->name ne $name ||
+ ($user->phone || "") ne $phone ||
+ ($user->from_body && $c->get_param('body') && $user->from_body->id ne $c->get_param('body')) ||
+ (!$user->from_body && $c->get_param('body'))
+ ) {
+ $edited = 1;
+ }
+
+ if ($existing_user_cobrand) {
+ $existing_user->adopt($user);
+ $c->forward( '/admin/log_edit', [ $id, 'user', 'merge' ] );
+ return $c->res->redirect( $c->uri_for_action( 'admin/users/edit', $existing_user->id ) );
+ }
+
+ $user->email($email) if !$existing_email;
+ $user->phone($phone) if !$existing_phone;
+ $user->email_verified( $email_v );
+ $user->phone_verified( $phone_v );
+ $user->name( $name );
+
+ $user->flagged( $c->get_param('flagged') || 0 );
+ # Only superusers can grant superuser status
+ $user->is_superuser( ( $c->user->is_superuser && $c->get_param('is_superuser') ) || 0 );
+ # Superusers can set from_body to any value, but other staff can only
+ # set from_body to the same value as their own from_body.
+ if ( $c->user->is_superuser || $c->cobrand->moniker eq 'zurich' ) {
+ $user->from_body( $c->get_param('body') || undef );
+ } elsif ( $c->user->has_body_permission_to('user_assign_body') ) {
+ if ($c->get_param('body') && $c->get_param('body') eq $c->user->from_body->id ) {
+ $user->from_body( $c->user->from_body );
+ } else {
+ $user->from_body( undef );
+ }
+ }
+
+ $c->forward('user_cobrand_extra_fields');
+
+ # Has the user's from_body changed since we fetched areas (if we ever did)?
+ # If so, we need to re-fetch areas so the UI is up to date.
+ if ( $user->from_body && $user->from_body->id ne $c->stash->{fetched_areas_body_id} ) {
+ $c->forward('/admin/fetch_body_areas', [ $user->from_body ]);
+ }
+
+ if (!$user->from_body) {
+ # Non-staff users aren't allowed any permissions or to be in an area
+ $user->admin_user_body_permissions->delete;
+ $user->area_ids(undef);
+ delete $c->stash->{areas};
+ delete $c->stash->{fetched_areas_body_id};
+ } elsif ($c->stash->{available_permissions}) {
+ my @all_permissions = map { keys %$_ } values %{ $c->stash->{available_permissions} };
+ my @user_permissions = grep { $c->get_param("permissions[$_]") ? 1 : undef } @all_permissions;
+ $user->admin_user_body_permissions->search({
+ body_id => $user->from_body->id,
+ permission_type => { '!=' => \@user_permissions },
+ })->delete;
+ foreach my $permission_type (@user_permissions) {
+ $user->user_body_permissions->find_or_create({
+ body_id => $user->from_body->id,
+ permission_type => $permission_type,
+ });
+ }
+ }
+
+ if ( $user->from_body && $c->user->has_permission_to('user_assign_areas', $user->from_body->id) ) {
+ my %valid_areas = map { $_->{id} => 1 } @{ $c->stash->{areas} };
+ my @area_ids = grep { $valid_areas{$_} } $c->get_param_list('area_ids');
+ $user->area_ids( @area_ids ? \@area_ids : undef );
+ }
+
+ # Handle 'trusted' flag(s)
+ my @trusted_bodies = $c->get_param_list('trusted_bodies');
+ if ( $c->user->is_superuser ) {
+ $user->user_body_permissions->search({
+ body_id => { '!=' => \@trusted_bodies },
+ permission_type => 'trusted',
+ })->delete;
+ foreach my $body_id (@trusted_bodies) {
+ $user->user_body_permissions->find_or_create({
+ body_id => $body_id,
+ permission_type => 'trusted',
+ });
+ }
+ } elsif ( $c->user->from_body ) {
+ my %trusted = map { $_ => 1 } @trusted_bodies;
+ my $body_id = $c->user->from_body->id;
+ if ( $trusted{$body_id} ) {
+ $user->user_body_permissions->find_or_create({
+ body_id => $body_id,
+ permission_type => 'trusted',
+ });
+ } else {
+ $user->user_body_permissions->search({
+ body_id => $body_id,
+ permission_type => 'trusted',
+ })->delete;
+ }
+ }
+
+ # Update the categories this user operates in
+ if ( $user->from_body ) {
+ $c->stash->{body} = $user->from_body;
+ $c->forward('/admin/fetch_contacts');
+ my @live_contacts = $c->stash->{live_contacts}->all;
+ my @live_contact_ids = map { $_->id } @live_contacts;
+ my @new_contact_ids = grep { $c->get_param("contacts[$_]") } @live_contact_ids;
+ $user->set_extra_metadata('categories', \@new_contact_ids);
+ }
+
+ $user->update;
+ if ($edited) {
+ $c->forward( '/admin/log_edit', [ $id, 'user', 'edit' ] );
+ }
+ $c->flash->{status_message} = _("Updated!");
+ return $c->res->redirect( $c->uri_for_action( 'admin/users/edit', $user->id ) );
+ }
+
+ if ( $user->from_body ) {
+ unless ( $c->stash->{live_contacts} ) {
+ $c->stash->{body} = $user->from_body;
+ $c->forward('/admin/fetch_contacts');
+ }
+ my @contacts = @{$user->get_extra_metadata('categories') || []};
+ my %active_contacts = map { $_ => 1 } @contacts;
+ my @live_contacts = $c->stash->{live_contacts}->all;
+ my @all_contacts = map { {
+ id => $_->id,
+ category => $_->category,
+ active => $active_contacts{$_->id},
+ } } @live_contacts;
+ $c->stash->{contacts} = \@all_contacts;
+ }
+
+ # this goes after in case we've delete any alerts
+ unless ( $c->cobrand->moniker eq 'zurich' ) {
+ $c->forward('user_alert_details');
+ }
+
+ return 1;
+}
+
+sub import :Local {
+ my ( $self, $c, $id ) = @_;
+
+ $c->forward('/auth/get_csrf_token');
+ return unless $c->user_exists && $c->user->is_superuser;
+
+ return unless $c->req->method eq 'POST';
+
+ $c->forward('/auth/check_csrf_token');
+ $c->stash->{new_users} = [];
+ $c->stash->{existing_users} = [];
+
+ my @all_permissions = map { keys %$_ } values %{ $c->cobrand->available_permissions };
+ my %available_permissions = map { $_ => 1 } @all_permissions;
+
+ my $csv = Text::CSV->new({ binary => 1});
+ my $fh = $c->req->upload('csvfile')->fh;
+ $csv->getline($fh); # discard the header
+ while (my $row = $csv->getline($fh)) {
+ my ($name, $email, $from_body, $permissions) = @$row;
+ $email = lc Utils::trim_text($email);
+ my @permissions = split(/:/, $permissions);
+
+ my $user = FixMyStreet::DB->resultset("User")->find_or_new({ email => $email, email_verified => 1 });
+ if ($user->in_storage) {
+ push @{$c->stash->{existing_users}}, $user;
+ next;
+ }
+
+ $user->name($name);
+ $user->from_body($from_body || undef);
+ $user->update_or_insert;
+
+ my @user_permissions = grep { $available_permissions{$_} } @permissions;
+ foreach my $permission_type (@user_permissions) {
+ $user->user_body_permissions->find_or_create({
+ body_id => $user->from_body->id,
+ permission_type => $permission_type,
+ });
+ }
+
+ push @{$c->stash->{new_users}}, $user;
+ }
+}
+
+sub phone_check : Private {
+ my ($self, $c, $phone) = @_;
+ my $parsed = FixMyStreet::SMS->parse_username($phone);
+ if ($parsed->{phone} && $parsed->{may_be_mobile}) {
+ return $parsed->{username};
+ } elsif ($parsed->{phone}) {
+ $c->stash->{field_errors}->{phone} = _('Please enter a mobile number');
+ } else {
+ $c->stash->{field_errors}->{phone} = _('Please check your phone number is correct');
+ }
+}
+
+sub user_cobrand_extra_fields : Private {
+ my ( $self, $c ) = @_;
+
+ my @extra_fields = @{ $c->cobrand->call_hook('user_extra_fields') || [] };
+ foreach ( @extra_fields ) {
+ $c->stash->{user}->set_extra_metadata( $_ => $c->get_param("extra[$_]") );
+ }
+}
+
+sub user_alert_details : Private {
+ my ( $self, $c ) = @_;
+
+ my @alerts = $c->stash->{user}->alerts({}, { prefetch => 'alert_type' })->all;
+ $c->stash->{alerts} = \@alerts;
+
+ my @wards;
+
+ for my $alert (@alerts) {
+ if ($alert->alert_type->ref eq 'ward_problems') {
+ push @wards, $alert->parameter2;
+ }
+ }
+
+ if (@wards) {
+ $c->stash->{alert_areas} = mySociety::MaPit::call('areas', join(',', @wards) );
+ }
+
+ my %body_names = map { $_->{id} => $_->{name} } @{ $c->stash->{bodies} };
+ $c->stash->{body_names} = \%body_names;
+}
+
+sub update_alerts : Private {
+ my ($self, $c) = @_;
+
+ my $changes;
+ for my $alert ( $c->stash->{user}->alerts ) {
+ my $edit_option = $c->get_param('edit_alert[' . $alert->id . ']');
+ next unless $edit_option;
+ $changes = 1;
+ if ( $edit_option eq 'delete' ) {
+ $alert->delete;
+ } elsif ( $edit_option eq 'disable' ) {
+ $alert->disable;
+ } elsif ( $edit_option eq 'enable' ) {
+ $alert->confirm;
+ }
+ }
+ $c->flash->{status_message} = _("Updated!") if $changes;
+}
+
+sub user_logout_everywhere : Private {
+ my ( $self, $c, $user ) = @_;
+ my $sessions = $user->get_extra_metadata('sessions');
+ foreach (grep { $_ ne $c->sessionid } @$sessions) {
+ $c->delete_session_data("session:$_");
+ }
+ $c->stash->{status_message} = _('That user has been logged out.');
+}
+
+sub user_anon_everywhere : Private {
+ my ( $self, $c, $user ) = @_;
+ $user->problems->update({anonymous => 1});
+ $user->comments->update({anonymous => 1});
+ $c->stash->{status_message} = _('That user has been made anonymous on all reports and updates.');
+}
+
+sub user_hide_everywhere : Private {
+ my ( $self, $c, $user ) = @_;
+ my $problems = $user->problems->search({ state => { '!=' => 'hidden' } });
+ while (my $problem = $problems->next) {
+ $problem->get_photoset->delete_cached;
+ $problem->update({ state => 'hidden' });
+ }
+ my $updates = $user->comments->search({ state => { '!=' => 'hidden' } });
+ while (my $update = $updates->next) {
+ $update->hide;
+ }
+ $c->stash->{status_message} = _('That user’s reports and updates have been hidden.');
+}
+
+sub send_login_email : Private {
+ my ( $self, $c, $args ) = @_;
+
+ my $token_data = {
+ email => $args->{email},
+ };
+
+ $token_data->{old_user_id} = $args->{user_id} if $args->{user_id};
+ $token_data->{name} = $args->{name} if $args->{name};
+
+ my $token_obj = $c->model('DB::Token')->create({
+ scope => 'email_sign_in',
+ data => $token_data,
+ });
+
+ $c->stash->{token} = $token_obj->token;
+ my $template = 'login.txt';
+
+ # do not use relative URIs in the email, obvs.
+ $c->uri_disposition('absolute');
+ $c->send_email( $template, { to => $args->{email} } );
+
+ $c->stash->{status_message} = _('The user has been sent a login email');
+}
+
+# Anonymize and remove name from all problems/updates, disable all alerts.
+# Remove their account's email address, phone number, password, etc.
+sub user_remove_account : Private {
+ my ( $self, $c, $user ) = @_;
+ $c->forward('user_logout_everywhere', [ $user ]);
+ $user->anonymize_account;
+ $c->stash->{status_message} = _('That user’s personal details have been removed.');
+}
+
+=head2 ban
+
+Add the user's email address/phone number to the abuse table if they are not
+already in there and sets status_message accordingly.
+
+=cut
+
+sub ban : Private {
+ my ( $self, $c ) = @_;
+
+ my $user;
+ if ($c->stash->{problem}) {
+ $user = $c->stash->{problem}->user;
+ } elsif ($c->stash->{update}) {
+ $user = $c->stash->{update}->user;
+ }
+ return unless $user;
+
+ if ($user->email_verified && $user->email) {
+ my $abuse = $c->model('DB::Abuse')->find_or_new({ email => $user->email });
+ if ( $abuse->in_storage ) {
+ $c->stash->{status_message} = _('User already in abuse list');
+ } else {
+ $abuse->insert;
+ $c->stash->{status_message} = _('User added to abuse list');
+ }
+ $c->stash->{username_in_abuse} = 1;
+ }
+ if ($user->phone_verified && $user->phone) {
+ my $abuse = $c->model('DB::Abuse')->find_or_new({ email => $user->phone });
+ if ( $abuse->in_storage ) {
+ $c->stash->{status_message} = _('User already in abuse list');
+ } else {
+ $abuse->insert;
+ $c->stash->{status_message} = _('User added to abuse list');
+ }
+ $c->stash->{username_in_abuse} = 1;
+ }
+ return 1;
+}
+
+sub unban : Private {
+ my ( $self, $c, $user ) = @_;
+
+ my @username;
+ if ($user->email_verified && $user->email) {
+ push @username, $user->email;
+ }
+ if ($user->phone_verified && $user->phone) {
+ push @username, $user->phone;
+ }
+ if (@username) {
+ my $abuse = $c->model('DB::Abuse')->search({ email => \@username });
+ if ( $abuse ) {
+ $abuse->delete;
+ $c->stash->{status_message} = _('user removed from abuse list');
+ } else {
+ $c->stash->{status_message} = _('user not in abuse list');
+ }
+ $c->stash->{username_in_abuse} = 0;
+ }
+}
+
+=head2 flag
+
+Sets the flag on a user
+
+=cut
+
+sub flag : Private {
+ my ( $self, $c ) = @_;
+
+ my $user;
+ if ($c->stash->{problem}) {
+ $user = $c->stash->{problem}->user;
+ } elsif ($c->stash->{update}) {
+ $user = $c->stash->{update}->user;
+ }
+
+ if ( !$user ) {
+ $c->stash->{status_message} = _('Could not find user');
+ } else {
+ $user->flagged(1);
+ $user->update;
+ $c->stash->{status_message} = _('User flagged');
+ }
+
+ $c->stash->{user_flagged} = 1;
+
+ return 1;
+}
+
+=head2 flag_remove
+
+Remove the flag on a user
+
+=cut
+
+sub flag_remove : Private {
+ my ( $self, $c ) = @_;
+
+ my $user;
+ if ($c->stash->{problem}) {
+ $user = $c->stash->{problem}->user;
+ } elsif ($c->stash->{update}) {
+ $user = $c->stash->{update}->user;
+ }
+
+ if ( !$user ) {
+ $c->stash->{status_message} = _('Could not find user');
+ } else {
+ $user->flagged(0);
+ $user->update;
+ $c->stash->{status_message} = _('User flag removed');
+ }
+
+ return 1;
+}
+
+
+sub trim {
+ my $self = shift;
+ my $e = shift;
+ $e =~ s/^\s+//;
+ $e =~ s/\s+$//;
+ return $e;
+}
+
+=head1 AUTHOR
+
+mySociety
+
+=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;
diff --git a/t/app/controller/admin/permissions.t b/t/app/controller/admin/permissions.t
index 036ec4509..ff5a8ec4f 100644
--- a/t/app/controller/admin/permissions.t
+++ b/t/app/controller/admin/permissions.t
@@ -99,7 +99,7 @@ FixMyStreet::override_config {
my $p = $perm ? 'with' : 'without';
my $r = $report->user eq $user2 ? 'with' : 'without';
subtest "User $u edit user for $b $p permission, $r cobrand relation" => sub {
- $mech->get("/admin/user_edit/$user2_id");
+ $mech->get("/admin/users/$user2_id");
my $success = $mech->res->is_success();
ok $result == 200 ? $success : !$success, "got correct response";
is $mech->res->code, $result, "got $result";
@@ -109,7 +109,7 @@ FixMyStreet::override_config {
}
subtest "Users can't edit users of their own council without permission" => sub {
- $mech->get_ok("/admin/user_edit/$user2_id");
+ $mech->get_ok("/admin/users/$user2_id");
$mech->submit_form_ok( { with_fields => {
email => $user2->email,
} } );
@@ -124,7 +124,7 @@ FixMyStreet::override_config {
});
subtest "Users can edit users of their own council" => sub {
- $mech->get_ok("/admin/user_edit/$user2_id");
+ $mech->get_ok("/admin/users/$user2_id");
$mech->content_contains( $user2->name );
# We shouldn't be able to see the permissions tick boxes
@@ -149,7 +149,7 @@ FixMyStreet::override_config {
subtest "Users can edit permissions" => sub {
is $user2->user_body_permissions->count, 0, 'user2 has no permissions';
- $mech->get_ok("/admin/user_edit/$user2_id");
+ $mech->get_ok("/admin/users/$user2_id");
$mech->content_contains('Moderate report details');
$mech->submit_form_ok( { with_fields => {
@@ -183,7 +183,7 @@ FixMyStreet::override_config {
is $user2->user_body_permissions->count, 1, 'user2 has 1 permission';
$user2->update({ area_ids => [123] }); # Set to check cleared
- $mech->get_ok("/admin/user_edit/$user2_id");
+ $mech->get_ok("/admin/users/$user2_id");
$mech->content_contains('Moderate report details');
$mech->submit_form_ok( { with_fields => {
diff --git a/t/app/controller/admin/users.t b/t/app/controller/admin/users.t
index d82a7eaef..e2c922a23 100644
--- a/t/app/controller/admin/users.t
+++ b/t/app/controller/admin/users.t
@@ -19,7 +19,7 @@ subtest 'search abuse' => sub {
};
subtest 'remove user from abuse list from edit user page' => sub {
- $mech->get_ok( '/admin/user_edit/' . $user->id );
+ $mech->get_ok( '/admin/users/' . $user->id );
$mech->content_contains('User in abuse table');
$mech->click_ok('unban');
@@ -31,7 +31,7 @@ subtest 'remove user from abuse list from edit user page' => sub {
subtest 'remove user with phone account from abuse list from edit user page' => sub {
my $abuse_user = $mech->create_user_ok('01234 456789');
my $abuse = FixMyStreet::App->model('DB::Abuse')->find_or_create( { email => $abuse_user->phone } );
- $mech->get_ok( '/admin/user_edit/' . $abuse_user->id );
+ $mech->get_ok( '/admin/users/' . $abuse_user->id );
$mech->content_contains('User in abuse table');
my $abuse_found = FixMyStreet::App->model('DB::Abuse')->find( { email => $abuse_user->phone } );
ok $abuse_found, 'user in abuse table';
@@ -45,7 +45,7 @@ subtest 'remove user with phone account from abuse list from edit user page' =>
subtest 'no option to remove user already in abuse list' => sub {
my $abuse = FixMyStreet::App->model('DB::Abuse')->find( { email => $user->email } );
$abuse->delete if $abuse;
- $mech->get_ok( '/admin/user_edit/' . $user->id );
+ $mech->get_ok( '/admin/users/' . $user->id );
$mech->content_lacks('User in abuse table');
};
@@ -66,11 +66,11 @@ subtest 'user search' => sub {
$mech->content_contains( $user->name);
my $u_id = $user->id;
- $mech->content_like( qr{user_edit/$u_id">Edit</a>} );
+ $mech->content_like( qr{users/$u_id">Edit</a>} );
$mech->get_ok('/admin/users?search=' . $user->email);
- $mech->content_like( qr{user_edit/$u_id">Edit</a>} );
+ $mech->content_like( qr{users/$u_id">Edit</a>} );
$user->from_body($haringey->id);
$user->update;
@@ -96,7 +96,7 @@ subtest 'user_edit does not show user from another council' => sub {
FixMyStreet::override_config {
ALLOWED_COBRANDS => [ 'oxfordshire' ],
}, sub {
- $mech->get('/admin/user_edit/' . $user->id);
+ $mech->get('/admin/users/' . $user->id);
ok !$mech->res->is_success(), "want a bad response";
is $mech->res->code, 404, "got 404";
};
@@ -345,7 +345,7 @@ FixMyStreet::override_config {
},
) {
subtest $test->{desc} => sub {
- $mech->get_ok( '/admin/user_edit/' . $user->id );
+ $mech->get_ok( '/admin/users/' . $user->id );
my $visible = $mech->visible_form_values;
is_deeply $visible, $test->{fields}, 'expected user';
@@ -381,7 +381,7 @@ FixMyStreet::override_config {
SMS_AUTHENTICATION => 1,
}, sub {
subtest "Test edit user add verified phone" => sub {
- $mech->get_ok( '/admin/user_edit/' . $user->id );
+ $mech->get_ok( '/admin/users/' . $user->id );
$mech->submit_form_ok( { with_fields => {
phone => '+61491570157',
phone_verified => 1,
@@ -393,9 +393,9 @@ FixMyStreet::override_config {
my $existing_user = $mech->create_user_ok('existing@example.com', name => 'Existing User');
$mech->create_problems_for_body(2, 2514, 'Title', { user => $existing_user });
my $count = FixMyStreet::DB->resultset('Problem')->search({ user_id => $user->id })->count;
- $mech->get_ok( '/admin/user_edit/' . $user->id );
+ $mech->get_ok( '/admin/users/' . $user->id );
$mech->submit_form_ok( { with_fields => { email => 'existing@example.com' } }, 'submit email change' );
- is $mech->uri->path, '/admin/user_edit/' . $existing_user->id, 'redirected';
+ is $mech->uri->path, '/admin/users/' . $existing_user->id, 'redirected';
my $p = FixMyStreet::DB->resultset('Problem')->search({ user_id => $existing_user->id })->count;
is $p, $count + 2, 'reports merged';
};
@@ -406,7 +406,7 @@ $user = $mech->create_user_ok('test@example.com', name => 'Test User');
subtest "Send login email from admin" => sub {
$mech->email_count_is(0);
- $mech->get_ok( '/admin/user_edit/' . $user->id );
+ $mech->get_ok( '/admin/users/' . $user->id );
$mech->submit_form_ok(
{
button => 'send_login_email'
@@ -435,7 +435,7 @@ subtest "Send login email from admin" => sub {
subtest "Send login email from admin for unverified email" => sub {
$user->update( { email_verified => 0 } );
$mech->email_count_is(0);
- $mech->get_ok( '/admin/user_edit/' . $user->id );
+ $mech->get_ok( '/admin/users/' . $user->id );
$mech->submit_form_ok(
{
button => 'send_login_email'
@@ -473,7 +473,7 @@ subtest "Anonymizing user from admin" => sub {
$mech->create_problems_for_body(4, 2237, 'Title');
my $count_p = FixMyStreet::DB->resultset('Problem')->search({ user_id => $user->id })->count;
my $count_u = FixMyStreet::DB->resultset('Comment')->search({ user_id => $user->id })->count;
- $mech->get_ok( '/admin/user_edit/' . $user->id );
+ $mech->get_ok( '/admin/users/' . $user->id );
$mech->submit_form_ok({ button => 'anon_everywhere' });
my $c = FixMyStreet::DB->resultset('Problem')->search({ user_id => $user->id, anonymous => 1 })->count;
is $c, $count_p;
@@ -484,7 +484,7 @@ subtest "Anonymizing user from admin" => sub {
subtest "Hiding user's reports from admin" => sub {
my $count_p = FixMyStreet::DB->resultset('Problem')->search({ user_id => $user->id })->count;
my $count_u = FixMyStreet::DB->resultset('Comment')->search({ user_id => $user->id })->count;
- $mech->get_ok( '/admin/user_edit/' . $user->id );
+ $mech->get_ok( '/admin/users/' . $user->id );
$mech->submit_form_ok({ button => 'hide_everywhere' });
my $c = FixMyStreet::DB->resultset('Problem')->search({ user_id => $user->id, state => 'hidden' })->count;
is $c, $count_p;
@@ -497,7 +497,7 @@ subtest "Logging user out" => sub {
$mech2->log_in_ok($user->email);
$mech2->logged_in_ok;
- $mech->get_ok( '/admin/user_edit/' . $user->id );
+ $mech->get_ok( '/admin/users/' . $user->id );
$mech->submit_form_ok({ button => 'logout_everywhere' }, 'Logging user out');
$mech2->not_logged_in_ok;
};
@@ -506,7 +506,7 @@ subtest "Removing account from admin" => sub {
$mech->create_problems_for_body(4, 2237, 'Title');
my $count_p = FixMyStreet::DB->resultset('Problem')->search({ user_id => $user->id })->count;
my $count_u = FixMyStreet::DB->resultset('Comment')->search({ user_id => $user->id })->count;
- $mech->get_ok( '/admin/user_edit/' . $user->id );
+ $mech->get_ok( '/admin/users/' . $user->id );
$mech->submit_form_ok({ button => 'remove_account' }, 'Removing account');
my $c = FixMyStreet::DB->resultset('Problem')->search({ user_id => $user->id, anonymous => 1, name => '' })->count;
is $c, $count_p, 'All reports anon/nameless';
@@ -519,7 +519,7 @@ subtest "Removing account from admin" => sub {
};
subtest "can view list of user's alerts" => sub {
- $mech->get_ok( '/admin/user_edit/' . $user->id );
+ $mech->get_ok( '/admin/users/' . $user->id );
$mech->content_lacks("User's alerts", 'no list of alerts');
$mech->create_problems_for_body(1, 2514, 'Title', { user => $user });
@@ -532,13 +532,13 @@ subtest "can view list of user's alerts" => sub {
});
- $mech->get_ok( '/admin/user_edit/' . $user->id );
+ $mech->get_ok( '/admin/users/' . $user->id );
$mech->content_contains("User's alerts", 'has list of alerts');
$mech->content_contains($alert->id, 'lists alert');
};
subtest "can edit list of user's alerts" => sub {
- $mech->get_ok( '/admin/user_edit/' . $user->id );
+ $mech->get_ok( '/admin/users/' . $user->id );
my $alert = FixMyStreet::DB->resultset('Alert')->search({
user_id => $user->id,
diff --git a/t/cobrand/bathnes.t b/t/cobrand/bathnes.t
index 59e0d5246..4db2f058c 100644
--- a/t/cobrand/bathnes.t
+++ b/t/cobrand/bathnes.t
@@ -105,7 +105,7 @@ subtest "Custom CSV fields permission can be granted" => sub {
is $counciluser->user_body_permissions->count, 0, 'counciluser has no permissions';
- $mech->get_ok("/admin/user_edit/" . $counciluser->id);
+ $mech->get_ok("/admin/users/" . $counciluser->id);
$mech->content_contains('Extra columns in CSV export');
$mech->submit_form_ok( { with_fields => {
diff --git a/t/cobrand/bromley.t b/t/cobrand/bromley.t
index 05581bdfe..129531fcb 100644
--- a/t/cobrand/bromley.t
+++ b/t/cobrand/bromley.t
@@ -220,7 +220,7 @@ subtest 'check special subcategories in admin' => sub {
ALLOWED_COBRANDS => 'bromley',
MAPIT_URL => 'http://mapit.uk/',
}, sub {
- $mech->get_ok('/admin/user_edit/' . $user->id);
+ $mech->get_ok('/admin/users/' . $user->id);
$mech->submit_form_ok({ with_fields => { 'contacts['.$contact->id.']' => 1, 'contacts[BLUE]' => 1 } });
};
$user->discard_changes;
diff --git a/t/cobrand/zurich.t b/t/cobrand/zurich.t
index 1faa0adf2..ee2724a07 100644
--- a/t/cobrand/zurich.t
+++ b/t/cobrand/zurich.t
@@ -1092,7 +1092,7 @@ FixMyStreet::override_config {
}, sub {
subtest 'users at the top level can be edited' => sub {
$mech->log_in_ok( $superuser->email );
- $mech->get_ok('/admin/user_edit/' . $superuser->id );
+ $mech->get_ok('/admin/users/' . $superuser->id );
};
};
diff --git a/templates/web/base/admin/_translations.html b/templates/web/base/admin/bodies/_translations.html
index d8f7d52fb..d8f7d52fb 100644
--- a/templates/web/base/admin/_translations.html
+++ b/templates/web/base/admin/bodies/_translations.html
diff --git a/templates/web/base/admin/body.html b/templates/web/base/admin/bodies/body.html
index 37ab24496..afd2eff46 100644
--- a/templates/web/base/admin/body.html
+++ b/templates/web/base/admin/bodies/body.html
@@ -29,7 +29,7 @@
[% END %]
<br>
<a href="[% c.uri_for_email(body.url) %]" class="admin-offsite-link">[% loc('List all reported problems' ) %]</a> |
- <a href="[% c.uri_for( 'body', body_id, { text => 1 } ) %]">[% loc('Text only version') %]</a>
+ <a href="[% c.uri_for_action( 'admin/bodies/edit', [ body_id ], { text => 1 } ) %]">[% loc('Text only version') %]</a>
</p>
@@ -65,7 +65,7 @@
</p>
[% END %]
-<form method="post" action="[% c.uri_for('body', body_id ) %]" enctype="application/x-www-form-urlencoded" accept-charset="utf-8">
+<form method="post" action="[% c.uri_for_action('admin/bodies/edit', [ body_id ] ) %]" enctype="application/x-www-form-urlencoded" accept-charset="utf-8">
<table cellspacing="0" cellpadding="2" border="1" id="admin_contacts">
<tr>
@@ -79,7 +79,7 @@
</tr>
[% WHILE ( cat = contacts.next ) %]
<tr [% IF cat.state == 'deleted' %]class="is-deleted"[% END %]>
- <td class="contact-category"><a href="[% c.uri_for( 'body', body_id, cat.category ) %]">[% cat.category_display | html %]</a>
+ <td class="contact-category"><a href="[% c.uri_for_action( '/admin/bodies/edit', [ body_id ], cat.category ) %]">[% cat.category_display | html %]</a>
<br>[% cat.email | html %]</td>
<td>
[% cat.state %]
@@ -130,16 +130,16 @@
<div class="fms-admin-warning">
[% errors.values.join('<br>') %]
</div>
- [% INCLUDE 'admin/contact-form.html' translations=contact_translations %]
+ [% INCLUDE 'admin/bodies/contact-form.html' translations=contact_translations %]
[% ELSE %]
- [% INCLUDE 'admin/contact-form.html' translations={} %]
+ [% INCLUDE 'admin/bodies/contact-form.html' translations={} %]
[% END %]
</div>
[% IF NOT errors and c.user.is_superuser %]
<div class="admin-box">
<h2>[% loc('Edit body details') %]</h2>
- [% INCLUDE 'admin/body-form.html' %]
+ [% INCLUDE 'admin/bodies/form.html' %]
</div>
[% END %][%# Only show all the above if no errors with category form %]
diff --git a/templates/web/base/admin/category_edit.html b/templates/web/base/admin/bodies/category.html
index 7ae4e59b4..2f789ed81 100644
--- a/templates/web/base/admin/category_edit.html
+++ b/templates/web/base/admin/bodies/category.html
@@ -19,7 +19,7 @@
[% END %]
</p>
-[% INCLUDE 'admin/contact-form.html' %]
+[% INCLUDE 'admin/bodies/contact-form.html' %]
<h2>[% loc('History') %]</h2>
<table border="1">
diff --git a/templates/web/base/admin/contact-form.html b/templates/web/base/admin/bodies/contact-form.html
index 7e2830e9c..c55c5c036 100644
--- a/templates/web/base/admin/contact-form.html
+++ b/templates/web/base/admin/bodies/contact-form.html
@@ -1,4 +1,4 @@
-<form method="post" action="[% c.uri_for('body', body_id ) %]" enctype="application/x-www-form-urlencoded" accept-charset="utf-8" id="category_edit">
+<form method="post" action="[% c.uri_for_action('admin/bodies/edit', [ body_id ] ) %]" enctype="application/x-www-form-urlencoded" accept-charset="utf-8" id="category_edit">
[% IF contact.in_storage %]
<p>
@@ -20,7 +20,7 @@
</p>
[% END %]
- [% INCLUDE 'admin/_translations.html' %]
+ [% INCLUDE 'admin/bodies/_translations.html' %]
<div class="admin-hint">
<p>
@@ -117,7 +117,7 @@ as well.") %]
</select>
</p>
- [% INCLUDE 'admin/open311-form-fields.html', object = contact%]
+ [% INCLUDE 'admin/bodies/open311-form-fields.html', object = contact%]
[% END %]
[% IF c.cobrand.enable_category_groups %]
diff --git a/templates/web/base/admin/edit-league.html b/templates/web/base/admin/bodies/edit-league.html
index 4f31eeb2e..4f31eeb2e 100644
--- a/templates/web/base/admin/edit-league.html
+++ b/templates/web/base/admin/bodies/edit-league.html
diff --git a/templates/web/base/admin/body-form.html b/templates/web/base/admin/bodies/form.html
index 958ea5d78..0b162c274 100644
--- a/templates/web/base/admin/body-form.html
+++ b/templates/web/base/admin/bodies/form.html
@@ -1,4 +1,4 @@
- <form method="post" action="[% body ? c.uri_for('body', body.id) : c.uri_for('bodies') %]" enctype="application/x-www-form-urlencoded" accept-charset="utf-8">
+ <form method="post" action="[% body ? c.uri_for_action('admin/bodies/edit', [ body.id ]) : c.uri_for_action('admin/bodies/index') %]" enctype="application/x-www-form-urlencoded" accept-charset="utf-8">
<div class="fms-admin-info">
[% loc(
"Add a <strong>body</strong> for each administrative body, such as a council or department
@@ -25,7 +25,7 @@
<input type="text" class="form-control" name="name" id="name" value="[% body.name | html %]" size="50">
</p>
- [% INCLUDE 'admin/_translations.html' %]
+ [% INCLUDE 'admin/bodies/_translations.html' %]
<div class="admin-hint">
<p>
@@ -114,7 +114,7 @@
</select>
</p>
- [% INCLUDE 'admin/open311-form-fields.html', object = body, show_body_fields = 1 %]
+ [% INCLUDE 'admin/bodies/open311-form-fields.html', object = body, show_body_fields = 1 %]
<div class="admin-hint">
<p>
diff --git a/templates/web/base/admin/bodies.html b/templates/web/base/admin/bodies/index.html
index 33ae5b25a..84e1cda3c 100644
--- a/templates/web/base/admin/bodies.html
+++ b/templates/web/base/admin/bodies/index.html
@@ -39,12 +39,12 @@
[% IF c.cobrand.moniker == 'zurich' %]
[% FILTER repeat(4*body.indent_level) %]&nbsp;[% END %]
[% IF admin_type == 'super' %]
- <a href="[% c.uri_for( 'body', id ) %]">[% body.name | html %]</a>
+ <a href="[% c.uri_for_action( 'admin/bodies/edit', [ id ] ) %]">[% body.name | html %]</a>
[% ELSE %]
[% body.name | html %]
[% END %]
[% ELSE %] [%# not Zurich: all bodies should be links %]
- <a href="[% c.uri_for( 'body', id ) %]">[% body.name | html%]</a>
+ <a href="[% c.uri_for_action( 'admin/bodies/edit', [ id ] ) %]">[% body.name | html %]</a>
[%- IF body.parent %], [% body.parent.name | html %][% END -%]
[% END %]
</td>
@@ -76,10 +76,10 @@
[% IF (c.cobrand.moniker == 'zurich' AND admin_type == 'super') OR c.user.is_superuser %]
<div class="admin-box">
<h2>[% loc('Add body') %]</h2>
- [% INCLUDE 'admin/body-form.html', body='' %]
+ [% INCLUDE 'admin/bodies/form.html', body='' %]
</div>
[% END %]
-[% INCLUDE 'admin/edit-league.html' %]
+[% INCLUDE 'admin/bodies/edit-league.html' %]
[% INCLUDE 'admin/footer.html' %]
diff --git a/templates/web/base/admin/open311-form-fields.html b/templates/web/base/admin/bodies/open311-form-fields.html
index b716cf175..b716cf175 100644
--- a/templates/web/base/admin/open311-form-fields.html
+++ b/templates/web/base/admin/bodies/open311-form-fields.html
diff --git a/templates/web/base/admin/user-alerts.html b/templates/web/base/admin/users/alerts.html
index 6058e595b..6058e595b 100644
--- a/templates/web/base/admin/user-alerts.html
+++ b/templates/web/base/admin/users/alerts.html
diff --git a/templates/web/base/admin/user_edit.html b/templates/web/base/admin/users/edit.html
index cc456914d..8dd2b926f 100644
--- a/templates/web/base/admin/user_edit.html
+++ b/templates/web/base/admin/users/edit.html
@@ -8,8 +8,8 @@
[% status_message %]
-[% INCLUDE 'admin/user-form.html' %]
+[% INCLUDE 'admin/users/form.html' %]
-[% INCLUDE 'admin/user-alerts.html' %]
+[% INCLUDE 'admin/users/alerts.html' %]
[% INCLUDE 'admin/footer.html' %]
diff --git a/templates/web/base/admin/user-form.html b/templates/web/base/admin/users/form.html
index 735e15e87..7bc291419 100644
--- a/templates/web/base/admin/user-form.html
+++ b/templates/web/base/admin/users/form.html
@@ -1,4 +1,4 @@
-<form method="post" id="user_edit" action="[% c.uri_for( 'user_edit', user.id ) %]" enctype="application/x-www-form-urlencoded" accept-charset="utf-8">
+<form method="post" id="user_edit" action="[% c.uri_for_action( 'admin/users/edit', user.id ) %]" enctype="application/x-www-form-urlencoded" accept-charset="utf-8">
<input type="hidden" name="token" value="[% csrf_token %]" >
<input type="hidden" name="submit" value="1" >
@@ -191,7 +191,7 @@
</ul>
[% END %]
[% END %]
- [% TRY %][% INCLUDE 'admin/user-form-extra-fields.html' %][% CATCH file %][% END %]
+ [% TRY %][% INCLUDE 'admin/users/form-extra-fields.html' %][% CATCH file %][% END %]
</ul>
<p>
<input type="submit" class="btn" name="Submit changes" value="[% loc('Submit changes') %]" >
diff --git a/templates/web/base/admin/user_import.html b/templates/web/base/admin/users/import.html
index f866ed955..0c0b903d7 100644
--- a/templates/web/base/admin/user_import.html
+++ b/templates/web/base/admin/users/import.html
@@ -3,7 +3,7 @@
[% status_message %]
-<form method="post" id="user_edit" action="[% c.uri_for( 'user_import' ) %]" enctype="multipart/form-data" accept-charset="utf-8">
+<form method="post" id="user_edit" enctype="multipart/form-data" accept-charset="utf-8">
<input type="hidden" name="token" value="[% csrf_token %]" >
<input type="hidden" name="submit" value="1" >
@@ -27,7 +27,7 @@
[% FOREACH user IN new_users %]
<tr>
<td>
- <a href="[% c.uri_for_action( 'admin/user_edit', user.id ) %]">
+ <a href="[% c.uri_for_action( 'admin/users/edit', user.id ) %]">
[% user.name %]
</a>
</td>
@@ -50,7 +50,7 @@
[% FOREACH user IN existing_users %]
<tr>
<td>
- <a href="[% c.uri_for_action( 'admin/user_edit', user.id ) %]">
+ <a href="[% c.uri_for_action( 'admin/users/edit', user.id ) %]">
[% user.name %]
</a>
</td>
diff --git a/templates/web/base/admin/users.html b/templates/web/base/admin/users/index.html
index 6dfcf4204..e939f008b 100644
--- a/templates/web/base/admin/users.html
+++ b/templates/web/base/admin/users/index.html
@@ -4,7 +4,7 @@
<div class="fms-admin-info">
[% loc("User search finds matches in users' names and email addresses.") %]
</div>
-<form method="get" action="[% c.uri_for('users') %]" enctype="application/x-www-form-urlencoded" accept-charset="utf-8">
+<form method="get" action="[% c.uri_for_action('admin/users/index') %]" enctype="application/x-www-form-urlencoded" accept-charset="utf-8">
<p><label for="search">[% loc('Search:') %]</label>
<input class="form-control" type="text" name="search" size="30" id="search" value="[% searched | html %]">
</form>
@@ -24,14 +24,14 @@
[%- FOREACH user IN users %]
<tr>
<td>[% PROCESS value_or_nbsp value=user.name %]</td>
- <td><a href="[% c.uri_for( 'reports', search => user.email ) %]">[% PROCESS value_or_nbsp value=user.email %]</a></td>
+ <td><a href="[% c.uri_for_action( 'admin/reports', search => user.email ) %]">[% PROCESS value_or_nbsp value=user.email %]</a></td>
<td>[% PROCESS value_or_nbsp value=user.from_body.name %]
[% IF user.is_superuser %] * [% END %]
</td>
[% IF c.cobrand.moniker != 'zurich' %]
<td>[% user.flagged == 2 ? loc('User in abuse table') : user.flagged ? loc('Yes') : '&nbsp;' %]</td>
[% END %]
- <td>[% IF user.id %]<a href="[% c.uri_for( 'user_edit', user.id ) %]">[% loc('Edit') %]</a>[% END %]</td>
+ <td>[% IF user.id %]<a href="[% c.uri_for_action( 'admin/users/edit', user.id ) %]">[% loc('Edit') %]</a>[% END %]</td>
</tr>
[%- END -%]
</table>
@@ -46,9 +46,9 @@
[% IF NOT searched %]
<h2>[% loc('Add user') %]</h2>
-[% INCLUDE 'admin/user-form.html', user = '' %]
+[% INCLUDE 'admin/users/form.html', user = '' %]
[% ELSE %]
-<a href="[% c.uri_for( c.controller('Admin').action_for('user_add') ) %]">[% loc('Add user') %]</a>
+<a href="[% c.uri_for_action('admin/users/add') %]">[% loc('Add user') %]</a>
[% END %]
[% INCLUDE 'admin/footer.html' %]
diff --git a/templates/web/zurich/admin/body.html b/templates/web/zurich/admin/bodies/body.html
index 11be6eef7..aab038ff8 100644
--- a/templates/web/zurich/admin/body.html
+++ b/templates/web/zurich/admin/bodies/body.html
@@ -20,7 +20,7 @@
</tr>
[% WHILE ( cat = contacts.next ) %]
<tr[% IF cat.state == 'deleted' %] class="is-deleted"[% END %]>
- <td><a href="[% c.uri_for( 'body', body_id, cat.category ) %]">[% cat.category_display %]</a></td>
+ <td><a href="[% c.uri_for_action( 'admin/bodies/edit', [ body_id ], cat.category ) %]">[% cat.category_display %]</a></td>
<td>[% cat.email | html %]</td>
<td>[% cat.editor %]</td>
<td>[% cat.note | html %]</td>
@@ -39,13 +39,13 @@
</div>
[% END %]
- [% INCLUDE 'admin/contact-form.html' %]
+ [% INCLUDE 'admin/bodies/contact-form.html' %]
[% END %]
[% IF NOT errors %]
<h2>[% loc('Edit body details') %]</h2>
- [% INCLUDE 'admin/body-form.html' %]
+ [% INCLUDE 'admin/bodies/form.html' %]
[% END %]
[% INCLUDE 'admin/footer.html' %]
diff --git a/templates/web/zurich/admin/contact-form.html b/templates/web/zurich/admin/bodies/contact-form.html
index 3840d46a4..7b59124fb 100644
--- a/templates/web/zurich/admin/contact-form.html
+++ b/templates/web/zurich/admin/bodies/contact-form.html
@@ -1,4 +1,4 @@
-<form method="post" action="[% c.uri_for('body', body_id ) %]" enctype="application/x-www-form-urlencoded" accept-charset="utf-8" id="category_edit">
+<form method="post" action="[% c.uri_for_action('admin/bodies/edit', [ body_id ] ) %]" enctype="application/x-www-form-urlencoded" accept-charset="utf-8" id="category_edit">
[% IF contact.in_storage %]
<h1>[% contact.category_display | html %]</h1>
diff --git a/templates/web/zurich/admin/edit-league.html b/templates/web/zurich/admin/bodies/edit-league.html
index e69de29bb..e69de29bb 100644
--- a/templates/web/zurich/admin/edit-league.html
+++ b/templates/web/zurich/admin/bodies/edit-league.html
diff --git a/templates/web/zurich/admin/body-form.html b/templates/web/zurich/admin/bodies/form.html
index 44adcbc72..b625efc44 100644
--- a/templates/web/zurich/admin/body-form.html
+++ b/templates/web/zurich/admin/bodies/form.html
@@ -1,4 +1,4 @@
- <form method="post" action="[% body ? c.uri_for('body', body.id) : c.uri_for('bodies') %]" enctype="application/x-www-form-urlencoded" accept-charset="utf-8">
+ <form method="post" action="[% body ? c.uri_for_action('admin/bodies/edit', [ body.id ]) : c.uri_for_action('admin/bodies/index') %]" enctype="application/x-www-form-urlencoded" accept-charset="utf-8">
<p>
<label for="name">[% loc('Name') %]</label>