FEATURE: Option to disable user presence and profile
This allows users who are privacy conscious to disable the presence features of the forum as well as their public profile.
This commit is contained in:
parent
fd48ba10b8
commit
a566ed42ae
|
@ -31,7 +31,8 @@ export default Ember.Controller.extend(PreferencesTabController, {
|
|||
"disable_jump_reply",
|
||||
"automatically_unpin_topics",
|
||||
"allow_private_messages",
|
||||
"homepage_id"
|
||||
"homepage_id",
|
||||
"hide_profile_and_presence"
|
||||
];
|
||||
|
||||
if (makeDefault) {
|
||||
|
|
|
@ -16,9 +16,9 @@ export default Ember.Controller.extend(CanCheckEmails, {
|
|||
return currentUser && username === currentUser.get("username");
|
||||
},
|
||||
|
||||
@computed("viewingSelf")
|
||||
canExpandProfile(viewingSelf) {
|
||||
return viewingSelf;
|
||||
@computed("viewingSelf", "model.profile_hidden")
|
||||
canExpandProfile(viewingSelf, profileHidden) {
|
||||
return !profileHidden && viewingSelf;
|
||||
},
|
||||
|
||||
@computed("model.profileBackground")
|
||||
|
@ -26,8 +26,11 @@ export default Ember.Controller.extend(CanCheckEmails, {
|
|||
return !Ember.isEmpty(background.toString());
|
||||
},
|
||||
|
||||
@computed("indexStream", "viewingSelf", "forceExpand")
|
||||
collapsedInfo(indexStream, viewingSelf, forceExpand) {
|
||||
@computed("model.profile_hidden", "indexStream", "viewingSelf", "forceExpand")
|
||||
collapsedInfo(profileHidden, indexStream, viewingSelf, forceExpand) {
|
||||
if (profileHidden) {
|
||||
return true;
|
||||
}
|
||||
return (!indexStream || viewingSelf) && !forceExpand;
|
||||
},
|
||||
|
||||
|
|
|
@ -284,7 +284,8 @@ const User = RestModel.extend({
|
|||
"include_tl0_in_digests",
|
||||
"theme_ids",
|
||||
"allow_private_messages",
|
||||
"homepage_id"
|
||||
"homepage_id",
|
||||
"hide_profile_and_presence"
|
||||
];
|
||||
|
||||
if (fields) {
|
||||
|
|
|
@ -102,6 +102,7 @@ export default function() {
|
|||
"user",
|
||||
{ path: "/u/:username", resetNamespace: true },
|
||||
function() {
|
||||
this.route("profile-hidden");
|
||||
this.route("summary");
|
||||
this.route(
|
||||
"userActivity",
|
||||
|
|
|
@ -1,6 +1,11 @@
|
|||
export default Discourse.Route.extend({
|
||||
model() {
|
||||
return this.modelFor("user");
|
||||
let user = this.modelFor("user");
|
||||
if (user.get("profile_hidden")) {
|
||||
return this.replaceWith("user.profile-hidden");
|
||||
}
|
||||
|
||||
return user;
|
||||
},
|
||||
|
||||
setupController(controller, user) {
|
||||
|
|
|
@ -2,7 +2,12 @@ export default Discourse.Route.extend({
|
|||
showFooter: true,
|
||||
|
||||
model() {
|
||||
return this.modelFor("user").summary();
|
||||
let user = this.modelFor("user");
|
||||
if (user.get("profile_hidden")) {
|
||||
return this.replaceWith("user.profile-hidden");
|
||||
}
|
||||
|
||||
return user.summary();
|
||||
},
|
||||
|
||||
actions: {
|
||||
|
|
|
@ -37,10 +37,10 @@
|
|||
{{preference-checkbox labelKey="user.enable_quoting" checked=model.user_option.enable_quoting}}
|
||||
{{preference-checkbox labelKey="user.dynamic_favicon" checked=model.user_option.dynamic_favicon}}
|
||||
{{preference-checkbox labelKey="user.disable_jump_reply" checked=model.user_option.disable_jump_reply}}
|
||||
|
||||
{{#if siteSettings.automatically_unpin_topics}}
|
||||
{{preference-checkbox labelKey="user.automatically_unpin_topics" checked=model.user_option.automatically_unpin_topics}}
|
||||
{{/if}}
|
||||
{{preference-checkbox labelKey="user.hide_profile_and_presence" checked=model.user_option.hide_profile_and_presence}}
|
||||
</div>
|
||||
|
||||
{{plugin-outlet name="user-preferences-interface" args=(hash model=model save=(action "save"))}}
|
||||
|
|
|
@ -195,8 +195,10 @@
|
|||
</section>
|
||||
|
||||
{{#mobile-nav class='main-nav' desktopClass="nav nav-pills user-nav" currentPath=currentPath}}
|
||||
<li>{{#link-to 'user.summary'}}{{i18n 'user.summary.title'}}{{/link-to}}</li>
|
||||
<li>{{#link-to 'userActivity'}}{{i18n 'user.activity_stream'}}{{/link-to}}</li>
|
||||
{{#unless model.profile_hidden}}
|
||||
<li>{{#link-to 'user.summary'}}{{i18n 'user.summary.title'}}{{/link-to}}</li>
|
||||
<li>{{#link-to 'userActivity'}}{{i18n 'user.activity_stream'}}{{/link-to}}</li>
|
||||
{{/unless}}
|
||||
{{#if showNotificationsTab}}
|
||||
<li>
|
||||
{{#link-to 'userNotifications'}}
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
<p class='user-profile-hidden'>{{i18n "user.profile_hidden"}}</p>
|
|
@ -41,6 +41,10 @@
|
|||
}
|
||||
}
|
||||
|
||||
.user-profile-hidden {
|
||||
font-size: 1.5em;
|
||||
text-align: center;
|
||||
}
|
||||
.user-navigation {
|
||||
display: table-cell;
|
||||
vertical-align: top;
|
||||
|
|
|
@ -7,6 +7,8 @@ class UserActionsController < ApplicationController
|
|||
per_chunk = 30
|
||||
|
||||
user = fetch_user_from_params(include_inactive: current_user.try(:staff?) || (current_user && SiteSetting.show_inactive_accounts))
|
||||
raise Discourse::NotFound unless guardian.can_see_profile?(user)
|
||||
|
||||
action_types = (params[:filter] || "").split(",").map(&:to_i)
|
||||
|
||||
opts = { user_id: user.id,
|
||||
|
|
|
@ -53,14 +53,18 @@ class UsersController < ApplicationController
|
|||
include_inactive: current_user.try(:staff?) || (current_user && SiteSetting.show_inactive_accounts)
|
||||
)
|
||||
|
||||
user_serializer = UserSerializer.new(@user, scope: guardian, root: 'user')
|
||||
user_serializer = nil
|
||||
if guardian.can_see_profile?(@user)
|
||||
user_serializer = UserSerializer.new(@user, scope: guardian, root: 'user')
|
||||
# TODO remove this options from serializer
|
||||
user_serializer.omit_stats = true
|
||||
|
||||
# TODO remove this options from serializer
|
||||
user_serializer.omit_stats = true
|
||||
|
||||
topic_id = params[:include_post_count_for].to_i
|
||||
if topic_id != 0
|
||||
user_serializer.topic_post_count = { topic_id => Post.secured(guardian).where(topic_id: topic_id, user_id: @user.id).count }
|
||||
topic_id = params[:include_post_count_for].to_i
|
||||
if topic_id != 0
|
||||
user_serializer.topic_post_count = { topic_id => Post.secured(guardian).where(topic_id: topic_id, user_id: @user.id).count }
|
||||
end
|
||||
else
|
||||
user_serializer = HiddenProfileSerializer.new(@user, scope: guardian, root: 'user')
|
||||
end
|
||||
|
||||
if !params[:skip_track_visit] && (@user != current_user)
|
||||
|
@ -204,8 +208,15 @@ class UsersController < ApplicationController
|
|||
end
|
||||
end
|
||||
|
||||
def profile_hidden
|
||||
render nothing: true
|
||||
end
|
||||
|
||||
def summary
|
||||
user = fetch_user_from_params(include_inactive: current_user.try(:staff?) || (current_user && SiteSetting.show_inactive_accounts))
|
||||
|
||||
raise Discourse::NotFound unless guardian.can_see_profile?(user)
|
||||
|
||||
summary = UserSummary.new(user, guardian)
|
||||
serializer = UserSummarySerializer.new(summary, scope: guardian)
|
||||
render_json_dump(serializer)
|
||||
|
|
|
@ -42,7 +42,8 @@ class CurrentUserSerializer < BasicUserSerializer
|
|||
:can_create_topic,
|
||||
:link_posting_access,
|
||||
:external_id,
|
||||
:top_category_ids
|
||||
:top_category_ids,
|
||||
:hide_profile_and_presence
|
||||
|
||||
def link_posting_access
|
||||
scope.link_posting_access
|
||||
|
@ -68,6 +69,10 @@ class CurrentUserSerializer < BasicUserSerializer
|
|||
object.user_stat.topic_reply_count
|
||||
end
|
||||
|
||||
def hide_profile_and_presence
|
||||
object.user_option.hide_profile_and_presence
|
||||
end
|
||||
|
||||
def enable_quoting
|
||||
object.user_option.enable_quoting
|
||||
end
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
class HiddenProfileSerializer < BasicUserSerializer
|
||||
attributes :profile_hidden?
|
||||
|
||||
def profile_hidden?
|
||||
true
|
||||
end
|
||||
end
|
|
@ -23,6 +23,7 @@ class UserOptionSerializer < ApplicationSerializer
|
|||
:theme_key_seq,
|
||||
:allow_private_messages,
|
||||
:homepage_id,
|
||||
:hide_profile_and_presence
|
||||
|
||||
def auto_track_topics_after_msecs
|
||||
object.auto_track_topics_after_msecs || SiteSetting.default_other_auto_track_topics_after_msecs
|
||||
|
|
|
@ -37,6 +37,7 @@ class UserUpdater
|
|||
:theme_ids,
|
||||
:allow_private_messages,
|
||||
:homepage_id,
|
||||
:hide_profile_and_presence
|
||||
]
|
||||
|
||||
def initialize(actor, user)
|
||||
|
|
|
@ -618,6 +618,7 @@ en:
|
|||
private_messages: "Messages"
|
||||
activity_stream: "Activity"
|
||||
preferences: "Preferences"
|
||||
profile_hidden: "This user's public profile is hidden."
|
||||
expand_profile: "Expand"
|
||||
collapse_profile: "Collapse"
|
||||
bookmarks: "Bookmarks"
|
||||
|
@ -887,6 +888,7 @@ en:
|
|||
|
||||
website: "Web Site"
|
||||
email_settings: "Email"
|
||||
hide_profile_and_presence: "Hide my public profile and presence features"
|
||||
like_notification_frequency:
|
||||
title: "Notify when liked"
|
||||
always: "Always"
|
||||
|
|
|
@ -432,6 +432,7 @@ Discourse::Application.routes.draw do
|
|||
get "#{root_path}/:username/flagged-posts" => "users#show", constraints: { username: RouteFormat.username }
|
||||
get "#{root_path}/:username/deleted-posts" => "users#show", constraints: { username: RouteFormat.username }
|
||||
get "#{root_path}/:username/topic-tracking-state" => "users#topic_tracking_state", constraints: { username: RouteFormat.username }
|
||||
get "#{root_path}/:username/profile-hidden" => "users#profile_hidden"
|
||||
end
|
||||
|
||||
get "user-badges/:username.json" => "user_badges#username", constraints: { username: RouteFormat.username }, defaults: { format: :json }
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
class AddHideProfileAndPresenceToUserOptions < ActiveRecord::Migration[5.2]
|
||||
def change
|
||||
add_column :user_options, :hide_profile_and_presence, :boolean, default: false, null: false
|
||||
end
|
||||
end
|
|
@ -93,4 +93,15 @@ module UserGuardian
|
|||
user && can_administer_user?(user)
|
||||
end
|
||||
|
||||
def can_see_profile?(user)
|
||||
return false if user.blank?
|
||||
|
||||
# If a user has hidden their profile, restrict it to them and staff
|
||||
if user.user_option.try(:hide_profile_and_presence?)
|
||||
return is_me?(user) || is_staff?
|
||||
end
|
||||
|
||||
true
|
||||
end
|
||||
|
||||
end
|
||||
|
|
|
@ -113,6 +113,12 @@ export default Ember.Component.extend({
|
|||
|
||||
publish(data) {
|
||||
this._lastPublish = new Date();
|
||||
|
||||
// Don't publish presence if disabled
|
||||
if (this.currentUser.hide_profile_and_presence) {
|
||||
return Ember.RSVP.Promise.resolve();
|
||||
}
|
||||
|
||||
return ajax("/presence/publish", { type: "POST", data });
|
||||
},
|
||||
|
||||
|
|
|
@ -107,8 +107,7 @@ after_initialize do
|
|||
ACTIONS ||= [-"edit", -"reply"].freeze
|
||||
|
||||
def publish
|
||||
|
||||
raise Discourse::NotFound if !current_user
|
||||
raise Discourse::NotFound if current_user.blank? || current_user.user_option.hide_profile_and_presence?
|
||||
|
||||
data = params.permit(
|
||||
:response_needed,
|
||||
|
|
|
@ -37,6 +37,12 @@ describe ::Presence::PresencesController do
|
|||
expect { post '/presence/publish.json' }.not_to raise_error
|
||||
end
|
||||
|
||||
it "does not publish for users with disabled presence features" do
|
||||
user1.user_option.update_column(:hide_profile_and_presence, true)
|
||||
post '/presence/publish.json'
|
||||
expect(response.code).to eq("404")
|
||||
end
|
||||
|
||||
it "uses guardian to secure endpoint" do
|
||||
private_post = Fabricate(:private_message_post)
|
||||
|
||||
|
|
|
@ -95,6 +95,41 @@ describe UserGuardian do
|
|||
expect(guardian.can_pick_avatar?(user_avatar, nil)).to eq(true)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "#can_see_profile?" do
|
||||
|
||||
it "is false for no user" do
|
||||
expect(Guardian.new.can_see_profile?(nil)).to eq(false)
|
||||
end
|
||||
|
||||
it "is true for a user whose profile is public" do
|
||||
expect(Guardian.new.can_see_profile?(user)).to eq(true)
|
||||
end
|
||||
|
||||
context "hidden profile" do
|
||||
let(:hidden_user) do
|
||||
result = Fabricate(:user)
|
||||
result.user_option.update_column(:hide_profile_and_presence, true)
|
||||
result
|
||||
end
|
||||
|
||||
it "is false for another user" do
|
||||
expect(Guardian.new(user).can_see_profile?(hidden_user)).to eq(false)
|
||||
end
|
||||
|
||||
it "is false for an anonymous user" do
|
||||
expect(Guardian.new.can_see_profile?(hidden_user)).to eq(false)
|
||||
end
|
||||
|
||||
it "is true for the user themselves" do
|
||||
expect(Guardian.new(hidden_user).can_see_profile?(hidden_user)).to eq(true)
|
||||
end
|
||||
|
||||
it "is true for a staff user" do
|
||||
expect(Guardian.new(admin).can_see_profile?(hidden_user)).to eq(true)
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -27,7 +27,14 @@ describe UserOption do
|
|||
user.user_option.expects(:redirected_to_top).returns(nil)
|
||||
expect(user.user_option.should_be_redirected_to_top).to eq(false)
|
||||
end
|
||||
end
|
||||
|
||||
describe "defaults" do
|
||||
let(:user) { Fabricate(:user) }
|
||||
|
||||
it "should not hide the profile and presence by default" do
|
||||
expect(user.user_option.hide_profile_and_presence).to eq(false)
|
||||
end
|
||||
end
|
||||
|
||||
describe "#mailing_list_mode" do
|
||||
|
|
|
@ -9,6 +9,15 @@ describe UserActionsController do
|
|||
expect(response.status).to eq(400)
|
||||
end
|
||||
|
||||
it "returns a 404 for a user with a hidden profile" do
|
||||
UserActionCreator.enable
|
||||
post = Fabricate(:post)
|
||||
post.user.user_option.update_column(:hide_profile_and_presence, true)
|
||||
|
||||
get "/user_actions.json", params: { username: post.user.username }
|
||||
expect(response.code).to eq("404")
|
||||
end
|
||||
|
||||
it 'renders list correctly' do
|
||||
UserActionCreator.enable
|
||||
post = Fabricate(:post)
|
||||
|
|
|
@ -2171,6 +2171,14 @@ describe UsersController do
|
|||
expect(json["user_summary"]["topic_count"]).to eq(1)
|
||||
expect(json["user_summary"]["post_count"]).to eq(0)
|
||||
end
|
||||
|
||||
it "returns 404 for a hidden profile" do
|
||||
user = Fabricate(:user)
|
||||
user.user_option.update_column(:hide_profile_and_presence, true)
|
||||
|
||||
get "/u/#{user.username_lower}/summary.json"
|
||||
expect(response.status).to eq(404)
|
||||
end
|
||||
end
|
||||
|
||||
describe '#confirm_admin' do
|
||||
|
@ -2417,7 +2425,23 @@ describe UsersController do
|
|||
it "returns success" do
|
||||
get "/u/#{user.username}.json"
|
||||
expect(response.status).to eq(200)
|
||||
expect(JSON.parse(response.body)["user"]["username"]).to eq(user.username)
|
||||
parsed = JSON.parse(response.body)["user"]
|
||||
|
||||
expect(parsed['username']).to eq(user.username)
|
||||
expect(parsed["profile_hidden"]).to be_blank
|
||||
expect(parsed["trust_level"]).to be_present
|
||||
end
|
||||
|
||||
it "returns a hidden profile" do
|
||||
user.user_option.update_column(:hide_profile_and_presence, true)
|
||||
|
||||
get "/u/#{user.username}.json"
|
||||
expect(response.status).to eq(200)
|
||||
parsed = JSON.parse(response.body)["user"]
|
||||
|
||||
expect(parsed["username"]).to eq(user.username)
|
||||
expect(parsed["profile_hidden"]).to eq(true)
|
||||
expect(parsed["trust_level"]).to be_blank
|
||||
end
|
||||
|
||||
it "should redirect to login page for anonymous user when profiles are hidden" do
|
||||
|
|
Loading…
Reference in New Issue