aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--db/schema.sql6
-rw-r--r--db/schema_0005-add_abuse_flags_to_users_and_reports.sql9
-rw-r--r--perllib/FixMyStreet/App/Controller/Admin.pm323
-rw-r--r--perllib/FixMyStreet/App/Controller/Contact.pm4
-rw-r--r--perllib/FixMyStreet/DB/Result/Problem.pm2
-rw-r--r--perllib/FixMyStreet/DB/Result/User.pm2
-rw-r--r--t/app/controller/admin.t195
-rw-r--r--t/app/controller/contact.t11
-rw-r--r--templates/web/default/admin/list_flagged.html51
-rw-r--r--templates/web/default/admin/problem_row.html30
-rw-r--r--templates/web/default/admin/report_blocks.html10
-rw-r--r--templates/web/default/admin/report_edit.html3
-rw-r--r--templates/web/default/admin/search_abuse.html21
-rw-r--r--templates/web/default/admin/search_reports.html31
-rw-r--r--templates/web/default/admin/update_edit.html2
15 files changed, 580 insertions, 120 deletions
diff --git a/db/schema.sql b/db/schema.sql
index 9c5b3d8fd..53c236dd6 100644
--- a/db/schema.sql
+++ b/db/schema.sql
@@ -130,7 +130,8 @@ create table users (
email text not null unique,
name text,
phone text,
- password text not null default ''
+ password text not null default '',
+ flagged boolean not null default 'f'
);
-- Problems reported by users of site
@@ -175,7 +176,8 @@ create table problem (
cobrand_data text not null default '' check (cobrand_data ~* '^[a-z0-9]*$'), -- Extra data used in cobranded versions of the site
lastupdate timestamp not null default ms_current_timestamp(),
whensent timestamp,
- send_questionnaire boolean not null default 't'
+ send_questionnaire boolean not null default 't',
+ flagged boolean not null default 'f'
);
create index problem_state_latitude_longitude_idx on problem(state, latitude, longitude);
create index problem_user_id_idx on problem ( user_id );
diff --git a/db/schema_0005-add_abuse_flags_to_users_and_reports.sql b/db/schema_0005-add_abuse_flags_to_users_and_reports.sql
new file mode 100644
index 000000000..040d3294d
--- /dev/null
+++ b/db/schema_0005-add_abuse_flags_to_users_and_reports.sql
@@ -0,0 +1,9 @@
+begin;
+
+ALTER table problem
+ ADD column flagged BOOL NOT NULL DEFAULT 'f';
+
+ALTER table users
+ ADD column flagged BOOL NOT NULL DEFAULT 'f';
+
+commit;
diff --git a/perllib/FixMyStreet/App/Controller/Admin.pm b/perllib/FixMyStreet/App/Controller/Admin.pm
index 3854e27aa..a7b0bda0f 100644
--- a/perllib/FixMyStreet/App/Controller/Admin.pm
+++ b/perllib/FixMyStreet/App/Controller/Admin.pm
@@ -448,9 +448,11 @@ sub search_reports : Path('search_reports') {
}
);
+ # we need to pass this in as an array as we can't
+ # query the object in the template as the quoting
+ # will have been turned off
$c->stash->{problems} = [ $problems->all ];
-
$c->stash->{edit_council_contacts} = 1
if ( grep {$_ eq 'councilcontacts'} keys %{$c->stash->{allowed_pages}});
@@ -499,6 +501,7 @@ sub report_edit : Path('report_edit') : Args(1) {
$c->forward('get_token');
$c->forward('check_page_allowed');
+ $c->forward('check_email_for_abuse', [ $problem->user->email ] );
$c->stash->{updates} =
[ $c->model('DB::Comment')
@@ -515,6 +518,17 @@ sub report_edit : Path('report_edit') : Args(1) {
$c->forward( 'log_edit', [ $id, 'problem', 'resend' ] );
}
+ elsif ( $c->req->param('flaguser') ) {
+ $c->forward('flag_user');
+ $c->stash->{problem}->discard_changes;
+ }
+ elsif ( $c->req->param('removeuserflag') ) {
+ $c->forward('remove_user_flag');
+ $c->stash->{problem}->discard_changes;
+ }
+ elsif ( $c->req->param('banuser') ) {
+ $c->forward('ban_user');
+ }
elsif ( $c->req->param('submit') ) {
$c->forward('check_token');
@@ -534,12 +548,15 @@ sub report_edit : Path('report_edit') : Args(1) {
$done = 1;
}
+ my $flagged = $c->req->param('flagged') ? 1 : 0;
+
# do this here so before we update the values in problem
if ( $c->req->param('anonymous') ne $problem->anonymous
|| $c->req->param('name') ne $problem->name
|| $c->req->param('email') ne $problem->user->email
|| $c->req->param('title') ne $problem->title
- || $c->req->param('detail') ne $problem->detail )
+ || $c->req->param('detail') ne $problem->detail
+ || $flagged != $problem->flagged )
{
$edited = 1;
}
@@ -549,6 +566,7 @@ sub report_edit : Path('report_edit') : Args(1) {
$problem->detail( $c->req->param('detail') );
$problem->state( $c->req->param('state') );
$problem->name( $c->req->param('name') );
+ $problem->flagged( $flagged );
if ( $c->req->param('email') ne $problem->user->email ) {
my $user = $c->model('DB::User')->find_or_create(
@@ -596,6 +614,147 @@ sub report_edit : Path('report_edit') : Args(1) {
return 1;
}
+sub update_edit : Path('update_edit') : Args(1) {
+ my ( $self, $c, $id ) = @_;
+
+ my ( $site_res_sql, $site_key, $site_restriction ) =
+ $c->cobrand->site_restriction;
+ my $update = $c->model('DB::Comment')->search(
+ {
+ id => $id,
+ %{$site_restriction},
+ }
+ )->first;
+
+ $c->detach( '/page_error_404_not_found',
+ [ _('The requested URL was not found on this server.') ] )
+ unless $update;
+
+ $c->forward('get_token');
+ $c->forward('check_page_allowed');
+
+ $c->stash->{update} = $update;
+
+ $c->forward('check_email_for_abuse', [ $update->user->email ] );
+
+ if ( $c->req->param('banuser') ) {
+ $c->forward('ban_user');
+ }
+ elsif ( $c->req->param('flaguser') ) {
+ $c->forward('flag_user');
+ $c->stash->{update}->discard_changes;
+ }
+ elsif ( $c->req->param('removeuserflag') ) {
+ $c->forward('remove_user_flag');
+ $c->stash->{update}->discard_changes;
+ }
+ elsif ( $c->req->param('submit') ) {
+ $c->forward('check_token');
+
+ my $old_state = $update->state;
+ my $new_state = $c->req->param('state');
+
+ my $edited = 0;
+
+ # $update->name can be null which makes ne unhappy
+ my $name = $update->name || '';
+
+ if ( $c->req->param('name') ne $name
+ || $c->req->param('email') ne $update->user->email
+ || $c->req->param('anonymous') ne $update->anonymous
+ || $c->req->param('text') ne $update->text ){
+ $edited = 1;
+ }
+
+ if ( $c->req->param('remove_photo') ) {
+ $update->photo(undef);
+ }
+
+ $update->name( $c->req->param('name') || '' );
+ $update->text( $c->req->param('text') );
+ $update->anonymous( $c->req->param('anonymous') );
+ $update->state( $c->req->param('state') );
+
+ if ( $c->req->param('email') ne $update->user->email ) {
+ my $user =
+ $c->model('DB::User')
+ ->find_or_create( { email => $c->req->param('email') } );
+
+ $user->insert unless $user->in_storage;
+ $update->user($user);
+ }
+
+ if ( $new_state eq 'confirmed' and $old_state eq 'unconfirmed' ) {
+ $update->confirmed( \'ms_current_timestamp()' );
+ }
+
+ $update->update;
+
+ $c->stash->{status_message} = '<p><em>' . _('Updated!') . '</em></p>';
+
+ # If we're hiding an update, see if it marked as fixed and unfix if so
+ if ( $new_state eq 'hidden' && $update->mark_fixed ) {
+ if ( $update->problem->state eq 'fixed' ) {
+ $update->problem->state('confirmed');
+ $update->problem->update;
+ }
+
+ $c->stash->{status_message} .=
+ '<p><em>' . _('Problem marked as open.') . '</em></p>';
+ }
+
+ if ( $new_state ne $old_state ) {
+ $c->forward( 'log_edit',
+ [ $update->id, 'update', 'state_change' ] );
+ }
+
+ if ($edited) {
+ $c->forward( 'log_edit', [ $update->id, 'update', 'edit' ] );
+ }
+
+ }
+
+ return 1;
+}
+
+sub search_abuse : Path('search_abuse') : Args(0) {
+ my ( $self, $c ) = @_;
+
+ $c->forward('check_page_allowed');
+
+ my $search = $c->req->param('search');
+
+ if ($search) {
+ my $emails = $c->model('DB::Abuse')->search(
+ {
+ email => { ilike => "\%$search\%" }
+ }
+ );
+
+ $c->stash->{emails} = [ $emails->all ];
+ }
+
+ return 1;
+}
+
+sub list_flagged : Path('list_flagged') : Args(0) {
+ my ( $self, $c ) = @_;
+
+ $c->forward('check_page_allowed');
+
+ my $problems = $c->model('DB::Problem')->search( { flagged => 1 } );
+
+ # pass in as array ref as using same template as search_reports
+ # which has to use an array ref for sql quoting reasons
+ $c->stash->{problems} = [ $problems->all ];
+
+ my $users = $c->model('DB::User')->search( { flagged => 1 } );
+
+ $c->stash->{users} = $users;
+
+ return 1;
+}
+
=head2 set_allowed_pages
Sets up the allowed_pages stash entry for checking if the current page is
@@ -615,10 +774,13 @@ sub set_allowed_pages : Private {
'search_reports' => [_('Search Reports'), 2],
'timeline' => [_('Timeline'), 3],
'questionnaire' => [_('Survey Results'), 4],
- 'council_contacts' => [undef, undef],
- 'council_edit' => [undef, undef],
- 'report_edit' => [undef, undef],
- 'update_edit' => [undef, undef],
+ 'search_abuse' => [_('Search Abuse'), 5],
+ 'list_flagged' => [_('List Flagged'), 6],
+ 'council_contacts' => [undef, undef],
+ 'council_edit' => [undef, undef],
+ 'report_edit' => [undef, undef],
+ 'update_edit' => [undef, undef],
+ 'abuse_edit' => [undef, undef],
}
}
@@ -688,98 +850,115 @@ sub log_edit : Private {
)->insert();
}
-sub update_edit : Path('update_edit') : Args(1) {
- my ( $self, $c, $id ) = @_;
+=head2 ban_user
- my ( $site_res_sql, $site_key, $site_restriction ) =
- $c->cobrand->site_restriction;
- my $update = $c->model('DB::Comment')->search(
- {
- id => $id,
- %{$site_restriction},
- }
- )->first;
+Add the email address in the email param of the request object to
+the abuse table if they are not already in there and sets status_message
+accordingly
- $c->detach( '/page_error_404_not_found',
- [ _('The requested URL was not found on this server.') ] )
- unless $update;
+=cut
- $c->forward('get_token');
- $c->forward('check_page_allowed');
+sub ban_user : Private {
+ my ( $self, $c ) = @_;
- $c->stash->{update} = $update;
+ my $email = $c->req->param('email');
- my $status_message = '';
- if ( $c->req->param('submit') ) {
- $c->forward('check_token');
+ return unless $email;
- my $old_state = $update->state;
- my $new_state = $c->req->param('state');
+ my $abuse = $c->model('DB::Abuse')->find_or_new({ email => $email });
- my $edited = 0;
+ if ( $abuse->in_storage ) {
+ $c->stash->{status_message} = _('Email already in abuse list');
+ } else {
+ $abuse->insert;
+ $c->stash->{status_message} = _('Email added to abuse list');
+ }
- # $update->name can be null which makes ne unhappy
- my $name = $update->name || '';
+ $c->stash->{email_in_abuse} = 1;
- if ( $c->req->param('name') ne $name
- || $c->req->param('email') ne $update->user->email
- || $c->req->param('anonymous') ne $update->anonymous
- || $c->req->param('text') ne $update->text ){
- $edited = 1;
- }
+ return 1;
+}
- if ( $c->req->param('remove_photo') ) {
- $update->photo(undef);
- }
+=head2 flag_user
- $update->name( $c->req->param('name') || '' );
- $update->text( $c->req->param('text') );
- $update->anonymous( $c->req->param('anonymous') );
- $update->state( $c->req->param('state') );
+Sets the flag on a user with the given email
- if ( $c->req->param('email') ne $update->user->email ) {
- my $user =
- $c->model('DB::User')
- ->find_or_create( { email => $c->req->param('email') } );
+=cut
- $user->insert unless $user->in_storage;
- $update->user($user);
- }
+sub flag_user : Private {
+ my ( $self, $c ) = @_;
- if ( $new_state eq 'confirmed' and $old_state eq 'unconfirmed' ) {
- $update->confirmed( \'ms_current_timestamp()' );
- }
+ my $email = $c->req->param('email');
- $update->update;
+ return unless $email;
- $status_message = '<p><em>' . _('Updated!') . '</em></p>';
+ my $user = $c->model('DB::User')->find({ email => $email });
- # If we're hiding an update, see if it marked as fixed and unfix if so
- if ( $new_state eq 'hidden' && $update->mark_fixed ) {
- if ( $update->problem->state eq 'fixed' ) {
- $update->problem->state('confirmed');
- $update->problem->update;
- }
+ if ( !$user ) {
+ $c->stash->{status_message} = _('Could not find user');
+ } else {
+ $user->flagged(1);
+ $user->update;
+ $c->stash->{status_message} = _('User flagged');
+ }
- $status_message .=
- '<p><em>' . _('Problem marked as open.') . '</em></p>';
- }
+ $c->stash->{user_flagged} = 1;
- if ( $new_state ne $old_state ) {
- $c->forward( 'log_edit',
- [ $update->id, 'update', 'state_change' ] );
- }
+ return 1;
+}
- if ($edited) {
- $c->forward( 'log_edit', [ $update->id, 'update', 'edit' ] );
- }
+=head2 remove_user_flag
+
+Remove the flag on a user with the given email
+
+=cut
+
+sub remove_user_flag : Private {
+ my ( $self, $c ) = @_;
+
+ my $email = $c->req->param('email');
+
+ return unless $email;
+
+ my $user = $c->model('DB::User')->find({ email => $email });
+ if ( !$user ) {
+ $c->stash->{status_message} = _('Could not find user');
+ } else {
+ $user->flagged(0);
+ $user->update;
+ $c->stash->{status_message} = _('User flag removed');
}
- $c->stash->{status_message} = $status_message;
return 1;
}
+
+=head2 check_email_for_abuse
+
+ $c->forward('check_email_for_abuse', [ $email ] );
+
+Checks if $email is in the abuse table and sets email_in_abuse accordingly
+
+=cut
+
+sub check_email_for_abuse : Private {
+ my ( $self, $c, $email ) =@_;
+
+ my $is_abuse = $c->model('DB::Abuse')->find({ email => $email });
+
+ $c->stash->{email_in_abuse} = 1 if $is_abuse;
+
+ return 1;
+}
+
+=head2 check_page_allowed
+
+Checks if the current catalyst action is in the list of allowed pages and
+if not then redirects to 404 error page.
+
+=cut
+
sub check_page_allowed : Private {
my ( $self, $c ) = @_;
diff --git a/perllib/FixMyStreet/App/Controller/Contact.pm b/perllib/FixMyStreet/App/Controller/Contact.pm
index 9d7051e2f..f28d37989 100644
--- a/perllib/FixMyStreet/App/Controller/Contact.pm
+++ b/perllib/FixMyStreet/App/Controller/Contact.pm
@@ -164,6 +164,10 @@ sub prepare_params_for_email : Private {
$c->stash->{problem}->id,
$problem_url, $admin_url
);
+
+ # flag this so it's automatically listed in the admin interface
+ $c->stash->{problem}->flagged(1);
+ $c->stash->{problem}->update;
}
return 1;
diff --git a/perllib/FixMyStreet/DB/Result/Problem.pm b/perllib/FixMyStreet/DB/Result/Problem.pm
index 2df26fde3..6472b91db 100644
--- a/perllib/FixMyStreet/DB/Result/Problem.pm
+++ b/perllib/FixMyStreet/DB/Result/Problem.pm
@@ -78,6 +78,8 @@ __PACKAGE__->add_columns(
{ data_type => "timestamp", is_nullable => 1 },
"send_questionnaire",
{ data_type => "boolean", default_value => \"true", is_nullable => 0 },
+ "flagged",
+ { data_type => "boolean", default_value => \"false", is_nullable => 0 },
);
__PACKAGE__->set_primary_key("id");
__PACKAGE__->has_many(
diff --git a/perllib/FixMyStreet/DB/Result/User.pm b/perllib/FixMyStreet/DB/Result/User.pm
index 4ee413a58..cf4fc56d5 100644
--- a/perllib/FixMyStreet/DB/Result/User.pm
+++ b/perllib/FixMyStreet/DB/Result/User.pm
@@ -26,6 +26,8 @@ __PACKAGE__->add_columns(
{ data_type => "text", is_nullable => 1 },
"password",
{ data_type => "text", default_value => "", is_nullable => 0 },
+ "flagged",
+ { data_type => "boolean", default_value => \"false", is_nullable => 0 },
);
__PACKAGE__->set_primary_key("id");
__PACKAGE__->add_unique_constraint("users_email_key", ["email"]);
diff --git a/t/app/controller/admin.t b/t/app/controller/admin.t
index 4e2ec82fe..8d55bbc18 100644
--- a/t/app/controller/admin.t
+++ b/t/app/controller/admin.t
@@ -233,6 +233,7 @@ foreach my $test (
name => 'Test User',
email => $user->email,
anonymous => 0,
+ flagged => undef,
},
changes => {
title => 'Edited Report',
@@ -250,6 +251,7 @@ foreach my $test (
name => 'Test User',
email => $user->email,
anonymous => 0,
+ flagged => undef,
},
changes => {
detail => 'Edited Detail',
@@ -267,6 +269,7 @@ foreach my $test (
name => 'Test User',
email => $user->email,
anonymous => 0,
+ flagged => undef,
},
changes => {
name => 'Edited User',
@@ -277,7 +280,7 @@ foreach my $test (
user => $user,
},
{
- description => 'edit report user email',
+ description => 'edit report set flagged true',
fields => {
title => 'Edited Report',
detail => 'Edited Detail',
@@ -285,13 +288,33 @@ foreach my $test (
name => 'Edited User',
email => $user->email,
anonymous => 0,
+ flagged => undef,
},
changes => {
- email => $user2->email,
+ flagged => 'on',
},
log_count => 4,
log_entries => [ qw/edit edit edit edit/ ],
resend => 0,
+ user => $user,
+ },
+ {
+ description => 'edit report user email',
+ fields => {
+ title => 'Edited Report',
+ detail => 'Edited Detail',
+ state => 'confirmed',
+ name => 'Edited User',
+ email => $user->email,
+ anonymous => 0,
+ flagged => 'on',
+ },
+ changes => {
+ email => $user2->email,
+ },
+ log_count => 5,
+ log_entries => [ qw/edit edit edit edit edit/ ],
+ resend => 0,
user => $user2,
},
{
@@ -303,12 +326,13 @@ foreach my $test (
name => 'Edited User',
email => $user2->email,
anonymous => 0,
+ flagged => 'on',
},
changes => {
state => 'unconfirmed'
},
- log_count => 5,
- log_entries => [ qw/state_change edit edit edit edit/ ],
+ log_count => 6,
+ log_entries => [ qw/state_change edit edit edit edit edit/ ],
resend => 0,
},
{
@@ -320,12 +344,13 @@ foreach my $test (
name => 'Edited User',
email => $user2->email,
anonymous => 0,
+ flagged => 'on',
},
changes => {
state => 'confirmed'
},
- log_count => 6,
- log_entries => [ qw/state_change state_change edit edit edit edit/ ],
+ log_count => 7,
+ log_entries => [ qw/state_change state_change edit edit edit edit edit/ ],
resend => 0,
},
{
@@ -337,12 +362,13 @@ foreach my $test (
name => 'Edited User',
email => $user2->email,
anonymous => 0,
+ flagged => 'on',
},
changes => {
state => 'fixed'
},
- log_count => 7,
- log_entries => [ qw/state_change state_change state_change edit edit edit edit/ ],
+ log_count => 8,
+ log_entries => [ qw/state_change state_change state_change edit edit edit edit edit/ ],
resend => 0,
},
{
@@ -354,12 +380,13 @@ foreach my $test (
name => 'Edited User',
email => $user2->email,
anonymous => 0,
+ flagged => 'on',
},
changes => {
state => 'hidden'
},
- log_count => 8,
- log_entries => [ qw/state_change state_change state_change state_change edit edit edit edit/ ],
+ log_count => 9,
+ log_entries => [ qw/state_change state_change state_change state_change edit edit edit edit edit/ ],
resend => 0,
},
{
@@ -371,13 +398,14 @@ foreach my $test (
name => 'Edited User',
email => $user2->email,
anonymous => 0,
+ flagged => 'on',
},
changes => {
state => 'confirmed',
anonymous => 1,
},
- log_count => 10,
- log_entries => [ qw/edit state_change state_change state_change state_change state_change edit edit edit edit/ ],
+ log_count => 11,
+ log_entries => [ qw/edit state_change state_change state_change state_change state_change edit edit edit edit edit/ ],
resend => 0,
},
{
@@ -389,11 +417,12 @@ foreach my $test (
name => 'Edited User',
email => $user2->email,
anonymous => 1,
+ flagged => 'on',
},
changes => {
},
- log_count => 11,
- log_entries => [ qw/resend edit state_change state_change state_change state_change state_change edit edit edit edit/ ],
+ log_count => 12,
+ log_entries => [ qw/resend edit state_change state_change state_change state_change state_change edit edit edit edit edit/ ],
resend => 1,
},
) {
@@ -426,6 +455,7 @@ foreach my $test (
$mech->content_lacks( 'type="submit" name="resend"', 'no resend button' );
}
+ $test->{changes}->{flagged} = 1 if $test->{changes}->{flagged};
is $report->$_, $test->{changes}->{$_}, "$_ updated" for grep { $_ ne 'email' } keys %{ $test->{changes} };
if ( $test->{user} ) {
@@ -449,6 +479,7 @@ subtest 'change email to new user' => sub {
name => $report->name,
email => $report->user->email,
anonymous => 1,
+ flagged => 'on',
};
is_deeply( $mech->visible_form_values(), $fields, 'initial form values' );
@@ -488,6 +519,65 @@ subtest 'change email to new user' => sub {
is $report->user_id, $user3->id, 'user changed to new user';
};
+subtest 'adding email to abuse list from report page' => sub {
+ my $email = $report->user->email;
+
+ my $abuse = FixMyStreet::App->model('DB::Abuse')->find( { email => $email } );
+ $abuse->delete if $abuse;
+
+ $mech->get_ok( '/admin/report_edit/' . $report->id );
+ $mech->content_contains('Ban email address');
+
+ $mech->click_ok('banuser');
+
+ $mech->content_contains('Email added to abuse list');
+ $mech->content_contains('<small>(Email in abuse table)</small>');
+
+ $abuse = FixMyStreet::App->model('DB::Abuse')->find( { email => $email } );
+ ok $abuse, 'entry created in abuse table';
+
+ $mech->get_ok( '/admin/report_edit/' . $report->id );
+ $mech->content_contains('<small>(Email in abuse table)</small>');
+};
+
+subtest 'flagging user from report page' => sub {
+ $report->user->flagged(0);
+ $report->user->update;
+
+ $mech->get_ok( '/admin/report_edit/' . $report->id );
+ $mech->content_contains('Flag user');
+
+ $mech->click_ok('flaguser');
+
+ $mech->content_contains('User flagged');
+ $mech->content_contains('Remove flag');
+
+ $report->discard_changes;
+ ok $report->user->flagged, 'user flagged';
+
+ $mech->get_ok( '/admin/report_edit/' . $report->id );
+ $mech->content_contains('Remove flag');
+};
+
+subtest 'unflagging user from report page' => sub {
+ $report->user->flagged(1);
+ $report->user->update;
+
+ $mech->get_ok( '/admin/report_edit/' . $report->id );
+ $mech->content_contains('Remove flag');
+
+ $mech->click_ok('removeuserflag');
+
+ $mech->content_contains('User flag removed');
+ $mech->content_contains('Flag user');
+
+ $report->discard_changes;
+ ok !$report->user->flagged, 'user not flagged';
+
+ $mech->get_ok( '/admin/report_edit/' . $report->id );
+ $mech->content_contains('Flag user');
+};
+
$log_entries->delete;
my $update = FixMyStreet::App->model('DB::Comment')->create(
@@ -668,6 +758,65 @@ subtest 'editing update email creates new user if required' => sub {
is $update->user->id, $user->id, 'update set to new user';
};
+subtest 'adding email to abuse list from update page' => sub {
+ my $email = $update->user->email;
+
+ my $abuse = FixMyStreet::App->model('DB::Abuse')->find( { email => $email } );
+ $abuse->delete if $abuse;
+
+ $mech->get_ok( '/admin/update_edit/' . $update->id );
+ $mech->content_contains('Ban email address');
+
+ $mech->click_ok('banuser');
+
+ $mech->content_contains('Email added to abuse list');
+ $mech->content_contains('<small>(Email in abuse table)</small>');
+
+ $abuse = FixMyStreet::App->model('DB::Abuse')->find( { email => $email } );
+ ok $abuse, 'entry created in abuse table';
+
+ $mech->get_ok( '/admin/update_edit/' . $update->id );
+ $mech->content_contains('<small>(Email in abuse table)</small>');
+};
+
+subtest 'flagging user from update page' => sub {
+ $update->user->flagged(0);
+ $update->user->update;
+
+ $mech->get_ok( '/admin/update_edit/' . $update->id );
+ $mech->content_contains('Flag user');
+
+ $mech->click_ok('flaguser');
+
+ $mech->content_contains('User flagged');
+ $mech->content_contains('Remove flag');
+
+ $update->discard_changes;
+ ok $update->user->flagged, 'user flagged';
+
+ $mech->get_ok( '/admin/update_edit/' . $update->id );
+ $mech->content_contains('Remove flag');
+};
+
+subtest 'unflagging user from update page' => sub {
+ $update->user->flagged(1);
+ $update->user->update;
+
+ $mech->get_ok( '/admin/update_edit/' . $update->id );
+ $mech->content_contains('Remove flag');
+
+ $mech->click_ok('removeuserflag');
+
+ $mech->content_contains('User flag removed');
+ $mech->content_contains('Flag user');
+
+ $update->discard_changes;
+ ok !$update->user->flagged, 'user not flagged';
+
+ $mech->get_ok( '/admin/update_edit/' . $update->id );
+ $mech->content_contains('Flag user');
+};
+
subtest 'hiding comment marked as fixed reopens report' => sub {
$update->mark_fixed( 1 );
$update->update;
@@ -724,6 +873,24 @@ subtest 'report search' => sub {
$mech->content_like( qr{<tr [^>]*hidden[^>]*> \s* <td> \s* $r_id \s* </td>}xs );
};
+subtest 'search abuse' => sub {
+ $mech->get_ok( '/admin/search_abuse?search=example' );
+
+ $mech->content_contains('test4@example.com');
+};
+
+subtest 'show flagged entries' => sub {
+ $report->flagged( 1 );
+ $report->update;
+
+ $user->flagged( 1 );
+ $user->update;
+
+ $mech->get_ok('/admin/list_flagged');
+ $mech->content_contains( $report->title );
+ $mech->content_contains( $user->email );
+};
+
$mech->delete_user( $user );
$mech->delete_user( $user2 );
$mech->delete_user( $user3 );
diff --git a/t/app/controller/contact.t b/t/app/controller/contact.t
index bbb3a0f83..86b845eb3 100644
--- a/t/app/controller/contact.t
+++ b/t/app/controller/contact.t
@@ -248,6 +248,9 @@ for my $test (
)
{
subtest 'check email sent correctly' => sub {
+ $problem_main->discard_changes;
+ ok !$problem_main->flagged, 'problem not flagged';
+
$mech->clear_emails_ok;
if ($test->{fields}{id}) {
$mech->get_ok('/contact?id=' . $test->{fields}{id});
@@ -267,6 +270,14 @@ for my $test (
my $problem_id = $test->{fields}{id};
like $email->body, qr/Complaint about report $problem_id/, 'reporting a report'
if $test->{fields}{id};
+
+ $problem_main->discard_changes;
+ if ( $problem_id ) {
+ ok $problem_main->flagged, 'problem flagged';
+ } else {
+ ok !$problem_main->flagged, 'problem not flagged';
+ }
+
};
}
diff --git a/templates/web/default/admin/list_flagged.html b/templates/web/default/admin/list_flagged.html
new file mode 100644
index 000000000..0b22bccb2
--- /dev/null
+++ b/templates/web/default/admin/list_flagged.html
@@ -0,0 +1,51 @@
+[% INCLUDE 'admin/header.html' title=loc('Search Reports') %]
+[% PROCESS 'admin/report_blocks.html' %]
+
+
+<h2>[% loc( 'Problems' ) %]</h2>
+[% IF problems.size > 0 %]
+<table cellspacing="0" cellpadding="2" border="1">
+ <tr>
+ <th>[% loc('ID') %]</th>
+ <th>[% loc('Title') %]</th>
+ <th>[% loc('Name') %]</th>
+ <th>[% loc('Email') %]</th>
+ <th>[% loc('Council') %]</th>
+ <th>[% loc('Category') %]</th>
+ <th>[% loc('Anonymous') %]</th>
+ <th>[% loc('Cobrand') %]</th>
+ <th>[% loc('Created') %]</th>
+ <th>[% loc('State') %]</th>
+ <th>[% loc('When sent') %]</th>
+ <th>*</th>
+ </tr>
+ [% INCLUDE 'admin/problem_row.html' %]
+</table>
+[% ELSE %]
+<p>
+[% loc('No flagged problems found') %]
+</p>
+[% END %]
+
+<h2>[% loc( 'Users' ) %]</h2>
+[% IF users%]
+<table cellspacing="0" cellpadding="2" border="1">
+ <tr>
+ <th>[% loc('Name') %]</th>
+ <th>[% loc('Email') %]</th>
+ </tr>
+[% WHILE ( user = users.next ) -%]
+ <tr>
+ <td>[% user.name | html %]</td>
+ <td>[% user.email | html %]</td>
+ <td><a href="[% c.uri_for( 'search_reports', search => user.email ) %]">list content</a></td>
+ </tr>
+[%- END %]
+</table>
+[%- ELSE %]
+<p>
+[% loc('No flagged users found') %]
+</p>
+[%- END %]
+
+[% INCLUDE 'admin/footer.html' %]
diff --git a/templates/web/default/admin/problem_row.html b/templates/web/default/admin/problem_row.html
new file mode 100644
index 000000000..664ff1b13
--- /dev/null
+++ b/templates/web/default/admin/problem_row.html
@@ -0,0 +1,30 @@
+[%- FOR problem IN problems %]
+ <tr[% ' class="hidden"' IF problem.state == 'hidden' %]>
+ <td>[%- IF problem.state == 'confirmed' || problem.state == 'fixed' -%]
+ [%- cobrand_data = problem.cobrand_data %]
+ [%- cobrand_data = c.data_for_generic_problem IF !problem.cobrand %]
+ <a href="[% c.uri_for_email( '/report', problem.id, cobrand_data ) %]">[% problem.id %]</a>
+ [%- ELSE %]
+ [%- problem.id %]
+ [%- END -%]</td>
+ <td>[% PROCESS value_or_nbsp value=problem.title %]</td>
+ <td>[% PROCESS value_or_nbsp value=problem.name %]</td>
+ <td>[% PROCESS value_or_nbsp value=problem.user.email %]</td>
+ <td>[%- IF edit_council_contacts -%]
+ <a href="[% c.uri_for('council_contacts', problem.council ) %]">[% PROCESS value_or_nbsp value=problem.council %]</a>
+ [%- ELSE -%]
+ [%- PROCESS value_or_nbsp value=problem.council -%]
+ [%- END -%]</td>
+ <td>[% PROCESS value_or_nbsp value=problem.category %]</td>
+ <td>[% IF problem.anonymous %][% loc('Yes') %][% ELSE %][% loc('No') %][% END %]</td>
+ <td>[% problem.cobrand %]<br>[% problem.cobrand_data | html %]</td>
+ <td>[% PROCESS format_time time=problem.created %]</td>
+ <td>[% problem.state %]<small>
+ [%- IF problem.state == 'fixed' || problem.state == 'confirmed' %]<br>[% loc('Confirmed:' ) %]&nbsp;[% PROCESS format_time time=problem.confirmed %][% END -%]
+ [%- IF problem.state == 'fixed' %]<br>[% loc('Fixed:') %] [% PROCESS format_time time=problem.lastupdate %][% END -%]
+ [%- IF problem.state == 'confirmed' %]<br>[% loc('Last&nbsp;update:') %] [% PROCESS format_time time=problem.lastupdate %][% END -%]</small>
+ </td>
+ <td>[% PROCESS format_time time=problem.whensent %]</td>
+ <td><a href="[% c.uri_for( 'report_edit', problem.id ) %]">[% loc('Edit') %]</a></td>
+ </tr>
+[%- END -%]
diff --git a/templates/web/default/admin/report_blocks.html b/templates/web/default/admin/report_blocks.html
index 1fe650f15..f08529ce7 100644
--- a/templates/web/default/admin/report_blocks.html
+++ b/templates/web/default/admin/report_blocks.html
@@ -5,3 +5,13 @@
[% BLOCK format_time -%]
[%- IF time %][% time.ymd %]&nbsp;[% time.hms %][% ELSE %][% no_time || '&nbsp;' %][% END %][% no_time = '' %]
[%- END %]
+
+[% BLOCK abuse_button -%]
+[% IF allowed_pages.abuse_edit -%]
+[% IF email_in_abuse %]<small>[% loc('(Email in abuse table)') %]</small>[% ELSE %]<input type="submit" name="banuser" value="[% loc('Ban email address') %]" />[% END %]
+[%- END %]
+[%- END %]
+
+[% BLOCK flag_button -%]
+[% IF user.flagged || user_flagged %]<input type="submit" name="removeuserflag" value="[% loc('Remove flag') %]">[% ELSE %]<input type="submit" name="flaguser" value="[% loc('Flag user') %]" />[% END %]
+[%- END %]
diff --git a/templates/web/default/admin/report_edit.html b/templates/web/default/admin/report_edit.html
index 9c38b014e..69d5d0b11 100644
--- a/templates/web/default/admin/report_edit.html
+++ b/templates/web/default/admin/report_edit.html
@@ -25,7 +25,7 @@
</select></li>
<li>[% loc('Category:') %] [% problem.category | html %] </li>
<li>[% loc('Name:') %] <input type='text' name='name' id='name' value='[% problem.name | html %]'></li>
-<li>[% loc('Email:') %] <input type='text' id='email' name='email' value='[% problem.user.email | html %]'></li>
+<li>[% loc('Email:') %] <input type='text' id='email' name='email' value='[% problem.user.email | html %]'> [% PROCESS abuse_button %] [% PROCESS flag_button user=problem.user %]</li>
<li>[% loc('Phone:') %] [% problem.user.phone | html %]</li>
<li>[% loc('Created:') %] [% PROCESS format_time time=problem.created %]</li>
<li>[% loc('Confirmed:') %] [% PROCESS format_time time=problem.confirmed no_time='-' %]</li>
@@ -35,6 +35,7 @@
<li>[% loc('Cobrand:') %] [% problem.cobrand %]</li>
<li>[% loc('Cobrand data:') %] [% problem.cobrand_data %]</li>
<li>[% loc('Going to send questionnaire?') %] [% IF problem.send_questionnaire %][% loc('Yes') %][% ELSE %][% loc('No') %][% END %]</li>
+<li><label for="flagged">[% loc('Flagged:') %]</label> <input type="checkbox" name="flagged"[% ' checked' IF problem.flagged %]></li>
[% IF problem.photo %]
[% photo = problem.get_photo_params %]
diff --git a/templates/web/default/admin/search_abuse.html b/templates/web/default/admin/search_abuse.html
new file mode 100644
index 000000000..0984e85cf
--- /dev/null
+++ b/templates/web/default/admin/search_abuse.html
@@ -0,0 +1,21 @@
+[% INCLUDE 'admin/header.html' title=loc('Search Abuse Table') %]
+
+<form method="get" action="[% c.uri_for('search_abuse') %]" enctype="application/x-www-form-urlencoded" accept-charset="utf-8">
+ <label for="search">[% loc('Search:') %]</label> <input type="text" name="search" size="30" id="search">
+</form>
+
+
+[% IF emails.size > 0 %]
+<table cellspacing="0" cellpadding="2" border="1">
+ <tr>
+ <th>[% loc('Email') %]</th>
+ </tr>
+[%- FOREACH foo IN emails %]
+ <tr>
+ <td>[%- foo.email | html -%]</td>
+ </tr>
+[%- END -%]
+</table>
+[% END %]
+
+[% INCLUDE 'admin/footer.html' %]
diff --git a/templates/web/default/admin/search_reports.html b/templates/web/default/admin/search_reports.html
index 3809965f6..9702c16a7 100644
--- a/templates/web/default/admin/search_reports.html
+++ b/templates/web/default/admin/search_reports.html
@@ -22,36 +22,7 @@
<th>[% loc('When sent') %]</th>
<th>*</th>
</tr>
-[%- FOREACH problem IN problems %]
- <tr[% ' class="hidden"' IF problem.state == 'hidden' %]>
- <td>[%- IF problem.state == 'confirmed' || problem.state == 'fixed' -%]
- [%- cobrand_data = problem.cobrand_data %]
- [%- cobrand_data = c.data_for_generic_problem IF !problem.cobrand %]
- <a href="[% c.uri_for_email( '/report', problem.id, cobrand_data ) %]">[% problem.id %]</a>
- [%- ELSE %]
- [%- problem.id %]
- [%- END -%]</td>
- <td>[% PROCESS value_or_nbsp value=problem.title %]</td>
- <td>[% PROCESS value_or_nbsp value=problem.name %]</td>
- <td>[% PROCESS value_or_nbsp value=problem.user.email %]</td>
- <td>[%- IF edit_council_contacts -%]
- <a href="[% c.uri_for('council_contacts', problem.council ) %]">[% PROCESS value_or_nbsp value=problem.council %]</a>
- [%- ELSE -%]
- [%- PROCESS value_or_nbsp value=problem.council -%]
- [%- END -%]</td>
- <td>[% PROCESS value_or_nbsp value=problem.category %]</td>
- <td>[% IF problem.anonymous %][% loc('Yes') %][% ELSE %][% loc('No') %][% END %]</td>
- <td>[% problem.cobrand %]<br>[% problem.cobrand_data | html %]</td>
- <td>[% PROCESS format_time time=problem.created %]</td>
- <td>[% problem.state %]<small>
- [%- IF problem.state == 'fixed' || problem.state == 'confirmed' %]<br>[% loc('Confirmed:' ) %]&nbsp;[% PROCESS format_time time=problem.confirmed %][% END -%]
- [%- IF problem.state == 'fixed' %]<br>[% loc('Fixed:') %] [% PROCESS format_time time=problem.lastupdate %][% END -%]
- [%- IF problem.state == 'confirmed' %]<br>[% loc('Last&nbsp;update:') %] [% PROCESS format_time time=problem.lastupdate %][% END -%]</small>
- </td>
- <td>[% PROCESS format_time time=problem.whensent %]</td>
- <td><a href="[% c.uri_for( 'report_edit', problem.id ) %]">[% loc('Edit') %]</a></td>
- </tr>
-[%- END -%]
+ [% INCLUDE 'admin/problem_row.html' %]
</table>
[% INCLUDE 'admin/list_updates.html' %]
diff --git a/templates/web/default/admin/update_edit.html b/templates/web/default/admin/update_edit.html
index d7f212052..d4ac1b440 100644
--- a/templates/web/default/admin/update_edit.html
+++ b/templates/web/default/admin/update_edit.html
@@ -21,7 +21,7 @@
[% END %]
</select></li>
<li>[% loc('Name:') %] <input type='text' name='name' id='name' value='[% update.name | html %]'></li>
-<li>[% loc('Email:') %] <input type='text' id='email' name='email' value='[% update.user.email | html %]'></li>
+<li>[% loc('Email:') %] <input type='text' id='email' name='email' value='[% update.user.email | html %]'> [% PROCESS abuse_button %] [% PROCESS flag_button user=update.user %]</li>
<li>[% loc('Cobrand:') %] [% update.cobrand %]</li>
<li>[% loc('Cobrand data:') %] [% update.cobrand_data %]</li>
<li>[% loc('Created:') %] [% PROCESS format_time time=update.created %]</li>