# frozen_string_literal: true class ::CustomWizard::CustomField include HasErrors include ActiveModel::Serialization attr_reader :id ATTRS ||= %w[name klass type serializers] REQUIRED ||= %w[name klass type] NAMESPACE ||= "custom_wizard_custom_fields" NAME_MIN_LENGTH ||= 3 CLASSES ||= { topic: %w[topic_view topic_list_item], group: ["basic_group"], category: ["basic_category"], post: ["post"], } TYPES ||= %w[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] send("#{attr}=", value) if value.present? end @subscription = CustomWizard::Subscription.new end def save validate if valid? data = {} key = name (ATTRS - ["name"]).each { |attr| data[attr] = send(attr) } 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 next if attr == "serializers" && !value.is_a?(Array) 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" add_error(I18n.t("#{i18n_key}.name_invalid", name: value)) unless value.is_a?(String) 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 StandardError 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 { |record| create_from_store(record) } end def self.cached_list @custom_wizard_cached_fields ||= ::CustomWizard::Cache.wrap(LIST_CACHE_KEY) do PluginStoreRow .where(plugin_name: NAMESPACE) .map { |record| create_from_store(record).as_json.with_indifferent_access } 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 @custom_wizard_cached_fields = 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| meta_data = klass.to_s.classify.constantize.send("custom_field_meta_data") if meta_data.present? meta_data.each do |name, data| unless list.any? { |field| field.name === name } field = new("external", name: name, klass: klass, type: data.type) external.push(field) end end end end external end def self.full_list (list + external_list).uniq end end