diff options
Diffstat (limited to 'perllib')
-rw-r--r-- | perllib/FixMyStreet/App/Controller/Admin.pm | 15 | ||||
-rw-r--r-- | perllib/FixMyStreet/App/Controller/Admin/DefectTypes.pm | 2 | ||||
-rw-r--r-- | perllib/FixMyStreet/App/Controller/Admin/ResponsePriorities.pm | 2 | ||||
-rw-r--r-- | perllib/FixMyStreet/App/Controller/Admin/Roles.pm | 96 | ||||
-rw-r--r-- | perllib/FixMyStreet/App/Controller/Admin/Users.pm | 177 | ||||
-rw-r--r-- | perllib/FixMyStreet/App/Form/I18N.pm | 13 | ||||
-rw-r--r-- | perllib/FixMyStreet/App/Form/Role.pm | 66 | ||||
-rw-r--r-- | perllib/FixMyStreet/App/Form/Widget/Field/CheckboxGroup.pm | 62 | ||||
-rw-r--r-- | perllib/FixMyStreet/Cobrand/Default.pm | 3 | ||||
-rw-r--r-- | perllib/FixMyStreet/DB/Result/Body.pm | 10 | ||||
-rw-r--r-- | perllib/FixMyStreet/DB/Result/Role.pm | 53 | ||||
-rw-r--r-- | perllib/FixMyStreet/DB/Result/User.pm | 45 | ||||
-rw-r--r-- | perllib/FixMyStreet/DB/Result/UserRole.pm | 50 |
13 files changed, 527 insertions, 67 deletions
diff --git a/perllib/FixMyStreet/App/Controller/Admin.pm b/perllib/FixMyStreet/App/Controller/Admin.pm index 6167a16f5..5f18f8557 100644 --- a/perllib/FixMyStreet/App/Controller/Admin.pm +++ b/perllib/FixMyStreet/App/Controller/Admin.pm @@ -484,15 +484,14 @@ sub report_edit : Path('report_edit') : Args(1) { $problem->resend; $problem->update(); - $c->stash->{status_message} = - '<p><em>' . _('That problem will now be resent.') . '</em></p>'; + $c->stash->{status_message} = _('That problem will now be resent.'); $c->forward( 'log_edit', [ $id, 'problem', 'resend' ] ); } elsif ( $c->get_param('mark_sent') ) { $c->forward('/auth/check_csrf_token'); $problem->update({ whensent => \'current_timestamp' })->discard_changes; - $c->stash->{status_message} = '<p><em>' . _('That problem has been marked as sent.') . '</em></p>'; + $c->stash->{status_message} = _('That problem has been marked as sent.'); $c->forward( 'log_edit', [ $id, 'problem', 'marked sent' ] ); } elsif ( $c->get_param('flaguser') ) { @@ -571,8 +570,7 @@ sub report_edit : Path('report_edit') : Args(1) { } $c->forward( 'log_edit', [ $id, 'problem', 'edit' ] ); - $c->stash->{status_message} = - '<p><em>' . _('Updated!') . '</em></p>'; + $c->stash->{status_message} = _('Updated!'); # do this here otherwise lastupdate and confirmed times # do not display correctly @@ -833,7 +831,7 @@ sub template_edit : Path('templates') : Args(2) { $template->update_or_insert; $template->contact_response_templates->search({ - contact_id => { '!=' => \@new_contact_ids }, + contact_id => { -not_in => \@new_contact_ids }, })->delete; foreach my $contact_id (@new_contact_ids) { $template->contact_response_templates->find_or_create({ @@ -921,13 +919,12 @@ sub update_edit : Path('update_edit') : Args(1) { $self->remove_photo($c, $update, $remove_photo_param); } - $c->stash->{status_message} = '<p><em>' . _('Updated!') . '</em></p>'; + $c->stash->{status_message} = _('Updated!'); # Must call update->hide while it's not hidden (so is_latest works) if ($new_state eq 'hidden') { my $outcome = $update->hide; - $c->stash->{status_message} .= - '<p><em>' . _('Problem marked as open.') . '</em></p>' + $c->stash->{status_message} .= _('Problem marked as open.') if $outcome->{reopened}; } diff --git a/perllib/FixMyStreet/App/Controller/Admin/DefectTypes.pm b/perllib/FixMyStreet/App/Controller/Admin/DefectTypes.pm index ed9b40fd0..6c1a25e5a 100644 --- a/perllib/FixMyStreet/App/Controller/Admin/DefectTypes.pm +++ b/perllib/FixMyStreet/App/Controller/Admin/DefectTypes.pm @@ -76,7 +76,7 @@ sub edit : Path : Args(2) { my @new_contact_ids = $c->get_param_list('categories'); @new_contact_ids = @{ mySociety::ArrayUtils::intersection(\@live_contact_ids, \@new_contact_ids) }; $defect_type->contact_defect_types->search({ - contact_id => { '!=' => \@new_contact_ids }, + contact_id => { -not_in => \@new_contact_ids }, })->delete; foreach my $contact_id (@new_contact_ids) { $defect_type->contact_defect_types->find_or_create({ diff --git a/perllib/FixMyStreet/App/Controller/Admin/ResponsePriorities.pm b/perllib/FixMyStreet/App/Controller/Admin/ResponsePriorities.pm index 2613f6ae0..5077fe78f 100644 --- a/perllib/FixMyStreet/App/Controller/Admin/ResponsePriorities.pm +++ b/perllib/FixMyStreet/App/Controller/Admin/ResponsePriorities.pm @@ -71,7 +71,7 @@ sub edit : Path : Args(2) { my @live_contact_ids = map { $_->id } @live_contacts; my @new_contact_ids = grep { $c->get_param("contacts[$_]") } @live_contact_ids; $priority->contact_response_priorities->search({ - contact_id => { '!=' => \@new_contact_ids }, + contact_id => { -not_in => \@new_contact_ids }, })->delete; foreach my $contact_id (@new_contact_ids) { $priority->contact_response_priorities->find_or_create({ diff --git a/perllib/FixMyStreet/App/Controller/Admin/Roles.pm b/perllib/FixMyStreet/App/Controller/Admin/Roles.pm new file mode 100644 index 000000000..15c96a4ed --- /dev/null +++ b/perllib/FixMyStreet/App/Controller/Admin/Roles.pm @@ -0,0 +1,96 @@ +package FixMyStreet::App::Controller::Admin::Roles; +use Moose; +use namespace::autoclean; + +BEGIN { extends 'Catalyst::Controller'; } + +use FixMyStreet::App::Form::Role; + +sub auto :Private { + my ($self, $c) = @_; + + my $user = $c->user; + if ($user->is_superuser) { + $c->stash(rs => $c->model('DB::Role')->search_rs({}, { join => 'body', order_by => ['body.name', 'me.name'] })); + } elsif ($user->from_body) { + $c->stash(rs => $user->from_body->roles->search_rs({}, { order_by => 'name' })); + } +} + +sub index :Path :Args(0) { + my ($self, $c) = @_; + + my $p = $c->cobrand->available_permissions; + my %labels; + foreach my $group (sort keys %$p) { + my $group_vals = $p->{$group}; + foreach (sort keys %$group_vals) { + $labels{$_} = $group_vals->{$_}; + } + } + + $c->stash( + roles => [ $c->stash->{rs}->all ], + labels => \%labels, + ); +} + +sub create :Local :Args(0) { + my ($self, $c, $id) = @_; + + my $role = $c->stash->{rs}->new_result({}); + return $self->form($c, $role); +} + +sub item :PathPart('admin/roles') :Chained :CaptureArgs(1) { + my ($self, $c, $id) = @_; + + my $obj = $c->stash->{rs}->find($id) + or $c->detach('/page_error_404_not_found', []); + $c->stash(obj => $obj); +} + +sub edit :PathPart('') :Chained('item') :Args(0) { + my ($self, $c) = @_; + return $self->form($c, $c->stash->{obj}); +} + +sub form { + my ($self, $c, $role) = @_; + + if ($c->get_param('delete_role')) { + $role->delete; + $c->response->redirect($c->uri_for($self->action_for('list'))); + $c->detach; + } + + my $perms = []; + my $p = $c->cobrand->available_permissions; + foreach my $group (sort keys %$p) { + my $group_vals = $p->{$group}; + my @foo; + foreach (sort keys %$group_vals) { + push @foo, { value => $_, label => $group_vals->{$_} }; + } + push @$perms, { group => $group, options => \@foo }; + } + my $opts = { + field_list => [ + '+permissions' => { options => $perms }, + ], + }; + + if (!$c->user->is_superuser && $c->user->from_body) { + push @{$opts->{field_list}}, '+body', { inactive => 1 }; + $opts->{body_id} = $c->user->from_body->id; + } + + my $form = FixMyStreet::App::Form::Role->new(%$opts); + $c->stash(template => 'admin/roles/form.html', form => $form); + $form->process(item => $role, params => $c->req->params); + return unless $form->validated; + + $c->response->redirect($c->uri_for($self->action_for('list'))); +} + +1; diff --git a/perllib/FixMyStreet/App/Controller/Admin/Users.pm b/perllib/FixMyStreet/App/Controller/Admin/Users.pm index 6af4ae831..e55a3d111 100644 --- a/perllib/FixMyStreet/App/Controller/Admin/Users.pm +++ b/perllib/FixMyStreet/App/Controller/Admin/Users.pm @@ -27,37 +27,69 @@ Admin pages for editing users 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, - ] + if ($c->req->method eq 'POST') { + my @uids = $c->get_param_list('uid'); + my @role_ids = $c->get_param_list('roles'); + my $user_rs = FixMyStreet::DB->resultset("User")->search({ id => \@uids }); + foreach my $user ($user_rs->all) { + $user->admin_user_body_permissions->delete; + $user->user_roles->search({ + role_id => { -not_in => \@role_ids }, + })->delete; + foreach my $role (@role_ids) { + $user->user_roles->find_or_create({ + role_id => $role, + }); } - ); + } + $c->stash->{status_message} = _('Updated!'); + } + + my $search = $c->get_param('search'); + my $role = $c->get_param('role'); + if ($search || $role) { + my $users = $c->cobrand->users; + my $isearch; + if ($search) { + $search = $self->trim($search); + $search =~ s/^<(.*)>$/$1/; # In case email wrapped in <...> + $c->stash->{searched} = $search; + + $isearch = '%' . $search . '%'; + my $search_n = 0; + $search_n = int($search) if $search =~ /^\d+$/; + + $users = $users->search( + { + -or => [ + email => { ilike => $isearch }, + phone => { ilike => $isearch }, + name => { ilike => $isearch }, + from_body => $search_n, + ] + } + ); + } + if ($role) { + $c->stash->{role_selected} = $role; + $users = $users->search({ + role_id => $role, + }, { + join => 'user_roles', + }); + } + my @users = $users->all; $c->stash->{users} = [ @users ]; - $c->forward('/admin/add_flags', [ { email => { ilike => $isearch } } ]); + if ($search) { + $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 } }, @@ -67,6 +99,14 @@ sub index :Path : Args(0) { $c->stash->{users} = \@users; } + my $rs; + if ($c->user->is_superuser) { + $rs = $c->model('DB::Role')->search_rs({}, { join => 'body', order_by => ['body.name', 'me.name'] }); + } elsif ($c->user->from_body) { + $rs = $c->user->from_body->roles->search_rs({}, { order_by => 'name' }); + } + $c->stash->{roles} = [ $rs->all ]; + return 1; } @@ -113,9 +153,7 @@ sub add : Local : Args(0) { $c->stash->{field_errors}->{username} = _('User already exists'); } - return if %{$c->stash->{field_errors}}; - - my $user = $c->model('DB::User')->create( { + my $user = $c->model('DB::User')->new( { name => $c->get_param('name'), email => $email ? $email : undef, email_verified => $email && $email_v ? 1 : 0, @@ -127,8 +165,11 @@ sub add : Local : Args(0) { is_superuser => ( $c->user->is_superuser && $c->get_param('is_superuser') ) || 0, } ); $c->stash->{user} = $user; + + return if %{$c->stash->{field_errors}}; + $c->forward('user_cobrand_extra_fields'); - $user->update; + $user->insert; $c->forward( '/admin/log_edit', [ $user->id, 'user', 'edit' ] ); @@ -136,6 +177,18 @@ sub add : Local : Args(0) { $c->res->redirect( $c->uri_for_action( 'admin/users/edit', $user->id ) ); } +sub fetch_body_roles : Private { + my ($self, $c, $body ) = @_; + + my $roles = $body->roles->search(undef, { order_by => 'name' }); + unless ($roles) { + delete $c->stash->{roles}; # Body doesn't have any roles + return; + } + + $c->stash->{roles} = [ $roles->all ]; +} + sub edit : Path : Args(1) { my ( $self, $c, $id ) = @_; @@ -157,11 +210,11 @@ sub edit : Path : Args(1) { $c->forward('/admin/fetch_all_bodies'); $c->forward('/admin/fetch_body_areas', [ $user->from_body ]) if $user->from_body; + $c->forward('fetch_body_roles', [ $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->stash->{status_message} = $c->flash->{status_message}; } $c->forward('/auth/check_csrf_token') if $c->get_param('submit'); @@ -270,26 +323,45 @@ sub edit : Path : Args(1) { # 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 ]); + $c->forward('fetch_body_roles', [ $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->user_roles->delete; $user->area_ids(undef); delete $c->stash->{areas}; + delete $c->stash->{roles}; 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({ + my %valid_roles = map { $_->id => 1 } @{$c->stash->{roles}}; + my @role_ids = grep { $valid_roles{$_} } $c->get_param_list('roles'); + if (@role_ids) { + # Roles take precedence over permissions + $user->admin_user_body_permissions->delete; + $user->user_roles->search({ + role_id => { -not_in => \@role_ids }, + })->delete; + foreach my $role (@role_ids) { + $user->user_roles->find_or_create({ + role_id => $role, + }); + } + } else { + $user->user_roles->delete; + 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 => $permission_type, - }); + permission_type => { -not_in => \@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, + }); + } } } @@ -303,7 +375,7 @@ sub edit : Path : Args(1) { my @trusted_bodies = $c->get_param_list('trusted_bodies'); if ( $c->user->is_superuser ) { $user->user_body_permissions->search({ - body_id => { '!=' => \@trusted_bodies }, + body_id => { -not_in => \@trusted_bodies }, permission_type => 'trusted', })->delete; foreach my $body_id (@trusted_bodies) { @@ -389,9 +461,8 @@ sub import :Local { 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; + my ($name, $email, $from_body, $permissions, $roles) = @$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) { @@ -403,12 +474,24 @@ sub import :Local { $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, - }); + if ($roles) { + my @roles = split(/:/, $roles); + foreach my $role (@roles) { + $role = FixMyStreet::DB->resultset("Role")->find({ + body_id => $user->from_body->id, + name => $role, + }) or next; + $user->add_to_roles($role); + } + } else { + my @permissions = split(/:/, $permissions); + 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; diff --git a/perllib/FixMyStreet/App/Form/I18N.pm b/perllib/FixMyStreet/App/Form/I18N.pm new file mode 100644 index 000000000..b37f7ac53 --- /dev/null +++ b/perllib/FixMyStreet/App/Form/I18N.pm @@ -0,0 +1,13 @@ +package FixMyStreet::App::Form::I18N; + +use Moo; + +sub maketext { + my ($self, $msg, @args) = @_; + + no if ($] >= 5.022), warnings => 'redundant'; + return sprintf(_($msg), @args); +} + +1; + diff --git a/perllib/FixMyStreet/App/Form/Role.pm b/perllib/FixMyStreet/App/Form/Role.pm new file mode 100644 index 000000000..f0711af15 --- /dev/null +++ b/perllib/FixMyStreet/App/Form/Role.pm @@ -0,0 +1,66 @@ +package FixMyStreet::App::Form::Role; + +use HTML::FormHandler::Moose; +use FixMyStreet::App::Form::I18N; +extends 'HTML::FormHandler::Model::DBIC'; +use namespace::autoclean; + +has 'body_id' => ( isa => 'Int', is => 'ro' ); + +has '+widget_name_space' => ( default => sub { ['FixMyStreet::App::Form::Widget'] } ); +has '+widget_tags' => ( default => sub { { wrapper_tag => 'p' } } ); +has '+item_class' => ( default => 'Role' ); +has_field 'name' => ( required => 1 ); +has_field 'body' => ( type => 'Select', empty_select => 'Select a body', required => 1 ); +has_field 'permissions' => ( + type => 'Multiple', + widget => 'CheckboxGroup', + tags => { inline => 1, wrapper_tag => 'fieldset', }, +); + +before 'update_model' => sub { + my $self = shift; + $self->item->body_id($self->body_id) if $self->body_id; +}; + +sub _build_language_handle { FixMyStreet::App::Form::I18N->new } + +has '+unique_messages' => ( + default => sub { + { roles_body_id_name_key => "Role names must be unique" }; + } +); + +sub validate { + my $self = shift; + + my $rs = $self->resultset; + my $value = $self->value; + + return 0 if $self->body_id; # The core validation catches this, because body_id is set on $self->item + return 0 if $self->item_id && $self->item->body_id == $value->{body}; # Correctly caught by core validation + + # Okay, due to a bug we need to check this ourselves + # https://github.com/gshank/html-formhandler-model-dbic/issues/20 + my @id_clause = (); + @id_clause = HTML::FormHandler::Model::DBIC::_id_clause( $rs, $self->item_id ) if defined $self->item; + + my %form_columns = (body => 'body_id', name => 'name'); + my %where = map { $form_columns{$_} => + exists( $value->{$_} ) ? $value->{$_} : undef || + ( $self->item ? $self->item->get_column($form_columns{$_}) : undef ) + } keys %form_columns; + + my $count = $rs->search( \%where )->search( {@id_clause} )->count; + return 0 if $count < 1; + + my $field = $self->field('name'); + my $constraint = 'roles_body_id_name_key'; + my $field_error = $self->unique_message_for_constraint($constraint); + $field->add_error( $field_error, $constraint ); + return 1; +} + +__PACKAGE__->meta->make_immutable; + +1; diff --git a/perllib/FixMyStreet/App/Form/Widget/Field/CheckboxGroup.pm b/perllib/FixMyStreet/App/Form/Widget/Field/CheckboxGroup.pm new file mode 100644 index 000000000..1dc55e49b --- /dev/null +++ b/perllib/FixMyStreet/App/Form/Widget/Field/CheckboxGroup.pm @@ -0,0 +1,62 @@ +package FixMyStreet::App::Form::Widget::Field::CheckboxGroup; + +use Moose::Role; +with 'HTML::FormHandler::Widget::Field::CheckboxGroup'; +use namespace::autoclean; + +sub render_element { + my ( $self, $result ) = @_; + $result ||= $self->result; + + my $output = '<ul class="permissions-checkboxes">'; + foreach my $option ( @{ $self->{options} } ) { + if ( my $label = $option->{group} ) { + $label = $self->_localize( $label ) if $self->localize_labels; + $output .= qq{\n<li>$label\n<ul class="no-margin no-bullets">}; + $output .= qq{\n<li>(<a href="#" data-select-all>} . _('all') . '</a> / '; + $output .= '<a href="#" data-select-none>' . _('none') . '</a>)</li>'; + foreach my $group_opt ( @{ $option->{options} } ) { + $output .= '<li>'; + $output .= $self->render_option( $group_opt, $result ); + $output .= "</li>\n"; + } + $output .= qq{</ul>\n</li>}; + } + else { + $output .= $self->render_option( $option, $result ); + } + } + $output .= '</ul>'; + $self->reset_options_index; + return $output; +} + +1; + +__END__ + +=pod + +=encoding UTF-8 + +=head1 NAME + +FixMyStreet::App::Form::Widget::Field::CheckboxGroup - checkbox group field role + +=head1 SYNOPSIS + +Subclass of HTML::FormHandler::Widget::Field::CheckboxGroup, but printed +as a nested <ul>. + +=head1 AUTHOR + +FormHandler Contributors - see HTML::FormHandler + +=head1 COPYRIGHT AND LICENSE + +This software is copyright (c) 2017 by Gerda Shank. + +This is free software; you can redistribute it and/or modify it under +the same terms as the Perl 5 programming language system itself. + +=cut diff --git a/perllib/FixMyStreet/Cobrand/Default.pm b/perllib/FixMyStreet/Cobrand/Default.pm index a08a8dff7..cc7f03adb 100644 --- a/perllib/FixMyStreet/Cobrand/Default.pm +++ b/perllib/FixMyStreet/Cobrand/Default.pm @@ -647,7 +647,7 @@ sub admin_pages { my $pages = { 'summary' => [_('Summary'), 0], 'timeline' => [_('Timeline'), 5], - 'stats' => [_('Stats'), 8], + 'stats' => [_('Stats'), 8.5], }; # There are some pages that only super users can see @@ -680,6 +680,7 @@ sub admin_pages { if ( $user->has_body_permission_to('user_edit') ) { $pages->{reports} = [ _('Reports'), 2 ]; $pages->{users} = [ _('Users'), 6 ]; + $pages->{roles} = [ _('Roles'), 7 ]; $pages->{user_edit} = [ undef, undef ]; } if ( $self->allow_report_extra_fields && $user->has_body_permission_to('category_edit') ) { diff --git a/perllib/FixMyStreet/DB/Result/Body.pm b/perllib/FixMyStreet/DB/Result/Body.pm index 663181746..9424eaf03 100644 --- a/perllib/FixMyStreet/DB/Result/Body.pm +++ b/perllib/FixMyStreet/DB/Result/Body.pm @@ -117,6 +117,12 @@ __PACKAGE__->has_many( { cascade_copy => 0, cascade_delete => 0 }, ); __PACKAGE__->has_many( + "roles", + "FixMyStreet::DB::Result::Role", + { "foreign.body_id" => "self.id" }, + { cascade_copy => 0, cascade_delete => 0 }, +); +__PACKAGE__->has_many( "user_body_permissions", "FixMyStreet::DB::Result::UserBodyPermission", { "foreign.body_id" => "self.id" }, @@ -130,8 +136,8 @@ __PACKAGE__->has_many( ); -# Created by DBIx::Class::Schema::Loader v0.07035 @ 2019-04-25 12:06:39 -# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:8CuxbffDaYS7TFlgff1nEg +# Created by DBIx::Class::Schema::Loader v0.07035 @ 2019-05-23 18:03:28 +# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:9sFgYQ9qhnZNcz3kUFYuvg __PACKAGE__->load_components("+FixMyStreet::DB::RABXColumn"); __PACKAGE__->rabx_column('extra'); diff --git a/perllib/FixMyStreet/DB/Result/Role.pm b/perllib/FixMyStreet/DB/Result/Role.pm new file mode 100644 index 000000000..e35b0b195 --- /dev/null +++ b/perllib/FixMyStreet/DB/Result/Role.pm @@ -0,0 +1,53 @@ +use utf8; +package FixMyStreet::DB::Result::Role; + +# Created by DBIx::Class::Schema::Loader +# DO NOT MODIFY THE FIRST PART OF THIS FILE + +use strict; +use warnings; + +use base 'DBIx::Class::Core'; +__PACKAGE__->load_components( + "FilterColumn", + "FixMyStreet::InflateColumn::DateTime", + "FixMyStreet::EncodedColumn", +); +__PACKAGE__->table("roles"); +__PACKAGE__->add_columns( + "id", + { + data_type => "integer", + is_auto_increment => 1, + is_nullable => 0, + sequence => "roles_id_seq", + }, + "body_id", + { data_type => "integer", is_foreign_key => 1, is_nullable => 0 }, + "name", + { data_type => "text", is_nullable => 1 }, + "permissions", + { data_type => "text[]", is_nullable => 1 }, +); +__PACKAGE__->set_primary_key("id"); +__PACKAGE__->add_unique_constraint("roles_body_id_name_key", ["body_id", "name"]); +__PACKAGE__->belongs_to( + "body", + "FixMyStreet::DB::Result::Body", + { id => "body_id" }, + { is_deferrable => 0, on_delete => "CASCADE,", on_update => "NO ACTION" }, +); +__PACKAGE__->has_many( + "user_roles", + "FixMyStreet::DB::Result::UserRole", + { "foreign.role_id" => "self.id" }, + { cascade_copy => 0, cascade_delete => 0 }, +); + + +# Created by DBIx::Class::Schema::Loader v0.07035 @ 2019-05-23 18:03:28 +# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:KkzVQZuzExH8PhZLJsnZgg + +__PACKAGE__->many_to_many( users => 'user_roles', 'user' ); + +1; diff --git a/perllib/FixMyStreet/DB/Result/User.pm b/perllib/FixMyStreet/DB/Result/User.pm index d01ba92d0..fc651b4d1 100644 --- a/perllib/FixMyStreet/DB/Result/User.pm +++ b/perllib/FixMyStreet/DB/Result/User.pm @@ -121,10 +121,16 @@ __PACKAGE__->has_many( { "foreign.user_id" => "self.id" }, { cascade_copy => 0, cascade_delete => 0 }, ); +__PACKAGE__->has_many( + "user_roles", + "FixMyStreet::DB::Result::UserRole", + { "foreign.user_id" => "self.id" }, + { cascade_copy => 0, cascade_delete => 0 }, +); -# Created by DBIx::Class::Schema::Loader v0.07035 @ 2019-04-25 12:06:39 -# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:BCCqv3JCec8psuRk/SdCJQ +# Created by DBIx::Class::Schema::Loader v0.07035 @ 2019-05-23 18:03:28 +# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:qtmzA7ywVkyQpjLh1ienNg # These are not fully unique constraints (they only are when the *_verified # is true), but this is managed in ResultSet::User's find() wrapper. @@ -143,6 +149,7 @@ use namespace::clean -except => [ 'meta' ]; with 'FixMyStreet::Roles::Extra'; __PACKAGE__->many_to_many( planned_reports => 'user_planned_reports', 'report' ); +__PACKAGE__->many_to_many( roles => 'user_roles', 'role' ); sub cost { FixMyStreet->test_mode ? 1 : 12; @@ -375,7 +382,18 @@ has body_permissions => ( lazy => 1, default => sub { my $self = shift; - return [ $self->user_body_permissions->all ]; + my $perms = []; + foreach my $role ($self->roles->all) { + push @$perms, map { { + body_id => $role->body_id, + permission => $_, + } } @{$role->permissions}; + } + push @$perms, map { { + body_id => $_->body_id, + permission => $_->permission_type, + } } $self->user_body_permissions->all; + return $perms; }, ); @@ -392,8 +410,8 @@ sub permissions { return unless $self->belongs_to_body($body_id); - my @permissions = grep { $_->body_id == $self->from_body->id } @{$self->body_permissions}; - return { map { $_->permission_type => 1 } @permissions }; + my @permissions = grep { $_->{body_id} == $self->from_body->id } @{$self->body_permissions}; + return { map { $_->{permission} => 1 } @permissions }; } sub has_permission_to { @@ -415,7 +433,7 @@ sub has_permission_to { my %body_ids = map { $_ => 1 } @$body_ids; foreach (@{$self->body_permissions}) { - return 1 if $_->permission_type eq $permission_type && $body_ids{$_->body_id}; + return 1 if $_->{permission} eq $permission_type && $body_ids{$_->{body_id}}; } return 0; } @@ -621,4 +639,19 @@ sub in_area { return $self->areas_hash->{$area}; } +has roles_hash => ( + is => 'ro', + lazy => 1, + default => sub { + my $self = shift; + my %ids = map { $_->role_id => 1 } $self->user_roles->all; + return \%ids; + }, +); + +sub in_role { + my ($self, $role) = @_; + return $self->roles_hash->{$role}; +} + 1; diff --git a/perllib/FixMyStreet/DB/Result/UserRole.pm b/perllib/FixMyStreet/DB/Result/UserRole.pm new file mode 100644 index 000000000..9186e2aa1 --- /dev/null +++ b/perllib/FixMyStreet/DB/Result/UserRole.pm @@ -0,0 +1,50 @@ +use utf8; +package FixMyStreet::DB::Result::UserRole; + +# Created by DBIx::Class::Schema::Loader +# DO NOT MODIFY THE FIRST PART OF THIS FILE + +use strict; +use warnings; + +use base 'DBIx::Class::Core'; +__PACKAGE__->load_components( + "FilterColumn", + "FixMyStreet::InflateColumn::DateTime", + "FixMyStreet::EncodedColumn", +); +__PACKAGE__->table("user_roles"); +__PACKAGE__->add_columns( + "id", + { + data_type => "integer", + is_auto_increment => 1, + is_nullable => 0, + sequence => "user_roles_id_seq", + }, + "role_id", + { data_type => "integer", is_foreign_key => 1, is_nullable => 0 }, + "user_id", + { data_type => "integer", is_foreign_key => 1, is_nullable => 0 }, +); +__PACKAGE__->set_primary_key("id"); +__PACKAGE__->belongs_to( + "role", + "FixMyStreet::DB::Result::Role", + { id => "role_id" }, + { is_deferrable => 0, on_delete => "CASCADE,", on_update => "NO ACTION" }, +); +__PACKAGE__->belongs_to( + "user", + "FixMyStreet::DB::Result::User", + { id => "user_id" }, + { is_deferrable => 0, on_delete => "CASCADE,", on_update => "NO ACTION" }, +); + + +# Created by DBIx::Class::Schema::Loader v0.07035 @ 2019-05-23 16:52:59 +# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:1BSR4j0o5PApKEZmzVAnLg + + +# You can replace this text with custom code or comments, and it will be preserved on regeneration +1; |