link.rb 8.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297
  1. # Copyright (C) 2012-2025 Zammad Foundation, https://zammad-foundation.org/
  2. class Link < ApplicationModel
  3. include Link::TriggersSubscriptions
  4. belongs_to :link_type, class_name: 'Link::Type', optional: true
  5. belongs_to :link_object, class_name: 'Link::Object', optional: true
  6. # Using custom validator instead of validates_uniqueness_of
  7. # To have a custom attribute-less error message
  8. validates_with Validations::LinkUniquenessValidator
  9. after_destroy :touch_link_references
  10. @map = {
  11. 'normal' => 'normal',
  12. 'parent' => 'child',
  13. 'child' => 'parent',
  14. }
  15. =begin
  16. links = Link.list(
  17. link_object: 'Ticket',
  18. link_object_value: 1
  19. )
  20. =end
  21. def self.list(data)
  22. linkobject = link_object_get(name: data[:link_object])
  23. return if !linkobject
  24. items = []
  25. # get links for one site
  26. list = Link.where(
  27. 'link_object_source_id = ? AND link_object_source_value = ?', linkobject.id, data[:link_object_value]
  28. )
  29. list.each do |item|
  30. link = {}
  31. link['link_type'] = @map[ Link::Type.find(item.link_type_id).name ]
  32. link['link_object'] = Link::Object.find(item.link_object_target_id).name
  33. link['link_object_value'] = item.link_object_target_value
  34. items.push link
  35. end
  36. # get links for the other site
  37. list = Link.where(
  38. 'link_object_target_id = ? AND link_object_target_value = ?', linkobject.id, data[:link_object_value]
  39. )
  40. list.each do |item|
  41. link = {}
  42. link['link_type'] = Link::Type.find(item.link_type_id).name
  43. link['link_object'] = Link::Object.find(item.link_object_source_id).name
  44. link['link_object_value'] = item.link_object_source_value
  45. items.push link
  46. end
  47. return items if data[:user].blank?
  48. items.select do |item|
  49. authorized_item?(data[:user], item)
  50. end
  51. end
  52. =begin
  53. Link.add(
  54. link_type: 'normal',
  55. link_object_source: 'Ticket',
  56. link_object_source_value: 6,
  57. link_object_target: 'Ticket',
  58. link_object_target_value: 31
  59. )
  60. Link.add(
  61. link_types_id: 12,
  62. link_object_source_id: 1,
  63. link_object_source_value: 1,
  64. link_object_target_id: 1,
  65. link_object_target_value: 1
  66. )
  67. =end
  68. def self.add(data)
  69. if data.key?(:link_type)
  70. linktype = link_type_get(name: data[:link_type])
  71. data[:link_type_id] = linktype.id
  72. data.delete(:link_type)
  73. end
  74. if data.key?(:link_object_source)
  75. linkobject = link_object_get(name: data[:link_object_source])
  76. data[:link_object_source_id] = linkobject.id
  77. touch_reference_by_params(
  78. object: data[:link_object_source],
  79. o_id: data[:link_object_source_value],
  80. )
  81. data.delete(:link_object_source)
  82. end
  83. if data.key?(:link_object_target)
  84. linkobject = link_object_get(name: data[:link_object_target])
  85. data[:link_object_target_id] = linkobject.id
  86. touch_reference_by_params(
  87. object: data[:link_object_target],
  88. o_id: data[:link_object_target_value],
  89. )
  90. data.delete(:link_object_target)
  91. end
  92. Link.create(data)
  93. end
  94. =begin
  95. Link.remove(
  96. link_type: 'normal',
  97. link_object_source: 'Ticket',
  98. link_object_source_value: 6,
  99. link_object_target: 'Ticket',
  100. link_object_target_value: 31
  101. )
  102. =end
  103. def self.remove(data)
  104. if data.key?(:link_object_source)
  105. linkobject = link_object_get(name: data[:link_object_source])
  106. data[:link_object_source_id] = linkobject.id
  107. end
  108. if data.key?(:link_object_target)
  109. linkobject = link_object_get(name: data[:link_object_target])
  110. data[:link_object_target_id] = linkobject.id
  111. end
  112. # from one site
  113. if data.key?(:link_type)
  114. linktype = link_type_get(name: data[:link_type])
  115. data[:link_type_id] = linktype.id
  116. end
  117. Link.where(
  118. link_type_id: data[:link_type_id],
  119. link_object_source_id: data[:link_object_source_id],
  120. link_object_source_value: data[:link_object_source_value],
  121. link_object_target_id: data[:link_object_target_id],
  122. link_object_target_value: data[:link_object_target_value]
  123. ).destroy_all
  124. # from the other site
  125. if data.key?(:link_type)
  126. linktype = link_type_get(name: @map[ data[:link_type] ])
  127. data[:link_type_id] = linktype.id
  128. end
  129. Link.where(
  130. link_type_id: data[:link_type_id],
  131. link_object_target_id: data[:link_object_source_id],
  132. link_object_target_value: data[:link_object_source_value],
  133. link_object_source_id: data[:link_object_target_id],
  134. link_object_source_value: data[:link_object_target_value]
  135. ).destroy_all
  136. end
  137. =begin
  138. Link.remove_all(
  139. link_object: 'Ticket',
  140. link_object_value: 6,
  141. )
  142. =end
  143. def self.remove_all(data)
  144. if data.key?(:link_object)
  145. linkobject = link_object_get(name: data[:link_object])
  146. data[:link_object_id] = linkobject.id
  147. end
  148. Link.where(
  149. link_object_target_id: data[:link_object_id],
  150. link_object_target_value: data[:link_object_value],
  151. ).destroy_all
  152. Link.where(
  153. link_object_source_id: data[:link_object_id],
  154. link_object_source_value: data[:link_object_value],
  155. ).destroy_all
  156. true
  157. end
  158. def touch_link_references
  159. Link.touch_reference_by_params(
  160. object: Link::Object.lookup(id: link_object_source_id).name,
  161. o_id: link_object_source_value,
  162. )
  163. Link.touch_reference_by_params(
  164. object: Link::Object.lookup(id: link_object_target_id).name,
  165. o_id: link_object_target_value,
  166. )
  167. end
  168. def self.link_type_get(data)
  169. linktype = Link::Type.find_by(name: data[:name])
  170. if !linktype
  171. linktype = Link::Type.create(name: data[:name])
  172. end
  173. linktype
  174. end
  175. def self.link_object_get(data)
  176. linkobject = Link::Object.find_by(name: data[:name])
  177. if !linkobject
  178. linkobject = Link::Object.create(name: data[:name])
  179. end
  180. linkobject
  181. end
  182. =begin
  183. Update assets according to given references list
  184. @param assets [Hash] hash with assets
  185. @param link_references [Array<Hash>] @see #list
  186. @return [Hash] assets including linked items
  187. @example Link.reduce_assets(assets, link_references)
  188. =end
  189. def self.reduce_assets(assets, link_references)
  190. link_items = link_references
  191. .filter_map { |elem| lookup_linked_object(elem) }
  192. ApplicationModel::CanAssets.reduce(link_items, assets)
  193. end
  194. def self.lookup_linked_object(elem)
  195. klass = elem['link_object'].safe_constantize
  196. id = elem['link_object_value']
  197. case klass.to_s
  198. when KnowledgeBase::Answer::Translation.to_s
  199. Setting.get('kb_active') ? klass.lookup(id: id) : nil
  200. else
  201. klass&.lookup(id: id)
  202. end
  203. end
  204. def self.duplicates(object1_id:, object1_value:, object2_value:, object2_id: nil)
  205. if !object2_id
  206. object2_id = object1_id
  207. end
  208. Link.joins(', links as linksb').where('
  209. (
  210. links.link_type_id = linksb.link_type_id
  211. AND links.link_object_source_id = linksb.link_object_source_id
  212. AND links.link_object_source_value = linksb.link_object_source_value
  213. AND links.link_object_target_id = ?
  214. AND linksb.link_object_target_id = ?
  215. AND links.link_object_target_value = ?
  216. AND linksb.link_object_target_value = ?
  217. )
  218. OR
  219. (
  220. links.link_type_id = linksb.link_type_id
  221. AND links.link_object_target_id = linksb.link_object_target_id
  222. AND links.link_object_target_value = linksb.link_object_target_value
  223. AND links.link_object_source_id = ?
  224. AND linksb.link_object_source_id = ?
  225. AND links.link_object_source_value = ?
  226. AND linksb.link_object_source_value = ?
  227. )
  228. ', object1_id, object2_id, object1_value, object2_value, object1_id, object2_id, object1_value, object2_value)
  229. end
  230. def self.authorized_item?(user, item)
  231. record = item['link_object'].constantize.lookup(id: item['link_object_value'])
  232. # non-ID records are not checked for authorization
  233. return true if record.blank?
  234. Pundit.authorize(user, record, :show?).present?
  235. rescue Pundit::NotAuthorizedError
  236. false
  237. rescue NameError, Pundit::NotDefinedError
  238. # NameError: no Model means no authorization check possible
  239. # Pundit::NotDefinedError: no Policy means no authorization check necessary
  240. true
  241. end
  242. end