search.rb 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223
  1. # Copyright (C) 2012-2024 Zammad Foundation, https://zammad-foundation.org/
  2. module Ticket::Search
  3. extend ActiveSupport::Concern
  4. # methods defined here are going to extend the class, not the instance of it
  5. class_methods do
  6. =begin
  7. search tickets preferences
  8. result = Ticket.search_preferences(user_model)
  9. returns if user has permissions to search
  10. result = {
  11. prio: 3000,
  12. direct_search_index: false
  13. }
  14. returns if user has no permissions to search
  15. result = false
  16. =end
  17. def search_preferences(current_user)
  18. return false if !current_user.permissions?(['ticket.agent', 'ticket.customer'])
  19. {
  20. prio: 3000,
  21. direct_search_index: false,
  22. }
  23. end
  24. =begin
  25. search tickets via search index
  26. result = Ticket.search(
  27. current_user: User.find(123),
  28. query: 'search something',
  29. scope: TicketPolicy::ReadScope, # defaults to ReadScope
  30. limit: 15,
  31. offset: 100,
  32. )
  33. returns
  34. result = [ticket_model1, ticket_model2]
  35. search tickets via search index
  36. result = Ticket.search(
  37. current_user: User.find(123),
  38. query: 'search something',
  39. limit: 15,
  40. offset: 100,
  41. full: false,
  42. )
  43. returns
  44. result = [1,3,5,6,7]
  45. search tickets via database
  46. result = Ticket.search(
  47. current_user: User.find(123),
  48. query: 'some query', # query or condition is required
  49. scope: TicketPolicy::ReadScope, # defaults to ReadScope
  50. condition: {
  51. 'tickets.owner_id' => {
  52. operator: 'is',
  53. value: user.id,
  54. },
  55. 'tickets.state_id' => {
  56. operator: 'is',
  57. value: Ticket::State.where(
  58. state_type_id: Ticket::StateType.where(
  59. name: [
  60. 'pending reminder',
  61. 'pending action',
  62. ],
  63. ).map(&:id),
  64. ),
  65. },
  66. },
  67. limit: 15,
  68. offset: 100,
  69. # sort single column
  70. sort_by: 'created_at',
  71. order_by: 'asc',
  72. # sort multiple columns
  73. sort_by: [ 'created_at', 'updated_at' ],
  74. order_by: [ 'asc', 'desc' ],
  75. full: false,
  76. )
  77. returns
  78. result = [1,3,5,6,7]
  79. =end
  80. def search(params)
  81. # get params
  82. query = params[:query]
  83. condition = params[:condition]
  84. scope = params[:scope] || TicketPolicy::ReadScope
  85. limit = params[:limit] || 12
  86. offset = params[:offset] || 0
  87. current_user = params[:current_user]
  88. full = false
  89. if params[:full] == true || params[:full] == 'true' || !params.key?(:full)
  90. full = true
  91. end
  92. sql_helper = ::SqlHelper.new(object: self)
  93. # check sort
  94. sort_by = sql_helper.get_sort_by(params, 'updated_at')
  95. # check order
  96. order_by = sql_helper.get_order_by(params, 'desc')
  97. # try search index backend
  98. if condition.blank? && SearchIndexBackend.enabled?
  99. query_or = []
  100. if current_user.permissions?('ticket.agent')
  101. group_ids = current_user.group_ids_access(scope.const_get(:ACCESS_TYPE))
  102. if group_ids.present?
  103. access_condition = {
  104. 'query_string' => { 'default_field' => 'group_id', 'query' => "\"#{group_ids.join('" OR "')}\"" }
  105. }
  106. query_or.push(access_condition)
  107. end
  108. end
  109. if current_user.permissions?('ticket.customer')
  110. organizations_query = current_user.all_organizations.where(shared: true).map { |o| "organization_id:#{o.id}" }.join(' OR ')
  111. access_condition = if organizations_query.present?
  112. {
  113. 'query_string' => { 'query' => "customer_id:#{current_user.id} OR #{organizations_query}" }
  114. }
  115. else
  116. {
  117. 'query_string' => { 'default_field' => 'customer_id', 'query' => current_user.id }
  118. }
  119. end
  120. query_or.push(access_condition)
  121. end
  122. return [] if query_or.blank?
  123. query_extension = {
  124. bool: {
  125. must: [
  126. {
  127. bool: {
  128. should: query_or,
  129. },
  130. },
  131. ],
  132. }
  133. }
  134. items = SearchIndexBackend.search(query, 'Ticket', limit: limit,
  135. query_extension: query_extension,
  136. from: offset,
  137. sort_by: sort_by,
  138. order_by: order_by)
  139. if !full
  140. return items.pluck(:id)
  141. end
  142. tickets = []
  143. items.each do |item|
  144. ticket = Ticket.lookup(id: item[:id])
  145. next if !ticket
  146. tickets.push ticket
  147. end
  148. return tickets
  149. end
  150. order_sql = sql_helper.get_order(sort_by, order_by, 'tickets.updated_at DESC')
  151. tickets_all = scope.new(current_user).resolve
  152. .reorder(Arel.sql(order_sql))
  153. .offset(offset)
  154. .limit(limit)
  155. ticket_ids = if query
  156. tickets_all.joins(:articles)
  157. .where(<<~SQL.squish, query: "%#{SqlHelper.quote_like(query.delete('*'))}%")
  158. tickets.title LIKE :query
  159. OR tickets.number LIKE :query
  160. OR ticket_articles.body LIKE :query
  161. OR ticket_articles.from LIKE :query
  162. OR ticket_articles.to LIKE :query
  163. OR ticket_articles.subject LIKE :query
  164. SQL
  165. else
  166. query_condition, bind_condition, tables = selector2sql(condition)
  167. tickets_all.joins(tables)
  168. .where(query_condition, *bind_condition)
  169. end.group(:id).pluck(:id)
  170. if full
  171. ticket_ids.map { |id| Ticket.lookup(id: id) }
  172. else
  173. ticket_ids
  174. end
  175. end
  176. end
  177. end