Class: UserSearch

Inherits:
Object
  • Object
show all
Defined in:
app/models/user_search.rb

Instance Method Summary collapse

Constructor Details

#initialize(term, opts = {}) ⇒ UserSearch

Returns a new instance of UserSearch.



6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# File 'app/models/user_search.rb', line 6

def initialize(term, opts = {})
  @term = term.downcase
  @term_like = @term.gsub("_", "\\_") + "%"
  @topic_id = opts[:topic_id]
  @category_id = opts[:category_id]
  @topic_allowed_users = opts[:topic_allowed_users]
  @searching_user = opts[:searching_user]
  @include_staged_users = opts[:include_staged_users] || false
  @last_seen_users = opts[:last_seen_users] || false
  @limit = opts[:limit] || 20
  @groups = opts[:groups]

  @topic = Topic.find(@topic_id) if @topic_id
  @category = Category.find(@category_id) if @category_id

  @guardian = Guardian.new(@searching_user)
  @guardian.ensure_can_see_groups_members!(@groups) if @groups
  @guardian.ensure_can_see_category!(@category) if @category
  @guardian.ensure_can_see_topic!(@topic) if @topic
end

Instance Method Details

#filtered_by_term_usersObject



49
50
51
52
53
54
55
56
57
58
59
60
61
62
# File 'app/models/user_search.rb', line 49

def filtered_by_term_users
  if @term.blank?
    scoped_users
  elsif SiteSetting.enable_names? && @term !~ /[_\.-]/
    query = Search.ts_query(term: @term, ts_config: "simple")

    scoped_users
      .includes(:user_search_data)
      .where("user_search_data.search_data @@ #{query}")
      .order(DB.sql_fragment("CASE WHEN username_lower LIKE ? THEN 0 ELSE 1 END ASC", @term_like))
  else
    scoped_users.where("username_lower LIKE :term_like", term_like: @term_like)
  end
end

#scoped_usersObject



27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
# File 'app/models/user_search.rb', line 27

def scoped_users
  users = User.where(active: true)
  users = users.where(approved: true) if SiteSetting.must_approve_users?
  users = users.where(staged: false) unless @include_staged_users
  users = users.not_suspended unless @searching_user&.staff?

  if @groups
    users = users.joins(:group_users).where("group_users.group_id IN (?)", @groups.map(&:id))
  end

  # Only show users who have access to private topic
  if @topic_allowed_users == "true" && @topic&.category&.read_restricted
    users =
      users
        .references(:categories)
        .includes(:secure_categories)
        .where("users.admin OR categories.id = ?", @topic.category_id)
  end

  users
end

#searchObject



199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
# File 'app/models/user_search.rb', line 199

def search
  ids = search_ids
  ids = DiscoursePluginRegistry.apply_modifier(:user_search_ids, ids)
  return User.none if ids.empty?

  results =
    User.joins(
      "JOIN (SELECT unnest uid, row_number() OVER () AS rn
    FROM unnest('{#{ids.join(",")}}'::int[])
  ) x on uid = users.id",
    ).order("rn")

  results = results.includes(:user_status) if SiteSetting.enable_user_status

  results
end

#search_idsObject



64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
# File 'app/models/user_search.rb', line 64

def search_ids
  users = Set.new

  # 1. exact username matches
  if @term.present?
    exact_matches = scoped_users.where(username_lower: @term)

    # don't pollute mentions with users who haven't shown up in over a year
    exact_matches = exact_matches.where("last_seen_at > ?", 1.year.ago) if @topic_id ||
      @category_id

    exact_matches.limit(@limit).pluck(:id).each { |id| users << id }
  end

  return users.to_a if users.size >= @limit

  # 2. in topic
  if @topic_id
    in_topic =
      filtered_by_term_users.where(
        "users.id IN (SELECT user_id FROM posts WHERE topic_id = ? AND post_type = ? AND deleted_at IS NULL)",
        @topic_id,
        Post.types[:regular],
      )

    in_topic = in_topic.where("users.id <> ?", @searching_user.id) if @searching_user.present?

    in_topic
      .order("last_seen_at DESC NULLS LAST")
      .limit(@limit - users.size)
      .pluck(:id)
      .each { |id| users << id }
  end

  return users.to_a if users.size >= @limit

  # 3. in category
  secure_category_id =
    if @category_id
      DB.query_single(<<~SQL, @category_id).first
        SELECT id
          FROM categories
         WHERE read_restricted
           AND id = ?
      SQL
    elsif @topic_id
      DB.query_single(<<~SQL, @topic_id).first
        SELECT id
          FROM categories
         WHERE read_restricted
           AND id IN (SELECT category_id FROM topics WHERE id = ?)
      SQL
    end

  if secure_category_id
    category_groups = Group.where(<<~SQL, secure_category_id, MAX_SIZE_PRIORITY_MENTION)
      groups.id IN (
        SELECT group_id
          FROM category_groups
          JOIN groups g ON group_id = g.id
         WHERE category_id = ?
           AND user_count < ?
      )
    SQL

    if @searching_user.present?
      category_groups = category_groups.members_visible_groups(@searching_user)
    end

    in_category = filtered_by_term_users.where(<<~SQL, category_groups.pluck(:id))
        users.id IN (
          SELECT gu.user_id
            FROM group_users gu
           WHERE group_id IN (?)
           LIMIT 200
        )
        SQL

    if @searching_user.present?
      in_category = in_category.where("users.id <> ?", @searching_user.id)
    end

    in_category
      .order("last_seen_at DESC NULLS LAST")
      .limit(@limit - users.size)
      .pluck(:id)
      .each { |id| users << id }
  end

  return users.to_a if users.size >= @limit

  # 4. global matches
  if @term.present?
    filtered_by_term_users
      .order("last_seen_at DESC NULLS LAST")
      .limit(@limit - users.size)
      .pluck(:id)
      .each { |id| users << id }
  end

  return users.to_a if users.size >= @limit

  # 5. last seen users (for search auto-suggestions)
  if @last_seen_users
    scoped_users
      .order("last_seen_at DESC NULLS LAST")
      .limit(@limit - users.size)
      .pluck(:id)
      .each { |id| users << id }
  end

  return users.to_a if users.size >= @limit

  # 6. similar usernames / names
  if @term.present? && SiteSetting.user_search_similar_results
    if SiteSetting.enable_names?
      scoped_users
        .where("username_lower <-> ? < 1 OR name <-> ? < 1", @term, @term)
        .order(["LEAST(username_lower <-> ?, name <-> ?) ASC", @term, @term])
        .limit(@limit - users.size)
        .pluck(:id)
        .each { |id| users << id }
    else
      scoped_users
        .where("username_lower <-> ? < 1", @term)
        .order(["username_lower <-> ? ASC", @term])
        .limit(@limit - users.size)
        .pluck(:id)
        .each { |id| users << id }
    end
  end

  users.to_a
end