notification.rb 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360
  1. # Copyright (C) 2012-2023 Zammad Foundation, https://zammad-foundation.org/
  2. class Transaction::Notification
  3. =begin
  4. {
  5. object: 'Ticket',
  6. type: 'update',
  7. object_id: 123,
  8. interface_handle: 'application_server', # application_server|websocket|scheduler
  9. changes: {
  10. 'attribute1' => [before, now],
  11. 'attribute2' => [before, now],
  12. },
  13. created_at: Time.zone.now,
  14. user_id: 123,
  15. },
  16. =end
  17. def initialize(item, params = {})
  18. @item = item
  19. @params = params
  20. end
  21. def perform
  22. # return if we run import mode
  23. return if Setting.get('import_mode')
  24. return if @item[:object] != 'Ticket'
  25. return if @params[:disable_notification]
  26. ticket = Ticket.find_by(id: @item[:object_id])
  27. return if !ticket
  28. if @item[:article_id]
  29. article = Ticket::Article.find(@item[:article_id])
  30. # ignore notifications
  31. sender = Ticket::Article::Sender.lookup(id: article.sender_id)
  32. if sender&.name == 'System'
  33. return if @item[:changes].blank? && article.preferences[:notification] != true
  34. if article.preferences[:notification] != true
  35. article = nil
  36. end
  37. end
  38. end
  39. # find recipients
  40. recipients_and_channels = []
  41. recipients_reason = {}
  42. # loop through all group users
  43. possible_recipients = possible_recipients_of_group(ticket.group_id)
  44. # loop through all mention users
  45. mention_users = Mention.where(mentionable_type: @item[:object], mentionable_id: @item[:object_id]).map(&:user)
  46. if mention_users.present?
  47. # only notify if read permission on group are given
  48. mention_users.each do |mention_user|
  49. next if !mention_user.group_access?(ticket.group_id, 'read')
  50. possible_recipients.push mention_user
  51. recipients_reason[mention_user.id] = __('You are receiving this because you were mentioned in this ticket.')
  52. end
  53. end
  54. # apply owner
  55. if ticket.owner_id != 1
  56. possible_recipients.push ticket.owner
  57. recipients_reason[ticket.owner_id] = __('You are receiving this because you are the owner of this ticket.')
  58. end
  59. # apply out of office agents
  60. possible_recipients_additions = Set.new
  61. possible_recipients.each do |user|
  62. ooo_replacements(
  63. user: user,
  64. replacements: possible_recipients_additions,
  65. reasons: recipients_reason,
  66. ticket: ticket,
  67. )
  68. end
  69. if possible_recipients_additions.present?
  70. # join unique entries
  71. possible_recipients |= possible_recipients_additions.to_a
  72. end
  73. already_checked_recipient_ids = {}
  74. possible_recipients.each do |user|
  75. result = NotificationFactory::Mailer.notification_settings(user, ticket, @item[:type])
  76. next if !result
  77. next if already_checked_recipient_ids[user.id]
  78. already_checked_recipient_ids[user.id] = true
  79. recipients_and_channels.push result
  80. next if recipients_reason[user.id]
  81. recipients_reason[user.id] = __('You are receiving this because you are a member of the group of this ticket.')
  82. end
  83. # send notifications
  84. recipients_and_channels.each do |item|
  85. user = item[:user]
  86. channels = item[:channels]
  87. # ignore user who changed it by him self via web
  88. if @params[:interface_handle] == 'application_server'
  89. next if article&.updated_by_id == user.id
  90. next if !article && @item[:user_id] == user.id
  91. end
  92. # ignore inactive users
  93. next if !user.active?
  94. # ignore if no changes has been done
  95. changes = human_changes(user, ticket)
  96. next if @item[:type] == 'update' && !article && changes.blank?
  97. # check if today already notified
  98. if @item[:type] == 'reminder_reached' || @item[:type] == 'escalation' || @item[:type] == 'escalation_warning'
  99. identifier = user.email
  100. if !identifier || identifier == ''
  101. identifier = user.login
  102. end
  103. already_notified_cutoff = Time.use_zone(Setting.get('timezone_default_sanitized')) { Time.current.beginning_of_day }
  104. already_notified = History.where(
  105. history_type_id: History.type_lookup('notification').id,
  106. history_object_id: History.object_lookup('Ticket').id,
  107. o_id: ticket.id
  108. ).where('created_at > ?', already_notified_cutoff).exists?(['value_to LIKE ?', "%#{identifier}(#{@item[:type]}:%"])
  109. next if already_notified
  110. end
  111. # create online notification
  112. used_channels = []
  113. if channels['online']
  114. used_channels.push 'online'
  115. created_by_id = @item[:user_id] || 1
  116. # delete old notifications
  117. if @item[:type] == 'reminder_reached'
  118. seen = false
  119. created_by_id = 1
  120. OnlineNotification.remove_by_type('Ticket', ticket.id, @item[:type], user)
  121. elsif @item[:type] == 'escalation' || @item[:type] == 'escalation_warning'
  122. seen = false
  123. created_by_id = 1
  124. OnlineNotification.remove_by_type('Ticket', ticket.id, 'escalation', user)
  125. OnlineNotification.remove_by_type('Ticket', ticket.id, 'escalation_warning', user)
  126. # on updates without state changes create unseen messages
  127. elsif @item[:type] != 'create' && (@item[:changes].blank? || @item[:changes]['state_id'].blank?)
  128. seen = false
  129. else
  130. seen = ticket.online_notification_seen_state(user.id)
  131. end
  132. OnlineNotification.add(
  133. type: @item[:type],
  134. object: 'Ticket',
  135. o_id: ticket.id,
  136. seen: seen,
  137. created_by_id: created_by_id,
  138. user_id: user.id,
  139. )
  140. Rails.logger.debug { "sent ticket online notifiaction to agent (#{@item[:type]}/#{ticket.id}/#{user.email})" }
  141. end
  142. # ignore email channel notification and empty emails
  143. if !channels['email'] || user.email.blank?
  144. add_recipient_list(ticket, user, used_channels, @item[:type])
  145. next
  146. end
  147. used_channels.push 'email'
  148. add_recipient_list(ticket, user, used_channels, @item[:type])
  149. # get user based notification template
  150. # if create, send create message / block update messages
  151. template = case @item[:type]
  152. when 'create'
  153. 'ticket_create'
  154. when 'update'
  155. 'ticket_update'
  156. when 'reminder_reached'
  157. 'ticket_reminder_reached'
  158. when 'escalation'
  159. 'ticket_escalation'
  160. when 'escalation_warning'
  161. 'ticket_escalation_warning'
  162. when 'update.merged_into'
  163. 'ticket_update_merged_into'
  164. when 'update.received_merge'
  165. 'ticket_update_received_merge'
  166. else
  167. raise "unknown type for notification #{@item[:type]}"
  168. end
  169. current_user = User.lookup(id: @item[:user_id])
  170. if !current_user
  171. current_user = User.lookup(id: 1)
  172. end
  173. attachments = []
  174. if article
  175. attachments = article.attachments_inline
  176. end
  177. NotificationFactory::Mailer.notification(
  178. template: template,
  179. user: user,
  180. objects: {
  181. ticket: ticket,
  182. article: article,
  183. recipient: user,
  184. current_user: current_user,
  185. changes: changes,
  186. reason: recipients_reason[user.id],
  187. },
  188. message_id: "<notification.#{DateTime.current.to_s(:number)}.#{ticket.id}.#{user.id}.#{SecureRandom.uuid}@#{Setting.get('fqdn')}>",
  189. references: ticket.get_references,
  190. main_object: ticket,
  191. attachments: attachments,
  192. )
  193. Rails.logger.debug { "sent ticket email notifiaction to agent (#{@item[:type]}/#{ticket.id}/#{user.email})" }
  194. end
  195. end
  196. def add_recipient_list(ticket, user, channels, type)
  197. return if channels.blank?
  198. identifier = user.email
  199. if !identifier || identifier == ''
  200. identifier = user.login
  201. end
  202. recipient_list = "#{identifier}(#{type}:#{channels.join(',')})"
  203. History.add(
  204. o_id: ticket.id,
  205. history_type: 'notification',
  206. history_object: 'Ticket',
  207. value_to: recipient_list,
  208. created_by_id: @item[:user_id] || 1
  209. )
  210. end
  211. def human_changes(user, record)
  212. return {} if !@item[:changes]
  213. locale = user.locale
  214. # only show allowed attributes
  215. attribute_list = ObjectManager::Object.new('Ticket').attributes(user).index_by { |item| item[:name] }
  216. user_related_changes = {}
  217. @item[:changes].each do |key, value|
  218. # if no config exists, use all attributes
  219. # or if config exists, just use existing attributes for user
  220. if attribute_list.blank? || attribute_list[key.to_s]
  221. user_related_changes[key] = value
  222. end
  223. end
  224. changes = {}
  225. user_related_changes.each do |key, value|
  226. # get attribute name
  227. attribute_name = key.to_s
  228. object_manager_attribute = attribute_list[attribute_name]
  229. if attribute_name[-3, 3] == '_id'
  230. attribute_name = attribute_name[ 0, attribute_name.length - 3 ].to_s
  231. end
  232. # add item to changes hash
  233. if key.to_s == attribute_name
  234. changes[attribute_name] = value
  235. end
  236. # if changed item is an _id field/reference, look up the real values
  237. value_id = []
  238. value_str = [ value[0], value[1] ]
  239. if key.to_s[-3, 3] == '_id'
  240. value_id[0] = value[0]
  241. value_id[1] = value[1]
  242. if record.respond_to?(attribute_name) && record.send(attribute_name)
  243. relation_class = record.send(attribute_name).class
  244. if relation_class && value_id[0]
  245. relation_model = relation_class.lookup(id: value_id[0])
  246. if relation_model
  247. if relation_model['name']
  248. value_str[0] = relation_model['name']
  249. elsif relation_model.respond_to?(:fullname)
  250. value_str[0] = relation_model.send(:fullname)
  251. end
  252. end
  253. end
  254. if relation_class && value_id[1]
  255. relation_model = relation_class.lookup(id: value_id[1])
  256. if relation_model
  257. if relation_model['name']
  258. value_str[1] = relation_model['name']
  259. elsif relation_model.respond_to?(:fullname)
  260. value_str[1] = relation_model.send(:fullname)
  261. end
  262. end
  263. end
  264. end
  265. end
  266. # check if we have a dedicated display name for it
  267. display = attribute_name
  268. if object_manager_attribute && object_manager_attribute[:display]
  269. # delete old key
  270. changes.delete(display)
  271. # set new key
  272. display = object_manager_attribute[:display].to_s
  273. end
  274. changes[display] = if object_manager_attribute && object_manager_attribute[:translate]
  275. from = Translation.translate(locale, value_str[0])
  276. to = Translation.translate(locale, value_str[1])
  277. [from, to]
  278. else
  279. [value_str[0].to_s, value_str[1].to_s]
  280. end
  281. end
  282. changes
  283. end
  284. private
  285. def ooo_replacements(user:, replacements:, ticket:, reasons:)
  286. replacement = user.out_of_office_agent
  287. return if !replacement
  288. return if !TicketPolicy.new(replacement, ticket).agent_read_access?
  289. return if !replacements.add?(replacement)
  290. reasons[replacement.id] = __('You are receiving this because you are out-of-office replacement for a participant of this ticket.')
  291. end
  292. def possible_recipients_of_group(group_id)
  293. Rails.cache.fetch("User/notification/possible_recipients_of_group/#{group_id}", expires_in: 20.seconds) do
  294. User.group_access(group_id, 'full').sort_by(&:login)
  295. end
  296. end
  297. end