ticket.rb 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901
  1. # Copyright (C) 2012-2014 Zammad Foundation, http://zammad-foundation.org/
  2. class Ticket < ApplicationModel
  3. include Ticket::Escalation
  4. include Ticket::Subject
  5. include Ticket::Permission
  6. load 'ticket/assets.rb'
  7. include Ticket::Assets
  8. load 'ticket/history_log.rb'
  9. include Ticket::HistoryLog
  10. load 'ticket/activity_stream_log.rb'
  11. include Ticket::ActivityStreamLog
  12. load 'ticket/search_index.rb'
  13. include Ticket::SearchIndex
  14. extend Ticket::Search
  15. store :preferences
  16. before_create :check_generate, :check_defaults, :check_title
  17. before_update :check_defaults, :check_title, :reset_pending_time
  18. before_destroy :destroy_dependencies
  19. notify_clients_support
  20. latest_change_support
  21. activity_stream_support ignore_attributes: {
  22. organization_id: true, # organization_id will channge automatically on user update
  23. create_article_type_id: true,
  24. create_article_sender_id: true,
  25. article_count: true,
  26. first_response: true,
  27. first_response_escal_date: true,
  28. first_response_sla_time: true,
  29. first_response_in_min: true,
  30. first_response_diff_in_min: true,
  31. close_time: true,
  32. close_time_escal_date: true,
  33. close_time_sla_time: true,
  34. close_time_in_min: true,
  35. close_time_diff_in_min: true,
  36. update_time_escal_date: true,
  37. update_time_sla_time: true,
  38. update_time_in_min: true,
  39. update_time_diff_in_min: true,
  40. last_contact: true,
  41. last_contact_agent: true,
  42. last_contact_customer: true,
  43. }
  44. history_support ignore_attributes: {
  45. create_article_type_id: true,
  46. create_article_sender_id: true,
  47. article_count: true,
  48. }
  49. search_index_support
  50. belongs_to :group, class_name: 'Group'
  51. has_many :articles, class_name: 'Ticket::Article', after_add: :cache_update, after_remove: :cache_update
  52. belongs_to :organization, class_name: 'Organization'
  53. belongs_to :state, class_name: 'Ticket::State'
  54. belongs_to :priority, class_name: 'Ticket::Priority'
  55. belongs_to :owner, class_name: 'User'
  56. belongs_to :customer, class_name: 'User'
  57. belongs_to :created_by, class_name: 'User'
  58. belongs_to :updated_by, class_name: 'User'
  59. belongs_to :create_article_type, class_name: 'Ticket::Article::Type'
  60. belongs_to :create_article_sender, class_name: 'Ticket::Article::Sender'
  61. self.inheritance_column = nil
  62. attr_accessor :callback_loop
  63. =begin
  64. list of agents in group of ticket
  65. ticket = Ticket.find(123)
  66. result = ticket.agent_of_group
  67. returns
  68. result = [user1, user2, ...]
  69. =end
  70. def agent_of_group
  71. Group.find(group_id)
  72. .users.where(active: true)
  73. .joins(:roles)
  74. .where('roles.name' => Z_ROLENAME_AGENT, 'roles.active' => true)
  75. .order('users.login')
  76. .uniq()
  77. end
  78. =begin
  79. get user access conditions
  80. conditions = Ticket.access_condition( User.find(1) )
  81. returns
  82. result = [user1, user2, ...]
  83. =end
  84. def self.access_condition(user)
  85. access_condition = []
  86. if user.role?(Z_ROLENAME_AGENT)
  87. group_ids = Group.select('groups.id').joins(:users)
  88. .where('groups_users.user_id = ?', user.id)
  89. .where('groups.active = ?', true)
  90. .map(&:id)
  91. access_condition = [ 'group_id IN (?)', group_ids ]
  92. else
  93. access_condition = if !user.organization || ( !user.organization.shared || user.organization.shared == false )
  94. [ 'tickets.customer_id = ?', user.id ]
  95. else
  96. [ '(tickets.customer_id = ? OR tickets.organization_id = ?)', user.id, user.organization.id ]
  97. end
  98. end
  99. access_condition
  100. end
  101. =begin
  102. processes tickets which have reached their pending time and sets next state_id
  103. processed_tickets = Ticket.process_pending
  104. returns
  105. processed_tickets = [<Ticket>, ...]
  106. =end
  107. def self.process_pending
  108. result = []
  109. # process pending action tickets
  110. pending_action = Ticket::StateType.find_by(name: 'pending action')
  111. ticket_states_pending_action = Ticket::State.where(state_type_id: pending_action)
  112. .where.not(next_state_id: nil)
  113. if !ticket_states_pending_action.empty?
  114. next_state_map = {}
  115. ticket_states_pending_action.each { |state|
  116. next_state_map[state.id] = state.next_state_id
  117. }
  118. tickets = where(state_id: next_state_map.keys)
  119. .where('pending_time <= ?', Time.zone.now)
  120. tickets.each { |ticket|
  121. ticket.state_id = next_state_map[ticket.state_id]
  122. ticket.updated_at = Time.zone.now
  123. ticket.updated_by_id = 1
  124. ticket.save!
  125. # we do not have an destructor at this point, so we need to
  126. # execute object transaction manually
  127. Observer::Transaction.commit
  128. result.push ticket
  129. }
  130. end
  131. # process pending reminder tickets
  132. pending_reminder = Ticket::StateType.find_by(name: 'pending reminder')
  133. ticket_states_pending_reminder = Ticket::State.where(state_type_id: pending_reminder)
  134. if !ticket_states_pending_reminder.empty?
  135. reminder_state_map = {}
  136. ticket_states_pending_reminder.each { |state|
  137. reminder_state_map[state.id] = state.next_state_id
  138. }
  139. tickets = where(state_id: reminder_state_map.keys)
  140. .where('pending_time <= ?', Time.zone.now)
  141. tickets.each { |ticket|
  142. article_id = nil
  143. article = Ticket::Article.last_customer_agent_article(ticket.id)
  144. if article
  145. article_id = article.id
  146. end
  147. # send notification
  148. Transaction::BackgroundJob.run(
  149. object: 'Ticket',
  150. type: 'reminder_reached',
  151. object_id: ticket.id,
  152. article_id: article_id,
  153. user_id: 1,
  154. )
  155. result.push ticket
  156. }
  157. end
  158. result
  159. end
  160. =begin
  161. processes escalated tickets
  162. processed_tickets = Ticket.process_escalation
  163. returns
  164. processed_tickets = [<Ticket>, ...]
  165. =end
  166. def self.process_escalation
  167. result = []
  168. # get max warning diff
  169. tickets = where('escalation_time <= ?', Time.zone.now + 15.minutes)
  170. tickets.each {|ticket|
  171. # get sla
  172. sla = ticket.escalation_calculation_get_sla
  173. article_id = nil
  174. article = Ticket::Article.last_customer_agent_article(ticket.id)
  175. if article
  176. article_id = article.id
  177. end
  178. # send escalation
  179. if ticket.escalation_time < Time.zone.now
  180. Transaction::BackgroundJob.run(
  181. object: 'Ticket',
  182. type: 'escalation',
  183. object_id: ticket.id,
  184. article_id: article_id,
  185. user_id: 1,
  186. )
  187. result.push ticket
  188. next
  189. end
  190. # check if warning need to be sent
  191. Transaction::BackgroundJob.run(
  192. object: 'Ticket',
  193. type: 'escalation_warning',
  194. object_id: ticket.id,
  195. article_id: article_id,
  196. user_id: 1,
  197. )
  198. result.push ticket
  199. }
  200. result
  201. end
  202. =begin
  203. merge tickets
  204. ticket = Ticket.find(123)
  205. result = ticket.merge_to(
  206. ticket_id: 123,
  207. user_id: 123,
  208. )
  209. returns
  210. result = true|false
  211. =end
  212. def merge_to(data)
  213. # update articles
  214. Ticket::Article.where(ticket_id: id).each(&:touch)
  215. # quiet update of reassign of articles
  216. Ticket::Article.where(ticket_id: id).update_all(['ticket_id = ?', data[:ticket_id] ])
  217. # touch new ticket (to broadcast change)
  218. Ticket.find(data[:ticket_id]).touch
  219. # update history
  220. # create new merge article
  221. Ticket::Article.create(
  222. ticket_id: id,
  223. type_id: Ticket::Article::Type.lookup(name: 'note').id,
  224. sender_id: Ticket::Article::Sender.lookup(name: Z_ROLENAME_AGENT).id,
  225. body: 'merged',
  226. internal: false,
  227. created_by_id: data[:user_id],
  228. updated_by_id: data[:user_id],
  229. )
  230. # add history to both
  231. # link tickets
  232. Link.add(
  233. link_type: 'parent',
  234. link_object_source: 'Ticket',
  235. link_object_source_value: data[:ticket_id],
  236. link_object_target: 'Ticket',
  237. link_object_target_value: id
  238. )
  239. # set state to 'merged'
  240. self.state_id = Ticket::State.lookup(name: 'merged').id
  241. # rest owner
  242. self.owner_id = User.find_by(login: '-').id
  243. # save ticket
  244. save
  245. end
  246. =begin
  247. check if online notifcation should be shown in general as already seen with current state
  248. ticket = Ticket.find(1)
  249. seen = ticket.online_notification_seen_state(user_id_check)
  250. returns
  251. result = true # or false
  252. check if online notifcation should be shown for this user as already seen with current state
  253. ticket = Ticket.find(1)
  254. seen = ticket.online_notification_seen_state(check_user_id)
  255. returns
  256. result = true # or false
  257. =end
  258. def online_notification_seen_state(user_id_check = nil)
  259. state = Ticket::State.lookup(id: state_id)
  260. state_type = Ticket::StateType.lookup(id: state.state_type_id)
  261. # always to set unseen for ticket owner
  262. if state_type.name != 'merged'
  263. if user_id_check
  264. return false if user_id_check == owner_id && user_id_check != updated_by_id
  265. end
  266. end
  267. # set all to seen if pending action state is a closed or merged state
  268. if state_type.name == 'pending action' && state.next_state_id
  269. state = Ticket::State.lookup(id: state.next_state_id)
  270. state_type = Ticket::StateType.lookup(id: state.state_type_id)
  271. end
  272. # set all to seen if new state is pending reminder state
  273. if state_type.name == 'pending reminder'
  274. if user_id_check
  275. return false if owner_id == 1
  276. return false if updated_by_id != owner_id && user_id_check == owner_id
  277. return true
  278. end
  279. return true
  280. end
  281. # set all to seen if new state is a closed or merged state
  282. return true if state_type.name == 'closed'
  283. return true if state_type.name == 'merged'
  284. false
  285. end
  286. =begin
  287. get count of tickets and tickets which match on selector
  288. ticket_count, tickets = Ticket.selectors(params[:condition], limit, current_user)
  289. =end
  290. def self.selectors(selectors, limit = 10, current_user = nil)
  291. raise 'no selectors given' if !selectors
  292. query, bind_params, tables = selector2sql(selectors, current_user)
  293. return [] if !query
  294. if !current_user
  295. ticket_count = Ticket.where(query, *bind_params).joins(tables).count
  296. tickets = Ticket.where(query, *bind_params).joins(tables).limit(limit)
  297. return [ticket_count, tickets]
  298. end
  299. access_condition = Ticket.access_condition(current_user)
  300. ticket_count = Ticket.where(access_condition).where(query, *bind_params).joins(tables).count
  301. tickets = Ticket.where(access_condition).where(query, *bind_params).joins(tables).limit(limit)
  302. [ticket_count, tickets]
  303. end
  304. =begin
  305. generate condition query to search for tickets based on condition
  306. query_condition, bind_condition = selector2sql(params[:condition], current_user)
  307. condition example
  308. {
  309. 'ticket.state_id' => {
  310. operator: 'is',
  311. value: [1,2,5]
  312. },
  313. 'ticket.created_at' => {
  314. operator: 'after (absolute)', # after,before
  315. value: '2015-10-17T06:00:00.000Z',
  316. },
  317. 'ticket.created_at' => {
  318. operator: 'within next (relative)', # before,within,in,after
  319. range: 'day', # minute|hour|day|month|year
  320. value: '25',
  321. },
  322. 'ticket.owner_id' => {
  323. operator: 'is', # is not
  324. pre_condition: 'current_user.id',
  325. },
  326. 'ticket.owner_id' => {
  327. operator: 'is', # is not
  328. pre_condition: 'specific',
  329. value: 4711,
  330. },
  331. 'ticket.escalation_time' => {
  332. operator: 'is not', # not
  333. value: nil,
  334. }
  335. }
  336. =end
  337. def self.selector2sql(selectors, current_user = nil)
  338. current_user_id = UserInfo.current_user_id
  339. if current_user
  340. current_user_id = current_user.id
  341. end
  342. return if !selectors
  343. # remember query and bind params
  344. query = ''
  345. bind_params = []
  346. like = Rails.application.config.db_like
  347. # get tables to join
  348. tables = ''
  349. selectors.each {|attribute, selector|
  350. selector = attribute.split(/\./)
  351. next if !selector[1]
  352. next if selector[0] == 'ticket'
  353. next if tables.include?(selector[0])
  354. if query != ''
  355. query += ' AND '
  356. end
  357. if selector[0] == 'customer'
  358. tables += ', users customers'
  359. query += 'tickets.customer_id = customers.id'
  360. elsif selector[0] == 'organization'
  361. tables += ', organizations'
  362. query += 'tickets.organization_id = organizations.id'
  363. elsif selector[0] == 'owner'
  364. tables += ', users owners'
  365. query += 'tickets.owner_id = owners.id'
  366. elsif selector[0] == 'article'
  367. tables += ', ticket_articles articles'
  368. query += 'tickets.id = articles.ticket_id'
  369. else
  370. raise "invalid selector #{attribute.inspect}->#{selector.inspect}"
  371. end
  372. }
  373. # add conditions
  374. selectors.each {|attribute, selector_raw|
  375. # validation
  376. raise "Invalid selector #{selector_raw.inspect}" if !selector_raw
  377. raise "Invalid selector #{selector_raw.inspect}" if !selector_raw.respond_to?(:key?)
  378. selector = selector_raw.stringify_keys
  379. raise "Invalid selector, operator missing #{selector.inspect}" if !selector['operator']
  380. # validate value / allow empty but only if pre_condition exists
  381. if !selector.key?('value') || ((selector['value'].class == String || selector['value'].class == Array) && (selector['value'].respond_to?(:empty?) && selector['value'].empty?))
  382. return nil if selector['pre_condition'].nil? || (selector['pre_condition'].respond_to?(:empty?) && selector['pre_condition'].empty?)
  383. end
  384. # validate pre_condition values
  385. return nil if selector['pre_condition'] && selector['pre_condition'] !~ /^(not_set|current_user\.|specific)/
  386. # get attributes
  387. attributes = attribute.split(/\./)
  388. attribute = "#{attributes[0]}s.#{attributes[1]}"
  389. if query != ''
  390. query += ' AND '
  391. end
  392. if selector['operator'] == 'is'
  393. if selector['pre_condition'] == 'not_set'
  394. if attributes[1] =~ /^(created_by|updated_by|owner|customer|user)_id/
  395. query += "#{attribute} IN (?)"
  396. bind_params.push 1
  397. else
  398. query += "#{attribute} IS NOT NULL"
  399. end
  400. elsif selector['pre_condition'] == 'current_user.id'
  401. raise "Use current_user.id in selector, but no current_user is set #{selector.inspect}" if !current_user_id
  402. query += "#{attribute} IN (?)"
  403. bind_params.push current_user_id
  404. elsif selector['pre_condition'] == 'current_user.organization_id'
  405. raise "Use current_user.id in selector, but no current_user is set #{selector.inspect}" if !current_user_id
  406. query += "#{attribute} IN (?)"
  407. user = User.lookup(id: current_user_id)
  408. bind_params.push user.organization_id
  409. else
  410. # rubocop:disable Style/IfInsideElse
  411. if selector['value'].nil?
  412. query += "#{attribute} IS NOT NULL"
  413. else
  414. query += "#{attribute} IN (?)"
  415. bind_params.push selector['value']
  416. end
  417. # rubocop:enable Style/IfInsideElse
  418. end
  419. elsif selector['operator'] == 'is not'
  420. if selector['pre_condition'] == 'not_set'
  421. if attributes[1] =~ /^(created_by|updated_by|owner|customer|user)_id/
  422. query += "#{attribute} NOT IN (?)"
  423. bind_params.push 1
  424. else
  425. query += "#{attribute} IS NULL"
  426. end
  427. elsif selector['pre_condition'] == 'current_user.id'
  428. query += "#{attribute} NOT IN (?)"
  429. bind_params.push current_user_id
  430. elsif selector['pre_condition'] == 'current_user.organization_id'
  431. query += "#{attribute} NOT IN (?)"
  432. user = User.lookup(id: current_user_id)
  433. bind_params.push user.organization_id
  434. else
  435. # rubocop:disable Style/IfInsideElse
  436. if selector['value'].nil?
  437. query += "#{attribute} IS NOT NULL"
  438. else
  439. query += "#{attribute} NOT IN (?)"
  440. bind_params.push selector['value']
  441. end
  442. # rubocop:enable Style/IfInsideElse
  443. end
  444. elsif selector['operator'] == 'contains'
  445. query += "#{attribute} #{like} (?)"
  446. value = "%#{selector['value']}%"
  447. bind_params.push value
  448. elsif selector['operator'] == 'contains not'
  449. query += "#{attribute} NOT #{like} (?)"
  450. value = "%#{selector['value']}%"
  451. bind_params.push value
  452. elsif selector['operator'] == 'before (absolute)'
  453. query += "#{attribute} <= ?"
  454. bind_params.push selector['value']
  455. elsif selector['operator'] == 'after (absolute)'
  456. query += "#{attribute} >= ?"
  457. bind_params.push selector['value']
  458. elsif selector['operator'] == 'within last (relative)'
  459. query += "#{attribute} >= ?"
  460. time = nil
  461. if selector['range'] == 'minute'
  462. time = Time.zone.now - selector['value'].to_i.minutes
  463. elsif selector['range'] == 'hour'
  464. time = Time.zone.now - selector['value'].to_i.hours
  465. elsif selector['range'] == 'day'
  466. time = Time.zone.now - selector['value'].to_i.days
  467. elsif selector['range'] == 'month'
  468. time = Time.zone.now - selector['value'].to_i.months
  469. elsif selector['range'] == 'year'
  470. time = Time.zone.now - selector['value'].to_i.years
  471. else
  472. raise "Unknown selector attributes '#{selector.inspect}'"
  473. end
  474. bind_params.push time
  475. elsif selector['operator'] == 'within next (relative)'
  476. query += "#{attribute} <= ?"
  477. time = nil
  478. if selector['range'] == 'minute'
  479. time = Time.zone.now + selector['value'].to_i.minutes
  480. elsif selector['range'] == 'hour'
  481. time = Time.zone.now + selector['value'].to_i.hours
  482. elsif selector['range'] == 'day'
  483. time = Time.zone.now + selector['value'].to_i.days
  484. elsif selector['range'] == 'month'
  485. time = Time.zone.now + selector['value'].to_i.months
  486. elsif selector['range'] == 'year'
  487. time = Time.zone.now + selector['value'].to_i.years
  488. else
  489. raise "Unknown selector attributes '#{selector.inspect}'"
  490. end
  491. bind_params.push time
  492. elsif selector['operator'] == 'before (relative)'
  493. query += "#{attribute} <= ?"
  494. time = nil
  495. if selector['range'] == 'minute'
  496. time = Time.zone.now - selector['value'].to_i.minutes
  497. elsif selector['range'] == 'hour'
  498. time = Time.zone.now - selector['value'].to_i.hours
  499. elsif selector['range'] == 'day'
  500. time = Time.zone.now - selector['value'].to_i.days
  501. elsif selector['range'] == 'month'
  502. time = Time.zone.now - selector['value'].to_i.months
  503. elsif selector['range'] == 'year'
  504. time = Time.zone.now - selector['value'].to_i.years
  505. else
  506. raise "Unknown selector attributes '#{selector.inspect}'"
  507. end
  508. bind_params.push time
  509. elsif selector['operator'] == 'after (relative)'
  510. query += "#{attribute} >= ?"
  511. time = nil
  512. if selector['range'] == 'minute'
  513. time = Time.zone.now + selector['value'].to_i.minutes
  514. elsif selector['range'] == 'hour'
  515. time = Time.zone.now + selector['value'].to_i.hours
  516. elsif selector['range'] == 'day'
  517. time = Time.zone.now + selector['value'].to_i.days
  518. elsif selector['range'] == 'month'
  519. time = Time.zone.now + selector['value'].to_i.months
  520. elsif selector['range'] == 'year'
  521. time = Time.zone.now + selector['value'].to_i.years
  522. else
  523. raise "Unknown selector attributes '#{selector.inspect}'"
  524. end
  525. bind_params.push time
  526. else
  527. raise "Invalid operator '#{selector['operator']}' for '#{selector['value'].inspect}'"
  528. end
  529. }
  530. [query, bind_params, tables]
  531. end
  532. =begin
  533. perform changes on ticket
  534. ticket.perform_changes({}, 'trigger', item)
  535. =end
  536. def perform_changes(perform, log, item = nil)
  537. logger.debug "Perform #{log} #{perform.inspect} on Ticket.find(#{id})"
  538. changed = false
  539. perform.each do |key, value|
  540. (object_name, attribute) = key.split('.', 2)
  541. raise "Unable to update object #{object_name}.#{attribute}, only can update tickets and send notifications!" if object_name != 'ticket' && object_name != 'notification'
  542. # send notification
  543. if object_name == 'notification'
  544. recipients = []
  545. if value['recipient'] == 'ticket_customer'
  546. recipients.push User.lookup(id: customer_id)
  547. elsif value['recipient'] == 'ticket_owner'
  548. recipients.push User.lookup(id: owner_id)
  549. elsif value['recipient'] == 'ticket_agents'
  550. recipients = recipients.concat(agent_of_group)
  551. else
  552. logger.error "Unknown email notification recipient '#{value['recipient']}'"
  553. next
  554. end
  555. recipient_string = ''
  556. recipient_already = {}
  557. recipients.each {|user|
  558. # send notifications only to email adresses
  559. next if !user.email
  560. next if user.email !~ /@/
  561. # do not sent notifications to this recipients
  562. send_no_auto_response_reg_exp = Setting.get('send_no_auto_response_reg_exp')
  563. begin
  564. next if user.email =~ /#{send_no_auto_response_reg_exp}/i
  565. rescue => e
  566. logger.error "ERROR: Invalid regex '#{send_no_auto_response_reg_exp}' in setting send_no_auto_response_reg_exp"
  567. logger.error 'ERROR: ' + e.inspect
  568. next if user.email =~ /(mailer-daemon|postmaster|abuse|root)@.+?\..+?/i
  569. end
  570. email = user.email.downcase.strip
  571. next if recipient_already[email]
  572. recipient_already[email] = true
  573. if recipient_string != ''
  574. recipient_string += ', '
  575. end
  576. recipient_string += email
  577. }
  578. next if recipient_string == ''
  579. group = self.group
  580. next if !group
  581. email_address = group.email_address
  582. next if !email_address
  583. next if !email_address.channel_id
  584. # check if notification should be send because of customer emails
  585. if item && item[:article_id]
  586. article = Ticket::Article.lookup(id: item[:article_id])
  587. if article
  588. type = Ticket::Article::Type.lookup(id: article.type_id)
  589. sender = Ticket::Article::Sender.lookup(id: article.sender_id)
  590. if sender && sender.name == 'Customer' && type && type.name == 'email'
  591. # get attachment
  592. list = Store.list(
  593. object: 'Ticket::Article::Mail',
  594. o_id: article.id,
  595. )
  596. if list && list[0]
  597. file = Store.find(list[0].id)
  598. if file
  599. content = file.content
  600. if content
  601. parser = Channel::EmailParser.new
  602. mail = parser.parse(content)
  603. # check headers
  604. next if mail['x-loop'.to_sym] =~ /yes/i
  605. next if mail['precedence'.to_sym] =~ /bulk/i
  606. next if mail['auto-submitted'.to_sym] =~ /auto-generated/i
  607. next if mail['x-auto-response-suppress'.to_sym] =~ /yes/i
  608. end
  609. end
  610. end
  611. end
  612. end
  613. end
  614. objects = {
  615. ticket: self,
  616. article: articles.last,
  617. #recipient: user,
  618. #changes: changes,
  619. }
  620. # get subject
  621. value['subject'].gsub!(/\#\{config\.(.+?)\}/, '<%= c "\\1", false %>')
  622. value['subject'].gsub!(/\#\{(.+?)\}/, '<%= d "\\1", false %>')
  623. subject = NotificationFactory::Mailer.template(
  624. templateInline: value['subject'],
  625. locale: 'en-en',
  626. objects: objects,
  627. )
  628. subject = subject_build(subject)
  629. value['body'].gsub!(/\#\{config\.(.+?)\}/, '<%= c "\\1", true %>')
  630. value['body'].gsub!(/\#\{(.+?)\}/, '<%= d "\\1", true %>')
  631. body = NotificationFactory::Mailer.template(
  632. templateInline: value['body'],
  633. locale: 'en-en',
  634. objects: objects,
  635. )
  636. Ticket::Article.create(
  637. ticket_id: id,
  638. to: recipient_string,
  639. subject: subject,
  640. content_type: 'text/html',
  641. body: body,
  642. internal: false,
  643. sender: Ticket::Article::Sender.find_by(name: 'System'),
  644. type: Ticket::Article::Type.find_by(name: 'email'),
  645. updated_by_id: 1,
  646. created_by_id: 1,
  647. )
  648. next
  649. end
  650. # update tags
  651. if key == 'ticket.tags'
  652. next if value['value'].empty?
  653. tags = value['value'].split(/,/)
  654. if value['operator'] == 'add'
  655. tags.each {|tag|
  656. Tag.tag_add(
  657. object: 'Ticket',
  658. o_id: id,
  659. item: tag,
  660. )
  661. }
  662. elsif value['operator'] == 'remove'
  663. tags.each {|tag|
  664. Tag.tag_remove(
  665. object: 'Ticket',
  666. o_id: id,
  667. item: tag,
  668. )
  669. }
  670. else
  671. logger.error "Unknown #{attribute} operator #{value['operator']}"
  672. end
  673. next
  674. end
  675. # update ticket
  676. next if self[attribute].to_s == value['value'].to_s
  677. changed = true
  678. self[attribute] = value['value']
  679. logger.debug "set #{object_name}.#{attribute} = #{value['value'].inspect}"
  680. end
  681. return if !changed
  682. save
  683. end
  684. =begin
  685. get all email references headers of a ticket, to exclude some, parse it as array into method
  686. references = ticket.get_references
  687. result
  688. ['message-id-1234', 'message-id-5678']
  689. ignore references header(s)
  690. references = ticket.get_references(['message-id-5678'])
  691. result
  692. ['message-id-1234']
  693. =end
  694. def get_references(ignore = [])
  695. references = []
  696. Ticket::Article.select('in_reply_to, message_id').where(ticket_id: id).each {|article|
  697. if !article.in_reply_to.empty?
  698. references.push article.in_reply_to
  699. end
  700. next if !article.message_id
  701. next if article.message_id.empty?
  702. references.push article.message_id
  703. }
  704. ignore.each {|item|
  705. references.delete(item)
  706. }
  707. references
  708. end
  709. private
  710. def check_generate
  711. return if number
  712. self.number = Ticket::Number.generate
  713. end
  714. def check_title
  715. return if !title
  716. title.gsub!(/\s|\t|\r/, ' ')
  717. end
  718. def check_defaults
  719. if !owner_id
  720. self.owner_id = 1
  721. end
  722. return if !customer_id
  723. customer = User.find(customer_id)
  724. return if organization_id == customer.organization_id
  725. self.organization_id = customer.organization_id
  726. end
  727. def reset_pending_time
  728. # ignore if no state has changed
  729. return if !changes['state_id']
  730. # check if new state isn't pending*
  731. current_state = Ticket::State.lookup(id: state_id)
  732. current_state_type = Ticket::StateType.lookup(id: current_state.state_type_id)
  733. # in case, set pending_time to nil
  734. return if current_state_type.name =~ /^pending/i
  735. self.pending_time = nil
  736. end
  737. def destroy_dependencies
  738. # delete articles
  739. articles.destroy_all
  740. # destroy online notifications
  741. OnlineNotification.remove(self.class.to_s, id)
  742. end
  743. end