ticket.rb 37 KB

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