Skip to content

Commit

Permalink
FEATURE: Staff members can lock posts
Browse files Browse the repository at this point in the history
Locking a post prevents it from being edited. This is useful if the user
has posted something which has been edited out, and the staff members don't
want them to be able to edit it back in again.
  • Loading branch information
eviltrout committed Jan 26, 2018
1 parent 7631795 commit 6b04967
Show file tree
Hide file tree
Showing 19 changed files with 219 additions and 6 deletions.
8 changes: 8 additions & 0 deletions app/assets/javascripts/discourse/controllers/topic.js.es6
Original file line number Diff line number Diff line change
Expand Up @@ -518,6 +518,14 @@ export default Ember.Controller.extend(BufferedContent, {
this.send('changeOwner');
},

lockPost(post) {
return post.updatePostField('locked', true);
},

unlockPost(post) {
return post.updatePostField('locked', false);
},

grantBadge(post) {
this.set("selectedPostIds", [post.id]);
this.send('showGrantBadgeModal');
Expand Down
1 change: 1 addition & 0 deletions app/assets/javascripts/discourse/lib/transform-post.js.es6
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ export function transformBasicPost(post) {
cooked_hidden: !!post.cooked_hidden,
expandablePost: false,
replyCount: post.reply_count,
locked: post.locked
};

_additionalAttributes.forEach(a => postAtts[a] = post[a]);
Expand Down
2 changes: 2 additions & 0 deletions app/assets/javascripts/discourse/templates/topic.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,8 @@
rebakePost=(action "rebakePost")
changePostOwner=(action "changePostOwner")
grantBadge=(action "grantBadge")
lockPost=(action "lockPost")
unlockPost=(action "unlockPost")
unhidePost=(action "unhidePost")
replyToPost=(action "replyToPost")
toggleWiki=(action "toggleWiki")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,15 @@ export function buildManageButtons(attrs, currentUser) {
action: 'grantBadge',
className: 'grant-badge'
});

const action = attrs.locked ? "unlock" : "lock";
contents.push({
icon: action,
label: `post.controls.${action}_post`,
action: `${action}Post`,
title: `post.controls.${action}_post_description`,
className: `${action}-post`
});
}

if (attrs.canManage || attrs.canWiki) {
Expand Down
11 changes: 11 additions & 0 deletions app/assets/javascripts/discourse/widgets/post.js.es6
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { h } from 'virtual-dom';
import DiscourseURL from 'discourse/lib/url';
import { dateNode } from 'discourse/helpers/node';
import { translateSize, avatarUrl, formatUsername } from 'discourse/lib/utilities';
import hbs from 'discourse/widgets/hbs-compiler';

export function avatarImg(wanted, attrs) {
const size = translateSize(wanted);
Expand Down Expand Up @@ -139,6 +140,12 @@ createWidget('post-avatar', {
}
});

createWidget('post-locked-indicator', {
tagName: 'div.post-info.post-locked',
template: hbs`{{d-icon "lock"}}`,
title: () => I18n.t("post.locked")
});

createWidget('post-email-indicator', {
tagName: 'div.post-info.via-email',

Expand Down Expand Up @@ -207,6 +214,10 @@ createWidget('post-meta-data', {
result.push(this.attach('post-email-indicator', attrs));
}

if (attrs.locked) {
result.push(this.attach('post-locked-indicator', attrs));
}

if (attrs.version > 1 || attrs.wiki) {
result.push(this.attach('post-edits-indicator', attrs));
}
Expand Down
3 changes: 3 additions & 0 deletions app/assets/stylesheets/common/base/topic-post.scss
Original file line number Diff line number Diff line change
Expand Up @@ -320,8 +320,11 @@ aside.quote {
}

.post-info {

&.via-email, &.whisper {
line-height: $line-height-medium;
}
&.via-email, &.whisper, &.post-locked {
margin-right: 5px;
.d-icon {
font-size: $font-0;
Expand Down
35 changes: 32 additions & 3 deletions app/controllers/posts_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,34 @@
require_dependency 'post_merger'
require_dependency 'distributed_memoizer'
require_dependency 'new_post_result_serializer'
require_dependency 'post_locker'

class PostsController < ApplicationController

before_action :ensure_logged_in, except: [:show, :replies, :by_number, :short_link, :reply_history, :replyIids, :revisions, :latest_revision, :expand_embed, :markdown_id, :markdown_num, :cooked, :latest, :user_posts_feed]

skip_before_action :preload_json, :check_xhr, only: [:markdown_id, :markdown_num, :short_link, :latest, :user_posts_feed]
before_action :ensure_logged_in, except: [
:show,
:replies,
:by_number,
:short_link,
:reply_history,
:replyIids,
:revisions,
:latest_revision,
:expand_embed,
:markdown_id,
:markdown_num,
:cooked,
:latest,
:user_posts_feed
]

skip_before_action :preload_json, :check_xhr, only: [
:markdown_id,
:markdown_num,
:short_link,
:latest,
:user_posts_feed
]

def markdown_id
markdown Post.find(params[:id].to_i)
Expand Down Expand Up @@ -381,6 +403,13 @@ def revert
render_json_dump(result)
end

def locked
post = find_post_from_params
locker = PostLocker.new(post, current_user)
params[:locked] === "true" ? locker.lock : locker.unlock
render_json_dump(locked: post.locked?)
end

def bookmark
if params[:bookmarked] == "true"
post = find_post_from_params
Expand Down
4 changes: 4 additions & 0 deletions app/models/post.rb
Original file line number Diff line number Diff line change
Expand Up @@ -735,6 +735,10 @@ def create_user_action
UserActionCreator.log_post(self)
end

def locked?
locked_by_id.present?
end

private

def parse_quote_into_arguments(quote)
Expand Down
8 changes: 6 additions & 2 deletions app/models/user_history.rb
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,9 @@ def self.actions
backup_download: 45,
backup_destroy: 46,
notified_about_get_a_room: 47,
change_name: 48)
change_name: 48,
post_locked: 49,
post_unlocked: 50)
end

# Staff actions is a subset of all actions, used to audit actions taken by staff users.
Expand Down Expand Up @@ -104,7 +106,9 @@ def self.staff_actions
:activate_user,
:change_readonly_mode,
:backup_download,
:backup_destroy]
:backup_destroy,
:post_locked,
:post_unlocked]
end

def self.staff_action_ids
Expand Down
12 changes: 11 additions & 1 deletion app/serializers/post_serializer.rb
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,8 @@ class PostSerializer < BasicPostSerializer
:is_auto_generated,
:action_code,
:action_code_who,
:last_wiki_edit
:last_wiki_edit,
:locked

def initialize(object, opts)
super(object, opts)
Expand Down Expand Up @@ -354,6 +355,15 @@ def include_action_code_who?
include_action_code? && action_code_who.present?
end

def locked
true
end

# Only show locked posts to the users who made the post and staff
def include_locked?
object.locked? && (yours || scope.is_staff?)
end

def last_wiki_edit
object.revisions.last.updated_at
end
Expand Down
8 changes: 8 additions & 0 deletions app/services/staff_action_logger.rb
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,14 @@ def log_lock_trust_level(user, opts = {})
target_user_id: user.id))
end

def log_post_lock(post, opts = {})
raise Discourse::InvalidParameters.new(:post) unless post && post.is_a?(Post)
UserHistory.create!(params(opts).merge(
action: UserHistory.actions[opts[:locked] ? :post_locked : :post_unlocked],
post_id: post.id)
)
end

def log_site_setting_change(setting_name, previous_value, new_value, opts = {})
raise Discourse::InvalidParameters.new(:setting_name) unless setting_name.present? && SiteSetting.respond_to?(setting_name)
UserHistory.create(params(opts).merge(action: UserHistory.actions[:change_site_setting],
Expand Down
7 changes: 7 additions & 0 deletions config/locales/client.en.yml
Original file line number Diff line number Diff line change
Expand Up @@ -1900,6 +1900,7 @@ en:
other: "(post withdrawn by author, will be automatically deleted in %{count} hours unless flagged)"
collapse: "collapse"
expand_collapse: "expand/collapse"
locked: "a staff member has locked this post from being edited"
gap:
one: "view 1 hidden reply"
other: "view {{count}} hidden replies"
Expand Down Expand Up @@ -1981,6 +1982,10 @@ en:
unhide: "Unhide"
change_owner: "Change Ownership"
grant_badge: "Grant Badge"
lock_post: "Lock Post"
lock_post_description: "prevent the poster from editing this post"
unlock_post: "Unlock Post"
unlock_post_description: "allow the poster to edit this post"

actions:
flag: 'Flag'
Expand Down Expand Up @@ -3220,6 +3225,8 @@ en:
backup_destroy: "destroy backup"
reviewed_post: "reviewed post"
custom_staff: "plugin custom action"
post_locked: "post locked"
post_unlocked: "post unlocked"
screened_emails:
title: "Screened Emails"
description: "When someone tries to create a new account, the following email addresses will be checked and the registration will be blocked, or some other action performed."
Expand Down
1 change: 1 addition & 0 deletions config/routes.rb
Original file line number Diff line number Diff line change
Expand Up @@ -475,6 +475,7 @@
put "post_type"
put "rebake"
put "unhide"
put "locked"
get "replies"
get "revisions/latest" => "posts#latest_revision"
get "revisions/:revision" => "posts#revisions", constraints: { revision: /\d+/ }
Expand Down
5 changes: 5 additions & 0 deletions db/migrate/20180125185717_add_locked_by_to_posts.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
class AddLockedByToPosts < ActiveRecord::Migration[5.1]
def change
add_column :posts, :locked_by_id, :integer
end
end
9 changes: 9 additions & 0 deletions lib/guardian/post_guardian.rb
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,10 @@ def post_can_act?(post, action_key, opts: {}, can_see_post: nil)
!!result
end

def can_lock_post?(post)
can_see_post?(post) && is_staff?
end

def can_defer_flags?(post)
can_see_post?(post) && is_staff? && post
end
Expand Down Expand Up @@ -98,6 +102,9 @@ def can_edit_post?(post)

return true if is_admin?

# Must be staff to edit a locked post
return false if post.locked? && !is_staff?

if is_staff? || @user.has_trust_level?(TrustLevel[4])
return can_create_post?(post.topic)
end
Expand All @@ -106,6 +113,7 @@ def can_edit_post?(post)
return false
end


if post.wiki && (@user.trust_level >= SiteSetting.min_trust_to_edit_wiki_post.to_i)
return can_create_post?(post.topic)
end
Expand All @@ -114,6 +122,7 @@ def can_edit_post?(post)
return false
end


if is_my_own?(post)
if post.hidden?
return false if post.hidden_at.present? &&
Expand Down
23 changes: 23 additions & 0 deletions lib/post_locker.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
class PostLocker
def initialize(post, user)
@post, @user = post, user
end

def lock
Guardian.new(@user).ensure_can_lock_post!(@post)

Post.transaction do
@post.update_column(:locked_by_id, @user.id)
StaffActionLogger.new(@user).log_post_lock(@post, locked: true)
end
end

def unlock
Guardian.new(@user).ensure_can_lock_post!(@post)

Post.transaction do
@post.update_column(:locked_by_id, nil)
StaffActionLogger.new(@user).log_post_lock(@post, locked: false)
end
end
end
11 changes: 11 additions & 0 deletions spec/components/guardian_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

require 'guardian'
require_dependency 'post_destroyer'
require_dependency 'post_locker'

describe Guardian do

Expand Down Expand Up @@ -1091,6 +1092,11 @@
expect(Guardian.new(post.user).can_edit?(post)).to be_truthy
end

it 'returns false if you try to edit a locked post' do
post.locked_by_id = moderator.id
expect(Guardian.new(post.user).can_edit?(post)).to be_falsey
end

it "returns false if the post is hidden due to flagging and it's too soon" do
post.hidden = true
post.hidden_at = Time.now
Expand Down Expand Up @@ -1164,6 +1170,11 @@
expect(Guardian.new(moderator).can_edit?(post)).to be_truthy
end

it 'returns true as a moderator, even if locked' do
post.locked_by_id = admin.id
expect(Guardian.new(moderator).can_edit?(post)).to be_truthy
end

it 'returns true as an admin' do
expect(Guardian.new(admin).can_edit?(post)).to be_truthy
end
Expand Down
Loading

1 comment on commit 6b04967

@discoursebot
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This commit has been mentioned on Discourse Meta. There might be relevant details there:

https://meta.discourse.org/t/locking-posts-preventing-users-from-undoing-staff-edits/79111/1

Please sign in to comment.