ticket.rb 53 KB

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