1# frozen_string_literal: true
2
3module Emails
4  module Members
5    extend ActiveSupport::Concern
6    include MembersHelper
7    include Gitlab::Experiment::Dsl
8
9    INITIAL_INVITE = 'initial_email'
10
11    included do
12      helper_method :member_source, :member
13      helper_method :experiment
14    end
15
16    def member_access_requested_email(member_source_type, member_id, recipient_id)
17      @member_source_type = member_source_type
18      @member_id = member_id
19
20      return unless member_exists?
21
22      user = User.find(recipient_id)
23
24      member_email_with_layout(
25        to: user.notification_email_for(notification_group),
26        subject: subject("Request to join the #{member_source.human_name} #{member_source.model_name.singular}"))
27    end
28
29    def member_access_granted_email(member_source_type, member_id)
30      @member_source_type = member_source_type
31      @member_id = member_id
32
33      return unless member_exists?
34
35      member_email_with_layout(
36        to: member.user.notification_email_for(notification_group),
37        subject: subject("Access to the #{member_source.human_name} #{member_source.model_name.singular} was granted"))
38    end
39
40    def member_access_denied_email(member_source_type, source_id, user_id)
41      @member_source_type = member_source_type
42      @member_source = member_source_class.find(source_id)
43
44      user = User.find(user_id)
45
46      @source_hidden = !member_source.readable_by?(user)
47
48      human_name = @source_hidden ? 'Hidden' : member_source.human_name
49
50      member_email_with_layout(
51        to: user.notification_email_for(notification_group),
52        subject: subject("Access to the #{human_name} #{member_source.model_name.singular} was denied"))
53    end
54
55    def member_invited_email(member_source_type, member_id, token)
56      @member_source_type = member_source_type
57      @member_id = member_id
58      @token = token
59
60      return unless member_exists?
61
62      Gitlab::Tracking.event(self.class.name, 'invite_email_sent', label: 'invite_email', property: member_id.to_s)
63
64      mail(to: member.invite_email, subject: invite_email_subject, **invite_email_headers) do |format|
65        format.html { render layout: 'unknown_user_mailer' }
66        format.text { render layout: 'unknown_user_mailer' }
67      end
68    end
69
70    def member_invited_reminder_email(member_source_type, member_id, token, reminder_index)
71      @member_source_type = member_source_type
72      @member_id = member_id
73      @token = token
74      @reminder_index = reminder_index
75
76      return unless member_exists? && member.created_by && member.invite_to_unknown_user?
77
78      subjects = {
79        0 => s_("InviteReminderEmail|%{inviter}'s invitation to GitLab is pending"),
80        1 => s_('InviteReminderEmail|%{inviter} is waiting for you to join GitLab'),
81        2 => s_('InviteReminderEmail|%{inviter} is still waiting for you to join GitLab')
82      }
83
84      subject_line = subjects[reminder_index] % { inviter: member.created_by.name }
85
86      member_email_with_layout(
87        layout: 'unknown_user_mailer',
88        to: member.invite_email,
89        subject: subject(subject_line)
90      )
91    end
92
93    def member_invite_accepted_email(member_source_type, member_id)
94      @member_source_type = member_source_type
95      @member_id = member_id
96
97      return unless member_exists?
98      return unless member.created_by
99
100      member_email_with_layout(
101        to: member.created_by.notification_email_for(notification_group),
102        subject: subject('Invitation accepted'))
103    end
104
105    def member_invite_declined_email(member_source_type, source_id, invite_email, created_by_id)
106      return unless created_by_id
107
108      @member_source_type = member_source_type
109      @member_source = member_source_class.find(source_id)
110      @invite_email = invite_email
111
112      user = User.find(created_by_id)
113
114      member_email_with_layout(
115        to: user.notification_email_for(notification_group),
116        subject: subject('Invitation declined'))
117    end
118
119    def member_expiration_date_updated_email(member_source_type, member_id)
120      @member_source_type = member_source_type
121      @member_id = member_id
122
123      return unless member_exists?
124
125      subject = if member.expires?
126                  _('Group membership expiration date changed')
127                else
128                  _('Group membership expiration date removed')
129                end
130
131      member_email_with_layout(
132        to: member.user.notification_email_for(notification_group),
133        subject: subject(subject))
134    end
135
136    # rubocop: disable CodeReuse/ActiveRecord
137    def member
138      @member ||= Member.find_by(id: @member_id)
139    end
140    # rubocop: enable CodeReuse/ActiveRecord
141
142    def member_source
143      @member_source ||= member.source
144    end
145
146    def notification_group
147      @member_source_type.casecmp?('project') ? member_source.group : member_source
148    end
149
150    private
151
152    def invite_email_subject
153      if member.created_by
154        subject(s_("MemberInviteEmail|%{member_name} invited you to join GitLab") % { member_name: member.created_by.name })
155      else
156        subject(s_("MemberInviteEmail|Invitation to join the %{project_or_group} %{project_or_group_name}") % { project_or_group: member_source.human_name, project_or_group_name: member_source.model_name.singular })
157      end
158    end
159
160    def invite_email_headers
161      if Gitlab::CurrentSettings.mailgun_events_enabled?
162        {
163          'X-Mailgun-Tag' => ::Members::Mailgun::INVITE_EMAIL_TAG,
164          'X-Mailgun-Variables' => { ::Members::Mailgun::INVITE_EMAIL_TOKEN_KEY => @token }.to_json
165        }
166      else
167        {}
168      end
169    end
170
171    def member_exists?
172      Gitlab::AppLogger.info("Tried to send an email invitation for a deleted group. Member id: #{@member_id}") if member.blank?
173      member.present?
174    end
175
176    def member_source_class
177      @member_source_type.classify.constantize
178    end
179
180    def member_email_with_layout(to:, subject:, layout: 'mailer')
181      mail(to: to, subject: subject) do |format|
182        format.html { render layout: layout }
183        format.text { render layout: layout }
184      end
185    end
186  end
187end
188
189Emails::Members.prepend_mod_with('Emails::Members')
190