SiteSetting to hide regular names from users

This commit is contained in:
Robin Ward 2013-10-30 15:45:13 -04:00
parent c8d5db38d6
commit 3d6d7c8abe
20 changed files with 236 additions and 83 deletions

View File

@ -12,9 +12,7 @@ Discourse.UserController = Discourse.ObjectController.extend({
return this.get('content.username') === Discourse.User.currentProp('username');
}.property('content.username'),
collapsedInfo: function() {
return !this.get('indexStream');
}.property('indexStream'),
collapsedInfo: Em.computed.not('indexStream'),
canSeePrivateMessages: function() {
return this.get('viewingSelf') || Discourse.User.currentProp('staff');

View File

@ -49,9 +49,9 @@ Discourse.Route.buildRoutes(function() {
this.route('index', { path: '/'} );
this.resource('userActivity', { path: '/activity' }, function() {
var resource = this;
var self = this;
Object.keys(Discourse.UserAction.TYPES).forEach(function (userAction) {
resource.route(userAction, { path: userAction.replace("_", "-") });
self.route(userAction, { path: userAction.replace("_", "-") });
});
});

View File

@ -15,6 +15,11 @@ Discourse.PreferencesRoute = Discourse.RestrictedUserRoute.extend({
this.render('preferences', { into: 'user', outlet: 'userOutlet', controller: 'preferences' });
},
setupController: function(controller, model) {
controller.set('model', model);
this.controllerFor('user').set('indexStream', false);
},
actions: {
showAvatarSelector: function() {
Discourse.Route.showModal(this, 'avatarSelector');

View File

@ -13,5 +13,11 @@ Discourse.UserInvitedRoute = Discourse.Route.extend({
model: function() {
return Discourse.InviteList.findInvitedBy(this.modelFor('user'));
},
setupController: function(controller, model) {
controller.set('model', model);
this.controllerFor('user').set('indexStream', false);
}
});

View File

@ -4,9 +4,9 @@
<section class='user-navigation'>
<ul class='action-list nav-stacked'>
{{activityFilter count=statsCountNonPM user=model}}
{{discourse-activity-filter count=statsCountNonPM user=model userActionType=userActionType indexStream=indexStream}}
{{#each stat in statsExcludingPms}}
{{activityFilter content=stat user=model}}
{{discourse-activity-filter content=stat user=model userActionType=userActionType indexStream=indexStream}}
{{/each}}
</ul>

View File

@ -135,36 +135,38 @@ Discourse.PostView = Discourse.GroupedView.extend(Ember.Evented, {
}
},
/**
Toggle the replies this post is a reply to
actions: {
/**
Toggle the replies this post is a reply to
@method showReplyHistory
**/
toggleReplyHistory: function(post) {
@method showReplyHistory
**/
toggleReplyHistory: function(post) {
var replyHistory = post.get('replyHistory'),
topicController = this.get('controller'),
origScrollTop = $(window).scrollTop();
var replyHistory = post.get('replyHistory'),
topicController = this.get('controller'),
origScrollTop = $(window).scrollTop();
if (replyHistory.length > 0) {
var origHeight = this.$('.embedded-posts.top').height();
replyHistory.clear();
Em.run.next(function() {
$(window).scrollTop(origScrollTop - origHeight);
});
} else {
post.set('loadingReplyHistory', true);
var self = this;
topicController.get('postStream').findReplyHistory(post).then(function () {
post.set('loadingReplyHistory', false);
if (replyHistory.length > 0) {
var origHeight = this.$('.embedded-posts.top').height();
replyHistory.clear();
Em.run.next(function() {
$(window).scrollTop(origScrollTop + self.$('.embedded-posts.top').height());
$(window).scrollTop(origScrollTop - origHeight);
});
});
} else {
post.set('loadingReplyHistory', true);
var self = this;
topicController.get('postStream').findReplyHistory(post).then(function () {
post.set('loadingReplyHistory', false);
Em.run.next(function() {
$(window).scrollTop(origScrollTop + self.$('.embedded-posts.top').height());
});
});
}
}
},

View File

@ -2,15 +2,14 @@
This view handles rendering of an activity in a user's profile
@class ActivityFilterView
@extends Discourse.View
@extends Ember.Component
@namespace Discourse
@module Discourse
**/
Discourse.ActivityFilterView = Discourse.View.extend({
Discourse.ActivityFilterView = Ember.Component.extend({
tagName: 'li',
classNameBindings: ['active', 'noGlyph'],
userActionType: Em.computed.alias('controller.userActionType'),
shouldRerender: Discourse.View.renderIfChanged('count'),
noGlyph: Em.computed.empty('icon'),
@ -19,9 +18,9 @@ Discourse.ActivityFilterView = Discourse.View.extend({
if (content) {
return parseInt(this.get('userActionType'), 10) === parseInt(Em.get(content, 'action_type'), 10);
} else {
return this.blank('userActionType');
return this.get('indexStream');
}
}.property('userActionType', 'content.action_type'),
}.property('userActionType', 'indexStream'),
activityCount: function() {
return this.get('content.count') || this.get('count');
@ -75,4 +74,4 @@ Discourse.ActivityFilterView = Discourse.View.extend({
});
Discourse.View.registerHelper('activityFilter', Discourse.ActivityFilterView);
Discourse.View.registerHelper('discourse-activity-filter', Discourse.ActivityFilterView);

View File

@ -240,10 +240,12 @@ class UsersController < ApplicationController
topic_id = params[:topic_id]
topic_id = topic_id.to_i if topic_id
results = UserSearch.search term, topic_id
results = UserSearch.new(term, topic_id).search
render json: { users: results.as_json(only: [ :username, :name, :use_uploaded_avatar, :upload_avatar_template, :uploaded_avatar_id],
methods: :avatar_template) }
user_fields = [:username, :use_uploaded_avatar, :upload_avatar_template, :uploaded_avatar_id]
user_fields << :name if SiteSetting.enable_names?
render json: { users: results.as_json(only: user_fields, methods: :avatar_template) }
end
# [LEGACY] avatars in quotes/oneboxes might still be pointing to this route

View File

@ -270,6 +270,8 @@ class SiteSetting < ActiveRecord::Base
# hidden setting only used by system
setting(:uncategorized_category_id, -1, hidden: true)
client_setting(:enable_names, true)
def self.call_discourse_hub?
self.enforce_global_nicknames? && self.discourse_org_access_key.present?
end

View File

@ -1,36 +1,38 @@
# Searches for a user by username or full text or name (if enabled in SiteSettings)
class UserSearch
def self.search term, topic_id = nil
sql = User.sql_builder(
"select id, username, name, email, use_uploaded_avatar, uploaded_avatar_template, uploaded_avatar_id from users u
/*left_join*/
/*where*/
/*order_by*/")
if topic_id
sql.left_join "(select distinct p.user_id from posts p where topic_id = :topic_id) s on s.user_id = u.id", topic_id: topic_id
end
if term.present?
sql.where("username_lower like :term_like or
to_tsvector('simple', name) @@
to_tsquery('simple',
regexp_replace(
regexp_replace(
cast(plainto_tsquery(:term) as text)
,'\''(?: |$)', ':*''', 'g'),
'''', '', 'g')
)", term: term, term_like: "#{term.downcase}%")
sql.order_by "case when username_lower = :term then 0 else 1 end asc"
end
if topic_id
sql.order_by "case when s.user_id is null then 0 else 1 end desc"
end
sql.order_by "case when last_seen_at is null then 0 else 1 end desc, last_seen_at desc, username asc limit(20)"
sql.exec
def initialize(term, topic_id=nil)
@term = term
@term_like = "#{term.downcase}%"
@topic_id = topic_id
end
def search
users = User.order(User.sql_fragment("CASE WHEN username_lower = ? THEN 0 ELSE 1 END ASC", :term))
if @term.present?
if SiteSetting.enable_names?
users = users.where("username_lower LIKE :term_like OR
TO_TSVECTOR('simple', name) @@
TO_TSQUERY('simple',
REGEXP_REPLACE(
REGEXP_REPLACE(
CAST(PLAINTO_TSQUERY(:term) AS TEXT)
,'\''(?: |$)', ':*''', 'g'),
'''', '', 'g')
)", term: @term, term_like: @term_like)
else
users = users.where("username_lower LIKE :term_like", term_like: @term_like)
end
end
if @topic_id
users = users.joins(User.sql_fragment("LEFT JOIN (SELECT DISTINCT p.user_id FROM POSTS p WHERE topic_id = ?) s ON s.user_id = users.id", @topic_id))
.order("CASE WHEN s.user_id IS NULL THEN 0 ELSE 1 END DESC")
end
users.order("CASE WHEN last_seen_at IS NULL THEN 0 ELSE 1 END DESC, last_seen_at DESC, username ASC")
.limit(20)
end
end

View File

@ -31,4 +31,8 @@ class BasicPostSerializer < ApplicationSerializer
end
end
def include_name?
SiteSetting.enable_names?
end
end

View File

@ -112,7 +112,6 @@ class PostSerializer < BasicPostSerializer
def reply_to_user
{
username: object.reply_to_user.username,
name: object.reply_to_user.name,
avatar_template: object.reply_to_user.avatar_template
}
end
@ -194,6 +193,10 @@ class PostSerializer < BasicPostSerializer
post_actions.present? && post_actions.keys.include?(PostActionType.types[:bookmark])
end
def include_display_username?
SiteSetting.enable_names?
end
private
def post_actions

View File

@ -92,4 +92,8 @@ class UserSerializer < BasicUserSerializer
User.gravatar_template(object.email)
end
def include_name?
SiteSetting.enable_names?
end
end

View File

@ -721,6 +721,8 @@ en:
dominating_topic_minimum_percent: "What percentage of posts a user has to make in a topic before we consider it dominating."
enable_names: "Allow users to show their full names"
notification_types:
mentioned: "%{display_username} mentioned you in %{link}"
liked: "%{display_username} liked your post in %{link}"

View File

@ -11,6 +11,10 @@ class ActiveRecord::Base
exec_sql(*args).cmd_tuples
end
def self.sql_fragment(*sql_array)
ActiveRecord::Base.send(:sanitize_sql_array, sql_array)
end
# exists fine in rails4
unless rails4?
# note: update_attributes still spins up a transaction this can cause contention

View File

@ -893,6 +893,30 @@ describe UsersController do
json["users"].map { |u| u["username"] }.should include(user.username)
end
context "when `enable_names` is true" do
before do
SiteSetting.stubs(:enable_names?).returns(true)
end
it "returns names" do
xhr :post, :search_users, term: user.name
json = JSON.parse(response.body)
json["users"].map { |u| u["name"] }.should include(user.name)
end
end
context "when `enable_names` is false" do
before do
SiteSetting.stubs(:enable_names?).returns(false)
end
it "returns names" do
xhr :post, :search_users, term: user.name
json = JSON.parse(response.body)
json["users"].map { |u| u["name"] }.should_not include(user.name)
end
end
end
describe 'send_activation_email' do

View File

@ -21,53 +21,68 @@ describe UserSearch do
Fabricate :post, user: user6, topic: topic
end
def search_for(*args)
UserSearch.new(*args).search
end
# this is a seriously expensive integration test, re-creating this entire test db is too expensive
# reuse
it "operates correctly" do
# normal search
results = UserSearch.search user1.name.split(" ").first
results = search_for(user1.name.split(" ").first)
results.size.should == 1
results.first.should == user1
# lower case
results = UserSearch.search user1.name.split(" ").first.downcase
results = search_for(user1.name.split(" ").first.downcase)
results.size.should == 1
results.first.should == user1
# username
results = UserSearch.search user4.username
results = search_for(user4.username)
results.size.should == 1
results.first.should == user4
# case insensitive
results = UserSearch.search user4.username.upcase
results = search_for(user4.username.upcase)
results.size.should == 1
results.first.should == user4
# substrings
results = UserSearch.search "mr"
results = search_for("mr")
results.size.should == 6
results = UserSearch.search "mrb"
results = search_for("mrb")
results.size.should == 3
results = UserSearch.search "MR"
results = search_for("MR")
results.size.should == 6
results = UserSearch.search "MRB"
results = search_for("MRB")
results.size.should == 3
# topic priority
results = UserSearch.search "mrb", topic.id
results = search_for("mrb", topic.id)
results.first.should == user1
results = UserSearch.search "mrb", topic2.id
results = search_for("mrb", topic2.id)
results.first.should == user2
results = UserSearch.search "mrb", topic3.id
results = search_for("mrb", topic3.id)
results.first.should == user5
# When searching by name is enabled, it returns the record
SiteSetting.stubs(:enable_names).returns(true)
results = search_for("Tarantino")
results.size.should == 1
# When searching by name is disabled, it will not return the record
SiteSetting.stubs(:enable_names).returns(false)
results = search_for("Tarantino")
results.size.should == 0
end
end

View File

@ -0,0 +1,25 @@
require 'spec_helper'
require_dependency 'post'
require_dependency 'user'
describe BasicPostSerializer do
context "name" do
let(:user) { Fabricate.build(:user) }
let(:post) { Fabricate.build(:post, user: user) }
let(:serializer) { BasicPostSerializer.new(post, scope: Guardian.new, root: false) }
let(:json) { serializer.as_json }
it "returns the name it when `enable_names` is true" do
SiteSetting.stubs(:enable_names?).returns(true)
json[:name].should be_present
end
it "doesn't return the name it when `enable_names` is false" do
SiteSetting.stubs(:enable_names?).returns(false)
json[:name].should be_blank
end
end
end

View File

@ -58,4 +58,21 @@ describe PostSerializer do
end
end
context "display_username" do
let(:user) { Fabricate.build(:user) }
let(:post) { Fabricate.build(:post, user: user) }
let(:serializer) { PostSerializer.new(post, scope: Guardian.new, root: false) }
let(:json) { serializer.as_json }
it "returns the display_username it when `enable_names` is on" do
SiteSetting.stubs(:enable_names).returns(true)
json[:display_username].should be_present
end
it "doesn't return the display_username it when `enable_names` is off" do
SiteSetting.stubs(:enable_names).returns(false)
json[:display_username].should be_blank
end
end
end

View File

@ -0,0 +1,39 @@
require 'spec_helper'
require_dependency 'user'
describe UserSerializer do
context "with a user" do
let(:user) { Fabricate.build(:user) }
let(:serializer) { UserSerializer.new(user, scope: Guardian.new, root: false) }
let(:json) { serializer.as_json }
it "produces json" do
json.should be_present
end
context "with `enable_names` true" do
before do
SiteSetting.stubs(:enable_names?).returns(true)
end
it "has a name" do
json[:name].should == "Bruce Wayne"
end
end
context "with `enable_names` false" do
before do
SiteSetting.stubs(:enable_names?).returns(false)
end
it "has a name" do
puts json[:name]
json[:name].should be_blank
end
end
end
end