aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMatthew Somerville <matthew@mysociety.org>2020-02-27 07:39:46 +0000
committerMatthew Somerville <matthew@mysociety.org>2020-02-27 07:39:46 +0000
commitb0b6810e372a154b1af7da72c1bd2143e5e251e9 (patch)
tree1ebbc91b59f8822e483e51f1f486c347718b0e65
parent152f8f4e608abc5029104e652f4ef37d4cfe02cb (diff)
parent18adbe946c1104338e6abff159b4f9877154969c (diff)
Merge branch 'admin-only-categories'
-rw-r--r--CHANGELOG.md1
-rwxr-xr-xbin/createsuperuser2
-rwxr-xr-xbin/update-schema1
-rw-r--r--db/downgrade_0072---0071.sql21
-rw-r--r--db/schema.sql2
-rw-r--r--db/schema_0072-add-staff-contact-state.sql23
-rw-r--r--perllib/DBIx/Class/FixMyStreet/EncodedColumn.pm2
-rw-r--r--perllib/FixMyStreet/App/Controller/Report/New.pm6
-rw-r--r--perllib/FixMyStreet/Cobrand/Bexley.pm2
-rw-r--r--perllib/FixMyStreet/Cobrand/FixMyStreet.pm47
-rw-r--r--perllib/FixMyStreet/Cobrand/IsleOfWight.pm16
-rw-r--r--perllib/FixMyStreet/Cobrand/TfL.pm2
-rw-r--r--perllib/FixMyStreet/DB/ResultSet/Contact.pm27
-rw-r--r--perllib/FixMyStreet/Script/CreateSuperuser.pm20
-rw-r--r--t/app/controller/report_new.t203
-rw-r--r--t/app/controller/report_new_staff.t257
-rw-r--r--t/script/createsuperuser.t23
-rw-r--r--templates/web/base/admin/bodies/contact-form.html5
18 files changed, 390 insertions, 270 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 809a24675..64b178919 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -44,6 +44,7 @@
- Allow staff users to sign other people up for alerts.
- Group categories on body page. #2850
- Add admin UI for managing web manifest themes. #2792
+ - Add a new "staff" contact state.
- New features:
- Categories can be listed under more than one group #2475
- OpenID Connect login support. #2523
diff --git a/bin/createsuperuser b/bin/createsuperuser
index 98b36aa36..7a4946228 100755
--- a/bin/createsuperuser
+++ b/bin/createsuperuser
@@ -30,4 +30,4 @@ BEGIN {
use FixMyStreet;
use FixMyStreet::Script::CreateSuperuser;
-FixMyStreet::Script::CreateSuperuser::createsuperuser();
+exit FixMyStreet::Script::CreateSuperuser::createsuperuser(@ARGV);
diff --git a/bin/update-schema b/bin/update-schema
index 7941bc542..8f31085c9 100755
--- a/bin/update-schema
+++ b/bin/update-schema
@@ -212,6 +212,7 @@ else {
# (assuming schema change files are never half-applied, which should be the case)
sub get_db_version {
return 'EMPTY' if ! table_exists('problem');
+ return '0072' if constraint_contains('contacts_state_check', 'staff');
return '0071' if table_exists('manifest_theme');
return '0070' if column_like('alert_type', "ref='new_problems'", 'head_title', '{{SITE_NAME}}');
return '0069' if constraint_contains('admin_log_object_type_check', 'template');
diff --git a/db/downgrade_0072---0071.sql b/db/downgrade_0072---0071.sql
new file mode 100644
index 000000000..aa649f63d
--- /dev/null
+++ b/db/downgrade_0072---0071.sql
@@ -0,0 +1,21 @@
+BEGIN;
+
+ALTER TABLE contacts DROP CONSTRAINT contacts_state_check;
+
+ALTER TABLE contacts ADD CONSTRAINT contacts_state_check CHECK (
+ state = 'unconfirmed'
+ or state = 'confirmed'
+ or state = 'inactive'
+ or state = 'deleted'
+);
+
+ALTER TABLE contacts_history DROP CONSTRAINT contacts_history_state_check;
+
+ALTER TABLE contacts_history ADD CONSTRAINT contacts_history_state_check CHECK (
+ state = 'unconfirmed'
+ or state = 'confirmed'
+ or state = 'inactive'
+ or state = 'deleted'
+);
+
+COMMIT;
diff --git a/db/schema.sql b/db/schema.sql
index 0e38ad862..ed21aded6 100644
--- a/db/schema.sql
+++ b/db/schema.sql
@@ -100,6 +100,7 @@ create table contacts (
state = 'unconfirmed'
or state = 'confirmed'
or state = 'inactive'
+ or state = 'staff'
or state = 'deleted'
),
@@ -137,6 +138,7 @@ create table contacts_history (
state = 'unconfirmed'
or state = 'confirmed'
or state = 'inactive'
+ or state = 'staff'
or state = 'deleted'
),
diff --git a/db/schema_0072-add-staff-contact-state.sql b/db/schema_0072-add-staff-contact-state.sql
new file mode 100644
index 000000000..efaac2d20
--- /dev/null
+++ b/db/schema_0072-add-staff-contact-state.sql
@@ -0,0 +1,23 @@
+BEGIN;
+
+ALTER TABLE contacts DROP CONSTRAINT contacts_state_check;
+
+ALTER TABLE contacts ADD CONSTRAINT contacts_state_check CHECK (
+ state = 'unconfirmed'
+ or state = 'confirmed'
+ or state = 'inactive'
+ or state = 'staff'
+ or state = 'deleted'
+);
+
+ALTER TABLE contacts_history DROP CONSTRAINT contacts_history_state_check;
+
+ALTER TABLE contacts_history ADD CONSTRAINT contacts_history_state_check CHECK (
+ state = 'unconfirmed'
+ or state = 'confirmed'
+ or state = 'inactive'
+ or state = 'staff'
+ or state = 'deleted'
+);
+
+COMMIT;
diff --git a/perllib/DBIx/Class/FixMyStreet/EncodedColumn.pm b/perllib/DBIx/Class/FixMyStreet/EncodedColumn.pm
index 3be6e4594..82e6e591e 100644
--- a/perllib/DBIx/Class/FixMyStreet/EncodedColumn.pm
+++ b/perllib/DBIx/Class/FixMyStreet/EncodedColumn.pm
@@ -10,7 +10,7 @@ sub set_column {
my $self = shift;
if ($_[0] eq 'password') {
my $cobrand = $self->result_source->schema->cobrand;
- if ($cobrand->moniker eq 'tfl') {
+ if ($cobrand && $cobrand->moniker eq 'tfl') {
if (defined $_[1]) {
if (defined $_[2]) {
$self->set_extra_metadata(tfl_password => $_[1]);
diff --git a/perllib/FixMyStreet/App/Controller/Report/New.pm b/perllib/FixMyStreet/App/Controller/Report/New.pm
index 14aa7b83e..ef1905c98 100644
--- a/perllib/FixMyStreet/App/Controller/Report/New.pm
+++ b/perllib/FixMyStreet/App/Controller/Report/New.pm
@@ -690,11 +690,7 @@ sub setup_categories_and_bodies : Private {
$c->cobrand->call_hook(munge_report_new_bodies => \%bodies);
- my $contacts #
- = $c #
- ->model('DB::Contact') #
- ->active
- ->search( { 'me.body_id' => [ keys %bodies ] }, { prefetch => 'body' } );
+ my $contacts = $c->model('DB::Contact')->for_new_reports($c, \%bodies);
my @contacts = $c->cobrand->categories_restriction($contacts)->all_sorted;
$c->cobrand->call_hook(munge_report_new_contacts => \@contacts);
diff --git a/perllib/FixMyStreet/Cobrand/Bexley.pm b/perllib/FixMyStreet/Cobrand/Bexley.pm
index 275d7dfaf..0586c18c4 100644
--- a/perllib/FixMyStreet/Cobrand/Bexley.pm
+++ b/perllib/FixMyStreet/Cobrand/Bexley.pm
@@ -212,7 +212,7 @@ sub dashboard_export_problems_add_columns {
my %groups;
if ($c->stash->{body}) {
- %groups = FixMyStreet::DB->resultset('Contact')->active->search({
+ %groups = FixMyStreet::DB->resultset('Contact')->search({
body_id => $c->stash->{body}->id,
})->group_lookup;
}
diff --git a/perllib/FixMyStreet/Cobrand/FixMyStreet.pm b/perllib/FixMyStreet/Cobrand/FixMyStreet.pm
index 97a0ab53a..a2dea1df4 100644
--- a/perllib/FixMyStreet/Cobrand/FixMyStreet.pm
+++ b/perllib/FixMyStreet/Cobrand/FixMyStreet.pm
@@ -131,51 +131,8 @@ sub munge_load_and_group_problems {
return unless $where->{category} && $self->{c}->stash->{body}->name eq 'Isle of Wight Council';
- $where->{category} = $self->expand_triage_cat_list($where->{category});
-}
-
-sub expand_triage_cat_list {
- my ($self, $categories) = @_;
-
- my $b = $self->{c}->stash->{body};
-
- my $all_cats = $self->{c}->model('DB::Contact')->not_deleted->search(
- {
- body_id => $b->id,
- send_method => [{ '!=', 'Triage'}, undef]
- }
- );
-
- my %group_to_category;
- while ( my $cat = $all_cats->next ) {
- next unless $cat->get_extra_metadata('group');
- my $groups = $cat->get_extra_metadata('group');
- $groups = ref $groups eq 'ARRAY' ? $groups : [ $groups ];
- for my $group ( @$groups ) {
- $group_to_category{$group} //= [];
- push @{ $group_to_category{$group} }, $cat->category;
- }
- }
-
- my $cats = $self->{c}->model('DB::Contact')->not_deleted->search(
- {
- body_id => $b->id,
- category => $categories
- }
- );
-
- my @cat_names;
- while ( my $cat = $cats->next ) {
- if ( $cat->send_method && $cat->send_method eq 'Triage' ) {
- # include the category itself
- push @cat_names, $cat->category;
- push @cat_names, @{ $group_to_category{$cat->category} } if $group_to_category{$cat->category};
- } else {
- push @cat_names, $cat->category;
- }
- }
-
- return \@cat_names;
+ my $iow = FixMyStreet::Cobrand->get_class_for_moniker( 'isleofwight' )->new({ c => $self->{c} });
+ $where->{category} = $iow->expand_triage_cat_list($where->{category}, $self->{c}->stash->{body});
}
sub title_list {
diff --git a/perllib/FixMyStreet/Cobrand/IsleOfWight.pm b/perllib/FixMyStreet/Cobrand/IsleOfWight.pm
index a46b540ad..db0a20b9c 100644
--- a/perllib/FixMyStreet/Cobrand/IsleOfWight.pm
+++ b/perllib/FixMyStreet/Cobrand/IsleOfWight.pm
@@ -142,7 +142,7 @@ sub munge_load_and_group_problems {
return unless $where->{category};
- $where->{category} = $self->expand_triage_cat_list($where->{category});
+ $where->{category} = $self->_expand_triage_cat_list($where->{category});
}
sub munge_around_filter_category_list {
@@ -151,17 +151,21 @@ sub munge_around_filter_category_list {
my $c = $self->{c};
return unless $c->stash->{filter_category};
- my $cat_names = $self->expand_triage_cat_list([ keys %{$c->stash->{filter_category}} ]);
+ my $cat_names = $self->_expand_triage_cat_list([ keys %{$c->stash->{filter_category}} ]);
$c->stash->{filter_category} = { map { $_ => 1 } @$cat_names };
}
+sub _expand_triage_cat_list {
+ my ($self, $categories) = @_;
+ my $b = $self->{c}->model('DB::Body')->for_areas( $self->council_area_id )->first;
+ return $self->expand_triage_cat_list($categories, $b);
+}
+
# this assumes that each Triage category has the same name as a group
# and uses this to generate a list of categories that a triage category
# could be triaged to
sub expand_triage_cat_list {
- my ($self, $categories) = @_;
-
- my $b = $self->{c}->model('DB::Body')->for_areas( $self->council_area_id )->first;
+ my ($self, $categories, $b) = @_;
my $all_cats = $self->{c}->model('DB::Contact')->not_deleted->search(
{
@@ -190,7 +194,7 @@ sub expand_triage_cat_list {
my @cat_names;
while ( my $cat = $cats->next ) {
- if ( $cat->send_method eq 'Triage' ) {
+ if ( $cat->send_method && $cat->send_method eq 'Triage' ) {
# include the category itself
push @cat_names, $cat->category;
push @cat_names, @{ $group_to_category{$cat->category} } if $group_to_category{$cat->category};
diff --git a/perllib/FixMyStreet/Cobrand/TfL.pm b/perllib/FixMyStreet/Cobrand/TfL.pm
index d1cfb8d42..40c2a5257 100644
--- a/perllib/FixMyStreet/Cobrand/TfL.pm
+++ b/perllib/FixMyStreet/Cobrand/TfL.pm
@@ -244,7 +244,7 @@ sub dashboard_export_problems_add_columns {
my %groups;
if ($c->stash->{body}) {
- %groups = FixMyStreet::DB->resultset('Contact')->active->search({
+ %groups = FixMyStreet::DB->resultset('Contact')->search({
body_id => $c->stash->{body}->id,
})->group_lookup;
}
diff --git a/perllib/FixMyStreet/DB/ResultSet/Contact.pm b/perllib/FixMyStreet/DB/ResultSet/Contact.pm
index 1643f9931..eb502c190 100644
--- a/perllib/FixMyStreet/DB/ResultSet/Contact.pm
+++ b/perllib/FixMyStreet/DB/ResultSet/Contact.pm
@@ -17,7 +17,7 @@ Filter down to not deleted contacts (so active or inactive).
sub not_deleted {
my $rs = shift;
- return $rs->search( { $rs->me('state') => { '!=' => 'deleted' } } );
+ return $rs->search( { $rs->me('state') => { -not_in => [ 'deleted', 'staff' ] } } );
}
sub active {
@@ -25,6 +25,31 @@ sub active {
$rs->search( { $rs->me('state') => [ 'unconfirmed', 'confirmed' ] } );
}
+sub for_new_reports {
+ my ($rs, $c, $bodies) = @_;
+ my $params = {
+ $rs->me('body_id') => [ keys %$bodies ],
+ };
+
+ if ($c->user_exists && $c->user->from_body) {
+ # Everything normal OR staff state in the user body
+ $params->{'-or'} = [
+ $rs->me('state') => [ 'unconfirmed', 'confirmed' ],
+ {
+ $rs->me('body_id') => $c->user->from_body->id,
+ $rs->me('state') => 'staff',
+ },
+ ];
+ } elsif ($c->user_exists && $c->user->is_superuser) {
+ # Everything normal OR any staff states
+ $params->{$rs->me('state')} = [ 'unconfirmed', 'confirmed', 'staff' ];
+ } else {
+ $params->{$rs->me('state')} = [ 'unconfirmed', 'confirmed' ];
+ }
+
+ $rs->search($params, { prefetch => 'body' });
+}
+
sub translated {
my $rs = shift;
my $schema = $rs->result_source->schema;
diff --git a/perllib/FixMyStreet/Script/CreateSuperuser.pm b/perllib/FixMyStreet/Script/CreateSuperuser.pm
index 69d165abb..cbbea577a 100644
--- a/perllib/FixMyStreet/Script/CreateSuperuser.pm
+++ b/perllib/FixMyStreet/Script/CreateSuperuser.pm
@@ -7,19 +7,27 @@ use FixMyStreet;
use FixMyStreet::DB;
sub createsuperuser {
- die "Specify a single email address and optionally password to create a superuser or grant superuser status to." if (@ARGV < 1 || @ARGV > 2);
+ my ($email, $password) = @_;
- my $user = FixMyStreet::DB->resultset('User')->find_or_new({ email => $ARGV[0] });
+ unless ($email) {
+ warn "Specify a single email address and optionally password to create a superuser or grant superuser status to.\n";
+ return 1;
+ }
+
+ my $user = FixMyStreet::DB->resultset('User')->find_or_new({ email => $email });
if ( !$user->in_storage ) {
- die "Specify a password for this new user." if (@ARGV < 2);
- $user->password($ARGV[1]);
+ unless ($password) {
+ warn "Specify a password for this new user.\n";
+ return 1;
+ }
+ $user->password($password);
$user->is_superuser(1);
$user->insert;
} else {
$user->update({ is_superuser => 1 });
}
print $user->email . " is now a superuser.\n";
+ return 0;
}
-
-1; \ No newline at end of file
+1;
diff --git a/t/app/controller/report_new.t b/t/app/controller/report_new.t
index 1f5ad2b52..b85bae43a 100644
--- a/t/app/controller/report_new.t
+++ b/t/app/controller/report_new.t
@@ -2185,207 +2185,4 @@ subtest "extra google analytics code displayed on email confirmation problem cre
};
};
-
-my $private_perms = $mech->create_user_ok('private_perms@example.org', name => 'private', from_body => $bodies[0]);
-subtest "report_mark_private allows users to mark reports as private" => sub {
- FixMyStreet::override_config {
- ALLOWED_COBRANDS => [ { fixmystreet => '.' } ],
- BASE_URL => 'https://www.fixmystreet.com',
- MAPIT_URL => 'http://mapit.uk/',
- }, sub {
- $mech->log_out_ok;
-
- $private_perms->user_body_permissions->find_or_create({
- body => $bodies[0],
- permission_type => 'report_mark_private',
- });
-
- $mech->log_in_ok('private_perms@example.org');
- $mech->get_ok('/');
- $mech->submit_form_ok( { with_fields => { pc => 'EH1 1BB' } },
- "submit location" );
- $mech->follow_link_ok(
- { text_regex => qr/skip this step/i, },
- "follow 'skip this step' link"
- );
-
- $mech->submit_form_ok(
- {
- with_fields => {
- title => "Private report",
- detail => 'Private report details.',
- photo1 => '',
- name => 'Joe Bloggs',
- may_show_name => '1',
- phone => '07903 123 456',
- category => 'Trees',
- non_public => 1,
- }
- },
- "submit good details"
- );
-
- $mech->content_contains('Great work. Now spread the word', 'shown confirmation page');
- }
-};
-
-my $inspector = $mech->create_user_ok('inspector@example.org', name => 'inspector', from_body => $bodies[0]);
-foreach my $test (
- { non_public => 0 },
- { non_public => 1 },
-) {
- subtest "inspectors get redirected directly to the report page, non_public=$test->{non_public}" => sub {
- FixMyStreet::override_config {
- ALLOWED_COBRANDS => [ { fixmystreet => '.' } ],
- BASE_URL => 'https://www.fixmystreet.com',
- MAPIT_URL => 'http://mapit.uk/',
- }, sub {
- $mech->log_out_ok;
-
- $inspector->user_body_permissions->find_or_create({
- body => $bodies[0],
- permission_type => 'planned_reports',
- });
- $inspector->user_body_permissions->find_or_create({
- body => $bodies[0],
- permission_type => 'report_inspect',
- });
-
- $mech->log_in_ok('inspector@example.org');
- $mech->get_ok('/');
- $mech->submit_form_ok( { with_fields => { pc => 'EH1 1BB' } },
- "submit location" );
- $mech->follow_link_ok(
- { text_regex => qr/skip this step/i, },
- "follow 'skip this step' link"
- );
-
- $mech->submit_form_ok(
- {
- with_fields => {
- title => "Inspector report",
- detail => 'Inspector report details.',
- photo1 => '',
- name => 'Joe Bloggs',
- may_show_name => '1',
- phone => '07903 123 456',
- category => 'Trees',
- non_public => $test->{non_public},
- }
- },
- "submit good details"
- );
-
- like $mech->uri->path, qr/\/report\/[0-9]+/, 'Redirects directly to report';
- }
- };
-}
-
-subtest "check map click ajax response for inspector" => sub {
- $mech->log_out_ok;
-
- my $extra_details;
- $inspector->user_body_permissions->find_or_create({
- body => $bodies[0],
- permission_type => 'planned_reports',
- });
- $inspector->user_body_permissions->find_or_create({
- body => $bodies[0],
- permission_type => 'report_inspect',
- });
-
- $mech->log_in_ok('inspector@example.org');
- FixMyStreet::override_config {
- ALLOWED_COBRANDS => [ { fixmystreet => '.' } ],
- MAPIT_URL => 'http://mapit.uk/',
- }, sub {
- $extra_details = $mech->get_ok_json( '/report/new/ajax?latitude=55.952055&longitude=-3.189579' );
- };
- like $extra_details->{category}, qr/data-prefill="0/, 'inspector prefill not set';
- ok !$extra_details->{contribute_as}, 'no contribute as section';
-};
-
-subtest "check map click ajax response for inspector and uk cobrand" => sub {
- $mech->log_out_ok;
-
- my $extra_details;
- $inspector->user_body_permissions->find_or_create({
- body => $bodies[4],
- permission_type => 'planned_reports',
- });
- $inspector->user_body_permissions->find_or_create({
- body => $bodies[4],
- permission_type => 'report_inspect',
- });
-
- $mech->log_in_ok('inspector@example.org');
- FixMyStreet::override_config {
- ALLOWED_COBRANDS => [ { bromley => '.' } ],
- MAPIT_URL => 'http://mapit.uk/',
- }, sub {
- $extra_details = $mech->get_ok_json( '/report/new/ajax?latitude=51.402096&longitude=0.015784' );
- };
- like $extra_details->{category}, qr/data-prefill="0/, 'inspector prefill not set';
-};
-
-for my $test (
- {
- desc => 'map click ajax for contribute_as_another_user',
- permissions => {
- contribute_as_another_user => 1,
- contribute_as_anonymous_user => undef,
- contribute_as_body => undef,
- }
- },
- {
- desc => 'map click ajax for contribute_as_anonymous_user',
- permissions => {
- contribute_as_another_user => undef,
- contribute_as_anonymous_user => 1,
- contribute_as_body => undef,
- }
- },
- {
- desc => 'map click ajax for contribute_as_body',
- permissions => {
- contribute_as_another_user => undef,
- contribute_as_anonymous_user => undef,
- contribute_as_body => 1,
- }
- },
-) {
- subtest $test->{desc} => sub {
- $mech->log_out_ok;
- my $extra_details;
- (my $name = $test->{desc}) =~ s/.*(contri.*)/$1/;
- my $user = $mech->create_user_ok("$name\@example.org", name => 'test user', from_body => $bodies[0]);
- for my $p ( keys %{$test->{permissions}} ) {
- next unless $test->{permissions}->{$p};
- $user->user_body_permissions->find_or_create({
- body => $bodies[0],
- permission_type => $p,
- });
- }
- $mech->log_in_ok("$name\@example.org");
- FixMyStreet::override_config {
- ALLOWED_COBRANDS => [ { fixmystreet => '.' } ],
- MAPIT_URL => 'http://mapit.uk/',
- }, sub {
- $extra_details = $mech->get_ok_json( '/report/new/ajax?latitude=55.952055&longitude=-3.189579' );
- };
- for my $p ( keys %{$test->{permissions}} ) {
- (my $key = $p) =~ s/contribute_as_//;
- is $extra_details->{contribute_as}->{$key}, $test->{permissions}->{$p}, "$key correctly set";
- }
-
- FixMyStreet::override_config {
- ALLOWED_COBRANDS => [ { fixmystreet => '.' } ],
- MAPIT_URL => 'http://mapit.uk/',
- }, sub {
- $extra_details = $mech->get_ok_json( '/report/new/ajax?latitude=51.754926&longitude=-1.256179' );
- };
- ok !$extra_details->{contribute_as}, 'no contribute as section for other council';
- };
-}
-
done_testing();
diff --git a/t/app/controller/report_new_staff.t b/t/app/controller/report_new_staff.t
new file mode 100644
index 000000000..ee9dff9e4
--- /dev/null
+++ b/t/app/controller/report_new_staff.t
@@ -0,0 +1,257 @@
+use FixMyStreet::TestMech;
+
+# disable info logs for this test run
+FixMyStreet::App->log->disable('info');
+END { FixMyStreet::App->log->enable('info'); }
+
+my $mech = FixMyStreet::TestMech->new;
+
+my %body_ids;
+for my $body (
+ { area_id => 2651, name => 'City of Edinburgh Council' },
+ { area_id => 2482, name => 'Bromley Council' },
+ { area_id => 2237, name => 'Oxfordshire County Council' },
+) {
+ my $body_obj = $mech->create_body_ok($body->{area_id}, $body->{name});
+ $body_ids{$body->{area_id}} = $body_obj->id;
+}
+
+# Let's make some contacts to send things to!
+$mech->create_contact_ok( body_id => $body_ids{2651}, category => 'Street lighting', email => 'highways@example.com' );
+my $edin_trees = $mech->create_contact_ok( body_id => $body_ids{2651}, category => 'Trees', email => 'trees@example.com' );
+$mech->create_contact_ok( body_id => $body_ids{2482}, category => 'Trees', email => 'trees@example.com' );
+$mech->create_contact_ok( body_id => $body_ids{2237}, category => 'Trees', email => 'trees-2247@example.com' );
+
+my $private_perms = $mech->create_user_ok('private_perms@example.org', name => 'private', from_body => $body_ids{2651});
+subtest "report_mark_private allows users to mark reports as private" => sub {
+ FixMyStreet::override_config {
+ ALLOWED_COBRANDS => 'fixmystreet',
+ BASE_URL => 'https://www.fixmystreet.com',
+ MAPIT_URL => 'http://mapit.uk/',
+ }, sub {
+ $mech->log_out_ok;
+
+ $private_perms->user_body_permissions->find_or_create({
+ body_id => $body_ids{2651},
+ permission_type => 'report_mark_private',
+ });
+
+ $mech->log_in_ok('private_perms@example.org');
+ $mech->get_ok('/');
+ $mech->submit_form_ok( { with_fields => { pc => 'EH1 1BB' } },
+ "submit location" );
+ $mech->follow_link_ok(
+ { text_regex => qr/skip this step/i, },
+ "follow 'skip this step' link"
+ );
+
+ $mech->submit_form_ok(
+ {
+ with_fields => {
+ title => "Private report",
+ detail => 'Private report details.',
+ photo1 => '',
+ name => 'Joe Bloggs',
+ may_show_name => '1',
+ phone => '07903 123 456',
+ category => 'Trees',
+ non_public => 1,
+ }
+ },
+ "submit good details"
+ );
+
+ $mech->content_contains('Great work. Now spread the word', 'shown confirmation page');
+ }
+};
+
+my $inspector = $mech->create_user_ok('inspector@example.org', name => 'inspector', from_body => $body_ids{2651});
+foreach my $test (
+ { non_public => 0 },
+ { non_public => 1 },
+) {
+ subtest "inspectors get redirected directly to the report page, non_public=$test->{non_public}" => sub {
+ FixMyStreet::override_config {
+ ALLOWED_COBRANDS => 'fixmystreet',
+ BASE_URL => 'https://www.fixmystreet.com',
+ MAPIT_URL => 'http://mapit.uk/',
+ }, sub {
+ $mech->log_out_ok;
+
+ $inspector->user_body_permissions->find_or_create({
+ body_id => $body_ids{2651},
+ permission_type => 'planned_reports',
+ });
+ $inspector->user_body_permissions->find_or_create({
+ body_id => $body_ids{2651},
+ permission_type => 'report_inspect',
+ });
+
+ $mech->log_in_ok('inspector@example.org');
+ $mech->get_ok('/');
+ $mech->submit_form_ok( { with_fields => { pc => 'EH1 1BB' } },
+ "submit location" );
+ $mech->follow_link_ok(
+ { text_regex => qr/skip this step/i, },
+ "follow 'skip this step' link"
+ );
+
+ $mech->submit_form_ok(
+ {
+ with_fields => {
+ title => "Inspector report",
+ detail => 'Inspector report details.',
+ photo1 => '',
+ name => 'Joe Bloggs',
+ may_show_name => '1',
+ phone => '07903 123 456',
+ category => 'Trees',
+ non_public => $test->{non_public},
+ }
+ },
+ "submit good details"
+ );
+
+ like $mech->uri->path, qr/\/report\/[0-9]+/, 'Redirects directly to report';
+ }
+ };
+}
+
+subtest "check map click ajax response for inspector" => sub {
+ $mech->log_out_ok;
+
+ my $extra_details;
+ $inspector->user_body_permissions->find_or_create({
+ body_id => $body_ids{2651},
+ permission_type => 'planned_reports',
+ });
+ $inspector->user_body_permissions->find_or_create({
+ body_id => $body_ids{2651},
+ permission_type => 'report_inspect',
+ });
+
+ $mech->log_in_ok('inspector@example.org');
+ FixMyStreet::override_config {
+ ALLOWED_COBRANDS => 'fixmystreet',
+ MAPIT_URL => 'http://mapit.uk/',
+ }, sub {
+ $extra_details = $mech->get_ok_json( '/report/new/ajax?latitude=55.952055&longitude=-3.189579' );
+ };
+ like $extra_details->{category}, qr/data-prefill="0/, 'inspector prefill not set';
+ ok !$extra_details->{contribute_as}, 'no contribute as section';
+};
+
+subtest "check map click ajax response for inspector and uk cobrand" => sub {
+ $mech->log_out_ok;
+
+ my $extra_details;
+ $inspector->user_body_permissions->find_or_create({
+ body_id => $body_ids{2482},
+ permission_type => 'planned_reports',
+ });
+ $inspector->user_body_permissions->find_or_create({
+ body_id => $body_ids{2482},
+ permission_type => 'report_inspect',
+ });
+
+ $mech->log_in_ok('inspector@example.org');
+ FixMyStreet::override_config {
+ ALLOWED_COBRANDS => 'bromley',
+ MAPIT_URL => 'http://mapit.uk/',
+ }, sub {
+ $extra_details = $mech->get_ok_json( '/report/new/ajax?latitude=51.402096&longitude=0.015784' );
+ };
+ like $extra_details->{category}, qr/data-prefill="0/, 'inspector prefill not set';
+};
+
+for my $test (
+ {
+ desc => 'map click ajax for contribute_as_another_user',
+ permissions => {
+ contribute_as_another_user => 1,
+ contribute_as_anonymous_user => undef,
+ contribute_as_body => undef,
+ }
+ },
+ {
+ desc => 'map click ajax for contribute_as_anonymous_user',
+ permissions => {
+ contribute_as_another_user => undef,
+ contribute_as_anonymous_user => 1,
+ contribute_as_body => undef,
+ }
+ },
+ {
+ desc => 'map click ajax for contribute_as_body',
+ permissions => {
+ contribute_as_another_user => undef,
+ contribute_as_anonymous_user => undef,
+ contribute_as_body => 1,
+ }
+ },
+) {
+ subtest $test->{desc} => sub {
+ $mech->log_out_ok;
+ my $extra_details;
+ (my $name = $test->{desc}) =~ s/.*(contri.*)/$1/;
+ my $user = $mech->create_user_ok("$name\@example.org", name => 'test user', from_body => $body_ids{2651});
+ for my $p ( keys %{$test->{permissions}} ) {
+ next unless $test->{permissions}->{$p};
+ $user->user_body_permissions->find_or_create({
+ body_id => $body_ids{2651},
+ permission_type => $p,
+ });
+ }
+ $mech->log_in_ok("$name\@example.org");
+ FixMyStreet::override_config {
+ ALLOWED_COBRANDS => 'fixmystreet',
+ MAPIT_URL => 'http://mapit.uk/',
+ }, sub {
+ $extra_details = $mech->get_ok_json( '/report/new/ajax?latitude=55.952055&longitude=-3.189579' );
+ };
+ for my $p ( keys %{$test->{permissions}} ) {
+ (my $key = $p) =~ s/contribute_as_//;
+ is $extra_details->{contribute_as}->{$key}, $test->{permissions}->{$p}, "$key correctly set";
+ }
+
+ FixMyStreet::override_config {
+ ALLOWED_COBRANDS => 'fixmystreet',
+ MAPIT_URL => 'http://mapit.uk/',
+ }, sub {
+ $extra_details = $mech->get_ok_json( '/report/new/ajax?latitude=51.754926&longitude=-1.256179' );
+ };
+ ok !$extra_details->{contribute_as}, 'no contribute as section for other council';
+ };
+}
+
+subtest 'staff-only categories when reporting' => sub {
+ FixMyStreet::override_config {
+ MAPIT_URL => 'http://mapit.uk/',
+ MAPIT_TYPES => ['UTA'],
+ }, sub {
+ $inspector->update({ is_superuser => 1 });
+ $mech->log_in_ok('inspector@example.org');
+
+ $mech->get_ok('/admin/body/' . $body_ids{2651} . '/Trees');
+ $mech->submit_form_ok({ with_fields => { state => 'staff' } }, 'mark Trees as staff-only');
+ $edin_trees->discard_changes;
+ is $edin_trees->state, 'staff', 'category is staff only';
+
+ my $extra_details = $mech->get_ok_json( '/report/new/ajax?latitude=55.952055&longitude=-3.189579' );
+ is_deeply [ sort keys %{$extra_details->{by_category}} ], [ 'Street lighting', 'Trees' ], 'Superuser can see staff-only category';
+
+ $inspector->update({ is_superuser => 0 });
+ $extra_details = $mech->get_ok_json( '/report/new/ajax?latitude=55.952055&longitude=-3.189579' );
+ is_deeply [ sort keys %{$extra_details->{by_category}} ], [ 'Street lighting', 'Trees' ], 'Body staff user can see staff-only category';
+
+ $inspector->update({ from_body => $body_ids{2482} });
+ $extra_details = $mech->get_ok_json( '/report/new/ajax?latitude=55.952055&longitude=-3.189579' );
+ is_deeply [ sort keys %{$extra_details->{by_category}} ], [ 'Street lighting' ], 'Different body staff user cannot see staff-only category';
+
+ $mech->log_out_ok;
+ $extra_details = $mech->get_ok_json( '/report/new/ajax?latitude=55.952055&longitude=-3.189579' );
+ is_deeply [ sort keys %{$extra_details->{by_category}} ], [ 'Street lighting' ], 'Normal user cannot see staff-only category';
+ };
+};
+
+done_testing;
diff --git a/t/script/createsuperuser.t b/t/script/createsuperuser.t
new file mode 100644
index 000000000..1d4826111
--- /dev/null
+++ b/t/script/createsuperuser.t
@@ -0,0 +1,23 @@
+use Test::More;
+use Test::Output;
+
+use_ok 'FixMyStreet::Script::CreateSuperuser';
+
+stderr_like { FixMyStreet::Script::CreateSuperuser::createsuperuser(); }
+ qr/Specify a single email address/, 'Email error shown';
+stderr_is { FixMyStreet::Script::CreateSuperuser::createsuperuser('test@example.org'); }
+ "Specify a password for this new user.\n", 'Password error shown';
+stdout_is { FixMyStreet::Script::CreateSuperuser::createsuperuser('test@example.org', 'password'); }
+ "test\@example.org is now a superuser.\n", 'Correct message shown';
+
+my $user = FixMyStreet::DB->resultset("User")->find({ email => 'test@example.org' });
+ok $user, 'user created';
+is $user->is_superuser, 1, 'is a superuser';
+
+$user->update({ is_superuser => 0 });
+stdout_is { FixMyStreet::Script::CreateSuperuser::createsuperuser('test@example.org'); }
+ "test\@example.org is now a superuser.\n", 'Correct message shown';
+$user->discard_changes;
+is $user->is_superuser, 1, 'is a superuser again';
+
+done_testing;
diff --git a/templates/web/base/admin/bodies/contact-form.html b/templates/web/base/admin/bodies/contact-form.html
index 921cb1380..b698fcea2 100644
--- a/templates/web/base/admin/bodies/contact-form.html
+++ b/templates/web/base/admin/bodies/contact-form.html
@@ -40,6 +40,11 @@
<label for="state-deleted">[% loc('Deleted') %]</label>
<p class="form-hint" id="state-deleted-hint">[% loc('Prevent new reports from using this category, <em>and</em> also remove it from map filters.') %]</p>
</div>
+ <div class="form-check form-check--inline">
+ <input type="radio" name="state" id="state-staff" aria-describedby="state-staff-hint" value="staff"[% ' checked' IF contact.state == 'staff' %]>
+ <label for="state-staff">[% loc('Staff only') %]</label>
+ <p class="form-hint" id="state-staff-hint">[% loc('Only staff users will be able to add reports in this category.') %]</p>
+ </div>
</fieldset>
<p class="form-check">