# frozen_string_literal: true class CustomWizard::Mapper attr_accessor :inputs, :data, :user USER_FIELDS = [ 'name', 'username', 'date_of_birth', 'title', 'locale', 'trust_level', 'email' ] USER_OPTION_FIELDS = [ 'email_level', 'email_messages_level', 'email_digests' ] PROFILE_FIELDS = [ 'location', 'website', 'bio_raw' ] def self.user_fields USER_FIELDS + USER_OPTION_FIELDS + PROFILE_FIELDS end OPERATORS = { equal: '==', not_equal: "!=", greater: '>', less: '<', greater_or_equal: '>=', less_or_equal: '<=', regex: '=~', is: { present: "present?", true: "==", false: "==" } } def initialize(params) @inputs = params[:inputs] || {} @data = params[:data] ? params[:data].with_indifferent_access : {} @user = params[:user] @opts = params[:opts] || {} end def perform multiple = @opts[:multiple] perform_result = multiple ? [] : nil inputs.each do |input| input_type = input['type'] pairs = input['pairs'] if (input_type === 'conditional' && validate_pairs(pairs)) || input_type === 'assignment' output = input['output'] output_type = input['output_type'] result = build_result(map_field(output, output_type), input_type) if multiple perform_result.push(result) else perform_result = result break end end if input_type === 'validation' result = build_result(validate_pairs(pairs), input_type) if multiple perform_result.push(result) else perform_result = result break end end if input_type === 'association' result = build_result(map_pairs(pairs), input_type) if multiple perform_result.push(result) else perform_result = result break end end end perform_result end def build_result(result, type) if @opts[:with_type] { type: type, result: result } else result end end def validate_pairs(pairs) pairs.all? do |pair| connector = pair['connector'] operator = map_operator(connector) key = map_field(pair['key'], pair['key_type']) value = cast_value(map_field(pair['value'], pair['value_type']), key, connector) begin validation_result(key, value, operator) rescue NoMethodError # end end end def cast_value(value, key, connector) if connector == 'regex' Regexp.new(value) else if key.is_a?(String) value.to_s elsif key.is_a?(Integer) value.to_i else value end end end def validation_result(key, value, operator) result = nil if operator.is_a?(Hash) && (operator = operator[value.to_sym]).present? if value == "present" result = key.public_send(operator) elsif ["true", "false"].include?(value) result = bool(key).public_send(operator, bool(value)) end elsif [key, value, operator].all? { |i| !i.nil? } result = key.public_send(operator, value) else result = false end if operator == '=~' result.nil? ? false : true else result end end def map_pairs(pairs) result = [] pairs.each do |pair| key = map_field(pair['key'], pair['key_type']) value = map_field(pair['value'], pair['value_type']) if key && value result.push( key: key, value: value ) end end result end def map_operator(connector) OPERATORS[connector.to_sym] || '==' end def map_field(value, type) method = "map_#{type}" if self.respond_to?(method) self.send(method, value) else value end end def map_text(value) interpolate(value) end def map_wizard_field(value) data && !data.key?("submitted_at") && data[value] end def map_wizard_action(value) data && !data.key?("submitted_at") && data[value] end def map_user_field(value) return nil unless user if value.include?(User::USER_FIELD_PREFIX) user.custom_fields[value] elsif PROFILE_FIELDS.include?(value) user.user_profile.send(value) elsif USER_FIELDS.include?(value) user.send(value) elsif USER_OPTION_FIELDS.include?(value) user.user_option.send(value) elsif value.include?('avatar') get_avatar_url(value) else nil end end def map_user_field_options(value) if value.include?(User::USER_FIELD_PREFIX) if field = UserField.find_by(id: value.split('_').last) field.user_field_options.map(&:value) end end end def interpolate(string, opts = { user: true, wizard: true, value: true, template: false }) return string if string.blank? || string.frozen? if opts[:user] && @user.present? string.gsub!(/u\{(.*?)\}/) { |match| map_user_field($1) || '' } end if opts[:wizard] string.gsub!(/w\{(.*?)\}/) { |match| recurse(data, [*$1.split('.')]) || '' } end if opts[:value] string.gsub!(/v\{(.*?)\}/) do |match| attrs = $1.split(':') key = attrs.first format = attrs.last if attrs.length > 1 result = '' if key == 'time' time_format = format.present? ? format : "%B %-d, %Y" result = Time.now.strftime(time_format) end result end end if opts[:template] #&& CustomWizard::Subscription.subscribed? template = Liquid::Template.parse(string) string = template.render(data) end string end def recurse(data, keys) return nil if data.nil? k = keys.shift result = data[k] if keys.empty? result.is_a?(Hash) ? "" : result else self.recurse(result, keys) end end def bool(value) ActiveRecord::Type::Boolean.new.cast(value) end def get_avatar_url(value) parts = value.split('.') valid_sizes = Discourse.avatar_sizes.to_a if value === 'avatar' || parts.size === 1 || valid_sizes.exclude?(parts.last.to_i) user.small_avatar_url else user.avatar_template_url.gsub("{size}", parts.last) end end def self.mapped_value?(value) value.is_a?(Array) && value.all? { |v| v.is_a?(Hash) && v.key?("type") } end end