summaryrefslogtreecommitdiff
path: root/app/helpers/users_helper.rb
blob: 60230d58e30fb157408b14350cb8655e2fd648dc (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
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
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
# frozen_string_literal: true

module UsersHelper
  def admin_users_data_attributes(users)
    {
      users: Admin::UserSerializer.new.represent(users, { current_user: current_user }).to_json,
      paths: admin_users_paths.to_json
    }
  end

  def user_clear_status_at(user)
    # The user.status can be nil when the user has no status, so we need to protect against that case.
    # iso8601 is the official RFC supported format for frontend parsing of date:
    # https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/Date
    user.status&.clear_status_at&.to_s(:iso8601)
  end

  def user_link(user)
    link_to(user.name, user_path(user), title: user.email, class: 'has-tooltip commit-committer-link')
  end

  def user_email_help_text(user)
    return _('We also use email for avatar detection if no avatar is uploaded.') unless user.unconfirmed_email.present?

    confirmation_link = link_to _('Resend confirmation e-mail'), user_confirmation_path(user: { email: user.unconfirmed_email }), method: :post
    h(_('Please click the link in the confirmation email before continuing. It was sent to %{html_tag_strong_start}%{email}%{html_tag_strong_end}.')) % {
      html_tag_strong_start: '<strong>'.html_safe,
      html_tag_strong_end: '</strong>'.html_safe,
      email: user.unconfirmed_email
    } + content_tag(:p) { confirmation_link }
  end

  def profile_tabs
    @profile_tabs ||= get_profile_tabs
  end

  def profile_tab?(tab)
    profile_tabs.include?(tab)
  end

  def user_internal_regex_data
    settings = Gitlab::CurrentSettings.current_application_settings

    pattern, options = if settings.user_default_internal_regex_enabled?
                         regex = settings.user_default_internal_regex_instance
                         JsRegex.new(regex).to_h.slice(:source, :options).values
                       end

    { user_internal_regex_pattern: pattern, user_internal_regex_options: options }
  end

  def current_user_menu_items
    @current_user_menu_items ||= get_current_user_menu_items
  end

  def current_user_menu?(item)
    current_user_menu_items.include?(item)
  end

  # Used to preload when you are rendering many projects and checking access
  def load_max_project_member_accesses(projects)
    # There are two different request store paradigms for max member access and
    # we need to preload both of them. One is keyed User the other is keyed by
    # Project. See https://gitlab.com/gitlab-org/gitlab/-/issues/396822

    # rubocop: disable CodeReuse/ActiveRecord: `projects` can be array which also responds to pluck
    project_ids = projects.pluck(:id)
    # rubocop: enable CodeReuse/ActiveRecord

    Preloaders::UserMaxAccessLevelInProjectsPreloader
      .new(project_ids, current_user)
      .execute

    current_user&.max_member_access_for_project_ids(project_ids)
  end

  def max_project_member_access(project)
    current_user&.max_member_access_for_project(project.id) || Gitlab::Access::NO_ACCESS
  end

  def max_project_member_access_cache_key(project)
    "access:#{max_project_member_access(project)}"
  end

  def user_status(user)
    return unless user

    unless user.association(:status).loaded?
      exception = RuntimeError.new("Status was not preloaded")
      Gitlab::ErrorTracking.track_and_raise_for_dev_exception(exception, user: user.inspect)
    end

    return unless user.status

    content_tag :span,
      class: 'user-status-emoji has-tooltip',
      title: user.status.message_html,
      data: { html: true, placement: 'top' } do
      emoji_icon user.status.emoji
    end
  end

  def impersonation_enabled?
    Gitlab.config.gitlab.impersonation_enabled
  end

  def user_badges_in_admin_section(user)
    [].tap do |badges|
      badges << blocked_user_badge(user) if user.blocked?
      badges << { text: s_('AdminUsers|Admin'), variant: 'success' } if user.admin? # rubocop:disable Cop/UserAdmin
      badges << { text: s_('AdminUsers|Bot'), variant: 'muted' } if user.bot?
      badges << { text: s_('AdminUsers|External'), variant: 'secondary' } if user.external?
      badges << { text: s_("AdminUsers|It's you!"), variant: 'muted' } if current_user == user
      badges << { text: s_("AdminUsers|Locked"), variant: 'warning' } if user.access_locked?
    end
  end

  def work_information(user, with_schema_markup: false)
    return unless user

    organization = user.organization
    job_title = user.job_title

    if organization.present? && job_title.present?
      render_job_title_and_organization(job_title, organization, with_schema_markup: with_schema_markup)
    elsif job_title.present?
      render_job_title(job_title, with_schema_markup: with_schema_markup)
    elsif organization.present?
      render_organization(organization, with_schema_markup: with_schema_markup)
    end
  end

  def can_force_email_confirmation?(user)
    !user.confirmed?
  end

  def confirm_user_data(user)
    message = if user.unconfirmed_email.present?
                _('This user has an unconfirmed email address (%{email}). You may force a confirmation.') % { email: user.unconfirmed_email }
              else
                _('This user has an unconfirmed email address. You may force a confirmation.')
              end

    modal_attributes = Gitlab::Json.dump({
      title: s_('AdminUsers|Confirm user %{username}?') % { username: sanitize_name(user.name) },
      messageHtml: message,
      actionPrimary: {
        text: s_('AdminUsers|Confirm user'),
        attributes: [{ variant: 'confirm', 'data-qa-selector': 'confirm_user_confirm_button' }]
      },
      actionSecondary: {
        text: _('Cancel'),
        attributes: [{ variant: 'default' }]
      }
    })

    {
      path: confirm_admin_user_path(user),
      method: 'put',
      modal_attributes: modal_attributes,
      qa_selector: 'confirm_user_button'
    }
  end

  def user_display_name(user)
    return s_('UserProfile|Blocked user') if user.blocked?

    can_read_profile = can?(current_user, :read_user_profile, user)
    return s_('UserProfile|Unconfirmed user') unless user.confirmed? || can_read_profile

    user.name
  end

  def admin_user_actions_data_attributes(user)
    {
      user: Admin::UserEntity.represent(user, { current_user: current_user }).to_json,
      paths: admin_users_paths.to_json
    }
  end

  def display_public_email?(user)
    user.public_email.present?
  end

  def user_profile_tabs_app_data(user)
    {
      followees: user.followees.count,
      followers: user.followers.count,
      user_calendar_path: user_calendar_path(user, :json),
      utc_offset: local_timezone_instance(user.timezone).now.utc_offset,
      user_id: user.id
    }
  end

  private

  def admin_users_paths
    {
      edit: edit_admin_user_path(:id),
      approve: approve_admin_user_path(:id),
      reject: reject_admin_user_path(:id),
      unblock: unblock_admin_user_path(:id),
      block: block_admin_user_path(:id),
      deactivate: deactivate_admin_user_path(:id),
      activate: activate_admin_user_path(:id),
      unlock: unlock_admin_user_path(:id),
      delete: admin_user_path(:id),
      delete_with_contributions: admin_user_path(:id, hard_delete: true),
      admin_user: admin_user_path(:id),
      ban: ban_admin_user_path(:id),
      unban: unban_admin_user_path(:id)
    }
  end

  def blocked_user_badge(user)
    pending_approval_badge = { text: s_('AdminUsers|Pending approval'), variant: 'info' }
    return pending_approval_badge if user.blocked_pending_approval?

    banned_badge = { text: s_('AdminUsers|Banned'), variant: 'danger' }
    return banned_badge if user.banned?

    ldap_blocked_badge = { text: s_('AdminUsers|LDAP Blocked'), variant: 'danger' }
    return ldap_blocked_badge if user.ldap_blocked?

    { text: s_('AdminUsers|Blocked'), variant: 'danger' }
  end

  def get_profile_tabs
    tabs = []

    if can?(current_user, :read_user_profile, @user)
      tabs += [:overview, :activity, :groups, :contributed, :projects, :starred, :snippets, :followers, :following]
    end

    tabs
  end

  def get_current_user_menu_items
    items = []

    items << :sign_out if current_user

    return items if current_user&.required_terms_not_accepted?

    items << :help
    items << :profile if can?(current_user, :read_user, current_user)
    items << :settings if can?(current_user, :update_user, current_user)

    items
  end

  def render_job_title(job_title, with_schema_markup: false)
    if with_schema_markup
      content_tag :span, itemprop: 'jobTitle' do
        job_title
      end
    else
      job_title
    end
  end

  def render_organization(organization, with_schema_markup: false)
    if with_schema_markup
      content_tag :span, itemprop: 'worksFor' do
        organization
      end
    else
      organization
    end
  end

  def render_job_title_and_organization(job_title, organization, with_schema_markup: false)
    if with_schema_markup
      job_title = '<span itemprop="jobTitle">'.html_safe + job_title + "</span>".html_safe
      organization = '<span itemprop="worksFor">'.html_safe + organization + "</span>".html_safe

      html_escape(s_('Profile|%{job_title} at %{organization}')) % { job_title: job_title, organization: organization }
    else
      s_('Profile|%{job_title} at %{organization}') % { job_title: job_title, organization: organization }
    end
  end

  def user_table_headers
    [
      {
        section_class_name: 'section-40',
        header_text: _('Name')
      },
      {
        section_class_name: 'section-10',
        header_text: _('Projects')
      },
      {
        section_class_name: 'section-15',
        header_text: _('Created on')
      },
      {
        section_class_name: 'section-15',
        header_text: _('Last activity')
      }
    ]
  end

  # the keys should match the user model defined roles in app/models/user.rb
  def localized_user_roles
    {
      software_developer: s_('User|Software Developer'),
      development_team_lead: s_('User|Development Team Lead'),
      devops_engineer: s_('User|Devops Engineer'),
      systems_administrator: s_('User|Systems Administrator'),
      security_analyst: s_('User|Security Analyst'),
      data_analyst: s_('User|Data Analyst'),
      product_manager: s_('User|Product Manager'),
      product_designer: s_('User|Product Designer'),
      other: s_('User|Other')
    }.with_indifferent_access.freeze
  end

  def saved_replies_enabled?
    Feature.enabled?(:saved_replies, current_user)
  end
end

UsersHelper.prepend_mod_with('UsersHelper')