ticket.rb 53 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595
  1. # Copyright (C) 2012-2016 Zammad Foundation, http://zammad-foundation.org/
  2. class Ticket < ApplicationModel
  3. include CanBeImported
  4. include HasActivityStreamLog
  5. include ChecksClientNotification
  6. include ChecksLatestChangeObserved
  7. include CanCsvImport
  8. include ChecksHtmlSanitized
  9. include HasHistory
  10. include HasTags
  11. include HasSearchIndexBackend
  12. include HasOnlineNotifications
  13. include HasKarmaActivityLog
  14. include HasLinks
  15. include Ticket::ChecksAccess
  16. include HasObjectManagerAttributesValidation
  17. include Ticket::Escalation
  18. include Ticket::Subject
  19. include Ticket::Assets
  20. include Ticket::SearchIndex
  21. include Ticket::Search
  22. store :preferences
  23. before_create :check_generate, :check_defaults, :check_title, :set_default_state, :set_default_priority
  24. before_update :check_defaults, :check_title, :reset_pending_time, :check_owner_active
  25. validates :group_id, presence: true
  26. activity_stream_permission 'ticket.agent'
  27. activity_stream_attributes_ignored :organization_id, # organization_id will change automatically on user update
  28. :create_article_type_id,
  29. :create_article_sender_id,
  30. :article_count,
  31. :first_response_at,
  32. :first_response_escalation_at,
  33. :first_response_in_min,
  34. :first_response_diff_in_min,
  35. :close_at,
  36. :close_escalation_at,
  37. :close_in_min,
  38. :close_diff_in_min,
  39. :update_escalation_at,
  40. :update_in_min,
  41. :update_diff_in_min,
  42. :last_contact_at,
  43. :last_contact_agent_at,
  44. :last_contact_customer_at,
  45. :last_owner_update_at,
  46. :preferences
  47. history_attributes_ignored :create_article_type_id,
  48. :create_article_sender_id,
  49. :article_count,
  50. :preferences
  51. history_relation_object 'Ticket::Article'
  52. sanitized_html :note
  53. belongs_to :group, optional: true
  54. belongs_to :organization, optional: true
  55. has_many :articles, class_name: 'Ticket::Article', after_add: :cache_update, after_remove: :cache_update, dependent: :destroy, inverse_of: :ticket
  56. has_many :ticket_time_accounting, class_name: 'Ticket::TimeAccounting', dependent: :destroy, inverse_of: :ticket
  57. belongs_to :state, class_name: 'Ticket::State', optional: true
  58. belongs_to :priority, class_name: 'Ticket::Priority', optional: true
  59. belongs_to :owner, class_name: 'User', optional: true
  60. belongs_to :customer, class_name: 'User', optional: true
  61. belongs_to :created_by, class_name: 'User', optional: true
  62. belongs_to :updated_by, class_name: 'User', optional: true
  63. belongs_to :create_article_type, class_name: 'Ticket::Article::Type', optional: true
  64. belongs_to :create_article_sender, class_name: 'Ticket::Article::Sender', optional: true
  65. self.inheritance_column = nil
  66. attr_accessor :callback_loop
  67. =begin
  68. get user access conditions
  69. conditions = Ticket.access_condition( User.find(1) , 'full')
  70. returns
  71. result = [user1, user2, ...]
  72. =end
  73. def self.access_condition(user, access)
  74. if user.permissions?('ticket.agent')
  75. ['group_id IN (?)', user.group_ids_access(access)]
  76. elsif !user.organization || ( !user.organization.shared || user.organization.shared == false )
  77. ['tickets.customer_id = ?', user.id]
  78. else
  79. ['(tickets.customer_id = ? OR tickets.organization_id = ?)', user.id, user.organization.id]
  80. end
  81. end
  82. =begin
  83. processes tickets which have reached their pending time and sets next state_id
  84. processed_tickets = Ticket.process_pending
  85. returns
  86. processed_tickets = [<Ticket>, ...]
  87. =end
  88. def self.process_pending
  89. result = []
  90. # process pending action tickets
  91. pending_action = Ticket::StateType.find_by(name: 'pending action')
  92. ticket_states_pending_action = Ticket::State.where(state_type_id: pending_action)
  93. .where.not(next_state_id: nil)
  94. if ticket_states_pending_action.present?
  95. next_state_map = {}
  96. ticket_states_pending_action.each do |state|
  97. next_state_map[state.id] = state.next_state_id
  98. end
  99. tickets = where(state_id: next_state_map.keys)
  100. .where('pending_time <= ?', Time.zone.now)
  101. tickets.each do |ticket|
  102. Transaction.execute do
  103. ticket.state_id = next_state_map[ticket.state_id]
  104. ticket.updated_at = Time.zone.now
  105. ticket.updated_by_id = 1
  106. ticket.save!
  107. end
  108. result.push ticket
  109. end
  110. end
  111. # process pending reminder tickets
  112. pending_reminder = Ticket::StateType.find_by(name: 'pending reminder')
  113. ticket_states_pending_reminder = Ticket::State.where(state_type_id: pending_reminder)
  114. if ticket_states_pending_reminder.present?
  115. reminder_state_map = {}
  116. ticket_states_pending_reminder.each do |state|
  117. reminder_state_map[state.id] = state.next_state_id
  118. end
  119. tickets = where(state_id: reminder_state_map.keys)
  120. .where('pending_time <= ?', Time.zone.now)
  121. tickets.each do |ticket|
  122. article_id = nil
  123. article = Ticket::Article.last_customer_agent_article(ticket.id)
  124. if article
  125. article_id = article.id
  126. end
  127. # send notification
  128. Transaction::BackgroundJob.run(
  129. object: 'Ticket',
  130. type: 'reminder_reached',
  131. object_id: ticket.id,
  132. article_id: article_id,
  133. user_id: 1,
  134. )
  135. result.push ticket
  136. end
  137. end
  138. result
  139. end
  140. =begin
  141. processes escalated tickets
  142. processed_tickets = Ticket.process_escalation
  143. returns
  144. processed_tickets = [<Ticket>, ...]
  145. =end
  146. def self.process_escalation
  147. result = []
  148. # get max warning diff
  149. tickets = where('escalation_at <= ?', Time.zone.now + 15.minutes)
  150. tickets.each do |ticket|
  151. # get sla
  152. ticket.escalation_calculation_get_sla
  153. article_id = nil
  154. article = Ticket::Article.last_customer_agent_article(ticket.id)
  155. if article
  156. article_id = article.id
  157. end
  158. # send escalation
  159. if ticket.escalation_at < Time.zone.now
  160. Transaction::BackgroundJob.run(
  161. object: 'Ticket',
  162. type: 'escalation',
  163. object_id: ticket.id,
  164. article_id: article_id,
  165. user_id: 1,
  166. )
  167. result.push ticket
  168. next
  169. end
  170. # check if warning need to be sent
  171. Transaction::BackgroundJob.run(
  172. object: 'Ticket',
  173. type: 'escalation_warning',
  174. object_id: ticket.id,
  175. article_id: article_id,
  176. user_id: 1,
  177. )
  178. result.push ticket
  179. end
  180. result
  181. end
  182. =begin
  183. processes tickets which auto unassign time has reached
  184. processed_tickets = Ticket.process_auto_unassign
  185. returns
  186. processed_tickets = [<Ticket>, ...]
  187. =end
  188. def self.process_auto_unassign
  189. # process pending action tickets
  190. state_ids = Ticket::State.by_category(:work_on).pluck(:id)
  191. return [] if state_ids.blank?
  192. result = []
  193. groups = Group.where(active: true).where('assignment_timeout IS NOT NULL AND groups.assignment_timeout != 0')
  194. return [] if groups.blank?
  195. groups.each do |group|
  196. next if group.assignment_timeout.blank?
  197. ticket_ids = Ticket.where('state_id IN (?) AND owner_id != 1 AND group_id = ? AND last_owner_update_at IS NOT NULL', state_ids, group.id).limit(600).pluck(:id)
  198. ticket_ids.each do |ticket_id|
  199. ticket = Ticket.find_by(id: ticket_id)
  200. next if !ticket
  201. minutes_since_last_assignment = Time.zone.now - ticket.last_owner_update_at
  202. next if (minutes_since_last_assignment / 60) <= group.assignment_timeout
  203. Transaction.execute do
  204. ticket.owner_id = 1
  205. ticket.updated_at = Time.zone.now
  206. ticket.updated_by_id = 1
  207. ticket.save!
  208. end
  209. result.push ticket
  210. end
  211. end
  212. result
  213. end
  214. =begin
  215. merge tickets
  216. ticket = Ticket.find(123)
  217. result = ticket.merge_to(
  218. ticket_id: 123,
  219. user_id: 123,
  220. )
  221. returns
  222. result = true|false
  223. =end
  224. def merge_to(data)
  225. # prevent cross merging tickets
  226. target_ticket = Ticket.find_by(id: data[:ticket_id])
  227. raise 'no target ticket given' if !target_ticket
  228. raise Exceptions::UnprocessableEntity, 'ticket already merged, no merge into merged ticket possible' if target_ticket.state.state_type.name == 'merged'
  229. # check different ticket ids
  230. raise Exceptions::UnprocessableEntity, 'Can\'t merge ticket with it self!' if id == target_ticket.id
  231. # update articles
  232. Transaction.execute do
  233. Ticket::Article.where(ticket_id: id).each(&:touch)
  234. # quiet update of reassign of articles
  235. Ticket::Article.where(ticket_id: id).update_all(['ticket_id = ?', data[:ticket_id]]) # rubocop:disable Rails/SkipsModelValidations
  236. # mark target ticket as updated
  237. # otherwise the "received_merge" history entry
  238. # will be the same as the last updated_at
  239. # which might be a long time ago
  240. target_ticket.updated_at = Time.zone.now
  241. # add merge event to both ticket's history (Issue #2469 - Add information "Ticket merged" to History)
  242. target_ticket.history_log(
  243. 'received_merge',
  244. data[:user_id],
  245. id_to: target_ticket.id,
  246. id_from: id,
  247. )
  248. history_log(
  249. 'merged_into',
  250. data[:user_id],
  251. id_to: target_ticket.id,
  252. id_from: id,
  253. )
  254. # create new merge article
  255. Ticket::Article.create(
  256. ticket_id: id,
  257. type_id: Ticket::Article::Type.lookup(name: 'note').id,
  258. sender_id: Ticket::Article::Sender.lookup(name: 'Agent').id,
  259. body: 'merged',
  260. internal: false,
  261. created_by_id: data[:user_id],
  262. updated_by_id: data[:user_id],
  263. )
  264. # reassign links to the new ticket
  265. # rubocop:disable Rails/SkipsModelValidations
  266. Link.where(
  267. link_object_source_id: Link::Object.find_by(name: 'Ticket').id,
  268. link_object_source_value: id,
  269. ).update_all(link_object_source_value: data[:ticket_id])
  270. Link.where(
  271. link_object_target_id: Link::Object.find_by(name: 'Ticket').id,
  272. link_object_target_value: id,
  273. ).update_all(link_object_target_value: data[:ticket_id])
  274. # rubocop:enable Rails/SkipsModelValidations
  275. # link tickets
  276. Link.add(
  277. link_type: 'parent',
  278. link_object_source: 'Ticket',
  279. link_object_source_value: data[:ticket_id],
  280. link_object_target: 'Ticket',
  281. link_object_target_value: id
  282. )
  283. # set state to 'merged'
  284. self.state_id = Ticket::State.lookup(name: 'merged').id
  285. # rest owner
  286. self.owner_id = 1
  287. # save ticket
  288. save!
  289. # touch new ticket (to broadcast change)
  290. target_ticket.touch # rubocop:disable Rails/SkipsModelValidations
  291. end
  292. true
  293. end
  294. =begin
  295. check if online notification should be shown in general as already seen with current state
  296. ticket = Ticket.find(1)
  297. seen = ticket.online_notification_seen_state(user_id_check)
  298. returns
  299. result = true # or false
  300. =end
  301. def online_notification_seen_state(user_id_check = nil)
  302. state = Ticket::State.lookup(id: state_id)
  303. state_type = Ticket::StateType.lookup(id: state.state_type_id)
  304. # always to set unseen for ticket owner and users which did not the update
  305. if state_type.name != 'merged'
  306. if user_id_check
  307. return false if user_id_check == owner_id && user_id_check != updated_by_id
  308. end
  309. end
  310. # set all to seen if pending action state is a closed or merged state
  311. if state_type.name == 'pending action' && state.next_state_id
  312. state = Ticket::State.lookup(id: state.next_state_id)
  313. state_type = Ticket::StateType.lookup(id: state.state_type_id)
  314. end
  315. # set all to seen if new state is pending reminder state
  316. if state_type.name == 'pending reminder'
  317. if user_id_check
  318. return false if owner_id == 1
  319. return false if updated_by_id != owner_id && user_id_check == owner_id
  320. return true
  321. end
  322. return true
  323. end
  324. # set all to seen if new state is a closed or merged state
  325. return true if state_type.name == 'closed'
  326. return true if state_type.name == 'merged'
  327. false
  328. end
  329. =begin
  330. get count of tickets and tickets which match on selector
  331. ticket_count, tickets = Ticket.selectors(params[:condition], limit: limit, current_user: current_user, access: 'full')
  332. =end
  333. def self.selectors(selectors, options)
  334. limit = options[:limit] || 10
  335. current_user = options[:current_user]
  336. access = options[:access] || 'full'
  337. raise 'no selectors given' if !selectors
  338. query, bind_params, tables = selector2sql(selectors, current_user: current_user)
  339. return [] if !query
  340. ActiveRecord::Base.transaction(requires_new: true) do
  341. if !current_user
  342. ticket_count = Ticket.distinct.where(query, *bind_params).joins(tables).count
  343. tickets = Ticket.distinct.where(query, *bind_params).joins(tables).limit(limit)
  344. return [ticket_count, tickets]
  345. end
  346. access_condition = Ticket.access_condition(current_user, access)
  347. ticket_count = Ticket.distinct.where(access_condition).where(query, *bind_params).joins(tables).count
  348. tickets = Ticket.distinct.where(access_condition).where(query, *bind_params).joins(tables).limit(limit)
  349. return [ticket_count, tickets]
  350. rescue ActiveRecord::StatementInvalid => e
  351. Rails.logger.error e
  352. raise ActiveRecord::Rollback
  353. end
  354. []
  355. end
  356. =begin
  357. generate condition query to search for tickets based on condition
  358. query_condition, bind_condition, tables = selector2sql(params[:condition], current_user: current_user)
  359. condition example
  360. {
  361. 'ticket.title' => {
  362. operator: 'contains', # contains not
  363. value: 'some value',
  364. },
  365. 'ticket.state_id' => {
  366. operator: 'is',
  367. value: [1,2,5]
  368. },
  369. 'ticket.created_at' => {
  370. operator: 'after (absolute)', # after,before
  371. value: '2015-10-17T06:00:00.000Z',
  372. },
  373. 'ticket.created_at' => {
  374. operator: 'within next (relative)', # within next, within last, after, before
  375. range: 'day', # minute|hour|day|month|year
  376. value: '25',
  377. },
  378. 'ticket.owner_id' => {
  379. operator: 'is', # is not
  380. pre_condition: 'current_user.id',
  381. },
  382. 'ticket.owner_id' => {
  383. operator: 'is', # is not
  384. pre_condition: 'specific',
  385. value: 4711,
  386. },
  387. 'ticket.escalation_at' => {
  388. operator: 'is not', # not
  389. value: nil,
  390. },
  391. 'ticket.tags' => {
  392. operator: 'contains all', # contains all|contains one|contains all not|contains one not
  393. value: 'tag1, tag2',
  394. },
  395. }
  396. =end
  397. def self.selector2sql(selectors, options = {})
  398. current_user = options[:current_user]
  399. current_user_id = UserInfo.current_user_id
  400. if current_user
  401. current_user_id = current_user.id
  402. end
  403. return if !selectors
  404. # remember query and bind params
  405. query = ''
  406. bind_params = []
  407. like = Rails.application.config.db_like
  408. if selectors.respond_to?(:permit!)
  409. selectors = selectors.permit!.to_h
  410. end
  411. # get tables to join
  412. tables = ''
  413. selectors.each_key do |attribute|
  414. selector = attribute.split(/\./)
  415. next if !selector[1]
  416. next if selector[0] == 'ticket'
  417. next if tables.include?(selector[0])
  418. if query != ''
  419. query += ' AND '
  420. end
  421. if selector[0] == 'customer'
  422. tables += ', users customers'
  423. query += 'tickets.customer_id = customers.id'
  424. elsif selector[0] == 'organization'
  425. tables += ', organizations'
  426. query += 'tickets.organization_id = organizations.id'
  427. elsif selector[0] == 'owner'
  428. tables += ', users owners'
  429. query += 'tickets.owner_id = owners.id'
  430. elsif selector[0] == 'article'
  431. tables += ', ticket_articles articles'
  432. query += 'tickets.id = articles.ticket_id'
  433. elsif selector[0] == 'ticket_state'
  434. tables += ', ticket_states'
  435. query += 'tickets.state_id = ticket_states.id'
  436. else
  437. raise "invalid selector #{attribute.inspect}->#{selector.inspect}"
  438. end
  439. end
  440. # add conditions
  441. selectors.each do |attribute, selector_raw|
  442. # validation
  443. raise "Invalid selector #{selector_raw.inspect}" if !selector_raw
  444. raise "Invalid selector #{selector_raw.inspect}" if !selector_raw.respond_to?(:key?)
  445. selector = selector_raw.stringify_keys
  446. raise "Invalid selector, operator missing #{selector.inspect}" if !selector['operator']
  447. raise "Invalid selector, operator #{selector['operator']} is invalid #{selector.inspect}" if selector['operator'] !~ /^(is|is\snot|contains|contains\s(not|all|one|all\snot|one\snot)|(after|before)\s\(absolute\)|(within\snext|within\slast|after|before)\s\(relative\))$/
  448. # validate value / allow blank but only if pre_condition exists and is not specific
  449. if !selector.key?('value') ||
  450. (selector['value'].class == Array && selector['value'].respond_to?(:blank?) && selector['value'].blank?) ||
  451. (selector['operator'] =~ /^contains/ && selector['value'].respond_to?(:blank?) && selector['value'].blank?)
  452. return nil if selector['pre_condition'].nil?
  453. return nil if selector['pre_condition'].respond_to?(:blank?) && selector['pre_condition'].blank?
  454. return nil if selector['pre_condition'] == 'specific'
  455. end
  456. # validate pre_condition values
  457. return nil if selector['pre_condition'] && selector['pre_condition'] !~ /^(not_set|current_user\.|specific)/
  458. # get attributes
  459. attributes = attribute.split(/\./)
  460. attribute = "#{ActiveRecord::Base.connection.quote_table_name("#{attributes[0]}s")}.#{ActiveRecord::Base.connection.quote_column_name(attributes[1])}"
  461. # magic selectors
  462. if attributes[0] == 'ticket' && attributes[1] == 'out_of_office_replacement_id'
  463. attribute = "#{ActiveRecord::Base.connection.quote_table_name("#{attributes[0]}s")}.#{ActiveRecord::Base.connection.quote_column_name('owner_id')}"
  464. end
  465. if attributes[0] == 'ticket' && attributes[1] == 'tags'
  466. selector['value'] = selector['value'].split(/,/).collect(&:strip)
  467. end
  468. if query != ''
  469. query += ' AND '
  470. end
  471. if selector['operator'] == 'is'
  472. if selector['pre_condition'] == 'not_set'
  473. if attributes[1].match?(/^(created_by|updated_by|owner|customer|user)_id/)
  474. query += "(#{attribute} IS NULL OR #{attribute} IN (?))"
  475. bind_params.push 1
  476. else
  477. query += "#{attribute} IS NULL"
  478. end
  479. elsif selector['pre_condition'] == 'current_user.id'
  480. raise "Use current_user.id in selector, but no current_user is set #{selector.inspect}" if !current_user_id
  481. query += "#{attribute} IN (?)"
  482. if attributes[1] == 'out_of_office_replacement_id'
  483. bind_params.push User.find(current_user_id).out_of_office_agent_of.pluck(:id)
  484. else
  485. bind_params.push current_user_id
  486. end
  487. elsif selector['pre_condition'] == 'current_user.organization_id'
  488. raise "Use current_user.id in selector, but no current_user is set #{selector.inspect}" if !current_user_id
  489. query += "#{attribute} IN (?)"
  490. user = User.find_by(id: current_user_id)
  491. bind_params.push user.organization_id
  492. else
  493. # rubocop:disable Style/IfInsideElse
  494. if selector['value'].nil?
  495. query += "#{attribute} IS NULL"
  496. else
  497. if attributes[1] == 'out_of_office_replacement_id'
  498. query += "#{attribute} IN (?)"
  499. bind_params.push User.find(selector['value']).out_of_office_agent_of.pluck(:id)
  500. else
  501. if selector['value'].class != Array
  502. selector['value'] = [selector['value']]
  503. end
  504. query += if selector['value'].include?('')
  505. "(#{attribute} IN (?) OR #{attribute} IS NULL)"
  506. else
  507. "#{attribute} IN (?)"
  508. end
  509. bind_params.push selector['value']
  510. end
  511. end
  512. # rubocop:enable Style/IfInsideElse
  513. end
  514. elsif selector['operator'] == 'is not'
  515. if selector['pre_condition'] == 'not_set'
  516. if attributes[1].match?(/^(created_by|updated_by|owner|customer|user)_id/)
  517. query += "(#{attribute} IS NOT NULL AND #{attribute} NOT IN (?))"
  518. bind_params.push 1
  519. else
  520. query += "#{attribute} IS NOT NULL"
  521. end
  522. elsif selector['pre_condition'] == 'current_user.id'
  523. query += "(#{attribute} IS NULL OR #{attribute} NOT IN (?))"
  524. if attributes[1] == 'out_of_office_replacement_id'
  525. bind_params.push User.find(current_user_id).out_of_office_agent_of.pluck(:id)
  526. else
  527. bind_params.push current_user_id
  528. end
  529. elsif selector['pre_condition'] == 'current_user.organization_id'
  530. query += "(#{attribute} IS NULL OR #{attribute} NOT IN (?))"
  531. user = User.find_by(id: current_user_id)
  532. bind_params.push user.organization_id
  533. else
  534. # rubocop:disable Style/IfInsideElse
  535. if selector['value'].nil?
  536. query += "#{attribute} IS NOT NULL"
  537. else
  538. if attributes[1] == 'out_of_office_replacement_id'
  539. bind_params.push User.find(selector['value']).out_of_office_agent_of.pluck(:id)
  540. query += "(#{attribute} IS NULL OR #{attribute} NOT IN (?))"
  541. else
  542. if selector['value'].class != Array
  543. selector['value'] = [selector['value']]
  544. end
  545. query += if selector['value'].include?('')
  546. "(#{attribute} IS NOT NULL AND #{attribute} NOT IN (?))"
  547. else
  548. "(#{attribute} IS NULL OR #{attribute} NOT IN (?))"
  549. end
  550. bind_params.push selector['value']
  551. end
  552. end
  553. # rubocop:enable Style/IfInsideElse
  554. end
  555. elsif selector['operator'] == 'contains'
  556. query += "#{attribute} #{like} (?)"
  557. value = "%#{selector['value']}%"
  558. bind_params.push value
  559. elsif selector['operator'] == 'contains not'
  560. query += "#{attribute} NOT #{like} (?)"
  561. value = "%#{selector['value']}%"
  562. bind_params.push value
  563. elsif selector['operator'] == 'contains all' && attributes[0] == 'ticket' && attributes[1] == 'tags'
  564. query += "? = (
  565. SELECT
  566. COUNT(*)
  567. FROM
  568. tag_objects,
  569. tag_items,
  570. tags
  571. WHERE
  572. tickets.id = tags.o_id AND
  573. tag_objects.id = tags.tag_object_id AND
  574. tag_objects.name = 'Ticket' AND
  575. tag_items.id = tags.tag_item_id AND
  576. tag_items.name IN (?)
  577. )"
  578. bind_params.push selector['value'].count
  579. bind_params.push selector['value']
  580. elsif selector['operator'] == 'contains one' && attributes[0] == 'ticket' && attributes[1] == 'tags'
  581. query += "1 <= (
  582. SELECT
  583. COUNT(*)
  584. FROM
  585. tag_objects,
  586. tag_items,
  587. tags
  588. WHERE
  589. tickets.id = tags.o_id AND
  590. tag_objects.id = tags.tag_object_id AND
  591. tag_objects.name = 'Ticket' AND
  592. tag_items.id = tags.tag_item_id AND
  593. tag_items.name IN (?)
  594. )"
  595. bind_params.push selector['value']
  596. elsif selector['operator'] == 'contains all not' && attributes[0] == 'ticket' && attributes[1] == 'tags'
  597. query += "0 = (
  598. SELECT
  599. COUNT(*)
  600. FROM
  601. tag_objects,
  602. tag_items,
  603. tags
  604. WHERE
  605. tickets.id = tags.o_id AND
  606. tag_objects.id = tags.tag_object_id AND
  607. tag_objects.name = 'Ticket' AND
  608. tag_items.id = tags.tag_item_id AND
  609. tag_items.name IN (?)
  610. )"
  611. bind_params.push selector['value']
  612. elsif selector['operator'] == 'contains one not' && attributes[0] == 'ticket' && attributes[1] == 'tags'
  613. query += "(
  614. SELECT
  615. COUNT(*)
  616. FROM
  617. tag_objects,
  618. tag_items,
  619. tags
  620. WHERE
  621. tickets.id = tags.o_id AND
  622. tag_objects.id = tags.tag_object_id AND
  623. tag_objects.name = 'Ticket' AND
  624. tag_items.id = tags.tag_item_id AND
  625. tag_items.name IN (?)
  626. ) BETWEEN 0 AND 0"
  627. bind_params.push selector['value']
  628. elsif selector['operator'] == 'before (absolute)'
  629. query += "#{attribute} <= ?"
  630. bind_params.push selector['value']
  631. elsif selector['operator'] == 'after (absolute)'
  632. query += "#{attribute} >= ?"
  633. bind_params.push selector['value']
  634. elsif selector['operator'] == 'within last (relative)'
  635. query += "#{attribute} >= ?"
  636. time = nil
  637. if selector['range'] == 'minute'
  638. time = Time.zone.now - selector['value'].to_i.minutes
  639. elsif selector['range'] == 'hour'
  640. time = Time.zone.now - selector['value'].to_i.hours
  641. elsif selector['range'] == 'day'
  642. time = Time.zone.now - selector['value'].to_i.days
  643. elsif selector['range'] == 'month'
  644. time = Time.zone.now - selector['value'].to_i.months
  645. elsif selector['range'] == 'year'
  646. time = Time.zone.now - selector['value'].to_i.years
  647. else
  648. raise "Unknown selector attributes '#{selector.inspect}'"
  649. end
  650. bind_params.push time
  651. elsif selector['operator'] == 'within next (relative)'
  652. query += "#{attribute} <= ?"
  653. time = nil
  654. if selector['range'] == 'minute'
  655. time = Time.zone.now + selector['value'].to_i.minutes
  656. elsif selector['range'] == 'hour'
  657. time = Time.zone.now + selector['value'].to_i.hours
  658. elsif selector['range'] == 'day'
  659. time = Time.zone.now + selector['value'].to_i.days
  660. elsif selector['range'] == 'month'
  661. time = Time.zone.now + selector['value'].to_i.months
  662. elsif selector['range'] == 'year'
  663. time = Time.zone.now + selector['value'].to_i.years
  664. else
  665. raise "Unknown selector attributes '#{selector.inspect}'"
  666. end
  667. bind_params.push time
  668. elsif selector['operator'] == 'before (relative)'
  669. query += "#{attribute} <= ?"
  670. time = nil
  671. if selector['range'] == 'minute'
  672. time = Time.zone.now - selector['value'].to_i.minutes
  673. elsif selector['range'] == 'hour'
  674. time = Time.zone.now - selector['value'].to_i.hours
  675. elsif selector['range'] == 'day'
  676. time = Time.zone.now - selector['value'].to_i.days
  677. elsif selector['range'] == 'month'
  678. time = Time.zone.now - selector['value'].to_i.months
  679. elsif selector['range'] == 'year'
  680. time = Time.zone.now - selector['value'].to_i.years
  681. else
  682. raise "Unknown selector attributes '#{selector.inspect}'"
  683. end
  684. bind_params.push time
  685. elsif selector['operator'] == 'after (relative)'
  686. query += "#{attribute} >= ?"
  687. time = nil
  688. if selector['range'] == 'minute'
  689. time = Time.zone.now + selector['value'].to_i.minutes
  690. elsif selector['range'] == 'hour'
  691. time = Time.zone.now + selector['value'].to_i.hours
  692. elsif selector['range'] == 'day'
  693. time = Time.zone.now + selector['value'].to_i.days
  694. elsif selector['range'] == 'month'
  695. time = Time.zone.now + selector['value'].to_i.months
  696. elsif selector['range'] == 'year'
  697. time = Time.zone.now + selector['value'].to_i.years
  698. else
  699. raise "Unknown selector attributes '#{selector.inspect}'"
  700. end
  701. bind_params.push time
  702. else
  703. raise "Invalid operator '#{selector['operator']}' for '#{selector['value'].inspect}'"
  704. end
  705. end
  706. [query, bind_params, tables]
  707. end
  708. =begin
  709. perform changes on ticket
  710. ticket.perform_changes({}, 'trigger', item, current_user_id)
  711. =end
  712. def perform_changes(perform, perform_origin, item = nil, current_user_id = nil)
  713. logger.debug { "Perform #{perform_origin} #{perform.inspect} on Ticket.find(#{id})" }
  714. article = begin
  715. Ticket::Article.find_by(id: item.try(:dig, :article_id))
  716. rescue ArgumentError
  717. nil
  718. end
  719. # if the configuration contains the deletion of the ticket then
  720. # we skip all other ticket changes because they does not matter
  721. if perform['ticket.action'].present? && perform['ticket.action']['value'] == 'delete'
  722. perform.each_key do |key|
  723. (object_name, attribute) = key.split('.', 2)
  724. next if object_name != 'ticket'
  725. next if attribute == 'action'
  726. perform.delete(key)
  727. end
  728. end
  729. perform_notification = {}
  730. perform_article = {}
  731. changed = false
  732. perform.each do |key, value|
  733. (object_name, attribute) = key.split('.', 2)
  734. raise "Unable to update object #{object_name}.#{attribute}, only can update tickets, send notifications and create articles!" if object_name != 'ticket' && object_name != 'article' && object_name != 'notification'
  735. # send notification/create article (after changes are done)
  736. if object_name == 'article'
  737. perform_article[key] = value
  738. next
  739. end
  740. if object_name == 'notification'
  741. perform_notification[key] = value
  742. next
  743. end
  744. # update tags
  745. if key == 'ticket.tags'
  746. next if value['value'].blank?
  747. tags = value['value'].split(/,/)
  748. if value['operator'] == 'add'
  749. tags.each do |tag|
  750. tag_add(tag, current_user_id || 1)
  751. end
  752. elsif value['operator'] == 'remove'
  753. tags.each do |tag|
  754. tag_remove(tag, current_user_id || 1)
  755. end
  756. else
  757. logger.error "Unknown #{attribute} operator #{value['operator']}"
  758. end
  759. next
  760. end
  761. # delete ticket
  762. if key == 'ticket.action'
  763. next if value['value'].blank?
  764. next if value['value'] != 'delete'
  765. logger.info { "Deleted ticket from #{perform_origin} #{perform.inspect} Ticket.find(#{id})" }
  766. destroy!
  767. next
  768. end
  769. # lookup pre_condition
  770. if value['pre_condition']
  771. if value['pre_condition'].match?(/^not_set/)
  772. value['value'] = 1
  773. elsif value['pre_condition'].match?(/^current_user\./)
  774. raise 'Unable to use current_user, got no current_user_id for ticket.perform_changes' if !current_user_id
  775. value['value'] = current_user_id
  776. end
  777. end
  778. # update ticket
  779. next if self[attribute].to_s == value['value'].to_s
  780. changed = true
  781. self[attribute] = value['value']
  782. logger.debug { "set #{object_name}.#{attribute} = #{value['value'].inspect} for ticket_id #{id}" }
  783. end
  784. if changed
  785. save!
  786. end
  787. perform_article.each do |key, value|
  788. raise 'Unable to create article, we only support article.note' if key != 'article.note'
  789. Ticket::Article.create!(
  790. ticket_id: id,
  791. subject: value[:subject],
  792. content_type: 'text/html',
  793. body: value[:body],
  794. internal: value[:internal],
  795. sender: Ticket::Article::Sender.find_by(name: 'System'),
  796. type: Ticket::Article::Type.find_by(name: 'note'),
  797. preferences: {
  798. perform_origin: perform_origin,
  799. },
  800. updated_by_id: 1,
  801. created_by_id: 1,
  802. )
  803. end
  804. perform_notification.each do |key, value|
  805. # send notification
  806. case key
  807. when 'notification.sms'
  808. send_sms_notification(value, article, perform_origin)
  809. next
  810. when 'notification.email'
  811. send_email_notification(value, article, perform_origin)
  812. end
  813. end
  814. true
  815. end
  816. =begin
  817. perform active triggers on ticket
  818. Ticket.perform_triggers(ticket, article, item, options)
  819. =end
  820. def self.perform_triggers(ticket, article, item, options = {})
  821. recursive = Setting.get('ticket_trigger_recursive')
  822. type = options[:type] || item[:type]
  823. local_options = options.clone
  824. local_options[:type] = type
  825. local_options[:reset_user_id] = true
  826. local_options[:disable] = ['Transaction::Notification']
  827. local_options[:trigger_ids] ||= {}
  828. local_options[:trigger_ids][ticket.id] ||= []
  829. local_options[:loop_count] ||= 0
  830. local_options[:loop_count] += 1
  831. ticket_trigger_recursive_max_loop = Setting.get('ticket_trigger_recursive_max_loop')&.to_i || 10
  832. if local_options[:loop_count] > ticket_trigger_recursive_max_loop
  833. message = "Stopped perform_triggers for this object (Ticket/#{ticket.id}), because loop count was #{local_options[:loop_count]}!"
  834. logger.info { message }
  835. return [false, message]
  836. end
  837. triggers = if Rails.configuration.db_case_sensitive
  838. ::Trigger.where(active: true).order(Arel.sql('LOWER(name)'))
  839. else
  840. ::Trigger.where(active: true).order(:name)
  841. end
  842. return [true, 'No triggers active'] if triggers.blank?
  843. # check if notification should be send because of customer emails
  844. send_notification = true
  845. if local_options[:send_notification] == false
  846. send_notification = false
  847. elsif item[:article_id]
  848. article = Ticket::Article.lookup(id: item[:article_id])
  849. if article&.preferences && article.preferences['send-auto-response'] == false
  850. send_notification = false
  851. end
  852. end
  853. Transaction.execute(local_options) do
  854. triggers.each do |trigger|
  855. logger.debug { "Probe trigger (#{trigger.name}/#{trigger.id}) for this object (Ticket:#{ticket.id}/Loop:#{local_options[:loop_count]})" }
  856. condition = trigger.condition
  857. # check if one article attribute is used
  858. one_has_changed_done = false
  859. article_selector = false
  860. trigger.condition.each_key do |key|
  861. (object_name, attribute) = key.split('.', 2)
  862. next if object_name != 'article'
  863. next if attribute == 'id'
  864. article_selector = true
  865. end
  866. if article && article_selector
  867. one_has_changed_done = true
  868. end
  869. if article && type == 'update'
  870. one_has_changed_done = true
  871. end
  872. # check ticket "has changed" options
  873. has_changed_done = true
  874. condition.each do |key, value|
  875. next if value.blank?
  876. next if value['operator'].blank?
  877. next if !value['operator']['has changed']
  878. # remove condition item, because it has changed
  879. (object_name, attribute) = key.split('.', 2)
  880. next if object_name != 'ticket'
  881. next if item[:changes].blank?
  882. next if !item[:changes].key?(attribute)
  883. condition.delete(key)
  884. one_has_changed_done = true
  885. end
  886. # check if we have not matching "has changed" attributes
  887. condition.each_value do |value|
  888. next if value.blank?
  889. next if value['operator'].blank?
  890. next if !value['operator']['has changed']
  891. has_changed_done = false
  892. break
  893. end
  894. # check ticket action
  895. if condition['ticket.action']
  896. next if condition['ticket.action']['operator'] == 'is' && condition['ticket.action']['value'] != type
  897. next if condition['ticket.action']['operator'] != 'is' && condition['ticket.action']['value'] == type
  898. condition.delete('ticket.action')
  899. end
  900. next if !has_changed_done
  901. # check in min one attribute of condition has changed on update
  902. one_has_changed_condition = false
  903. if type == 'update'
  904. # verify if ticket condition exists
  905. condition.each_key do |key|
  906. (object_name, attribute) = key.split('.', 2)
  907. next if object_name != 'ticket'
  908. one_has_changed_condition = true
  909. next if item[:changes].blank?
  910. next if !item[:changes].key?(attribute)
  911. one_has_changed_done = true
  912. break
  913. end
  914. next if one_has_changed_condition && !one_has_changed_done
  915. end
  916. # check if ticket selector is matching
  917. condition['ticket.id'] = {
  918. operator: 'is',
  919. value: ticket.id,
  920. }
  921. next if article_selector && !article
  922. # check if article selector is matching
  923. if article_selector
  924. condition['article.id'] = {
  925. operator: 'is',
  926. value: article.id,
  927. }
  928. end
  929. # verify is condition is matching
  930. ticket_count, tickets = Ticket.selectors(condition, limit: 1)
  931. next if ticket_count.blank?
  932. next if ticket_count.zero?
  933. next if tickets.first.id != ticket.id
  934. user_id = ticket.updated_by_id
  935. if article
  936. user_id = article.updated_by_id
  937. end
  938. if recursive == false && local_options[:loop_count] > 1
  939. message = "Do not execute recursive triggers per default until Zammad 3.0. With Zammad 3.0 and higher the following trigger is executed '#{trigger.name}' on Ticket:#{ticket.id}. Please review your current triggers and change them if needed."
  940. logger.info { message }
  941. return [true, message]
  942. end
  943. if article && send_notification == false && trigger.perform['notification.email'] && trigger.perform['notification.email']['recipient']
  944. recipient = trigger.perform['notification.email']['recipient']
  945. local_options[:send_notification] = false
  946. if recipient.include?('ticket_customer') || recipient.include?('article_last_sender')
  947. logger.info { "Skip trigger (#{trigger.name}/#{trigger.id}) because sender do not want to get auto responder for object (Ticket/#{ticket.id}/Article/#{article.id})" }
  948. next
  949. end
  950. end
  951. if local_options[:trigger_ids][ticket.id].include?(trigger.id)
  952. logger.info { "Skip trigger (#{trigger.name}/#{trigger.id}) because was already executed for this object (Ticket:#{ticket.id}/Loop:#{local_options[:loop_count]})" }
  953. next
  954. end
  955. local_options[:trigger_ids][ticket.id].push trigger.id
  956. logger.info { "Execute trigger (#{trigger.name}/#{trigger.id}) for this object (Ticket:#{ticket.id}/Loop:#{local_options[:loop_count]})" }
  957. ticket.perform_changes(trigger.perform, 'trigger', item, user_id)
  958. if recursive == true
  959. Observer::Transaction.commit(local_options)
  960. end
  961. end
  962. end
  963. [true, ticket, local_options]
  964. end
  965. =begin
  966. get all email references headers of a ticket, to exclude some, parse it as array into method
  967. references = ticket.get_references
  968. result
  969. ['message-id-1234', 'message-id-5678']
  970. ignore references header(s)
  971. references = ticket.get_references(['message-id-5678'])
  972. result
  973. ['message-id-1234']
  974. =end
  975. def get_references(ignore = [])
  976. references = []
  977. Ticket::Article.select('in_reply_to, message_id').where(ticket_id: id).each do |article|
  978. if article.in_reply_to.present?
  979. references.push article.in_reply_to
  980. end
  981. next if article.message_id.blank?
  982. references.push article.message_id
  983. end
  984. ignore.each do |item|
  985. references.delete(item)
  986. end
  987. references
  988. end
  989. =begin
  990. get all articles of a ticket in correct order (overwrite active record default method)
  991. articles = ticket.articles
  992. result
  993. [article1, article2]
  994. =end
  995. def articles
  996. Ticket::Article.where(ticket_id: id).order(:created_at, :id)
  997. end
  998. private
  999. def check_generate
  1000. return true if number
  1001. self.number = Ticket::Number.generate
  1002. true
  1003. end
  1004. def check_title
  1005. return true if !title
  1006. title.gsub!(/\s|\t|\r/, ' ')
  1007. true
  1008. end
  1009. def check_defaults
  1010. if !owner_id
  1011. self.owner_id = 1
  1012. end
  1013. return true if !customer_id
  1014. customer = User.find_by(id: customer_id)
  1015. return true if !customer
  1016. return true if organization_id == customer.organization_id
  1017. self.organization_id = customer.organization_id
  1018. true
  1019. end
  1020. def reset_pending_time
  1021. # ignore if no state has changed
  1022. return true if !changes_to_save['state_id']
  1023. # ignore if new state is blank and
  1024. # let handle ActiveRecord the error
  1025. return if state_id.blank?
  1026. # check if new state isn't pending*
  1027. current_state = Ticket::State.lookup(id: state_id)
  1028. current_state_type = Ticket::StateType.lookup(id: current_state.state_type_id)
  1029. # in case, set pending_time to nil
  1030. return true if current_state_type.name.match?(/^pending/i)
  1031. self.pending_time = nil
  1032. true
  1033. end
  1034. def set_default_state
  1035. return true if state_id
  1036. default_ticket_state = Ticket::State.find_by(default_create: true)
  1037. return true if !default_ticket_state
  1038. self.state_id = default_ticket_state.id
  1039. true
  1040. end
  1041. def set_default_priority
  1042. return true if priority_id
  1043. default_ticket_priority = Ticket::Priority.find_by(default_create: true)
  1044. return true if !default_ticket_priority
  1045. self.priority_id = default_ticket_priority.id
  1046. true
  1047. end
  1048. def check_owner_active
  1049. return true if Setting.get('import_mode')
  1050. # return when ticket is unassigned
  1051. return true if owner_id.blank?
  1052. return true if owner_id == 1
  1053. # return if owner is active, is agent and has access to group of ticket
  1054. return true if owner.active? && owner.permissions?('ticket.agent') && owner.group_access?(group_id, 'full')
  1055. # else set the owner of the ticket to the default user as unassigned
  1056. self.owner_id = 1
  1057. true
  1058. end
  1059. # articles.last breaks (returns the wrong article)
  1060. # if another email notification trigger preceded this one
  1061. # (see https://github.com/zammad/zammad/issues/1543)
  1062. def build_notification_template_objects(article)
  1063. {
  1064. ticket: self,
  1065. article: article || articles.last
  1066. }
  1067. end
  1068. def send_email_notification(value, article, perform_origin)
  1069. # value['recipient'] was a string in the past (single-select) so we convert it to array if needed
  1070. value_recipient = Array(value['recipient'])
  1071. recipients_raw = []
  1072. value_recipient.each do |recipient|
  1073. if recipient == 'article_last_sender'
  1074. if article.present?
  1075. if article.reply_to.present?
  1076. recipients_raw.push(article.reply_to)
  1077. elsif article.from.present?
  1078. recipients_raw.push(article.from)
  1079. elsif article.origin_by_id
  1080. email = User.find_by(id: article.origin_by_id).email
  1081. recipients_raw.push(email)
  1082. elsif article.created_by_id
  1083. email = User.find_by(id: article.created_by_id).email
  1084. recipients_raw.push(email)
  1085. end
  1086. end
  1087. elsif recipient == 'ticket_customer'
  1088. email = User.find_by(id: customer_id).email
  1089. recipients_raw.push(email)
  1090. elsif recipient == 'ticket_owner'
  1091. email = User.find_by(id: owner_id).email
  1092. recipients_raw.push(email)
  1093. elsif recipient == 'ticket_agents'
  1094. User.group_access(group_id, 'full').sort_by(&:login).each do |user|
  1095. recipients_raw.push(user.email)
  1096. end
  1097. else
  1098. logger.error "Unknown email notification recipient '#{recipient}'"
  1099. next
  1100. end
  1101. end
  1102. recipients_checked = []
  1103. recipients_raw.each do |recipient_email|
  1104. skip_user = false
  1105. users = User.where(email: recipient_email)
  1106. users.each do |user|
  1107. next if user.preferences[:mail_delivery_failed] != true
  1108. next if !user.preferences[:mail_delivery_failed_data]
  1109. till_blocked = ((user.preferences[:mail_delivery_failed_data] - Time.zone.now - 60.days) / 60 / 60 / 24).round
  1110. next if till_blocked.positive?
  1111. logger.info "Send no trigger based notification to #{recipient_email} because email is marked as mail_delivery_failed for #{till_blocked} days"
  1112. skip_user = true
  1113. break
  1114. end
  1115. next if skip_user
  1116. # send notifications only to email addresses
  1117. next if recipient_email.blank?
  1118. next if recipient_email !~ /@/
  1119. # check if address is valid
  1120. begin
  1121. Mail::AddressList.new(recipient_email).addresses.each do |address|
  1122. recipient_email = address.address
  1123. break if recipient_email.present? && recipient_email =~ /@/ && !recipient_email.match?(/\s/)
  1124. end
  1125. rescue
  1126. if recipient_email.present?
  1127. if recipient_email !~ /^(.+?)<(.+?)@(.+?)>$/
  1128. next # no usable format found
  1129. end
  1130. recipient_email = "#{$2}@#{$3}"
  1131. end
  1132. next if recipient_email.blank?
  1133. next if recipient_email !~ /@/
  1134. next if recipient_email.match?(/\s/)
  1135. end
  1136. # do not sent notifications to this recipients
  1137. send_no_auto_response_reg_exp = Setting.get('send_no_auto_response_reg_exp')
  1138. begin
  1139. next if recipient_email.match?(/#{send_no_auto_response_reg_exp}/i)
  1140. rescue => e
  1141. logger.error "ERROR: Invalid regex '#{send_no_auto_response_reg_exp}' in setting send_no_auto_response_reg_exp"
  1142. logger.error 'ERROR: ' + e.inspect
  1143. next if recipient_email.match?(/(mailer-daemon|postmaster|abuse|root|noreply|noreply.+?|no-reply|no-reply.+?)@.+?/i)
  1144. end
  1145. # check if notification should be send because of customer emails
  1146. if article.present? && article.preferences.fetch('is-auto-response', false) == true && article.from && article.from =~ /#{Regexp.quote(recipient_email)}/i
  1147. logger.info "Send no trigger based notification to #{recipient_email} because of auto response tagged incoming email"
  1148. next
  1149. end
  1150. # loop protection / check if maximal count of trigger mail has reached
  1151. map = {
  1152. 10 => 10,
  1153. 30 => 15,
  1154. 60 => 25,
  1155. 180 => 50,
  1156. 600 => 100,
  1157. }
  1158. skip = false
  1159. map.each do |minutes, count|
  1160. already_sent = Ticket::Article.where(
  1161. ticket_id: id,
  1162. sender: Ticket::Article::Sender.find_by(name: 'System'),
  1163. type: Ticket::Article::Type.find_by(name: 'email'),
  1164. ).where('ticket_articles.created_at > ? AND ticket_articles.to LIKE ?', Time.zone.now - minutes.minutes, "%#{recipient_email.strip}%").count
  1165. next if already_sent < count
  1166. logger.info "Send no trigger based notification to #{recipient_email} because already sent #{count} for this ticket within last #{minutes} minutes (loop protection)"
  1167. skip = true
  1168. break
  1169. end
  1170. next if skip
  1171. map = {
  1172. 10 => 30,
  1173. 30 => 60,
  1174. 60 => 120,
  1175. 180 => 240,
  1176. 600 => 360,
  1177. }
  1178. skip = false
  1179. map.each do |minutes, count|
  1180. already_sent = Ticket::Article.where(
  1181. sender: Ticket::Article::Sender.find_by(name: 'System'),
  1182. type: Ticket::Article::Type.find_by(name: 'email'),
  1183. ).where('ticket_articles.created_at > ? AND ticket_articles.to LIKE ?', Time.zone.now - minutes.minutes, "%#{recipient_email.strip}%").count
  1184. next if already_sent < count
  1185. logger.info "Send no trigger based notification to #{recipient_email} because already sent #{count} in total within last #{minutes} minutes (loop protection)"
  1186. skip = true
  1187. break
  1188. end
  1189. next if skip
  1190. email = recipient_email.downcase.strip
  1191. next if recipients_checked.include?(email)
  1192. recipients_checked.push(email)
  1193. end
  1194. return if recipients_checked.blank?
  1195. recipient_string = recipients_checked.join(', ')
  1196. group_id = self.group_id
  1197. return if !group_id
  1198. email_address = Group.find(group_id).email_address
  1199. if !email_address
  1200. logger.info "Unable to send trigger based notification to #{recipient_string} because no email address is set for group '#{group.name}'"
  1201. return
  1202. end
  1203. if !email_address.channel_id
  1204. logger.info "Unable to send trigger based notification to #{recipient_string} because no channel is set for email address '#{email_address.email}' (id: #{email_address.id})"
  1205. return
  1206. end
  1207. objects = build_notification_template_objects(article)
  1208. # get subject
  1209. subject = NotificationFactory::Mailer.template(
  1210. templateInline: value['subject'],
  1211. locale: 'en-en',
  1212. objects: objects,
  1213. quote: false,
  1214. )
  1215. subject = subject_build(subject)
  1216. body = NotificationFactory::Mailer.template(
  1217. templateInline: value['body'],
  1218. locale: 'en-en',
  1219. objects: objects,
  1220. quote: true,
  1221. )
  1222. (body, attachments_inline) = HtmlSanitizer.replace_inline_images(body, id)
  1223. message = Ticket::Article.create(
  1224. ticket_id: id,
  1225. to: recipient_string,
  1226. subject: subject,
  1227. content_type: 'text/html',
  1228. body: body,
  1229. internal: false,
  1230. sender: Ticket::Article::Sender.find_by(name: 'System'),
  1231. type: Ticket::Article::Type.find_by(name: 'email'),
  1232. preferences: {
  1233. perform_origin: perform_origin,
  1234. },
  1235. updated_by_id: 1,
  1236. created_by_id: 1,
  1237. )
  1238. attachments_inline.each do |attachment|
  1239. Store.add(
  1240. object: 'Ticket::Article',
  1241. o_id: message.id,
  1242. data: attachment[:data],
  1243. filename: attachment[:filename],
  1244. preferences: attachment[:preferences],
  1245. )
  1246. end
  1247. original_article = objects[:article]
  1248. if original_article&.should_clone_inline_attachments? # rubocop:disable Style/GuardClause
  1249. original_article.clone_attachments('Ticket::Article', message.id, only_inline_attachments: true)
  1250. original_article.should_clone_inline_attachments = false # cancel the temporary flag after cloning
  1251. end
  1252. end
  1253. def sms_recipients_by_type(recipient_type, article)
  1254. case recipient_type
  1255. when 'article_last_sender'
  1256. return nil if article.blank?
  1257. if article.origin_by_id
  1258. article.origin_by_id
  1259. elsif article.created_by_id
  1260. article.created_by_id
  1261. end
  1262. when 'ticket_customer'
  1263. customer_id
  1264. when 'ticket_owner'
  1265. owner_id
  1266. when 'ticket_agents'
  1267. User.group_access(group_id, 'full').sort_by(&:login)
  1268. else
  1269. logger.error "Unknown sms notification recipient '#{recipient}'"
  1270. nil
  1271. end
  1272. end
  1273. def build_sms_recipients_list(value, article)
  1274. Array(value['recipient'])
  1275. .each_with_object([]) { |recipient_type, sum| sum.concat(Array(sms_recipients_by_type(recipient_type, article))) }
  1276. .map { |user_or_id| User.lookup(id: user_or_id) }
  1277. .select { |user| user.mobile.present? }
  1278. end
  1279. def send_sms_notification(value, article, perform_origin)
  1280. sms_recipients = build_sms_recipients_list(value, article)
  1281. if sms_recipients.blank?
  1282. logger.debug "No SMS recipients found for Ticket# #{number}"
  1283. return
  1284. end
  1285. sms_recipients_to = sms_recipients
  1286. .map { |recipient| "#{recipient.fullname} (#{recipient.mobile})" }
  1287. .join(', ')
  1288. channel = Channel.find_by(area: 'Sms::Notification')
  1289. if !channel.active?
  1290. # write info message since we have an active trigger
  1291. logger.info "Found possible SMS recipient(s) (#{sms_recipients_to}) for Ticket# #{number} but SMS channel is not active."
  1292. return
  1293. end
  1294. objects = build_notification_template_objects(article)
  1295. body = NotificationFactory::Renderer.new(
  1296. objects: objects,
  1297. locale: 'en-en',
  1298. timezone: Setting.get('timezone_default'),
  1299. template: value['body'],
  1300. escape: false
  1301. ).render.html2text.tr(' ', ' ') # convert non-breaking space to simple space
  1302. # attributes content_type is not needed for SMS
  1303. Ticket::Article.create(
  1304. ticket_id: id,
  1305. subject: 'SMS notification',
  1306. to: sms_recipients_to,
  1307. body: body,
  1308. internal: true,
  1309. sender: Ticket::Article::Sender.find_by(name: 'System'),
  1310. type: Ticket::Article::Type.find_by(name: 'sms'),
  1311. preferences: {
  1312. perform_origin: perform_origin,
  1313. sms_recipients: sms_recipients.map(&:mobile),
  1314. channel_id: channel.id,
  1315. },
  1316. updated_by_id: 1,
  1317. created_by_id: 1,
  1318. )
  1319. end
  1320. end