group.rb 4.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154
  1. # Copyright (C) 2012-2024 Zammad Foundation, https://zammad-foundation.org/
  2. class Group < ApplicationModel
  3. include HasDefaultModelUserRelations
  4. include CanBeImported
  5. include HasActivityStreamLog
  6. include ChecksClientNotification
  7. include ChecksHtmlSanitized
  8. include HasHistory
  9. include HasObjectManagerAttributes
  10. include HasCollectionUpdate
  11. include HasSearchIndexBackend
  12. include CanSelector
  13. include CanSearch
  14. include Group::Assets
  15. scope :sorted, -> { order(:name) }
  16. belongs_to :email_address, optional: true
  17. belongs_to :signature, optional: true
  18. belongs_to :parent, optional: true, class_name: 'Group'
  19. # workflow checks should run after before_create and before_update callbacks
  20. include ChecksCoreWorkflow
  21. core_workflow_screens 'create', 'edit'
  22. core_workflow_admin_screens 'create', 'edit'
  23. before_validation :ensure_name_last_and_parent, :check_max_depth
  24. before_save :update_path
  25. after_save :update_path_children
  26. validates :name, uniqueness: { case_sensitive: false }
  27. validates :name_last, presence: true, format: { without: %r{::}, message: __('No double colons (::) allowed, reserved delimiter') }
  28. validates :note, length: { maximum: 250 }
  29. sanitized_html :note, no_images: true
  30. activity_stream_permission 'admin.group'
  31. @@max_depth = nil # rubocop:disable Style/ClassVars
  32. def guess_name_last_and_parent
  33. split = name.split('::')
  34. self.name_last = split[-1]
  35. return if parent_id
  36. return if split.size == 1
  37. check_parent = Group.find_by(name: split[..-2].join('::'))
  38. if check_parent.blank?
  39. errors.add(:name, 'contains invalid path')
  40. raise ActiveRecord::RecordInvalid, self
  41. end
  42. self.parent = check_parent
  43. end
  44. def ensure_name_last_and_parent
  45. if persisted?
  46. return if name_last_changed?
  47. return if !name_changed?
  48. else
  49. return if name_last.present?
  50. return if name.blank?
  51. end
  52. guess_name_last_and_parent
  53. end
  54. def check_max_depth
  55. old_depth = if persisted?
  56. self.class.find(id).depth
  57. else
  58. 0
  59. end
  60. new_depth = depth(force: true)
  61. return if new_depth < self.class.max_depth && all_children(force: true).all? { |child| new_depth + (child.depth - old_depth) < self.class.max_depth }
  62. raise Exceptions::UnprocessableEntity, __('This group or its children exceed the allowed nesting depth.')
  63. end
  64. def update_path
  65. self.name = path(force: true).join('::')
  66. end
  67. def update_path_children
  68. return if !saved_change_to_attribute?(:parent_id) && !saved_change_to_attribute?(:name_last)
  69. all_children.each do |child|
  70. child.update_path
  71. child.save!
  72. end
  73. end
  74. def all_parents(force: false)
  75. Rails.cache.fetch("Group/#{Group.latest_change}/all_parents/#{id}", force: force) do
  76. result = []
  77. check_next = self
  78. (self.class.max_depth * 2).times do
  79. break if check_next.parent.blank?
  80. result << check_next.parent
  81. check_next = check_next.parent
  82. end
  83. result
  84. end
  85. end
  86. def all_children(force: false)
  87. return [] if !persisted?
  88. Rails.cache.fetch("Group/#{Group.latest_change}/all_children/#{id}", force: force) do
  89. result = []
  90. check_next = [self]
  91. (self.class.max_depth * 2).times do
  92. break if check_next.blank?
  93. children = self.class.where(parent_id: check_next)
  94. result += children
  95. check_next = children
  96. end
  97. result
  98. end
  99. end
  100. def self.all_max_depth(force: false)
  101. Rails.cache.fetch("Group/#{Group.latest_change}/all_max_depth", force: force) do
  102. Group.select { |group| group.depth >= max_depth }
  103. end
  104. end
  105. def depth(force: false)
  106. all_parents(force: force).count
  107. end
  108. def fullname
  109. path.join(' › ')
  110. end
  111. def path(force: false)
  112. all_parents(force: force).map(&:name_last).reverse + [name_last]
  113. end
  114. def self.max_depth
  115. return @@max_depth if @@max_depth
  116. @@max_depth = ActiveRecord::Base.connection_db_config.configuration_hash[:adapter] == 'mysql2' ? 6 : 10 # rubocop:disable Style/ClassVars
  117. end
  118. end