notification.rb 8.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274
  1. # Copyright (C) 2012-2024 Zammad Foundation, https://zammad-foundation.org/
  2. class Transaction::Notification
  3. include ChecksHumanChanges
  4. =begin
  5. {
  6. object: 'Ticket',
  7. type: 'update',
  8. object_id: 123,
  9. interface_handle: 'application_server', # application_server|websocket|scheduler
  10. changes: {
  11. 'attribute1' => [before, now],
  12. 'attribute2' => [before, now],
  13. },
  14. created_at: Time.zone.now,
  15. user_id: 123,
  16. },
  17. =end
  18. def initialize(item, params = {})
  19. @item = item
  20. @params = params
  21. end
  22. def perform
  23. # return if we run import mode
  24. return if Setting.get('import_mode')
  25. return if @item[:object] != 'Ticket'
  26. return if @params[:disable_notification]
  27. ticket = Ticket.find_by(id: @item[:object_id])
  28. return if !ticket
  29. if @item[:article_id]
  30. article = Ticket::Article.find(@item[:article_id])
  31. # ignore notifications
  32. sender = Ticket::Article::Sender.lookup(id: article.sender_id)
  33. if sender&.name == 'System'
  34. return if @item[:changes].blank? && article.preferences[:notification] != true
  35. if article.preferences[:notification] != true
  36. article = nil
  37. end
  38. end
  39. end
  40. # find recipients
  41. recipients_and_channels = []
  42. recipients_reason = {}
  43. # loop through all group users
  44. possible_recipients = possible_recipients_of_group(ticket.group_id)
  45. # loop through all mention users
  46. mention_users = Mention.where(mentionable_type: @item[:object], mentionable_id: @item[:object_id]).map(&:user)
  47. if mention_users.present?
  48. # only notify if read permission on group are given
  49. mention_users.each do |mention_user|
  50. next if !mention_user.group_access?(ticket.group_id, 'read')
  51. possible_recipients.push mention_user
  52. recipients_reason[mention_user.id] = __('You are receiving this because you were mentioned in this ticket.')
  53. end
  54. end
  55. # apply owner
  56. if ticket.owner_id != 1
  57. possible_recipients.push ticket.owner
  58. recipients_reason[ticket.owner_id] = __('You are receiving this because you are the owner of this ticket.')
  59. end
  60. # apply out of office agents
  61. possible_recipients_additions = Set.new
  62. possible_recipients.each do |user|
  63. ooo_replacements(
  64. user: user,
  65. replacements: possible_recipients_additions,
  66. reasons: recipients_reason,
  67. ticket: ticket,
  68. )
  69. end
  70. if possible_recipients_additions.present?
  71. # join unique entries
  72. possible_recipients |= possible_recipients_additions.to_a
  73. end
  74. already_checked_recipient_ids = {}
  75. possible_recipients.each do |user|
  76. result = NotificationFactory::Mailer.notification_settings(user, ticket, @item[:type])
  77. next if !result
  78. next if already_checked_recipient_ids[user.id]
  79. already_checked_recipient_ids[user.id] = true
  80. recipients_and_channels.push result
  81. next if recipients_reason[user.id]
  82. recipients_reason[user.id] = __('You are receiving this because you are a member of the group of this ticket.')
  83. end
  84. # send notifications
  85. recipients_and_channels.each do |item|
  86. user = item[:user]
  87. channels = item[:channels]
  88. # ignore user who changed it by him self via web
  89. if @params[:interface_handle] == 'application_server'
  90. next if article&.updated_by_id == user.id
  91. next if !article && @item[:user_id] == user.id
  92. end
  93. # ignore inactive users
  94. next if !user.active?
  95. # ignore if no changes has been done
  96. changes = human_changes(@item[:changes], ticket, user)
  97. next if @item[:type] == 'update' && !article && changes.blank?
  98. # check if today already notified
  99. if @item[:type] == 'reminder_reached' || @item[:type] == 'escalation' || @item[:type] == 'escalation_warning'
  100. identifier = user.email
  101. if !identifier || identifier == ''
  102. identifier = user.login
  103. end
  104. already_notified_cutoff = Time.use_zone(Setting.get('timezone_default')) { Time.current.beginning_of_day }
  105. already_notified = History.where(
  106. history_type_id: History.type_lookup('notification').id,
  107. history_object_id: History.object_lookup('Ticket').id,
  108. o_id: ticket.id
  109. ).where('created_at > ?', already_notified_cutoff).exists?(['value_to LIKE ?', "%#{SqlHelper.quote_like(identifier)}(#{SqlHelper.quote_like(@item[:type])}:%"])
  110. next if already_notified
  111. end
  112. # create online notification
  113. used_channels = []
  114. if channels['online']
  115. used_channels.push 'online'
  116. created_by_id = @item[:user_id] || 1
  117. # delete old notifications
  118. if @item[:type] == 'reminder_reached'
  119. seen = false
  120. created_by_id = 1
  121. OnlineNotification.remove_by_type('Ticket', ticket.id, @item[:type], user)
  122. elsif @item[:type] == 'escalation' || @item[:type] == 'escalation_warning'
  123. seen = false
  124. created_by_id = 1
  125. OnlineNotification.remove_by_type('Ticket', ticket.id, 'escalation', user)
  126. OnlineNotification.remove_by_type('Ticket', ticket.id, 'escalation_warning', user)
  127. # on updates without state changes create unseen messages
  128. elsif @item[:type] != 'create' && (@item[:changes].blank? || @item[:changes]['state_id'].blank?)
  129. seen = false
  130. else
  131. seen = OnlineNotification.seen_state?(ticket, user.id)
  132. end
  133. OnlineNotification.add(
  134. type: @item[:type],
  135. object: 'Ticket',
  136. o_id: ticket.id,
  137. seen: seen,
  138. created_by_id: created_by_id,
  139. user_id: user.id,
  140. )
  141. Rails.logger.debug { "sent ticket online notifiaction to agent (#{@item[:type]}/#{ticket.id}/#{user.email})" }
  142. end
  143. # ignore email channel notification and empty emails
  144. if !channels['email'] || user.email.blank?
  145. add_recipient_list(ticket, user, used_channels, @item[:type])
  146. next
  147. end
  148. used_channels.push 'email'
  149. add_recipient_list(ticket, user, used_channels, @item[:type])
  150. # get user based notification template
  151. # if create, send create message / block update messages
  152. template = case @item[:type]
  153. when 'create'
  154. 'ticket_create'
  155. when 'update'
  156. 'ticket_update'
  157. when 'reminder_reached'
  158. 'ticket_reminder_reached'
  159. when 'escalation'
  160. 'ticket_escalation'
  161. when 'escalation_warning'
  162. 'ticket_escalation_warning'
  163. when 'update.merged_into'
  164. 'ticket_update_merged_into'
  165. when 'update.received_merge'
  166. 'ticket_update_received_merge'
  167. else
  168. raise "unknown type for notification #{@item[:type]}"
  169. end
  170. current_user = User.lookup(id: @item[:user_id])
  171. if !current_user
  172. current_user = User.lookup(id: 1)
  173. end
  174. attachments = []
  175. if article
  176. attachments = article.attachments_inline
  177. end
  178. NotificationFactory::Mailer.notification(
  179. template: template,
  180. user: user,
  181. objects: {
  182. ticket: ticket,
  183. article: article,
  184. recipient: user,
  185. current_user: current_user,
  186. changes: changes,
  187. reason: recipients_reason[user.id],
  188. },
  189. message_id: "<notification.#{DateTime.current.to_fs(:number)}.#{ticket.id}.#{user.id}.#{SecureRandom.uuid}@#{Setting.get('fqdn')}>",
  190. references: ticket.get_references,
  191. main_object: ticket,
  192. attachments: attachments,
  193. )
  194. Rails.logger.debug { "sent ticket email notifiaction to agent (#{@item[:type]}/#{ticket.id}/#{user.email})" }
  195. end
  196. end
  197. def add_recipient_list(ticket, user, channels, type)
  198. return if channels.blank?
  199. identifier = user.email
  200. if !identifier || identifier == ''
  201. identifier = user.login
  202. end
  203. recipient_list = "#{identifier}(#{type}:#{channels.join(',')})"
  204. History.add(
  205. o_id: ticket.id,
  206. history_type: 'notification',
  207. history_object: 'Ticket',
  208. value_to: recipient_list,
  209. created_by_id: @item[:user_id] || 1
  210. )
  211. end
  212. private
  213. def ooo_replacements(user:, replacements:, ticket:, reasons:)
  214. replacement = user.out_of_office_agent
  215. return if !replacement
  216. return if !TicketPolicy.new(replacement, ticket).agent_read_access?
  217. return if !replacements.add?(replacement)
  218. reasons[replacement.id] = __('You are receiving this because you are out-of-office replacement for a participant of this ticket.')
  219. end
  220. def possible_recipients_of_group(group_id)
  221. Rails.cache.fetch("User/notification/possible_recipients_of_group/#{group_id}", expires_in: 20.seconds) do
  222. User.group_access(group_id, 'full').sort_by(&:login)
  223. end
  224. end
  225. end