# frozen_string_literal: true class ::CustomWizard::CustomField include HasErrors include ActiveModel::Serialization attr_reader :id ATTRS ||= ["name", "klass", "type", "serializers"] REQUIRED ||= ["name", "klass", "type"] NAMESPACE ||= "custom_wizard_custom_fields" NAME_MIN_LENGTH ||= 3 CLASSES ||= { topic: ["topic_view", "topic_list_item"], group: ["basic_group"], category: ["basic_category"], post: ["post"] } TYPES ||= ["string", "boolean", "integer", "json"] LIST_CACHE_KEY ||= 'custom_field_list' def self.serializers CLASSES.values.flatten.uniq end def initialize(id, data) @id = id data = data.with_indifferent_access ATTRS.each do |attr| self.class.class_eval { attr_accessor attr } value = data[attr] if value.present? send("#{attr}=", value) end end @subscription = CustomWizard::Subscription.new end def save validate if valid? data = {} key = name (ATTRS - ['name']).each do |attr| data[attr] = send(attr) end if self.class.save_to_store(id, key, data) self.class.invalidate_cache true else false end else false end end def validate ATTRS.each do |attr| value = send(attr) i18n_key = "wizard.custom_field.error" if value.blank? && REQUIRED.include?(attr) add_error(I18n.t("#{i18n_key}.required_attribute", attr: attr)) break end if attr == 'serializers' && !value.is_a?(Array) next end if (attr == 'klass' && CLASSES.keys.exclude?(value.to_sym)) || (attr == 'serializers' && CLASSES[klass.to_sym].blank?) add_error(I18n.t("#{i18n_key}.unsupported_class", class: value)) next end if attr == 'klass' && !@subscription.includes?(:custom_field, :klass, value) add_error(I18n.t("wizard.custom_field.error.subscription_type", type: value)) end if attr == 'serializers' && (unsupported = value - CLASSES[klass.to_sym]).length > 0 add_error(I18n.t("#{i18n_key}.unsupported_serializers", class: klass, serializers: unsupported.join(", ") )) end if attr == 'type' && TYPES.exclude?(value) add_error(I18n.t("#{i18n_key}.unsupported_type", type: value)) end if attr == 'type' && !@subscription.includes?(:custom_field, :type, value) add_error(I18n.t("wizard.custom_field.error.subscription_type", type: value)) end if attr == 'name' unless value.is_a?(String) add_error(I18n.t("#{i18n_key}.name_invalid", name: value)) end if value.length < NAME_MIN_LENGTH add_error(I18n.t("#{i18n_key}.name_too_short", name: value, min_length: NAME_MIN_LENGTH)) end if new? && self.class.exists?(name) add_error(I18n.t("#{i18n_key}.name_already_taken", name: value)) end begin @name = value.parameterize(separator: '_') rescue add_error(I18n.t("#{i18n_key}.name_invalid", name: value)) end end end end def new? id.blank? end def valid? errors.blank? end def self.list PluginStoreRow.where(plugin_name: NAMESPACE).map do |record| create_from_store(record) end end def self.cached_list @cached_list ||= ::CustomWizard::Cache.wrap(LIST_CACHE_KEY) do PluginStoreRow.where(plugin_name: NAMESPACE).map do |record| create_from_store(record).as_json.with_indifferent_access end end end def self.list_by(attr, value, cached: true) attr = attr.to_sym fields = cached ? cached_list : list fields.select do |cf| if attr == :serializers cf[attr] && cf[attr].include?(value) else cf[attr] == value end end end def self.exists?(name) PluginStoreRow.where(plugin_name: NAMESPACE, key: name).exists? end def self.find(field_id) record = PluginStoreRow.find_by(id: field_id, plugin_name: NAMESPACE) if record create_from_store(record) else false end end def self.find_by_name(name) record = PluginStoreRow.find_by(key: name, plugin_name: NAMESPACE) if record create_from_store(record) else false end end def self.create_from_store(record) data = JSON.parse(record.value) data[:name] = record.key new(record.id, data) end def self.save_to_store(id = nil, key, data) if id record = PluginStoreRow.find_by(id: id, plugin_name: NAMESPACE) return false if !record record.key = key record.value = data.to_json record.save else record = PluginStoreRow.new(plugin_name: NAMESPACE, key: key) record.type_name = "JSON" record.value = data.to_json record.save end end def self.destroy(name) if exists?(name) PluginStoreRow.where(plugin_name: NAMESPACE, key: name).destroy_all invalidate_cache true else false end end def self.invalidate_cache @cached_list = nil CustomWizard::Cache.new(LIST_CACHE_KEY).delete Discourse.clear_readonly! Discourse.request_refresh! end def self.any? cached_list.length > 0 end def self.enabled? any? end def self.external_list external = [] CLASSES.keys.each do |klass| field_types = klass.to_s.classify.constantize.custom_field_types if field_types.present? field_types.each do |name, type| unless list.any? { |field| field.name === name } field = new( 'external', name: name, klass: klass, type: type ) external.push(field) end end end end external end def self.full_list (list + external_list).uniq end end