ticket.rb 41 KB

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