ticket.rb 36 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110
  1. # Copyright (C) 2012-2016 Zammad Foundation, http://zammad-foundation.org/
  2. class Ticket < ApplicationModel
  3. include HasActivityStreamLog
  4. include ChecksClientNotification
  5. include ChecksLatestChangeObserved
  6. include HasHistory
  7. include HasTags
  8. include HasSearchIndexBackend
  9. include HasOnlineNotifications
  10. include HasKarmaActivityLog
  11. include HasLinks
  12. include Ticket::ChecksAccess
  13. include Ticket::Escalation
  14. include Ticket::Subject
  15. load 'ticket/assets.rb'
  16. include Ticket::Assets
  17. load 'ticket/search_index.rb'
  18. include Ticket::SearchIndex
  19. extend Ticket::Search
  20. store :preferences
  21. before_create :check_generate, :check_defaults, :check_title, :set_default_state, :set_default_priority
  22. after_create :check_escalation_update
  23. before_update :check_defaults, :check_title, :reset_pending_time
  24. after_update :check_escalation_update
  25. validates :group_id, presence: true
  26. activity_stream_permission 'ticket.agent'
  27. activity_stream_attributes_ignored :organization_id, # organization_id will channge 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. :preferences
  46. history_attributes_ignored :create_article_type_id,
  47. :create_article_sender_id,
  48. :article_count,
  49. :preferences
  50. belongs_to :group, class_name: 'Group'
  51. has_many :articles, class_name: 'Ticket::Article', after_add: :cache_update, after_remove: :cache_update, dependent: :destroy
  52. has_many :ticket_time_accounting, class_name: 'Ticket::TimeAccounting', dependent: :destroy
  53. belongs_to :organization, class_name: 'Organization'
  54. belongs_to :state, class_name: 'Ticket::State'
  55. belongs_to :priority, class_name: 'Ticket::Priority'
  56. belongs_to :owner, class_name: 'User'
  57. belongs_to :customer, class_name: 'User'
  58. belongs_to :created_by, class_name: 'User'
  59. belongs_to :updated_by, class_name: 'User'
  60. belongs_to :create_article_type, class_name: 'Ticket::Article::Type'
  61. belongs_to :create_article_sender, class_name: 'Ticket::Article::Sender'
  62. self.inheritance_column = nil
  63. attr_accessor :callback_loop
  64. =begin
  65. get user access conditions
  66. conditions = Ticket.access_condition( User.find(1) , 'full')
  67. returns
  68. result = [user1, user2, ...]
  69. =end
  70. def self.access_condition(user, access)
  71. if user.permissions?('ticket.agent')
  72. ['group_id IN (?)', user.group_ids_access(access)]
  73. elsif !user.organization || ( !user.organization.shared || user.organization.shared == false )
  74. ['tickets.customer_id = ?', user.id]
  75. else
  76. ['(tickets.customer_id = ? OR tickets.organization_id = ?)', user.id, user.organization.id]
  77. end
  78. end
  79. =begin
  80. processes tickets which have reached their pending time and sets next state_id
  81. processed_tickets = Ticket.process_pending
  82. returns
  83. processed_tickets = [<Ticket>, ...]
  84. =end
  85. def self.process_pending
  86. result = []
  87. # process pending action tickets
  88. pending_action = Ticket::StateType.find_by(name: 'pending action')
  89. ticket_states_pending_action = Ticket::State.where(state_type_id: pending_action)
  90. .where.not(next_state_id: nil)
  91. if !ticket_states_pending_action.empty?
  92. next_state_map = {}
  93. ticket_states_pending_action.each { |state|
  94. next_state_map[state.id] = state.next_state_id
  95. }
  96. tickets = where(state_id: next_state_map.keys)
  97. .where('pending_time <= ?', Time.zone.now)
  98. tickets.each { |ticket|
  99. Transaction.execute do
  100. ticket.state_id = next_state_map[ticket.state_id]
  101. ticket.updated_at = Time.zone.now
  102. ticket.updated_by_id = 1
  103. ticket.save!
  104. end
  105. result.push ticket
  106. }
  107. end
  108. # process pending reminder tickets
  109. pending_reminder = Ticket::StateType.find_by(name: 'pending reminder')
  110. ticket_states_pending_reminder = Ticket::State.where(state_type_id: pending_reminder)
  111. if !ticket_states_pending_reminder.empty?
  112. reminder_state_map = {}
  113. ticket_states_pending_reminder.each { |state|
  114. reminder_state_map[state.id] = state.next_state_id
  115. }
  116. tickets = where(state_id: reminder_state_map.keys)
  117. .where('pending_time <= ?', Time.zone.now)
  118. tickets.each { |ticket|
  119. article_id = nil
  120. article = Ticket::Article.last_customer_agent_article(ticket.id)
  121. if article
  122. article_id = article.id
  123. end
  124. # send notification
  125. Transaction::BackgroundJob.run(
  126. object: 'Ticket',
  127. type: 'reminder_reached',
  128. object_id: ticket.id,
  129. article_id: article_id,
  130. user_id: 1,
  131. )
  132. result.push ticket
  133. }
  134. end
  135. result
  136. end
  137. =begin
  138. processes escalated tickets
  139. processed_tickets = Ticket.process_escalation
  140. returns
  141. processed_tickets = [<Ticket>, ...]
  142. =end
  143. def self.process_escalation
  144. result = []
  145. # get max warning diff
  146. tickets = where('escalation_at <= ?', Time.zone.now + 15.minutes)
  147. tickets.each { |ticket|
  148. # get sla
  149. sla = ticket.escalation_calculation_get_sla
  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. }
  177. result
  178. end
  179. =begin
  180. merge tickets
  181. ticket = Ticket.find(123)
  182. result = ticket.merge_to(
  183. ticket_id: 123,
  184. user_id: 123,
  185. )
  186. returns
  187. result = true|false
  188. =end
  189. def merge_to(data)
  190. # update articles
  191. Transaction.execute do
  192. Ticket::Article.where(ticket_id: id).each(&:touch)
  193. # quiet update of reassign of articles
  194. Ticket::Article.where(ticket_id: id).update_all(['ticket_id = ?', data[:ticket_id]])
  195. # update history
  196. # create new merge article
  197. Ticket::Article.create(
  198. ticket_id: id,
  199. type_id: Ticket::Article::Type.lookup(name: 'note').id,
  200. sender_id: Ticket::Article::Sender.lookup(name: 'Agent').id,
  201. body: 'merged',
  202. internal: false,
  203. created_by_id: data[:user_id],
  204. updated_by_id: data[:user_id],
  205. )
  206. # add history to both
  207. # reassign links to the new ticket
  208. Link.where(
  209. link_object_source_id: Link::Object.find_by(name: 'Ticket').id,
  210. link_object_source_value: id,
  211. ).update_all(link_object_source_value: data[:ticket_id])
  212. Link.where(
  213. link_object_target_id: Link::Object.find_by(name: 'Ticket').id,
  214. link_object_target_value: id,
  215. ).update_all(link_object_target_value: data[:ticket_id])
  216. # link tickets
  217. Link.add(
  218. link_type: 'parent',
  219. link_object_source: 'Ticket',
  220. link_object_source_value: data[:ticket_id],
  221. link_object_target: 'Ticket',
  222. link_object_target_value: id
  223. )
  224. # set state to 'merged'
  225. self.state_id = Ticket::State.lookup(name: 'merged').id
  226. # rest owner
  227. self.owner_id = 1
  228. # save ticket
  229. save!
  230. # touch new ticket (to broadcast change)
  231. Ticket.find(data[:ticket_id]).touch
  232. end
  233. true
  234. end
  235. =begin
  236. check if online notifcation should be shown in general as already seen with current state
  237. ticket = Ticket.find(1)
  238. seen = ticket.online_notification_seen_state(user_id_check)
  239. returns
  240. result = true # or false
  241. check if online notifcation should be shown for this user as already seen with current state
  242. ticket = Ticket.find(1)
  243. seen = ticket.online_notification_seen_state(check_user_id)
  244. returns
  245. result = true # or false
  246. =end
  247. def online_notification_seen_state(user_id_check = nil)
  248. state = Ticket::State.lookup(id: state_id)
  249. state_type = Ticket::StateType.lookup(id: state.state_type_id)
  250. # always to set unseen for ticket owner
  251. if state_type.name != 'merged'
  252. if user_id_check
  253. return false if user_id_check == owner_id && user_id_check != updated_by_id
  254. end
  255. end
  256. # set all to seen if pending action state is a closed or merged state
  257. if state_type.name == 'pending action' && state.next_state_id
  258. state = Ticket::State.lookup(id: state.next_state_id)
  259. state_type = Ticket::StateType.lookup(id: state.state_type_id)
  260. end
  261. # set all to seen if new state is pending reminder state
  262. if state_type.name == 'pending reminder'
  263. if user_id_check
  264. return false if owner_id == 1
  265. return false if updated_by_id != owner_id && user_id_check == owner_id
  266. return true
  267. end
  268. return true
  269. end
  270. # set all to seen if new state is a closed or merged state
  271. return true if state_type.name == 'closed'
  272. return true if state_type.name == 'merged'
  273. false
  274. end
  275. =begin
  276. get count of tickets and tickets which match on selector
  277. ticket_count, tickets = Ticket.selectors(params[:condition], limit, current_user, 'full')
  278. =end
  279. def self.selectors(selectors, limit = 10, current_user = nil, access = 'full')
  280. raise 'no selectors given' if !selectors
  281. query, bind_params, tables = selector2sql(selectors, current_user)
  282. return [] if !query
  283. if !current_user
  284. ticket_count = Ticket.where(query, *bind_params).joins(tables).count
  285. tickets = Ticket.where(query, *bind_params).joins(tables).limit(limit)
  286. return [ticket_count, tickets]
  287. end
  288. access_condition = Ticket.access_condition(current_user, access)
  289. ticket_count = Ticket.where(access_condition).where(query, *bind_params).joins(tables).count
  290. tickets = Ticket.where(access_condition).where(query, *bind_params).joins(tables).limit(limit)
  291. [ticket_count, tickets]
  292. end
  293. =begin
  294. generate condition query to search for tickets based on condition
  295. query_condition, bind_condition, tables = selector2sql(params[:condition], current_user)
  296. condition example
  297. {
  298. 'ticket.title' => {
  299. operator: 'contains', # contains not
  300. value: 'some value',
  301. },
  302. 'ticket.state_id' => {
  303. operator: 'is',
  304. value: [1,2,5]
  305. },
  306. 'ticket.created_at' => {
  307. operator: 'after (absolute)', # after,before
  308. value: '2015-10-17T06:00:00.000Z',
  309. },
  310. 'ticket.created_at' => {
  311. operator: 'within next (relative)', # before,within,in,after
  312. range: 'day', # minute|hour|day|month|year
  313. value: '25',
  314. },
  315. 'ticket.owner_id' => {
  316. operator: 'is', # is not
  317. pre_condition: 'current_user.id',
  318. },
  319. 'ticket.owner_id' => {
  320. operator: 'is', # is not
  321. pre_condition: 'specific',
  322. value: 4711,
  323. },
  324. 'ticket.escalation_at' => {
  325. operator: 'is not', # not
  326. value: nil,
  327. },
  328. 'ticket.tags' => {
  329. operator: 'contains all', # contains all|contains one|contains all not|contains one not
  330. value: 'tag1, tag2',
  331. },
  332. }
  333. =end
  334. def self.selector2sql(selectors, current_user = nil)
  335. current_user_id = UserInfo.current_user_id
  336. if current_user
  337. current_user_id = current_user.id
  338. end
  339. return if !selectors
  340. # remember query and bind params
  341. query = ''
  342. bind_params = []
  343. like = Rails.application.config.db_like
  344. # get tables to join
  345. tables = ''
  346. selectors.each { |attribute, selector|
  347. selector = attribute.split(/\./)
  348. next if !selector[1]
  349. next if selector[0] == 'ticket'
  350. next if tables.include?(selector[0])
  351. if query != ''
  352. query += ' AND '
  353. end
  354. if selector[0] == 'customer'
  355. tables += ', users customers'
  356. query += 'tickets.customer_id = customers.id'
  357. elsif selector[0] == 'organization'
  358. tables += ', organizations'
  359. query += 'tickets.organization_id = organizations.id'
  360. elsif selector[0] == 'owner'
  361. tables += ', users owners'
  362. query += 'tickets.owner_id = owners.id'
  363. elsif selector[0] == 'article'
  364. tables += ', ticket_articles articles'
  365. query += 'tickets.id = articles.ticket_id'
  366. else
  367. raise "invalid selector #{attribute.inspect}->#{selector.inspect}"
  368. end
  369. }
  370. # add conditions
  371. selectors.each { |attribute, selector_raw|
  372. # validation
  373. raise "Invalid selector #{selector_raw.inspect}" if !selector_raw
  374. raise "Invalid selector #{selector_raw.inspect}" if !selector_raw.respond_to?(:key?)
  375. selector = selector_raw.stringify_keys
  376. raise "Invalid selector, operator missing #{selector.inspect}" if !selector['operator']
  377. # validate value / allow empty but only if pre_condition exists and is not specific
  378. if !selector.key?('value') || ((selector['value'].class == String || selector['value'].class == Array) && (selector['value'].respond_to?(:empty?) && selector['value'].empty?))
  379. return nil if selector['pre_condition'].nil?
  380. return nil if selector['pre_condition'].respond_to?(:empty?) && selector['pre_condition'].empty?
  381. return nil if selector['pre_condition'] == 'specific'
  382. end
  383. # validate pre_condition values
  384. return nil if selector['pre_condition'] && selector['pre_condition'] !~ /^(not_set|current_user\.|specific)/
  385. # get attributes
  386. attributes = attribute.split(/\./)
  387. attribute = "#{attributes[0]}s.#{attributes[1]}"
  388. if attributes[0] == 'ticket' && attributes[1] == 'tags'
  389. selector['value'] = selector['value'].split(/,/).collect(&:strip)
  390. end
  391. if query != ''
  392. query += ' AND '
  393. end
  394. if selector['operator'] == 'is'
  395. if selector['pre_condition'] == 'not_set'
  396. if attributes[1] =~ /^(created_by|updated_by|owner|customer|user)_id/
  397. query += "#{attribute} IN (?)"
  398. bind_params.push 1
  399. else
  400. query += "#{attribute} IS NOT NULL"
  401. end
  402. elsif selector['pre_condition'] == 'current_user.id'
  403. raise "Use current_user.id in selector, but no current_user is set #{selector.inspect}" if !current_user_id
  404. query += "#{attribute} IN (?)"
  405. bind_params.push current_user_id
  406. elsif selector['pre_condition'] == 'current_user.organization_id'
  407. raise "Use current_user.id in selector, but no current_user is set #{selector.inspect}" if !current_user_id
  408. query += "#{attribute} IN (?)"
  409. user = User.lookup(id: current_user_id)
  410. bind_params.push user.organization_id
  411. else
  412. # rubocop:disable Style/IfInsideElse
  413. if selector['value'].nil?
  414. query += "#{attribute} IS NOT NULL"
  415. else
  416. query += "#{attribute} IN (?)"
  417. bind_params.push selector['value']
  418. end
  419. # rubocop:enable Style/IfInsideElse
  420. end
  421. elsif selector['operator'] == 'is not'
  422. if selector['pre_condition'] == 'not_set'
  423. if attributes[1] =~ /^(created_by|updated_by|owner|customer|user)_id/
  424. query += "#{attribute} NOT IN (?)"
  425. bind_params.push 1
  426. else
  427. query += "#{attribute} IS NULL"
  428. end
  429. elsif selector['pre_condition'] == 'current_user.id'
  430. query += "#{attribute} NOT IN (?)"
  431. bind_params.push current_user_id
  432. elsif selector['pre_condition'] == 'current_user.organization_id'
  433. query += "#{attribute} NOT IN (?)"
  434. user = User.lookup(id: current_user_id)
  435. bind_params.push user.organization_id
  436. else
  437. # rubocop:disable Style/IfInsideElse
  438. if selector['value'].nil?
  439. query += "#{attribute} IS NOT NULL"
  440. else
  441. query += "#{attribute} NOT IN (?)"
  442. bind_params.push selector['value']
  443. end
  444. # rubocop:enable Style/IfInsideElse
  445. end
  446. elsif selector['operator'] == 'contains'
  447. query += "#{attribute} #{like} (?)"
  448. value = "%#{selector['value']}%"
  449. bind_params.push value
  450. elsif selector['operator'] == 'contains not'
  451. query += "#{attribute} NOT #{like} (?)"
  452. value = "%#{selector['value']}%"
  453. bind_params.push value
  454. elsif selector['operator'] == 'contains all' && attributes[0] == 'ticket' && attributes[1] == 'tags'
  455. query += "? = (
  456. SELECT
  457. COUNT(*)
  458. FROM
  459. tag_objects,
  460. tag_items,
  461. tags
  462. WHERE
  463. tickets.id = tags.o_id AND
  464. tag_objects.id = tags.tag_object_id AND
  465. tag_objects.name = 'Ticket' AND
  466. tag_items.id = tags.tag_item_id AND
  467. tag_items.name IN (?)
  468. )"
  469. bind_params.push selector['value'].count
  470. bind_params.push selector['value']
  471. elsif selector['operator'] == 'contains one' && attributes[0] == 'ticket' && attributes[1] == 'tags'
  472. query += "1 <= (
  473. SELECT
  474. COUNT(*)
  475. FROM
  476. tag_objects,
  477. tag_items,
  478. tags
  479. WHERE
  480. tickets.id = tags.o_id AND
  481. tag_objects.id = tags.tag_object_id AND
  482. tag_objects.name = 'Ticket' AND
  483. tag_items.id = tags.tag_item_id AND
  484. tag_items.name IN (?)
  485. )"
  486. bind_params.push selector['value']
  487. elsif selector['operator'] == 'contains all not' && attributes[0] == 'ticket' && attributes[1] == 'tags'
  488. query += "0 = (
  489. SELECT
  490. COUNT(*)
  491. FROM
  492. tag_objects,
  493. tag_items,
  494. tags
  495. WHERE
  496. tickets.id = tags.o_id AND
  497. tag_objects.id = tags.tag_object_id AND
  498. tag_objects.name = 'Ticket' AND
  499. tag_items.id = tags.tag_item_id AND
  500. tag_items.name IN (?)
  501. )"
  502. bind_params.push selector['value']
  503. elsif selector['operator'] == 'contains one not' && attributes[0] == 'ticket' && attributes[1] == 'tags'
  504. query += "(
  505. SELECT
  506. COUNT(*)
  507. FROM
  508. tag_objects,
  509. tag_items,
  510. tags
  511. WHERE
  512. tickets.id = tags.o_id AND
  513. tag_objects.id = tags.tag_object_id AND
  514. tag_objects.name = 'Ticket' AND
  515. tag_items.id = tags.tag_item_id AND
  516. tag_items.name IN (?)
  517. ) BETWEEN ? AND ?"
  518. bind_params.push selector['value']
  519. bind_params.push selector['value'].count - 1
  520. bind_params.push selector['value'].count
  521. elsif selector['operator'] == 'before (absolute)'
  522. query += "#{attribute} <= ?"
  523. bind_params.push selector['value']
  524. elsif selector['operator'] == 'after (absolute)'
  525. query += "#{attribute} >= ?"
  526. bind_params.push selector['value']
  527. elsif selector['operator'] == 'within last (relative)'
  528. query += "#{attribute} >= ?"
  529. time = nil
  530. if selector['range'] == 'minute'
  531. time = Time.zone.now - selector['value'].to_i.minutes
  532. elsif selector['range'] == 'hour'
  533. time = Time.zone.now - selector['value'].to_i.hours
  534. elsif selector['range'] == 'day'
  535. time = Time.zone.now - selector['value'].to_i.days
  536. elsif selector['range'] == 'month'
  537. time = Time.zone.now - selector['value'].to_i.months
  538. elsif selector['range'] == 'year'
  539. time = Time.zone.now - selector['value'].to_i.years
  540. else
  541. raise "Unknown selector attributes '#{selector.inspect}'"
  542. end
  543. bind_params.push time
  544. elsif selector['operator'] == 'within next (relative)'
  545. query += "#{attribute} <= ?"
  546. time = nil
  547. if selector['range'] == 'minute'
  548. time = Time.zone.now + selector['value'].to_i.minutes
  549. elsif selector['range'] == 'hour'
  550. time = Time.zone.now + selector['value'].to_i.hours
  551. elsif selector['range'] == 'day'
  552. time = Time.zone.now + selector['value'].to_i.days
  553. elsif selector['range'] == 'month'
  554. time = Time.zone.now + selector['value'].to_i.months
  555. elsif selector['range'] == 'year'
  556. time = Time.zone.now + selector['value'].to_i.years
  557. else
  558. raise "Unknown selector attributes '#{selector.inspect}'"
  559. end
  560. bind_params.push time
  561. elsif selector['operator'] == 'before (relative)'
  562. query += "#{attribute} <= ?"
  563. time = nil
  564. if selector['range'] == 'minute'
  565. time = Time.zone.now - selector['value'].to_i.minutes
  566. elsif selector['range'] == 'hour'
  567. time = Time.zone.now - selector['value'].to_i.hours
  568. elsif selector['range'] == 'day'
  569. time = Time.zone.now - selector['value'].to_i.days
  570. elsif selector['range'] == 'month'
  571. time = Time.zone.now - selector['value'].to_i.months
  572. elsif selector['range'] == 'year'
  573. time = Time.zone.now - selector['value'].to_i.years
  574. else
  575. raise "Unknown selector attributes '#{selector.inspect}'"
  576. end
  577. bind_params.push time
  578. elsif selector['operator'] == 'after (relative)'
  579. query += "#{attribute} >= ?"
  580. time = nil
  581. if selector['range'] == 'minute'
  582. time = Time.zone.now + selector['value'].to_i.minutes
  583. elsif selector['range'] == 'hour'
  584. time = Time.zone.now + selector['value'].to_i.hours
  585. elsif selector['range'] == 'day'
  586. time = Time.zone.now + selector['value'].to_i.days
  587. elsif selector['range'] == 'month'
  588. time = Time.zone.now + selector['value'].to_i.months
  589. elsif selector['range'] == 'year'
  590. time = Time.zone.now + selector['value'].to_i.years
  591. else
  592. raise "Unknown selector attributes '#{selector.inspect}'"
  593. end
  594. bind_params.push time
  595. else
  596. raise "Invalid operator '#{selector['operator']}' for '#{selector['value'].inspect}'"
  597. end
  598. }
  599. [query, bind_params, tables]
  600. end
  601. =begin
  602. perform changes on ticket
  603. ticket.perform_changes({}, 'trigger', item, current_user_id)
  604. =end
  605. def perform_changes(perform, perform_origin, item = nil, current_user_id = nil)
  606. logger.debug "Perform #{perform_origin} #{perform.inspect} on Ticket.find(#{id})"
  607. # if the configuration contains the deletion of the ticket then
  608. # we skip all other ticket changes because they does not matter
  609. if perform['ticket.action'].present? && perform['ticket.action']['value'] == 'delete'
  610. perform.each do |key, _value|
  611. (object_name, attribute) = key.split('.', 2)
  612. next if object_name != 'ticket'
  613. next if attribute == 'action'
  614. perform.delete(key)
  615. end
  616. end
  617. changed = false
  618. perform.each do |key, value|
  619. (object_name, attribute) = key.split('.', 2)
  620. raise "Unable to update object #{object_name}.#{attribute}, only can update tickets and send notifications!" if object_name != 'ticket' && object_name != 'notification'
  621. # send notification
  622. if object_name == 'notification'
  623. # value['recipient'] was a string in the past (single-select) so we convert it to array if needed
  624. value_recipient = value['recipient']
  625. if !value_recipient.is_a?(Array)
  626. value_recipient = [value_recipient]
  627. end
  628. recipients_raw = []
  629. value_recipient.each { |recipient|
  630. if recipient == 'article_last_sender'
  631. if item && item[:article_id]
  632. article = Ticket::Article.lookup(id: item[:article_id])
  633. if article.reply_to.present?
  634. recipients_raw.push(article.reply_to)
  635. elsif article.from.present?
  636. recipients_raw.push(article.from)
  637. elsif article.origin_by_id
  638. email = User.lookup(id: article.origin_by_id).email
  639. recipients_raw.push(email)
  640. elsif article.created_by_id
  641. email = User.lookup(id: article.created_by_id).email
  642. recipients_raw.push(email)
  643. end
  644. end
  645. elsif recipient == 'ticket_customer'
  646. email = User.lookup(id: customer_id).email
  647. recipients_raw.push(email)
  648. elsif recipient == 'ticket_owner'
  649. email = User.lookup(id: owner_id).email
  650. recipients_raw.push(email)
  651. elsif recipient == 'ticket_agents'
  652. User.group_access(group_id, 'full').sort_by(&:login).each do |user|
  653. recipients_raw.push(user.email)
  654. end
  655. else
  656. logger.error "Unknown email notification recipient '#{recipient}'"
  657. next
  658. end
  659. }
  660. recipients_checked = []
  661. recipients_raw.each { |recipient_email|
  662. skip_user = false
  663. users = User.where(email: recipient_email)
  664. users.each { |user|
  665. next if user.preferences[:mail_delivery_failed] != true
  666. next if !user.preferences[:mail_delivery_failed_data]
  667. till_blocked = ((user.preferences[:mail_delivery_failed_data] - Time.zone.now - 60.days) / 60 / 60 / 24).round
  668. next if till_blocked.positive?
  669. logger.info "Send no trigger based notification to #{recipient_email} because email is marked as mail_delivery_failed for #{till_blocked} days"
  670. skip_user = true
  671. break
  672. }
  673. next if skip_user
  674. # send notifications only to email adresses
  675. next if !recipient_email
  676. next if recipient_email !~ /@/
  677. # check if address is valid
  678. begin
  679. recipient_email = Mail::Address.new(recipient_email).address
  680. rescue
  681. next # because unable to parse
  682. end
  683. # do not sent notifications to this recipients
  684. send_no_auto_response_reg_exp = Setting.get('send_no_auto_response_reg_exp')
  685. begin
  686. next if recipient_email =~ /#{send_no_auto_response_reg_exp}/i
  687. rescue => e
  688. logger.error "ERROR: Invalid regex '#{send_no_auto_response_reg_exp}' in setting send_no_auto_response_reg_exp"
  689. logger.error 'ERROR: ' + e.inspect
  690. next if recipient_email =~ /(mailer-daemon|postmaster|abuse|root)@.+?\..+?/i
  691. end
  692. # check if notification should be send because of customer emails
  693. if item && item[:article_id]
  694. article = Ticket::Article.lookup(id: item[:article_id])
  695. if article && article.preferences['is-auto-response'] == true && article.from && article.from =~ /#{Regexp.quote(recipient_email)}/i
  696. logger.info "Send no trigger based notification to #{recipient_email} because of auto response tagged incoming email"
  697. next
  698. end
  699. end
  700. # loop protection / check if maximal count of trigger mail has reached
  701. map = {
  702. 10 => 10,
  703. 30 => 15,
  704. 60 => 25,
  705. 180 => 50,
  706. 600 => 100,
  707. }
  708. skip = false
  709. map.each { |minutes, count|
  710. already_sent = Ticket::Article.where(
  711. ticket_id: id,
  712. sender: Ticket::Article::Sender.find_by(name: 'System'),
  713. type: Ticket::Article::Type.find_by(name: 'email'),
  714. ).where("ticket_articles.created_at > ? AND ticket_articles.to LIKE '%#{recipient_email.strip}%'", Time.zone.now - minutes.minutes).count
  715. next if already_sent < count
  716. logger.info "Send no trigger based notification to #{recipient_email} because already sent #{count} for this ticket within last #{minutes} minutes (loop protection)"
  717. skip = true
  718. break
  719. }
  720. next if skip
  721. map = {
  722. 10 => 30,
  723. 30 => 60,
  724. 60 => 120,
  725. 180 => 240,
  726. 600 => 360,
  727. }
  728. skip = false
  729. map.each { |minutes, count|
  730. already_sent = Ticket::Article.where(
  731. sender: Ticket::Article::Sender.find_by(name: 'System'),
  732. type: Ticket::Article::Type.find_by(name: 'email'),
  733. ).where("ticket_articles.created_at > ? AND ticket_articles.to LIKE '%#{recipient_email.strip}%'", Time.zone.now - minutes.minutes).count
  734. next if already_sent < count
  735. logger.info "Send no trigger based notification to #{recipient_email} because already sent #{count} in total within last #{minutes} minutes (loop protection)"
  736. skip = true
  737. break
  738. }
  739. next if skip
  740. email = recipient_email.downcase.strip
  741. next if recipients_checked.include?(email)
  742. recipients_checked.push(email)
  743. }
  744. next if recipients_checked.blank?
  745. recipient_string = recipients_checked.join(', ')
  746. group = self.group
  747. next if !group
  748. email_address = group.email_address
  749. if !email_address
  750. logger.info "Unable to send trigger based notification to #{recipient_string} because no email address is set for group '#{group.name}'"
  751. next
  752. end
  753. if !email_address.channel_id
  754. 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})"
  755. next
  756. end
  757. objects = {
  758. ticket: self,
  759. article: articles.last,
  760. }
  761. # get subject
  762. subject = NotificationFactory::Mailer.template(
  763. templateInline: value['subject'],
  764. locale: 'en-en',
  765. objects: objects,
  766. quote: false,
  767. )
  768. subject = subject_build(subject)
  769. body = NotificationFactory::Mailer.template(
  770. templateInline: value['body'],
  771. locale: 'en-en',
  772. objects: objects,
  773. quote: true,
  774. )
  775. Ticket::Article.create(
  776. ticket_id: id,
  777. to: recipient_string,
  778. subject: subject,
  779. content_type: 'text/html',
  780. body: body,
  781. internal: false,
  782. sender: Ticket::Article::Sender.find_by(name: 'System'),
  783. type: Ticket::Article::Type.find_by(name: 'email'),
  784. preferences: {
  785. perform_origin: perform_origin,
  786. },
  787. updated_by_id: 1,
  788. created_by_id: 1,
  789. )
  790. next
  791. end
  792. # update tags
  793. if key == 'ticket.tags'
  794. next if value['value'].blank?
  795. tags = value['value'].split(/,/)
  796. if value['operator'] == 'add'
  797. tags.each { |tag|
  798. tag_add(tag)
  799. }
  800. elsif value['operator'] == 'remove'
  801. tags.each { |tag|
  802. tag_remove(tag)
  803. }
  804. else
  805. logger.error "Unknown #{attribute} operator #{value['operator']}"
  806. end
  807. next
  808. end
  809. # delete ticket
  810. if key == 'ticket.action'
  811. next if value['value'].blank?
  812. next if value['value'] != 'delete'
  813. destroy
  814. next
  815. end
  816. # lookup pre_condition
  817. if value['pre_condition']
  818. if value['pre_condition'] =~ /^not_set/
  819. value['value'] = 1
  820. elsif value['pre_condition'] =~ /^current_user\./
  821. raise 'Unable to use current_user, got no current_user_id for ticket.perform_changes' if !current_user_id
  822. value['value'] = current_user_id
  823. end
  824. end
  825. # update ticket
  826. next if self[attribute].to_s == value['value'].to_s
  827. changed = true
  828. self[attribute] = value['value']
  829. logger.debug "set #{object_name}.#{attribute} = #{value['value'].inspect}"
  830. end
  831. return if !changed
  832. save
  833. end
  834. =begin
  835. get all email references headers of a ticket, to exclude some, parse it as array into method
  836. references = ticket.get_references
  837. result
  838. ['message-id-1234', 'message-id-5678']
  839. ignore references header(s)
  840. references = ticket.get_references(['message-id-5678'])
  841. result
  842. ['message-id-1234']
  843. =end
  844. def get_references(ignore = [])
  845. references = []
  846. Ticket::Article.select('in_reply_to, message_id').where(ticket_id: id).each { |article|
  847. if !article.in_reply_to.empty?
  848. references.push article.in_reply_to
  849. end
  850. next if !article.message_id
  851. next if article.message_id.empty?
  852. references.push article.message_id
  853. }
  854. ignore.each { |item|
  855. references.delete(item)
  856. }
  857. references
  858. end
  859. =begin
  860. get all articles of a ticket in correct order (overwrite active record default method)
  861. artilces = ticket.articles
  862. result
  863. [article1, articl2]
  864. =end
  865. def articles
  866. Ticket::Article.where(ticket_id: id).order(:created_at, :id)
  867. end
  868. def history_get(fulldata = false)
  869. list = History.list(self.class.name, self['id'], 'Ticket::Article')
  870. return list if !fulldata
  871. # get related objects
  872. assets = {}
  873. list.each { |item|
  874. record = Kernel.const_get(item['object']).find(item['o_id'])
  875. assets = record.assets(assets)
  876. if item['related_object']
  877. record = Kernel.const_get(item['related_object']).find( item['related_o_id'])
  878. assets = record.assets(assets)
  879. end
  880. }
  881. {
  882. history: list,
  883. assets: assets,
  884. }
  885. end
  886. private
  887. def check_generate
  888. return true if number
  889. self.number = Ticket::Number.generate
  890. true
  891. end
  892. def check_title
  893. return true if !title
  894. title.gsub!(/\s|\t|\r/, ' ')
  895. true
  896. end
  897. def check_defaults
  898. if !owner_id
  899. self.owner_id = 1
  900. end
  901. return true if !customer_id
  902. customer = User.find_by(id: customer_id)
  903. return true if !customer
  904. return true if organization_id == customer.organization_id
  905. self.organization_id = customer.organization_id
  906. true
  907. end
  908. def reset_pending_time
  909. # ignore if no state has changed
  910. return true if !changes['state_id']
  911. # check if new state isn't pending*
  912. current_state = Ticket::State.lookup(id: state_id)
  913. current_state_type = Ticket::StateType.lookup(id: current_state.state_type_id)
  914. # in case, set pending_time to nil
  915. return true if current_state_type.name =~ /^pending/i
  916. self.pending_time = nil
  917. true
  918. end
  919. def check_escalation_update
  920. escalation_calculation
  921. true
  922. end
  923. def set_default_state
  924. return true if state_id
  925. default_ticket_state = Ticket::State.find_by(default_create: true)
  926. return true if !default_ticket_state
  927. self.state_id = default_ticket_state.id
  928. true
  929. end
  930. def set_default_priority
  931. return true if priority_id
  932. default_ticket_priority = Ticket::Priority.find_by(default_create: true)
  933. return true if !default_ticket_priority
  934. self.priority_id = default_ticket_priority.id
  935. true
  936. end
  937. end