aboutsummaryrefslogtreecommitdiffstats
path: root/perllib/FixMyStreet/App/Controller/Moderate.pm
diff options
context:
space:
mode:
Diffstat (limited to 'perllib/FixMyStreet/App/Controller/Moderate.pm')
-rw-r--r--perllib/FixMyStreet/App/Controller/Moderate.pm365
1 files changed, 365 insertions, 0 deletions
diff --git a/perllib/FixMyStreet/App/Controller/Moderate.pm b/perllib/FixMyStreet/App/Controller/Moderate.pm
new file mode 100644
index 000000000..bf70d52c5
--- /dev/null
+++ b/perllib/FixMyStreet/App/Controller/Moderate.pm
@@ -0,0 +1,365 @@
+package FixMyStreet::App::Controller::Moderate;
+
+use Moose;
+use namespace::autoclean;
+use Algorithm::Diff;
+BEGIN { extends 'Catalyst::Controller'; }
+
+=head1 NAME
+
+FixMyStreet::App::Controller::Moderate - process a moderation event
+
+=head1 DESCRIPTION
+
+The intent of this is that council users will be able to moderate reports
+by themselves, but without requiring access to the full admin panel.
+
+From a given report page, an authenticated user will be able to press
+the "moderate" button on report and any updates to bring up a form with
+data to change.
+
+(Authentication requires:
+
+ - user to be from_body
+ - user to have a "moderate" record in user_body_permissions (there is
+ currently no admin interface for this. Should be added, but
+ while we're trialing this, it's a simple case of adding a DB record
+ manually)
+
+The original data of the report is stored in moderation_original_data, so
+that it can be reverted/consulted if required. All moderation events are
+stored in moderation_log. (NB: In future, this could be combined with
+admin_log).
+
+=head1 SEE ALSO
+
+DB tables:
+
+ AdminLog
+ ModerationOriginalData
+ UserBodyPermissions
+
+=cut
+
+sub moderate : Chained('/') : PathPart('moderate') : CaptureArgs(0) { }
+
+sub report : Chained('moderate') : PathPart('report') : CaptureArgs(1) {
+ my ($self, $c, $id) = @_;
+ my $problem = $c->model('DB::Problem')->find($id);
+
+ my $cobrand_base = $c->cobrand->base_url_for_report( $problem );
+ my $report_uri = $cobrand_base . $problem->url;
+ $c->stash->{cobrand_base} = $cobrand_base;
+ $c->stash->{report_uri} = $report_uri;
+ $c->res->redirect( $report_uri ); # this will be the final endpoint after all processing...
+
+ # ... and immediately, if the user isn't authorized
+ $c->detach unless $c->user_exists;
+ $c->detach unless $c->user->has_permission_to(moderate => $problem->bodies_str);
+
+ my $original = $problem->find_or_new_related( moderation_original_data => {
+ title => $problem->title,
+ detail => $problem->detail,
+ photo => $problem->photo,
+ anonymous => $problem->anonymous,
+ });
+ $c->stash->{problem} = $problem;
+ $c->stash->{problem_original} = $original;
+ $c->stash->{moderation_reason} = $c->req->param('moderation_reason') // '';
+}
+
+sub moderate_report : Chained('report') : PathPart('') : Args(0) {
+ my ($self, $c) = @_;
+
+ $c->forward('report_moderate_hide');
+
+ my @types = grep $_,
+ $c->forward('report_moderate_title'),
+ $c->forward('report_moderate_detail'),
+ $c->forward('report_moderate_anon'),
+ $c->forward('report_moderate_photo');
+
+ $c->detach( 'report_moderate_audit', \@types )
+}
+
+sub report_moderate_audit : Private {
+ my ($self, $c, @types) = @_;
+
+ my $user = $c->user->obj;
+ my $reason = $c->stash->{'moderation_reason'};
+ my $problem = $c->stash->{problem} or die;
+
+ my $types_csv = join ', ' => @types;
+
+ $c->model('DB::AdminLog')->create({
+ action => 'moderation',
+ user => $user,
+ admin_user => $user->name,
+ object_id => $problem->id,
+ object_type => 'problem',
+ reason => (sprintf '%s (%s)', $reason, $types_csv),
+ });
+
+ my $cobrand = FixMyStreet::Cobrand->get_class_for_moniker($problem->cobrand)->new();
+
+ my $sender = FixMyStreet->config('DO_NOT_REPLY_EMAIL');
+ my $sender_name = _($cobrand->contact_name);
+
+ $c->send_email( 'problem-moderated.txt', {
+
+ to => [ [ $user->email, $user->name ] ],
+ from => [ $sender, $sender_name ],
+ types => $types_csv,
+ user => $user,
+ problem => $problem,
+ report_uri => $c->stash->{report_uri},
+ report_complain_uri => $c->stash->{cobrand_base} . '/contact?m=1&id=' . $problem->id,
+ });
+}
+
+sub report_moderate_hide : Private {
+ my ( $self, $c ) = @_;
+
+ my $problem = $c->stash->{problem} or die;
+
+ if ($c->req->param('problem_hide')) {
+
+
+ $problem->update({ state => 'hidden' });
+
+ $c->detach( 'report_moderate_audit', ['hide'] ); # break chain here.
+ }
+}
+
+sub report_moderate_title : Private {
+ my ( $self, $c ) = @_;
+
+ my $problem = $c->stash->{problem} or die;
+ my $original = $c->stash->{problem_original};
+
+ my $old_title = $problem->title;
+ my $original_title = $original->title;
+
+ my $title = $c->req->param('problem_revert_title') ?
+ $original_title
+ : $self->diff($original_title, $c->req->param('problem_title'));
+
+ if ($title ne $old_title) {
+ $original->insert unless $original->in_storage;
+ $problem->update({ title => $title });
+ return 'title';
+ }
+
+ return;
+}
+
+sub report_moderate_detail : Private {
+ my ( $self, $c ) = @_;
+
+ my $problem = $c->stash->{problem} or die;
+ my $original = $c->stash->{problem_original};
+
+ my $old_detail = $problem->detail;
+ my $original_detail = $original->detail;
+ my $detail = $c->req->param('problem_revert_detail') ?
+ $original_detail
+ : $self->diff($original_detail, $c->req->param('problem_detail'));
+
+ if ($detail ne $old_detail) {
+ $original->insert unless $original->in_storage;
+ $problem->update({ detail => $detail });
+ return 'detail';
+ }
+ return;
+}
+
+sub report_moderate_anon : Private {
+ my ( $self, $c ) = @_;
+
+ my $problem = $c->stash->{problem} or die;
+ my $original = $c->stash->{problem_original};
+
+ my $show_user = $c->req->param('problem_show_name') ? 1 : 0;
+ my $anonymous = $show_user ? 0 : 1;
+ my $old_anonymous = $problem->anonymous ? 1 : 0;
+
+ if ($anonymous != $old_anonymous) {
+
+ $original->insert unless $original->in_storage;
+ $problem->update({ anonymous => $anonymous });
+ return 'anonymous';
+ }
+ return;
+}
+
+sub report_moderate_photo : Private {
+ my ( $self, $c ) = @_;
+
+ my $problem = $c->stash->{problem} or die;
+ my $original = $c->stash->{problem_original};
+
+ return unless $original->photo;
+
+ my $show_photo = $c->req->param('problem_show_photo') ? 1 : 0;
+ my $old_show_photo = $problem->photo ? 1 : 0;
+
+ if ($show_photo != $old_show_photo) {
+ $original->insert unless $original->in_storage;
+ $problem->update({ photo => $show_photo ? $original->photo : undef });
+ return 'photo';
+ }
+ return;
+}
+
+sub update : Chained('report') : PathPart('update') : CaptureArgs(1) {
+ my ($self, $c, $id) = @_;
+ my $comment = $c->stash->{problem}->comments->find($id);
+
+ my $original = $comment->find_or_new_related( moderation_original_data => {
+ detail => $comment->text,
+ photo => $comment->photo,
+ anonymous => $comment->anonymous,
+ });
+ $c->stash->{comment} = $comment;
+ $c->stash->{comment_original} = $original;
+}
+
+sub moderate_update : Chained('update') : PathPart('') : Args(0) {
+ my ($self, $c) = @_;
+
+ $c->forward('update_moderate_hide');
+
+ my @types = grep $_,
+ $c->forward('update_moderate_detail'),
+ $c->forward('update_moderate_anon'),
+ $c->forward('update_moderate_photo');
+
+ $c->detach( 'update_moderate_audit', \@types )
+}
+
+sub update_moderate_audit : Private {
+ my ($self, $c, @types) = @_;
+
+ my $user = $c->user->obj;
+ my $reason = $c->stash->{'moderation_reason'};
+ my $problem = $c->stash->{problem} or die;
+ my $comment = $c->stash->{comment} or die;
+
+ my $types_csv = join ', ' => @types;
+
+ $c->model('DB::AdminLog')->create({
+ action => 'moderation',
+ user => $user,
+ admin_user => $user->name,
+ object_id => $comment->id,
+ object_type => 'update',
+ reason => (sprintf '%s (%s)', $reason, $types_csv),
+ });
+}
+
+sub update_moderate_hide : Private {
+ my ( $self, $c ) = @_;
+
+ my $problem = $c->stash->{problem} or die;
+ my $comment = $c->stash->{comment} or die;
+
+ if ($c->req->param('update_hide')) {
+ $comment->update({ state => 'hidden' });
+ $c->detach( 'update_moderate_audit', ['hide'] ); # break chain here.
+ }
+ return;
+}
+
+sub update_moderate_detail : Private {
+ my ( $self, $c ) = @_;
+
+ my $problem = $c->stash->{problem} or die;
+ my $comment = $c->stash->{comment} or die;
+ my $original = $c->stash->{comment_original};
+
+ my $old_detail = $comment->text;
+ my $original_detail = $original->detail;
+ my $detail = $c->req->param('update_revert_detail') ?
+ $original_detail
+ : $self->diff($original_detail, $c->req->param('update_detail'));
+
+ if ($detail ne $old_detail) {
+ $original->insert unless $original->in_storage;
+ $comment->update({ text => $detail });
+ return 'detail';
+ }
+ return;
+}
+
+sub update_moderate_anon : Private {
+ my ( $self, $c ) = @_;
+
+ my $problem = $c->stash->{problem} or die;
+ my $comment = $c->stash->{comment} or die;
+ my $original = $c->stash->{comment_original};
+
+ my $show_user = $c->req->param('update_show_name') ? 1 : 0;
+ my $anonymous = $show_user ? 0 : 1;
+ my $old_anonymous = $comment->anonymous ? 1 : 0;
+
+ if ($anonymous != $old_anonymous) {
+ $original->insert unless $original->in_storage;
+ $comment->update({ anonymous => $anonymous });
+ return 'anonymous';
+ }
+ return;
+}
+
+sub update_moderate_photo : Private {
+ my ( $self, $c ) = @_;
+
+ my $problem = $c->stash->{problem} or die;
+ my $comment = $c->stash->{comment} or die;
+ my $original = $c->stash->{comment_original};
+
+ return unless $original->photo;
+
+ my $show_photo = $c->req->param('update_show_photo') ? 1 : 0;
+ my $old_show_photo = $comment->photo ? 1 : 0;
+
+ if ($show_photo != $old_show_photo) {
+ $original->insert unless $original->in_storage;
+ $comment->update({ photo => $show_photo ? $original->photo : undef });
+ return 'photo';
+ }
+}
+
+sub return_text : Private {
+ my ($self, $c, $text) = @_;
+
+ $c->res->content_type('text/plain; charset=utf-8');
+ $c->res->body( $text // '' );
+}
+
+sub diff {
+ my ($self, $old, $new) = @_;
+
+ $new =~s/\[\.{3}\]//g;
+
+ my $diff = Algorithm::Diff->new( [ split //, $old ], [ split //, $new ] );
+ my $string;
+ while ($diff->Next) {
+ my $d = $diff->Diff;
+ if ($d & 1) {
+ my $deleted = join '', $diff->Items(1);
+ unless ($deleted =~/^\s*$/) {
+ $string .= ' ' if $deleted =~/^ /;
+ my $letters = ($deleted=~s/\W//r);
+ $string .= '[...]';
+ $string .= ' ' if $deleted =~/ $/;
+ }
+ }
+ $string .= join '', $diff->Items(2);
+ }
+ return $string;
+}
+
+
+__PACKAGE__->meta->make_immutable;
+
+1;