ticket.rb 48 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429
  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. include Ticket::Assets
  17. include Ticket::SearchIndex
  18. include Ticket::Search
  19. store :preferences
  20. before_create :check_generate, :check_defaults, :check_title, :set_default_state, :set_default_priority
  21. before_update :check_defaults, :check_title, :reset_pending_time, :check_owner_active
  22. validates :group_id, presence: true
  23. activity_stream_permission 'ticket.agent'
  24. activity_stream_attributes_ignored :organization_id, # organization_id will channge automatically on user update
  25. :create_article_type_id,
  26. :create_article_sender_id,
  27. :article_count,
  28. :first_response_at,
  29. :first_response_escalation_at,
  30. :first_response_in_min,
  31. :first_response_diff_in_min,
  32. :close_at,
  33. :close_escalation_at,
  34. :close_in_min,
  35. :close_diff_in_min,
  36. :update_escalation_at,
  37. :update_in_min,
  38. :update_diff_in_min,
  39. :last_contact_at,
  40. :last_contact_agent_at,
  41. :last_contact_customer_at,
  42. :last_owner_update_at,
  43. :preferences
  44. history_attributes_ignored :create_article_type_id,
  45. :create_article_sender_id,
  46. :article_count,
  47. :preferences
  48. belongs_to :group
  49. belongs_to :organization
  50. has_many :articles, class_name: 'Ticket::Article', after_add: :cache_update, after_remove: :cache_update, dependent: :destroy, inverse_of: :ticket
  51. has_many :ticket_time_accounting, class_name: 'Ticket::TimeAccounting', dependent: :destroy, inverse_of: :ticket
  52. # rubocop:disable Rails/InverseOf
  53. belongs_to :state, class_name: 'Ticket::State'
  54. belongs_to :priority, class_name: 'Ticket::Priority'
  55. belongs_to :owner, class_name: 'User'
  56. belongs_to :customer, class_name: 'User'
  57. belongs_to :created_by, class_name: 'User'
  58. belongs_to :updated_by, class_name: 'User'
  59. belongs_to :create_article_type, class_name: 'Ticket::Article::Type'
  60. belongs_to :create_article_sender, class_name: 'Ticket::Article::Sender'
  61. # rubocop:enable Rails/InverseOf
  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 = "#{ActiveRecord::Base.connection.quote_table_name("#{attributes[0]}s")}.#{ActiveRecord::Base.connection.quote_column_name(attributes[1])}"
  443. # magic selectors
  444. if attributes[0] == 'ticket' && attributes[1] == 'out_of_office_replacement_id'
  445. attribute = "#{ActiveRecord::Base.connection.quote_table_name("#{attributes[0]}s")}.#{ActiveRecord::Base.connection.quote_column_name('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} IS NULL OR #{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.find_by(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} IS NOT NULL AND #{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} IS NULL OR #{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} IS NULL OR #{attribute} NOT IN (?))"
  505. user = User.find_by(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} IS NULL OR #{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. article = begin
  681. Ticket::Article.find_by(id: item.try(:dig, :article_id))
  682. rescue ArgumentError
  683. nil
  684. end
  685. # if the configuration contains the deletion of the ticket then
  686. # we skip all other ticket changes because they does not matter
  687. if perform['ticket.action'].present? && perform['ticket.action']['value'] == 'delete'
  688. perform.each_key do |key|
  689. (object_name, attribute) = key.split('.', 2)
  690. next if object_name != 'ticket'
  691. next if attribute == 'action'
  692. perform.delete(key)
  693. end
  694. end
  695. perform_notification = {}
  696. changed = false
  697. perform.each do |key, value|
  698. (object_name, attribute) = key.split('.', 2)
  699. raise "Unable to update object #{object_name}.#{attribute}, only can update tickets and send notifications!" if object_name != 'ticket' && object_name != 'notification'
  700. # send notification (after changes are done)
  701. if object_name == 'notification'
  702. perform_notification[key] = value
  703. next
  704. end
  705. # update tags
  706. if key == 'ticket.tags'
  707. next if value['value'].blank?
  708. tags = value['value'].split(/,/)
  709. if value['operator'] == 'add'
  710. tags.each do |tag|
  711. tag_add(tag, current_user_id || 1)
  712. end
  713. elsif value['operator'] == 'remove'
  714. tags.each do |tag|
  715. tag_remove(tag, current_user_id || 1)
  716. end
  717. else
  718. logger.error "Unknown #{attribute} operator #{value['operator']}"
  719. end
  720. next
  721. end
  722. # delete ticket
  723. if key == 'ticket.action'
  724. next if value['value'].blank?
  725. next if value['value'] != 'delete'
  726. destroy!
  727. next
  728. end
  729. # lookup pre_condition
  730. if value['pre_condition']
  731. if value['pre_condition'].match?(/^not_set/)
  732. value['value'] = 1
  733. elsif value['pre_condition'].match?(/^current_user\./)
  734. raise 'Unable to use current_user, got no current_user_id for ticket.perform_changes' if !current_user_id
  735. value['value'] = current_user_id
  736. end
  737. end
  738. # update ticket
  739. next if self[attribute].to_s == value['value'].to_s
  740. changed = true
  741. self[attribute] = value['value']
  742. logger.debug { "set #{object_name}.#{attribute} = #{value['value'].inspect} for ticket_id #{id}" }
  743. end
  744. if changed
  745. save!
  746. end
  747. perform_notification.each do |key, value|
  748. perform_changes_notification(key, value, perform_origin, article)
  749. end
  750. true
  751. end
  752. def perform_changes_notification(_key, value, perform_origin, article)
  753. # value['recipient'] was a string in the past (single-select) so we convert it to array if needed
  754. value_recipient = value['recipient']
  755. if !value_recipient.is_a?(Array)
  756. value_recipient = [value_recipient]
  757. end
  758. recipients_raw = []
  759. value_recipient.each do |recipient|
  760. if recipient == 'article_last_sender'
  761. if article.present?
  762. if article.reply_to.present?
  763. recipients_raw.push(article.reply_to)
  764. elsif article.from.present?
  765. recipients_raw.push(article.from)
  766. elsif article.origin_by_id
  767. email = User.find_by(id: article.origin_by_id).email
  768. recipients_raw.push(email)
  769. elsif article.created_by_id
  770. email = User.find_by(id: article.created_by_id).email
  771. recipients_raw.push(email)
  772. end
  773. end
  774. elsif recipient == 'ticket_customer'
  775. email = User.find_by(id: customer_id).email
  776. recipients_raw.push(email)
  777. elsif recipient == 'ticket_owner'
  778. email = User.find_by(id: owner_id).email
  779. recipients_raw.push(email)
  780. elsif recipient == 'ticket_agents'
  781. User.group_access(group_id, 'full').sort_by(&:login).each do |user|
  782. recipients_raw.push(user.email)
  783. end
  784. else
  785. logger.error "Unknown email notification recipient '#{recipient}'"
  786. next
  787. end
  788. end
  789. recipients_checked = []
  790. recipients_raw.each do |recipient_email|
  791. skip_user = false
  792. users = User.where(email: recipient_email)
  793. users.each do |user|
  794. next if user.preferences[:mail_delivery_failed] != true
  795. next if !user.preferences[:mail_delivery_failed_data]
  796. till_blocked = ((user.preferences[:mail_delivery_failed_data] - Time.zone.now - 60.days) / 60 / 60 / 24).round
  797. next if till_blocked.positive?
  798. logger.info "Send no trigger based notification to #{recipient_email} because email is marked as mail_delivery_failed for #{till_blocked} days"
  799. skip_user = true
  800. break
  801. end
  802. next if skip_user
  803. # send notifications only to email adresses
  804. next if recipient_email.blank?
  805. next if recipient_email !~ /@/
  806. # check if address is valid
  807. begin
  808. Mail::AddressList.new(recipient_email).addresses.each do |address|
  809. recipient_email = address.address
  810. break if recipient_email.present? && recipient_email =~ /@/ && !recipient_email.match?(/\s/)
  811. end
  812. rescue
  813. if recipient_email.present?
  814. if recipient_email !~ /^(.+?)<(.+?)@(.+?)>$/
  815. next # no usable format found
  816. end
  817. recipient_email = "#{$2}@#{$3}"
  818. end
  819. next if recipient_email.blank?
  820. next if recipient_email !~ /@/
  821. next if recipient_email.match?(/\s/)
  822. end
  823. # do not sent notifications to this recipients
  824. send_no_auto_response_reg_exp = Setting.get('send_no_auto_response_reg_exp')
  825. begin
  826. next if recipient_email.match?(/#{send_no_auto_response_reg_exp}/i)
  827. rescue => e
  828. logger.error "ERROR: Invalid regex '#{send_no_auto_response_reg_exp}' in setting send_no_auto_response_reg_exp"
  829. logger.error 'ERROR: ' + e.inspect
  830. next if recipient_email.match?(/(mailer-daemon|postmaster|abuse|root|noreply|noreply.+?|no-reply|no-reply.+?)@.+?/i)
  831. end
  832. # check if notification should be send because of customer emails
  833. if article.present? && article.preferences.fetch('is-auto-response', false) == true && article.from && article.from =~ /#{Regexp.quote(recipient_email)}/i
  834. logger.info "Send no trigger based notification to #{recipient_email} because of auto response tagged incoming email"
  835. next
  836. end
  837. # loop protection / check if maximal count of trigger mail has reached
  838. map = {
  839. 10 => 10,
  840. 30 => 15,
  841. 60 => 25,
  842. 180 => 50,
  843. 600 => 100,
  844. }
  845. skip = false
  846. map.each do |minutes, count|
  847. already_sent = Ticket::Article.where(
  848. ticket_id: id,
  849. sender: Ticket::Article::Sender.find_by(name: 'System'),
  850. type: Ticket::Article::Type.find_by(name: 'email'),
  851. ).where('ticket_articles.created_at > ? AND ticket_articles.to LIKE ?', Time.zone.now - minutes.minutes, "%#{recipient_email.strip}%").count
  852. next if already_sent < count
  853. logger.info "Send no trigger based notification to #{recipient_email} because already sent #{count} for this ticket within last #{minutes} minutes (loop protection)"
  854. skip = true
  855. break
  856. end
  857. next if skip
  858. map = {
  859. 10 => 30,
  860. 30 => 60,
  861. 60 => 120,
  862. 180 => 240,
  863. 600 => 360,
  864. }
  865. skip = false
  866. map.each do |minutes, count|
  867. already_sent = Ticket::Article.where(
  868. sender: Ticket::Article::Sender.find_by(name: 'System'),
  869. type: Ticket::Article::Type.find_by(name: 'email'),
  870. ).where('ticket_articles.created_at > ? AND ticket_articles.to LIKE ?', Time.zone.now - minutes.minutes, "%#{recipient_email.strip}%").count
  871. next if already_sent < count
  872. logger.info "Send no trigger based notification to #{recipient_email} because already sent #{count} in total within last #{minutes} minutes (loop protection)"
  873. skip = true
  874. break
  875. end
  876. next if skip
  877. email = recipient_email.downcase.strip
  878. next if recipients_checked.include?(email)
  879. recipients_checked.push(email)
  880. end
  881. return if recipients_checked.blank?
  882. recipient_string = recipients_checked.join(', ')
  883. group_id = self.group_id
  884. return if !group_id
  885. email_address = Group.find(group_id).email_address
  886. if !email_address
  887. logger.info "Unable to send trigger based notification to #{recipient_string} because no email address is set for group '#{group.name}'"
  888. return
  889. end
  890. if !email_address.channel_id
  891. 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})"
  892. return
  893. end
  894. # articles.last breaks (returns the wrong article)
  895. # if another email notification trigger preceded this one
  896. # (see https://github.com/zammad/zammad/issues/1543)
  897. objects = {
  898. ticket: self,
  899. article: article || articles.last
  900. }
  901. # get subject
  902. subject = NotificationFactory::Mailer.template(
  903. templateInline: value['subject'],
  904. locale: 'en-en',
  905. objects: objects,
  906. quote: false,
  907. )
  908. subject = subject_build(subject)
  909. body = NotificationFactory::Mailer.template(
  910. templateInline: value['body'],
  911. locale: 'en-en',
  912. objects: objects,
  913. quote: true,
  914. )
  915. (body, attachments_inline) = HtmlSanitizer.replace_inline_images(body, id)
  916. message = Ticket::Article.create(
  917. ticket_id: id,
  918. to: recipient_string,
  919. subject: subject,
  920. content_type: 'text/html',
  921. body: body,
  922. internal: false,
  923. sender: Ticket::Article::Sender.find_by(name: 'System'),
  924. type: Ticket::Article::Type.find_by(name: 'email'),
  925. preferences: {
  926. perform_origin: perform_origin,
  927. },
  928. updated_by_id: 1,
  929. created_by_id: 1,
  930. )
  931. attachments_inline.each do |attachment|
  932. Store.add(
  933. object: 'Ticket::Article',
  934. o_id: message.id,
  935. data: attachment[:data],
  936. filename: attachment[:filename],
  937. preferences: attachment[:preferences],
  938. )
  939. end
  940. true
  941. end
  942. =begin
  943. perform active triggers on ticket
  944. Ticket.perform_triggers(ticket, article, item, options)
  945. =end
  946. def self.perform_triggers(ticket, article, item, options = {})
  947. recursive = Setting.get('ticket_trigger_recursive')
  948. type = options[:type] || item[:type]
  949. local_options = options.clone
  950. local_options[:type] = type
  951. local_options[:reset_user_id] = true
  952. local_options[:disable] = ['Transaction::Notification']
  953. local_options[:trigger_ids] ||= {}
  954. local_options[:trigger_ids][ticket.id] ||= []
  955. local_options[:loop_count] ||= 0
  956. local_options[:loop_count] += 1
  957. ticket_trigger_recursive_max_loop = Setting.get('ticket_trigger_recursive_max_loop')&.to_i || 10
  958. if local_options[:loop_count] > ticket_trigger_recursive_max_loop
  959. message = "Stopped perform_triggers for this object (Ticket/#{ticket.id}), because loop count was #{local_options[:loop_count]}!"
  960. logger.info { message }
  961. return [false, message]
  962. end
  963. triggers = if Rails.configuration.db_case_sensitive
  964. ::Trigger.where(active: true).order('LOWER(name)')
  965. else
  966. ::Trigger.where(active: true).order(:name)
  967. end
  968. return [true, 'No triggers active'] if triggers.blank?
  969. # check if notification should be send because of customer emails
  970. send_notification = true
  971. if local_options[:send_notification] == false
  972. send_notification = false
  973. elsif item[:article_id]
  974. article = Ticket::Article.lookup(id: item[:article_id])
  975. if article&.preferences && article.preferences['send-auto-response'] == false
  976. send_notification = false
  977. end
  978. end
  979. Transaction.execute(local_options) do
  980. triggers.each do |trigger|
  981. logger.debug { "Probe trigger (#{trigger.name}/#{trigger.id}) for this object (Ticket:#{ticket.id}/Loop:#{local_options[:loop_count]})" }
  982. condition = trigger.condition
  983. # check if one article attribute is used
  984. one_has_changed_done = false
  985. article_selector = false
  986. trigger.condition.each_key do |key|
  987. (object_name, attribute) = key.split('.', 2)
  988. next if object_name != 'article'
  989. next if attribute == 'id'
  990. article_selector = true
  991. end
  992. if article && article_selector
  993. one_has_changed_done = true
  994. end
  995. if article && type == 'update'
  996. one_has_changed_done = true
  997. end
  998. # check ticket "has changed" options
  999. has_changed_done = true
  1000. condition.each do |key, value|
  1001. next if value.blank?
  1002. next if value['operator'].blank?
  1003. next if !value['operator']['has changed']
  1004. # remove condition item, because it has changed
  1005. (object_name, attribute) = key.split('.', 2)
  1006. next if object_name != 'ticket'
  1007. next if item[:changes].blank?
  1008. next if !item[:changes].key?(attribute)
  1009. condition.delete(key)
  1010. one_has_changed_done = true
  1011. end
  1012. # check if we have not matching "has changed" attributes
  1013. condition.each_value do |value|
  1014. next if value.blank?
  1015. next if value['operator'].blank?
  1016. next if !value['operator']['has changed']
  1017. has_changed_done = false
  1018. break
  1019. end
  1020. # check ticket action
  1021. if condition['ticket.action']
  1022. next if condition['ticket.action']['operator'] == 'is' && condition['ticket.action']['value'] != type
  1023. next if condition['ticket.action']['operator'] != 'is' && condition['ticket.action']['value'] == type
  1024. condition.delete('ticket.action')
  1025. end
  1026. next if !has_changed_done
  1027. # check in min one attribute of condition has changed on update
  1028. one_has_changed_condition = false
  1029. if type == 'update'
  1030. # verify if ticket condition exists
  1031. condition.each_key do |key|
  1032. (object_name, attribute) = key.split('.', 2)
  1033. next if object_name != 'ticket'
  1034. one_has_changed_condition = true
  1035. next if item[:changes].blank?
  1036. next if !item[:changes].key?(attribute)
  1037. one_has_changed_done = true
  1038. break
  1039. end
  1040. next if one_has_changed_condition && !one_has_changed_done
  1041. end
  1042. # check if ticket selector is matching
  1043. condition['ticket.id'] = {
  1044. operator: 'is',
  1045. value: ticket.id,
  1046. }
  1047. next if article_selector && !article
  1048. # check if article selector is matching
  1049. if article_selector
  1050. condition['article.id'] = {
  1051. operator: 'is',
  1052. value: article.id,
  1053. }
  1054. end
  1055. # verify is condition is matching
  1056. ticket_count, tickets = Ticket.selectors(condition, 1)
  1057. next if ticket_count.blank?
  1058. next if ticket_count.zero?
  1059. next if tickets.first.id != ticket.id
  1060. user_id = ticket.updated_by_id
  1061. if article
  1062. user_id = article.updated_by_id
  1063. end
  1064. if recursive == false && local_options[:loop_count] > 1
  1065. message = "Do not execute recursive triggers per default until Zammad 3.0. With Zammad 3.0 and higher the following trigger is executed '#{trigger.name}' on Ticket:#{ticket.id}. Please review your current triggers and change them if needed."
  1066. logger.info { message }
  1067. return [true, message]
  1068. end
  1069. local_send_notification = true
  1070. if article && send_notification == false && trigger.perform['notification.email'] && trigger.perform['notification.email']['recipient']
  1071. recipient = trigger.perform['notification.email']['recipient']
  1072. local_send_notification = false
  1073. local_options[:send_notification] = false
  1074. if recipient.include?('ticket_customer') || recipient.include?('article_last_sender')
  1075. logger.info { "Skip trigger (#{trigger.name}/#{trigger.id}) because sender do not want to get auto responder for object (Ticket/#{ticket.id}/Article/#{article.id})" }
  1076. next
  1077. end
  1078. end
  1079. if local_options[:trigger_ids][ticket.id].include?(trigger.id)
  1080. logger.info { "Skip trigger (#{trigger.name}/#{trigger.id}) because was already executed for this object (Ticket:#{ticket.id}/Loop:#{local_options[:loop_count]})" }
  1081. next
  1082. end
  1083. local_options[:trigger_ids][ticket.id].push trigger.id
  1084. logger.info { "Execute trigger (#{trigger.name}/#{trigger.id}) for this object (Ticket:#{ticket.id}/Loop:#{local_options[:loop_count]})" }
  1085. ticket.perform_changes(trigger.perform, 'trigger', item, user_id)
  1086. if recursive == true
  1087. Observer::Transaction.commit(local_options)
  1088. end
  1089. end
  1090. end
  1091. [true, ticket, local_options]
  1092. end
  1093. =begin
  1094. get all email references headers of a ticket, to exclude some, parse it as array into method
  1095. references = ticket.get_references
  1096. result
  1097. ['message-id-1234', 'message-id-5678']
  1098. ignore references header(s)
  1099. references = ticket.get_references(['message-id-5678'])
  1100. result
  1101. ['message-id-1234']
  1102. =end
  1103. def get_references(ignore = [])
  1104. references = []
  1105. Ticket::Article.select('in_reply_to, message_id').where(ticket_id: id).each do |article|
  1106. if article.in_reply_to.present?
  1107. references.push article.in_reply_to
  1108. end
  1109. next if article.message_id.blank?
  1110. references.push article.message_id
  1111. end
  1112. ignore.each do |item|
  1113. references.delete(item)
  1114. end
  1115. references
  1116. end
  1117. =begin
  1118. get all articles of a ticket in correct order (overwrite active record default method)
  1119. artilces = ticket.articles
  1120. result
  1121. [article1, articl2]
  1122. =end
  1123. def articles
  1124. Ticket::Article.where(ticket_id: id).order(:created_at, :id)
  1125. end
  1126. def history_get(fulldata = false)
  1127. list = History.list(self.class.name, self['id'], 'Ticket::Article')
  1128. return list if !fulldata
  1129. # get related objects
  1130. assets = {}
  1131. list.each do |item|
  1132. record = Kernel.const_get(item['object']).find(item['o_id'])
  1133. assets = record.assets(assets)
  1134. if item['related_object']
  1135. record = Kernel.const_get(item['related_object']).find( item['related_o_id'])
  1136. assets = record.assets(assets)
  1137. end
  1138. end
  1139. {
  1140. history: list,
  1141. assets: assets,
  1142. }
  1143. end
  1144. private
  1145. def check_generate
  1146. return true if number
  1147. self.number = Ticket::Number.generate
  1148. true
  1149. end
  1150. def check_title
  1151. return true if !title
  1152. title.gsub!(/\s|\t|\r/, ' ')
  1153. true
  1154. end
  1155. def check_defaults
  1156. if !owner_id
  1157. self.owner_id = 1
  1158. end
  1159. return true if !customer_id
  1160. customer = User.find_by(id: customer_id)
  1161. return true if !customer
  1162. return true if organization_id == customer.organization_id
  1163. self.organization_id = customer.organization_id
  1164. true
  1165. end
  1166. def reset_pending_time
  1167. # ignore if no state has changed
  1168. return true if !changes_to_save['state_id']
  1169. # ignore if new state is blank and
  1170. # let handle ActiveRecord the error
  1171. return if state_id.blank?
  1172. # check if new state isn't pending*
  1173. current_state = Ticket::State.lookup(id: state_id)
  1174. current_state_type = Ticket::StateType.lookup(id: current_state.state_type_id)
  1175. # in case, set pending_time to nil
  1176. return true if current_state_type.name.match?(/^pending/i)
  1177. self.pending_time = nil
  1178. true
  1179. end
  1180. def set_default_state
  1181. return true if state_id
  1182. default_ticket_state = Ticket::State.find_by(default_create: true)
  1183. return true if !default_ticket_state
  1184. self.state_id = default_ticket_state.id
  1185. true
  1186. end
  1187. def set_default_priority
  1188. return true if priority_id
  1189. default_ticket_priority = Ticket::Priority.find_by(default_create: true)
  1190. return true if !default_ticket_priority
  1191. self.priority_id = default_ticket_priority.id
  1192. true
  1193. end
  1194. def check_owner_active
  1195. return true if Setting.get('import_mode')
  1196. # return when ticket is unassigned
  1197. return true if owner_id.blank?
  1198. return true if owner_id == 1
  1199. # return if owner is active, is agent and has access to group of ticket
  1200. return true if owner.active? && owner.permissions?('ticket.agent') && owner.group_access?(group_id, 'full')
  1201. # else set the owner of the ticket to the default user as unassigned
  1202. self.owner_id = 1
  1203. true
  1204. end
  1205. end