Browse Source

Fixed issues #1259 and #1598 by adding ObjectManager::Attribute validations.

Thorsten Eckel 6 years ago
parent
commit
444e48e377

+ 9 - 0
app/models/concerns/has_object_manager_attributes_validation.rb

@@ -0,0 +1,9 @@
+# Copyright (C) 2018 Zammad Foundation, http://zammad-foundation.org/
+module HasObjectManagerAttributesValidation
+  extend ActiveSupport::Concern
+
+  included do
+    include ActiveModel::Validations
+    validates_with ObjectManager::Attribute::Validation, on: %i[create update]
+  end
+end

+ 1 - 0
app/models/group.rb

@@ -6,6 +6,7 @@ class Group < ApplicationModel
   include ChecksClientNotification
   include ChecksLatestChangeObserved
   include HasHistory
+  include HasObjectManagerAttributesValidation
 
   belongs_to :email_address
   belongs_to :signature

+ 66 - 0
app/models/object_manager/attribute/validation.rb

@@ -0,0 +1,66 @@
+class ObjectManager::Attribute::Validation < ActiveModel::Validator
+  include ::Mixin::HasBackends
+
+  def validate(record)
+    return if validation_unneeded?
+
+    @record = record
+    sanitize_memory_cache
+
+    return if attributes_unchanged?
+
+    model_attributes.select(&:editable).each do |attribute|
+      perform_validation(attribute)
+    end
+  end
+
+  private
+
+  attr_reader :record
+
+  def validation_unneeded?
+    return true if Setting.get('import_mode')
+
+    ApplicationHandleInfo.current != 'application_server'
+  end
+
+  def attributes_unchanged?
+    model_attributes.none? do |attribute|
+      record.will_save_change_to_attribute?(attribute.name)
+    end
+  end
+
+  def model_attributes
+    @model_attributes ||= begin
+      object_lookup_id = ObjectLookup.by_name(record.class.name)
+      @active_attributes.select { |attribute| attribute.object_lookup_id == object_lookup_id }
+    end
+  end
+
+  def perform_validation(attribute)
+    backends.each do |backend|
+      backend.validate(
+        record:    record,
+        attribute: attribute
+      )
+    end
+  end
+
+  def validations
+    @validations ||= backend.map { backend.new }
+  end
+
+  def sanitize_memory_cache
+    @model_attributes = nil
+
+    latest_cache_key = active_attributes_in_db.cache_key
+    return if @previous_cache_key == latest_cache_key
+
+    @previous_cache_key = latest_cache_key
+    @active_attributes  = active_attributes_in_db.to_a
+  end
+
+  def active_attributes_in_db
+    ObjectManager::Attribute.where(active: true)
+  end
+end

+ 26 - 0
app/models/object_manager/attribute/validation/backend.rb

@@ -0,0 +1,26 @@
+class ObjectManager::Attribute::Validation::Backend
+  include Mixin::IsBackend
+
+  def self.inherited(subclass)
+    subclass.is_backend_of(::ObjectManager::Attribute::Validation)
+  end
+
+  def self.validate(*args)
+    new(*args).validate
+  end
+
+  attr_reader :record, :attribute, :value, :previous_value
+
+  def initialize(record:, attribute:)
+    @record         = record
+    @attribute      = attribute
+    @value          = record[attribute.name]
+    @previous_value = record.attribute_in_database(attribute.name)
+  end
+
+  def invalid_because_attribute(message)
+    record.errors.add attribute.name.to_sym, message
+  end
+end
+
+Mixin::RequiredSubPaths.eager_load_recursive(__dir__)

+ 30 - 0
app/models/object_manager/attribute/validation/date.rb

@@ -0,0 +1,30 @@
+class ObjectManager::Attribute::Validation::Date < ObjectManager::Attribute::Validation::Backend
+
+  def validate
+    return if value.blank?
+    return if irrelevant_attribute?
+
+    validate_past
+    validate_future
+  end
+
+  private
+
+  def irrelevant_attribute?
+    %w[date datetime].exclude?(attribute.data_type)
+  end
+
+  def validate_past
+    return if attribute.data_option[:past]
+    return if !value.past?
+
+    invalid_because_attribute('does not allow past dates.')
+  end
+
+  def validate_future
+    return if attribute.data_option[:future]
+    return if !value.future?
+
+    invalid_because_attribute('does not allow future dates.')
+  end
+end

+ 45 - 0
app/models/object_manager/attribute/validation/required.rb

@@ -0,0 +1,45 @@
+class ObjectManager::Attribute::Validation::Required < ObjectManager::Attribute::Validation::Backend
+
+  def validate
+    return if value.present?
+    return if optional_for_user?
+
+    invalid_because_attribute('is required but missing.')
+  end
+
+  private
+
+  def optional_for_user?
+    return true if system_user?
+    return true if required_for_permissions.blank?
+    return false if required_for_permissions.include?('-all-')
+
+    !user.permissions?(required_for_permissions)
+  end
+
+  def system_user?
+    user_id.blank? || user_id == 1
+  end
+
+  def user_id
+    user_id ||= UserInfo.current_user_id
+  end
+
+  def user
+    @user ||= User.lookup(id: user_id)
+  end
+
+  def required_for_permissions
+    @required_for_permissions ||= begin
+      attribute.screens[action]&.each_with_object([]) do |(permission, config), result|
+        result.push(permission) if config[:required].present?
+      end
+    end
+  end
+
+  def action
+    return :edit if record.persisted?
+
+    attribute.screens.keys.find { |e| e.start_with?('create') }
+  end
+end

+ 1 - 0
app/models/organization.rb

@@ -8,6 +8,7 @@ class Organization < ApplicationModel
   include HasSearchIndexBackend
   include CanCsvImport
   include ChecksHtmlSanitized
+  include HasObjectManagerAttributesValidation
 
   include Organization::ChecksAccess
   include Organization::Assets

+ 1 - 0
app/models/ticket.rb

@@ -14,6 +14,7 @@ class Ticket < ApplicationModel
   include HasKarmaActivityLog
   include HasLinks
   include Ticket::ChecksAccess
+  include HasObjectManagerAttributesValidation
 
   include Ticket::Escalation
   include Ticket::Subject

+ 1 - 0
app/models/ticket/article.rb

@@ -6,6 +6,7 @@ class Ticket::Article < ApplicationModel
   include HasHistory
   include ChecksHtmlSanitized
   include CanCsvImport
+  include HasObjectManagerAttributesValidation
 
   include Ticket::Article::ChecksAccess
   include Ticket::Article::Assets

+ 1 - 0
app/models/user.rb

@@ -9,6 +9,7 @@ class User < ApplicationModel
   include ChecksHtmlSanitized
   include HasGroups
   include HasRoles
+  include HasObjectManagerAttributesValidation
 
   include User::ChecksAccess
   include User::Assets

Some files were not shown because too many files changed in this diff