ticket.rb 40 KB

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