diff --git a/assets/javascripts/discourse/components/wizard-custom-field.js.es6 b/assets/javascripts/discourse/components/wizard-custom-field.js.es6
index f5d85a48..93d5143a 100644
--- a/assets/javascripts/discourse/components/wizard-custom-field.js.es6
+++ b/assets/javascripts/discourse/components/wizard-custom-field.js.es6
@@ -46,11 +46,11 @@ export default Ember.Component.extend({
return options;
},
- canFilter: or('isCategory', 'isTag', 'isGroup'),
+ contentEnabled: or('isCategory', 'isTag', 'isGroup'),
@computed('field.type')
- filterOptions(fieldType) {
- if (!this.canFilter) return {};
+ contentOptions(fieldType) {
+ if (!this.contentEnabled) return {};
let options = {
hasOutput: true,
diff --git a/assets/javascripts/discourse/components/wizard-custom-input.js.es6 b/assets/javascripts/discourse/components/wizard-custom-input.js.es6
index 1dc54a8e..349fee7d 100644
--- a/assets/javascripts/discourse/components/wizard-custom-input.js.es6
+++ b/assets/javascripts/discourse/components/wizard-custom-input.js.es6
@@ -23,9 +23,14 @@ export default Ember.Component.extend({
if (!this.type) this.set('type', defaultInputType(this.options));
},
- @discourseComputed
- inputTypes() {
- return ['conditional', 'assignment'].map((type) => {
+ @discourseComputed('options.allowedInputs')
+ allowedInputs(option) {
+ return option || 'conditional,assignment';
+ },
+
+ @discourseComputed('allowedInputs')
+ inputTypes(allowedInputs) {
+ return allowedInputs.split(',').map((type) => {
return {
id: type,
name: I18n.t(`admin.wizard.input.${type}.prefix`)
diff --git a/assets/javascripts/discourse/components/wizard-field-mapper.js.es6 b/assets/javascripts/discourse/components/wizard-field-mapper.js.es6
index a0b352f0..aac69c5b 100644
--- a/assets/javascripts/discourse/components/wizard-field-mapper.js.es6
+++ b/assets/javascripts/discourse/components/wizard-field-mapper.js.es6
@@ -1,9 +1,15 @@
import { getOwner } from 'discourse-common/lib/get-owner';
import { on } from 'discourse-common/utils/decorators';
import { newInput } from '../lib/custom-wizard';
+import { default as discourseComputed } from 'discourse-common/utils/decorators';
export default Ember.Component.extend({
classNames: 'field-mapper',
+
+ @discourseComputed('inputs.[]', 'options.singular')
+ canAdd(inputs, singular) {
+ return !singular || !inputs || inputs.length < 1;
+ },
actions: {
add() {
diff --git a/assets/javascripts/discourse/lib/custom-wizard.js.es6 b/assets/javascripts/discourse/lib/custom-wizard.js.es6
index 8ffdcd97..2c5b373e 100644
--- a/assets/javascripts/discourse/lib/custom-wizard.js.es6
+++ b/assets/javascripts/discourse/lib/custom-wizard.js.es6
@@ -10,16 +10,15 @@ function generateName(id) {
const profileFields = [
'name',
- 'user_avatar',
+ 'username',
+ 'email',
'date_of_birth',
'title',
'locale',
'location',
'website',
'bio_raw',
- 'profile_background',
- 'card_background',
- 'theme_id'
+ 'trust_level'
];
const connectors = [
@@ -69,7 +68,10 @@ const inputTypes = [
]
function defaultInputType(options = {}) {
- return options.hasOutput ? 'conditional' : 'pair';
+ if (!options.hasOutput) return 'pair';
+ const allowedInputs = options.allowedInputs;
+ if (!allowedInputs) return 'conditional';
+ return allowedInputs.split(',')[0];
}
function defaultSelectionType(inputType, options = {}) {
diff --git a/assets/javascripts/discourse/models/custom-wizard.js.es6 b/assets/javascripts/discourse/models/custom-wizard.js.es6
index 2706acea..fdcc045d 100644
--- a/assets/javascripts/discourse/models/custom-wizard.js.es6
+++ b/assets/javascripts/discourse/models/custom-wizard.js.es6
@@ -12,8 +12,8 @@ const wizardProperties = [
'required',
'prompt_completion',
'restart_on_revisit',
- 'min_trust',
- 'theme_id'
+ 'theme_id',
+ 'permitted'
];
const CustomWizard = EmberObject.extend({
@@ -267,7 +267,7 @@ CustomWizard.reopenClass({
props['required'] = false;
props['prompt_completion'] = false;
props['restart_on_revisit'] = false;
- props['min_trust'] = 0;
+ props['permitted'] = null;
props['steps'] = Ember.A();
};
diff --git a/assets/javascripts/discourse/templates/admin-wizard.hbs b/assets/javascripts/discourse/templates/admin-wizard.hbs
index 31064780..40bd6be3 100644
--- a/assets/javascripts/discourse/templates/admin-wizard.hbs
+++ b/assets/javascripts/discourse/templates/admin-wizard.hbs
@@ -22,13 +22,7 @@
placeholderKey="admin.wizard.name_placeholder"}}
-
-
-
-
-
@@ -106,16 +106,6 @@
-
-
-
-
-
- {{i18n 'admin.wizard.min_trust_label'}}
- {{input type='number' value=model.min_trust class='input-small'}}
-
-
-
@@ -144,17 +134,17 @@
-
+
{{wizard-field-mapper
- inputs=model.group
+ inputs=model.permitted
options=(hash
hasOutput=true
- enableConnectors=true
- userFieldSelection='key,value'
- groupSelection=true
+ groupSelection='output'
textDisabled='output'
+ allowedInputs='assignment'
+ singular=true
)}}
diff --git a/assets/javascripts/discourse/templates/components/wizard-custom-field.hbs b/assets/javascripts/discourse/templates/components/wizard-custom-field.hbs
index d4251c2e..0ebb3229 100644
--- a/assets/javascripts/discourse/templates/components/wizard-custom-field.hbs
+++ b/assets/javascripts/discourse/templates/components/wizard-custom-field.hbs
@@ -170,17 +170,17 @@
-{{#if canFilter}}
+{{#if contentEnabled}}
-
+
{{wizard-field-mapper
- inputs=field.filters
+ inputs=field.content
wizardFields=wizardFields
- options=filterOptions}}
+ options=contentOptions}}
{{/if}}
diff --git a/assets/javascripts/discourse/templates/components/wizard-field-mapper.hbs b/assets/javascripts/discourse/templates/components/wizard-field-mapper.hbs
index a7cb5375..322a8a77 100644
--- a/assets/javascripts/discourse/templates/components/wizard-field-mapper.hbs
+++ b/assets/javascripts/discourse/templates/components/wizard-field-mapper.hbs
@@ -10,6 +10,8 @@
remove=(action 'remove')}}
{{/each}}
-
- {{d-button action='add' label='admin.wizard.add' icon='plus'}}
-
\ No newline at end of file
+{{#if canAdd}}
+
+ {{d-button action='add' label='admin.wizard.add' icon='plus'}}
+
+{{/if}}
\ No newline at end of file
diff --git a/assets/javascripts/wizard-custom.js b/assets/javascripts/wizard-custom.js
index acc1cb3d..e03cf6a8 100644
--- a/assets/javascripts/wizard-custom.js
+++ b/assets/javascripts/wizard-custom.js
@@ -30,6 +30,7 @@
//= require discourse/lib/show-modal
//= require discourse/lib/key-value-store
//= require discourse/lib/settings
+//= require discourse/lib/user-presence
//= require discourse/mixins/singleton
diff --git a/assets/javascripts/wizard/components/wizard-field-group.js.es6 b/assets/javascripts/wizard/components/wizard-field-group.js.es6
deleted file mode 100644
index 3f94b6ae..00000000
--- a/assets/javascripts/wizard/components/wizard-field-group.js.es6
+++ /dev/null
@@ -1,5 +0,0 @@
-export default Ember.Component.extend({
- didInsertElement() {
- console.log(this.field)
- }
-})
\ No newline at end of file
diff --git a/assets/javascripts/wizard/components/wizard-group-selector.js.es6 b/assets/javascripts/wizard/components/wizard-group-selector.js.es6
index af21db23..0029bae2 100644
--- a/assets/javascripts/wizard/components/wizard-group-selector.js.es6
+++ b/assets/javascripts/wizard/components/wizard-group-selector.js.es6
@@ -2,11 +2,16 @@ import ComboBox from 'select-kit/components/combo-box';
import { computed } from "@ember/object";
import { makeArray } from "discourse-common/lib/helpers";
-export default ComboBox.extend({
- content: computed("groups.[]", "whitelist.[]", function() {
- const whitelist = makeArray(this.whitelist);
+export default ComboBox.extend({
+ content: computed("groups.[]", "field.content.[]", function() {
+ const whitelist = makeArray(this.field.content);
return this.groups.filter(group => {
return !whitelist.length || whitelist.indexOf(group.id) > -1;
+ }).map(g => {
+ return {
+ id: g.id,
+ name: g.name
+ }
});
})
})
\ No newline at end of file
diff --git a/assets/javascripts/wizard/routes/custom-index.js.es6 b/assets/javascripts/wizard/routes/custom-index.js.es6
index c857753d..c15dfb98 100644
--- a/assets/javascripts/wizard/routes/custom-index.js.es6
+++ b/assets/javascripts/wizard/routes/custom-index.js.es6
@@ -14,7 +14,6 @@ export default Ember.Route.extend({
if (model) {
const completed = model.get('completed');
const permitted = model.get('permitted');
- const minTrust = model.get('min_trust');
const wizardId = model.get('id');
const user = model.get('user');
const name = model.get('name');
diff --git a/assets/javascripts/wizard/templates/components/wizard-field-category.hbs b/assets/javascripts/wizard/templates/components/wizard-field-category.hbs
index 5ea598a3..1843a277 100644
--- a/assets/javascripts/wizard/templates/components/wizard-field-category.hbs
+++ b/assets/javascripts/wizard/templates/components/wizard-field-category.hbs
@@ -1,6 +1,6 @@
{{wizard-category-selector
categories=categories
- whitelist=field.filter
+ whitelist=field.content
maximum=field.limit
onChange=(action (mut categories))}}
diff --git a/assets/javascripts/wizard/templates/components/wizard-field-group.hbs b/assets/javascripts/wizard/templates/components/wizard-field-group.hbs
index dc7be340..f10aae2e 100644
--- a/assets/javascripts/wizard/templates/components/wizard-field-group.hbs
+++ b/assets/javascripts/wizard/templates/components/wizard-field-group.hbs
@@ -1,6 +1,7 @@
{{wizard-group-selector
groups=wizard.groups
- whitelist=field.filter
+ field=field
+ whitelist=field.content
value=field.value
onChange=(action (mut field.value))
options=(hash
diff --git a/assets/javascripts/wizard/templates/custom.index.hbs b/assets/javascripts/wizard/templates/custom.index.hbs
index f6ac1df5..c8e8966c 100644
--- a/assets/javascripts/wizard/templates/custom.index.hbs
+++ b/assets/javascripts/wizard/templates/custom.index.hbs
@@ -5,7 +5,7 @@
{{wizard-no-access text=(i18n 'wizard.requires_login' name=name) wizardId=wizardId}}
{{else}}
{{#if notPermitted}}
- {{wizard-no-access text=(i18n 'wizard.not_permitted' name=name level=minTrust) wizardId=wizardId}}
+ {{wizard-no-access text=(i18n 'wizard.not_permitted' name=name) wizardId=wizardId}}
{{else}}
{{#if completed}}
{{wizard-no-access text=(i18n 'wizard.completed' name=name) wizardId=wizardId}}
diff --git a/assets/stylesheets/wizard_custom_admin.scss b/assets/stylesheets/wizard_custom_admin.scss
index 71814605..4bc608ff 100644
--- a/assets/stylesheets/wizard_custom_admin.scss
+++ b/assets/stylesheets/wizard_custom_admin.scss
@@ -61,10 +61,6 @@ $setting-background: dark-light-diff($primary, $secondary, 96%, -65%);
}
}
-.wizard-basic-details {
- margin-bottom: 10px;
-}
-
.content-list + .content {
overflow: hidden;
}
diff --git a/config/locales/client.en.yml b/config/locales/client.en.yml
index 6d713193..2cc266f6 100644
--- a/config/locales/client.en.yml
+++ b/config/locales/client.en.yml
@@ -36,8 +36,6 @@ en:
prompt_completion_label: "Prompt user to complete wizard."
restart_on_revisit: "Restart"
restart_on_revisit_label: "Restart the the wizard whenever the user revisits, regardless of prior progress."
- min_trust: "Trust"
- min_trust_label: "Trust level required to access wizard."
theme_id: "Theme"
no_theme: "Select a Theme (optional)"
save: "Save Changes"
@@ -61,6 +59,7 @@ en:
submission_key: 'submission key'
param_key: 'param'
group: "Group"
+ permitted: "Permitted"
editor:
show: "Show"
@@ -125,7 +124,7 @@ en:
limit: "Limit"
property: "Property"
prefill: "Prefill"
- filter: "Content"
+ content: "Content"
action:
header: "Actions"
@@ -306,12 +305,12 @@ en:
wizard:
completed: "You have completed the {{name}} wizard."
- not_permitted: "You need to be trust level {{level}} or higher to access the {{name}} wizard."
+ not_permitted: "You are not permitted to access the {{name}} wizard."
none: "There is no wizard here."
return_to_site: "Return to {{siteName}}"
requires_login: "You need to be logged in to access the {{name}} wizard."
reset: "Reset this wizard."
- step_not_permitted: "You're not allowed to view this step."
+ step_not_permitted: "You're not permitted to view this step."
wizard_composer:
show_preview: "Preview Post"
diff --git a/config/locales/client.fr.yml b/config/locales/client.fr.yml
index 65e5335e..45169ac4 100644
--- a/config/locales/client.fr.yml
+++ b/config/locales/client.fr.yml
@@ -33,8 +33,6 @@ fr:
required_label: "Les utilisatrices doivent compléter l'assistant."
prompt_completion: "Invitation"
prompt_completion_label: "Inviter l'utilisatrice à compléter l'assistant."
- min_trust: "Confiance"
- min_trust_label: "Niveau de confiance requis pour accéder à l'assistant."
theme_id: "Thème"
no_theme: "Choisir un thème (optionnel)"
save: "Sauvegarder"
diff --git a/controllers/custom_wizard/admin.rb b/controllers/custom_wizard/admin.rb
index 2b09fcf2..23944c1e 100644
--- a/controllers/custom_wizard/admin.rb
+++ b/controllers/custom_wizard/admin.rb
@@ -14,11 +14,8 @@ class CustomWizard::AdminController < ::ApplicationController
params.require(:wizard)
wizard = ::JSON.parse(params[:wizard])
-
existing = PluginStore.get('custom_wizard', wizard['id']) || {}
-
new_time = false
-
error = nil
if wizard["id"].blank?
diff --git a/lib/custom_wizard/actions.rb b/lib/custom_wizard/actions.rb
new file mode 100644
index 00000000..995281c7
--- /dev/null
+++ b/lib/custom_wizard/actions.rb
@@ -0,0 +1,289 @@
+class CustomWizard::Action
+ attr_accessor :data,
+ :action,
+ :user,
+ :updater
+
+ def initialize(params)
+ @action = params[:action]
+ @user = params[:user]
+ @data = params[:data]
+ @updater = params[:updater]
+ end
+
+ def perform
+ ActiveRecord::Base.transaction { self.send(action['type'].to_sym) }
+ end
+
+ def mapper
+ @mapper ||= CustomWizard::Mapper.new(user: user, data: data)
+ end
+
+ def create_topic
+ if action['custom_title_enabled']
+ title = mapper.interpolate(action['custom_title'])
+ else
+ title = data[action['title']]
+ end
+
+ if action['post_builder']
+ post = mapper.interpolate(action['post_template'])
+ else
+ post = data[action['post']]
+ end
+
+ if title
+ params = {
+ title: title,
+ raw: post,
+ skip_validations: true
+ }
+
+ params[:category] = action_category_id(action, data)
+ tags = action_tags(action, data)
+ params[:tags] = tags
+
+ topic_custom_fields = {}
+
+ if action['add_fields']
+ action['add_fields'].each do |field|
+ value = field['value_custom'].present? ? field['value_custom'] : data[field['value']]
+ key = field['key']
+
+ if key && (value.present? || value === false)
+ if key.include?('custom_fields')
+ keyArr = key.split('.')
+
+ if keyArr.length === 3
+ custom_key = keyArr.last
+ type = keyArr.first
+
+ if type === 'topic'
+ topic_custom_fields[custom_key] = value
+ elsif type === 'post'
+ params[:custom_fields] ||= {}
+ params[:custom_fields][custom_key.to_sym] = value
+ end
+ end
+ else
+ value = [*value] + tags if key === 'tags'
+ params[key.to_sym] = value
+ end
+ end
+ end
+ end
+
+ creator = PostCreator.new(user, params)
+ post = creator.create
+
+ if creator.errors.present?
+ updater.errors.add(:create_topic, creator.errors.full_messages.join(" "))
+ else
+ if topic_custom_fields.present?
+ topic_custom_fields.each do |k, v|
+ post.topic.custom_fields[k] = v
+ end
+ post.topic.save_custom_fields(true)
+ end
+
+ unless action['skip_redirect']
+ data['redirect_on_complete'] = post.topic.url
+ end
+ end
+ end
+ end
+
+ def send_message
+ if action['required'].present? && data[action['required']].blank?
+ return
+ end
+
+ if action['custom_title_enabled']
+ title = mapper.interpolate(action['custom_title'])
+ else
+ title = data[action['title']]
+ end
+
+ if action['post_builder']
+ post = mapper.interpolate(action['post_template'])
+ else
+ post = data[action['post']]
+ end
+
+ if title && post
+ creator = PostCreator.new(user,
+ title: title,
+ raw: post,
+ archetype: Archetype.private_message,
+ target_usernames: action['username']
+ )
+
+ post = creator.create
+
+ if creator.errors.present?
+ updater.errors.add(:send_message, creator.errors.full_messages.join(" "))
+ else
+ unless action['skip_redirect']
+ data['redirect_on_complete'] = post.topic.url
+ end
+ end
+ end
+ end
+
+ def update_profile
+ return unless action['profile_updates'].length
+
+ attributes = {}
+ custom_fields = {}
+
+ action['profile_updates'].each do |pu|
+ value = pu['value']
+ key = pu['key']
+
+ return if data[key].blank?
+
+ if user_field || custom_field
+ custom_fields[user_field || custom_field] = data[key]
+ else
+ updater_key = value
+ if ['profile_background', 'card_background'].include?(value)
+ updater_key = "#{value}_upload_url"
+ end
+ attributes[updater_key.to_sym] = data[key] if updater_key
+ end
+
+ if ['user_avatar'].include?(value)
+ this_upload_id = data[key][:id]
+ user.create_user_avatar unless user.user_avatar
+ user.user_avatar.custom_upload_id = this_upload_id
+ user.uploaded_avatar_id = this_upload_id
+ user.save!
+ user.user_avatar.save!
+ end
+ end
+
+ if custom_fields.present?
+ attributes[:custom_fields] = custom_fields
+ end
+
+ if attributes.present?
+ user_updater = UserUpdater.new(user, user)
+ user_updater.update(attributes)
+ end
+ end
+
+ def send_to_api
+ api_body = nil
+
+ if action['api_body'] != ""
+ begin
+ api_body_parsed = JSON.parse(action['api_body'])
+ rescue JSON::ParserError
+ raise Discourse::InvalidParameters, "Invalid API body definition: #{action['api_body']} for #{action['title']}"
+ end
+ api_body = JSON.parse(mapper.interpolate(JSON.generate(api_body_parsed)))
+ end
+
+ result = CustomWizard::Api::Endpoint.request(user, action['api'], action['api_endpoint'], api_body)
+
+ if error = result['error'] || (result[0] && result[0]['error'])
+ error = error['message'] || error
+ updater.errors.add(:send_to_api, error)
+ else
+ ## add validation callback
+ end
+ end
+
+ def open_composer
+ if action['custom_title_enabled']
+ title = mapper.interpolate(action['custom_title'])
+ else
+ title = data[action['title']]
+ end
+
+ url = "/new-topic?title=#{title}"
+
+ if action['post_builder']
+ post = mapper.interpolate(action['post_template'])
+ else
+ post = data[action['post']]
+ end
+
+ url += "&body=#{post}"
+
+ if category_id = action_category_id(action, data)
+ if category = Category.find(category_id)
+ url += "&category=#{category.full_slug('/')}"
+ end
+ end
+
+ if tags = action_tags(action, data)
+ url += "&tags=#{tags.join(',')}"
+ end
+
+ data['redirect_on_complete'] = Discourse.base_uri + URI.encode(url)
+ end
+
+ def add_to_group
+ groups = CustomWizard::Mapper.new(
+ inputs: action['inputs'],
+ data: data,
+ user: user,
+ opts: {
+ multiple: true
+ }
+ ).output
+
+ groups = groups.flatten.reduce([]) do |result, g|
+ begin
+ result.push(Integer(g))
+ rescue ArgumentError
+ group = Group.find_by(name: g)
+ result.push(group.id) if group
+ end
+
+ result
+ end
+
+ if groups.present?
+ groups.each do |group_id|
+ group = Group.find(group_id) if group_id
+ group.add(user) if group
+ end
+ end
+ end
+
+ def route_to
+ url = mapper.interpolate(action['url'])
+ if action['code']
+ data[action['code']] = SecureRandom.hex(8)
+ url += "{action['code']}=#{data[action['code']]}"
+ end
+ data['route_to'] = URI.encode(url)
+ end
+
+ def action_category_id
+ if action['custom_category_enabled']
+ if action['custom_category_wizard_field']
+ data[action['category_id']]
+ elsif action['custom_category_user_field_key']
+ if action['custom_category_user_field_key'].include?('custom_fields')
+ field = action['custom_category_user_field_key'].split('.').last
+ user.custom_fields[field]
+ else
+ user.send(action['custom_category_user_field_key'])
+ end
+ end
+ else
+ action['category_id']
+ end
+ end
+
+ def action_tags
+ if action['custom_tag_enabled']
+ data[action['custom_tag_field']]
+ else
+ action['tags']
+ end
+ end
+end
\ No newline at end of file
diff --git a/lib/custom_wizard/builder.rb b/lib/custom_wizard/builder.rb
index 4c746166..af07de50 100644
--- a/lib/custom_wizard/builder.rb
+++ b/lib/custom_wizard/builder.rb
@@ -1,7 +1,4 @@
-TagStruct = Struct.new(:id, :name)
-
class CustomWizard::Builder
-
attr_accessor :wizard, :updater, :submissions
def initialize(user=nil, wizard_id)
@@ -10,10 +7,7 @@ class CustomWizard::Builder
@steps = data['steps']
@wizard = CustomWizard::Wizard.new(user, data)
-
- if user
- @submissions = Array.wrap(PluginStore.get("#{wizard_id}_submissions", user.id))
- end
+ @submissions = Array.wrap(PluginStore.get("#{wizard_id}_submissions", user.id)) if user
end
def self.sorted_handlers
@@ -42,47 +36,6 @@ class CustomWizard::Builder
@sorted_field_validators.sort_by! { |h| -h[:priority] }
end
- USER_FIELDS = ['name', 'username', 'email', 'date_of_birth', 'title', 'locale']
- PROFILE_FIELDS = ['location', 'website', 'bio_raw', 'profile_background', 'card_background']
- OPERATORS = {
- 'eq': '==',
- 'gt': '>',
- 'lt': '<',
- 'gte': '>=',
- 'lte': '<='
- }
-
- def self.fill_placeholders(string, user, data)
- result = string.gsub(/u\{(.*?)\}/) do |match|
- result = ''
- result = user.send($1) if USER_FIELDS.include?($1)
- result = user.user_profile.send($1) if PROFILE_FIELDS.include?($1)
- result
- end
-
- result = result.gsub(/w\{(.*?)\}/) { |match| recurse(data, [*$1.split('.')]) }
-
- result.gsub(/v\{(.*?)\}/) do |match|
- attrs = $1.split(':')
- key = attrs.first
- format = attrs.length > 1 ? attrs.last : nil
- v = nil
-
- if key == 'time'
- time_format = format.present? ? format : "%B %-d, %Y"
- v = Time.now.strftime(time_format)
- end
-
- v
- end
- end
-
- def self.recurse(data, keys)
- k = keys.shift
- result = data[k]
- keys.empty? ? result : self.recurse(result, keys)
- end
-
def build(build_opts = {}, params = {})
return @wizard if !SiteSetting.custom_wizard_enabled ||
@@ -135,13 +88,24 @@ class CustomWizard::Builder
step.permitted = false
else
required_data.each do |required|
- pairs = required['pairs'].map { |p| p['value'] = @submissions.last[p['value']] }
- step.permitted = false unless validate_pairs(pairs)
+ pairs = required['pairs'].map do |p|
+ p['key'] = @submissions.last[p['key']]
+ end
+
+ unless CustomWizard::Mapper.new(
+ user: @wizard.user,
+ data: @submissions.last
+ ).validate_pairs(pairs)
+ step.permitted = false
+ end
end
end
if !step.permitted
- step.permitted_message = step_template['required_data_message'] if step_template['required_data_message']
+ if step_template['required_data_message']
+ step.permitted_message = step_template['required_data_message']
+ end
+
next
end
end
@@ -183,7 +147,12 @@ class CustomWizard::Builder
if step_template['actions'] && step_template['actions'].length && data
step_template['actions'].each do |action|
- self.send(action['type'].to_sym, user, action, data)
+ CustomWizard::Action.new(
+ action: action,
+ user: user,
+ data: data,
+ updater: updater
+ ).perform
end
end
@@ -237,6 +206,10 @@ class CustomWizard::Builder
end
params[:value] = prefill_field(field_template, step_template) || params[:value]
+
+ if field_template['type'] === 'group'
+ params[:value] = params[:value].first
+ end
if field_template['type'] === 'checkbox'
params[:value] = standardise_boolean(params[:value])
@@ -262,8 +235,12 @@ class CustomWizard::Builder
@wizard.needs_groups = true
end
- if (prefill = field_template['filters']).present?
- params[:filter] = get_output(field_template['filters'])
+ if (content = field_template['content']).present?
+ params[:content] = CustomWizard::Mapper.new(
+ inputs: content,
+ user: @wizard.user,
+ data: @submissions.last
+ ).output
end
field = step.add_field(params)
@@ -275,76 +252,11 @@ class CustomWizard::Builder
def prefill_field(field_template, step_template)
if (prefill = field_template['prefill']).present?
- get_output(prefill)
- end
- end
-
- def get_output(inputs, opts = {})
- output = opts[:multiple] ? [] : nil
-
- inputs.each do |input|
- if input['type'] === 'conditional' && validate_pairs(input['pairs'])
- if opts[:multiple]
- output.push(get_field(input['output'], input['output_type'], opts))
- else
- output = get_field(input['output'], input['output_type'], opts)
- break
- end
- end
-
- if input['type'] === 'assignment'
- value = get_field(input['output'], input['output_type'], opts)
-
- if opts[:multiple]
- output.push(value)
- else
- output = value
- break
- end
- end
- end
-
- output
- end
-
- def validate_pairs(pairs)
- failed = false
-
- pairs.each do |pair|
- key = get_field(pair['key'], pair['key_type'])
- value = get_field(pair['value'], pair['value_type'])
- failed = true unless key.public_send(get_operator(pair['connector']), value)
- end
-
- !failed
- end
-
- def get_operator(connector)
- OPERATORS[connector] || '=='
- end
-
- def get_field(value, type, opts = {})
- method = "get_#{type}_field"
-
- if self.respond_to?(method)
- self.send(method, value, opts)
- else
- value
- end
- end
-
- def get_wizard_field(value, opts = {})
- data = opts[:data] || @submissions.last
- data && !data.key?("submitted_at") && data[value]
- end
-
- def get_user_field(value, opts = {})
- if value.include?('user_field_')
- UserCustomField.where(user_id: @wizard.user.id, name: value).pluck(:value).first
- elsif UserProfile.column_names.include? value
- UserProfile.find_by(user_id: @wizard.user.id).send(value)
- elsif User.column_names.include? value
- User.find(@wizard.user.id).send(value)
+ CustomWizard::Mapper.new(
+ inputs: prefill,
+ user: @wizard.user,
+ data: @submissions.last
+ ).output
end
end
@@ -382,7 +294,10 @@ class CustomWizard::Builder
end
if min_length && value.is_a?(String) && value.strip.length < min_length.to_i
- updater.errors.add(field['id'].to_s, I18n.t('wizard.field.too_short', label: label, min: min_length.to_i))
+ updater.errors.add(
+ field['id'].to_s,
+ I18n.t('wizard.field.too_short', label: label, min: min_length.to_i)
+ )
end
## ensure all checkboxes are booleans
@@ -405,245 +320,6 @@ class CustomWizard::Builder
ActiveRecord::Type::Boolean.new.cast(value)
end
- def create_topic(user, action, data)
- if action['custom_title_enabled']
- title = CustomWizard::Builder.fill_placeholders(action['custom_title'], user, data)
- else
- title = data[action['title']]
- end
-
- if action['post_builder']
- post = CustomWizard::Builder.fill_placeholders(action['post_template'], user, data)
- else
- post = data[action['post']]
- end
-
- if title
- params = {
- title: title,
- raw: post,
- skip_validations: true
- }
-
- params[:category] = action_category_id(action, data)
-
- tags = action_tags(action, data)
-
- params[:tags] = tags
-
- topic_custom_fields = {}
-
- if action['add_fields']
- action['add_fields'].each do |field|
- value = field['value_custom'].present? ? field['value_custom'] : data[field['value']]
- key = field['key']
-
- if key && (value.present? || value === false)
- if key.include?('custom_fields')
- keyArr = key.split('.')
-
- if keyArr.length === 3
- custom_key = keyArr.last
- type = keyArr.first
-
- if type === 'topic'
- topic_custom_fields[custom_key] = value
- elsif type === 'post'
- params[:custom_fields] ||= {}
- params[:custom_fields][custom_key.to_sym] = value
- end
- end
- else
- value = [*value] + tags if key === 'tags'
- params[key.to_sym] = value
- end
- end
- end
- end
-
- creator = PostCreator.new(user, params)
- post = creator.create
-
- if creator.errors.present?
- updater.errors.add(:create_topic, creator.errors.full_messages.join(" "))
- else
- if topic_custom_fields.present?
- topic_custom_fields.each do |k, v|
- post.topic.custom_fields[k] = v
- end
- post.topic.save_custom_fields(true)
- end
-
- unless action['skip_redirect']
- data['redirect_on_complete'] = post.topic.url
- end
- end
- end
- end
-
- def send_message(user, action, data)
-
- if action['required'].present? && data[action['required']].blank?
- return
- end
-
- if action['custom_title_enabled']
- title = CustomWizard::Builder.fill_placeholders(action['custom_title'], user, data)
- else
- title = data[action['title']]
- end
-
- if action['post_builder']
- post = CustomWizard::Builder.fill_placeholders(action['post_template'], user, data)
- else
- post = data[action['post']]
- end
-
- if title && post
- creator = PostCreator.new(user,
- title: title,
- raw: post,
- archetype: Archetype.private_message,
- target_usernames: action['username']
- )
-
- post = creator.create
-
- if creator.errors.present?
- updater.errors.add(:send_message, creator.errors.full_messages.join(" "))
- else
- unless action['skip_redirect']
- data['redirect_on_complete'] = post.topic.url
- end
- end
- end
- end
-
- def update_profile(user, action, data)
- return unless action['profile_updates'].length
-
- attributes = {}
- custom_fields = {}
-
- action['profile_updates'].each do |pu|
- value = pu['value']
- key = pu['key']
-
- return if data[key].blank?
-
- if user_field || custom_field
- custom_fields[user_field || custom_field] = data[key]
- else
- updater_key = value
- if ['profile_background', 'card_background'].include?(value)
- updater_key = "#{value}_upload_url"
- end
- attributes[updater_key.to_sym] = data[key] if updater_key
- end
-
- if ['user_avatar'].include?(value)
- this_upload_id = data[key][:id]
- user.create_user_avatar unless user.user_avatar
- user.user_avatar.custom_upload_id = this_upload_id
- user.uploaded_avatar_id = this_upload_id
- user.save!
- user.user_avatar.save!
- end
- end
-
- if custom_fields.present?
- attributes[:custom_fields] = custom_fields
- end
-
- if attributes.present?
- user_updater = UserUpdater.new(user, user)
- user_updater.update(attributes)
- end
- end
-
- def send_to_api(user, action, data)
- api_body = nil
-
- if action['api_body'] != ""
- begin
- api_body_parsed = JSON.parse(action['api_body'])
- rescue JSON::ParserError
- raise Discourse::InvalidParameters, "Invalid API body definition: #{action['api_body']} for #{action['title']}"
- end
- api_body = JSON.parse(CustomWizard::Builder.fill_placeholders(JSON.generate(api_body_parsed), user, data))
- end
-
- result = CustomWizard::Api::Endpoint.request(user, action['api'], action['api_endpoint'], api_body)
-
- if error = result['error'] || (result[0] && result[0]['error'])
- error = error['message'] || error
- updater.errors.add(:send_to_api, error)
- else
- ## add validation callback
- end
- end
-
- def open_composer(user, action, data)
- if action['custom_title_enabled']
- title = CustomWizard::Builder.fill_placeholders(action['custom_title'], user, data)
- else
- title = data[action['title']]
- end
-
- url = "/new-topic?title=#{title}"
-
- if action['post_builder']
- post = CustomWizard::Builder.fill_placeholders(action['post_template'], user, data)
- else
- post = data[action['post']]
- end
-
- url += "&body=#{post}"
-
- if category_id = action_category_id(action, data)
- if category = Category.find(category_id)
- url += "&category=#{category.full_slug('/')}"
- end
- end
-
- if tags = action_tags(action, data)
- url += "&tags=#{tags.join(',')}"
- end
-
- data['redirect_on_complete'] = Discourse.base_uri + URI.encode(url)
- end
-
- def add_to_group(user, action, data)
- groups = get_output(action['inputs'], multiple: true, data: data)
-
- groups = groups.flatten.reduce([]) do |result, g|
- begin
- result.push(Integer(g))
- rescue ArgumentError
- group = Group.find_by(name: g)
- result.push(group.id) if group
- end
-
- result
- end
-
- if groups.present?
- groups.each do |group_id|
- group = Group.find(group_id) if group_id
- group.add(user) if group
- end
- end
- end
-
- def route_to(user, action, data)
- url = CustomWizard::Builder.fill_placeholders(action['url'], user, data)
- if action['code']
- data[action['code']] = SecureRandom.hex(8)
- url += "{action['code']}=#{data[action['code']]}"
- end
- data['route_to'] = URI.encode(url)
- end
-
def save_submissions(data, final_step)
if final_step
data['submitted_at'] = Time.now.iso8601
@@ -661,29 +337,4 @@ class CustomWizard::Builder
PluginStore.set("#{@wizard.id}_submissions", @wizard.user.id, @submissions)
@wizard.reset
end
-
- def action_category_id(action, data)
- if action['custom_category_enabled']
- if action['custom_category_wizard_field']
- data[action['category_id']]
- elsif action['custom_category_user_field_key']
- if action['custom_category_user_field_key'].include?('custom_fields')
- field = action['custom_category_user_field_key'].split('.').last
- user.custom_fields[field]
- else
- user.send(action['custom_category_user_field_key'])
- end
- end
- else
- action['category_id']
- end
- end
-
- def action_tags(action, data)
- if action['custom_tag_enabled']
- data[action['custom_tag_field']]
- else
- action['tags']
- end
- end
end
diff --git a/lib/custom_wizard/mapper.rb b/lib/custom_wizard/mapper.rb
new file mode 100644
index 00000000..e94f2b65
--- /dev/null
+++ b/lib/custom_wizard/mapper.rb
@@ -0,0 +1,114 @@
+class CustomWizard::Mapper
+ attr_accessor :inputs, :data, :user
+
+ USER_FIELDS = ['name', 'username', 'email', 'date_of_birth', 'title', 'locale', 'trust_level']
+ PROFILE_FIELDS = ['location', 'website', 'bio_raw']
+ OPERATORS = { 'eq': '==', 'gt': '>', 'lt': '<', 'gte': '>=', 'lte': '<=' }
+
+ def initialize(params)
+ @inputs = params[:inputs] || {}
+ @data = params[:data] || {}
+ @user = params[:user]
+ @opts = params[:opts] || {}
+ end
+
+ def output
+ multiple = @opts[:multiple]
+ output = multiple ? [] : nil
+
+ inputs.each do |input|
+ if input['type'] === 'conditional' && validate_pairs(input['pairs'])
+ if multiple
+ output.push(map_field(input['output'], input['output_type']))
+ else
+ output = map_field(input['output'], input['output_type'])
+ break
+ end
+ end
+
+ if input['type'] === 'assignment'
+ value = map_field(input['output'], input['output_type'])
+
+ if @opts[:multiple]
+ output.push(value)
+ else
+ output = value
+ break
+ end
+ end
+ end
+
+ output
+ end
+
+ def validate_pairs(pairs)
+ failed = false
+
+ pairs.each do |pair|
+ key = map_field(pair['key'], pair['key_type'])
+ value = map_field(pair['value'], pair['value_type'])
+ failed = true unless key.public_send(operator(pair['connector']), value)
+ end
+
+ !failed
+ end
+
+ def operator(connector)
+ OPERATORS[connector] || '=='
+ end
+
+ def map_field(value, type)
+ method = "#{type}_field"
+
+ if self.respond_to?(method)
+ self.send(method, value)
+ else
+ value
+ end
+ end
+
+ def wizard_field(value)
+ data && !data.key?("submitted_at") && data[value]
+ end
+
+ def user_field(value)
+ if value.include?('user_field_')
+ UserCustomField.where(user_id: user.id, name: value).pluck(:value).first
+ elsif PROFILE_FIELDS.include?(value)
+ UserProfile.find_by(user_id: user.id).send(value)
+ elsif USER_FIELDS.include?(value)
+ User.find(user.id).send(value)
+ end
+ end
+
+ def interpolate(string)
+ result = string.gsub(/u\{(.*?)\}/) do |match|
+ result = ''
+ result = user.send($1) if USER_FIELDS.include?($1)
+ result = user.user_profile.send($1) if PROFILE_FIELDS.include?($1)
+ result
+ end
+
+ result = result.gsub(/w\{(.*?)\}/) { |match| recurse(data, [*$1.split('.')]) }
+
+ result.gsub(/v\{(.*?)\}/) do |match|
+ attrs = $1.split(':')
+ key = attrs.first
+ format = attrs.length > 1 ? attrs.last : nil
+ val = nil
+
+ if key == 'time'
+ time_format = format.present? ? format : "%B %-d, %Y"
+ val = Time.now.strftime(time_format)
+ end
+
+ val
+ end
+ end
+
+ def recurse(data, keys)
+ k = keys.shift
+ result = data[k]
+ keys.empty? ? result : self.recurse(result, keys)
+ end
+end
\ No newline at end of file
diff --git a/lib/custom_wizard/template.rb b/lib/custom_wizard/template.rb
index 2cc348df..f236fdcf 100644
--- a/lib/custom_wizard/template.rb
+++ b/lib/custom_wizard/template.rb
@@ -8,12 +8,12 @@ class CustomWizard::Template
:multiple_submissions,
:prompt_completion,
:restart_on_revisit,
- :min_trust,
:after_signup,
:after_time,
:after_time_scheduled,
:required,
- :theme_id
+ :theme_id,
+ :permitted
def initialize(data)
data = data.is_a?(String) ? ::JSON.parse(data) : data
@@ -28,12 +28,12 @@ class CustomWizard::Template
@multiple_submissions = data['multiple_submissions'] || false
@prompt_completion = data['prompt_completion'] || false
@restart_on_revisit = data['restart_on_revisit'] || false
- @min_trust = data['min_trust'] || 0
@after_signup = data['after_signup']
@after_time = data['after_time']
@after_time_scheduled = data['after_time_scheduled']
@required = data['required'] || false
@theme_id = data['theme_id']
+ @permitted = data['permitted'] || nil
if data['theme']
theme = Theme.find_by(name: data['theme'])
diff --git a/lib/custom_wizard/wizard.rb b/lib/custom_wizard/wizard.rb
index c65f0b4d..79c40574 100644
--- a/lib/custom_wizard/wizard.rb
+++ b/lib/custom_wizard/wizard.rb
@@ -14,13 +14,13 @@ class CustomWizard::Wizard
:background,
:save_submissions,
:multiple_submissions,
- :min_trust,
:after_time,
:after_time_scheduled,
:after_signup,
:required,
:prompt_completion,
:restart_on_revisit,
+ :permitted,
:needs_categories,
:needs_groups
@@ -127,7 +127,10 @@ class CustomWizard::Wizard
end
def permitted?
- user && (user.staff? || user.trust_level.to_i >= min_trust.to_i)
+ return false unless user
+ return true if user.admin? || permitted.blank?
+ group_ids = permitted.first['output']
+ return GroupUser.exists?(group_id: group_ids, user_id: user.id)
end
def reset
@@ -146,22 +149,37 @@ class CustomWizard::Wizard
def groups
@groups ||= ::Site.new(Guardian.new(@user)).groups
end
+
+ def self.templates(filter = nil)
+ rows = [*PluginStoreRow.where(plugin_name: 'custom_wizard')]
+ rows = rows.select { |r| r.value[filter] } if filter
+ rows
+ end
- def self.after_signup
- rows = PluginStoreRow.where(plugin_name: 'custom_wizard')
- wizards = [*rows].select { |r| r.value['after_signup'] }
- if wizards.any?
- wizards.first.key
+ def self.after_signup(user)
+ if (temps = templates('after_signup')).any?
+ wizard = nil
+
+ temps
+ .sort_by { |t| template.value['permitted'].present? }
+ .each do |template|
+ wizard = CustomWizard::Wizard.new(user, template)
+
+ if wizard.permitted?
+ wizard = wizard
+ break
+ end
+ end
+
+ wizard
else
false
end
end
def self.prompt_completion(user)
- rows = PluginStoreRow.where(plugin_name: 'custom_wizard')
- wizards = [*rows].select { |r| r.value['prompt_completion'] }
- if wizards.any?
- wizards.reduce([]) do |result, w|
+ if (temps = templates('prompt_completion')).any?
+ temps.reduce([]) do |result, w|
data = ::JSON.parse(w.value)
id = data['id']
name = data['name']
@@ -175,10 +193,8 @@ class CustomWizard::Wizard
end
def self.restart_on_revisit
- rows = PluginStoreRow.where(plugin_name: 'custom_wizard')
- wizards = [*rows].select { |r| r.value['restart_on_revisit'] }
- if wizards.any?
- wizards.first.key
+ if (temps = templates('restart_on_revisit')).any?
+ temps.first.key
else
false
end
diff --git a/lib/wizard/field.rb b/lib/wizard/field.rb
index 7de580bc..eefaa063 100644
--- a/lib/wizard/field.rb
+++ b/lib/wizard/field.rb
@@ -7,7 +7,7 @@ module CustomWizardFieldExtension
:file_types,
:limit,
:property,
- :filter
+ :content
attr_accessor :dropdown_none
@@ -26,7 +26,7 @@ module CustomWizardFieldExtension
@file_types = attrs[:file_types]
@limit = attrs[:limit]
@property = attrs[:property]
- @filter = attrs[:filter]
+ @content = attrs[:content]
end
def label
diff --git a/plugin.rb b/plugin.rb
index c3d928b3..93cb13a1 100644
--- a/plugin.rb
+++ b/plugin.rb
@@ -37,42 +37,44 @@ if respond_to?(:register_svg_icon)
end
after_initialize do
- [
- '../lib/custom_wizard/engine.rb',
- '../config/routes.rb',
- '../controllers/custom_wizard/wizard.rb',
- '../controllers/custom_wizard/steps.rb',
- '../controllers/custom_wizard/admin.rb',
- '../controllers/custom_wizard/transfer.rb',
- '../controllers/custom_wizard/api.rb',
- '../controllers/application_controller.rb',
- '../controllers/extra_locales_controller.rb',
- '../controllers/invites_controller.rb',
- '../jobs/clear_after_time_wizard.rb',
- '../jobs/refresh_api_access_token.rb',
- '../jobs/set_after_time_wizard.rb',
- '../lib/custom_wizard/builder.rb',
- '../lib/custom_wizard/field.rb',
- '../lib/custom_wizard/step_updater.rb',
- '../lib/custom_wizard/template.rb',
- '../lib/custom_wizard/wizard.rb',
- '../lib/custom_wizard/api/api.rb',
- '../lib/custom_wizard/api/authorization.rb',
- '../lib/custom_wizard/api/endpoint.rb',
- '../lib/custom_wizard/api/log_entry.rb',
- '../lib/wizard/choice.rb',
- '../lib/wizard/field.rb',
- '../lib/wizard/step.rb',
- '../serializers/custom_wizard/api/authorization_serializer.rb',
- '../serializers/custom_wizard/api/basic_endpoint_serializer.rb',
- '../serializers/custom_wizard/api/endpoint_serializer.rb',
- '../serializers/custom_wizard/api/log_serializer.rb',
- '../serializers/custom_wizard/api_serializer.rb',
- '../serializers/custom_wizard/basic_api_serializer.rb',
- '../serializers/custom_wizard/wizard_field_serializer.rb',
- '../serializers/custom_wizard/wizard_step_serializer.rb',
- '../serializers/custom_wizard/wizard_serializer.rb',
- '../serializers/site_serializer.rb'
+ %w[
+ ../lib/custom_wizard/engine.rb
+ ../config/routes.rb
+ ../controllers/custom_wizard/wizard.rb
+ ../controllers/custom_wizard/steps.rb
+ ../controllers/custom_wizard/admin.rb
+ ../controllers/custom_wizard/transfer.rb
+ ../controllers/custom_wizard/api.rb
+ ../controllers/application_controller.rb
+ ../controllers/extra_locales_controller.rb
+ ../controllers/invites_controller.rb
+ ../jobs/clear_after_time_wizard.rb
+ ../jobs/refresh_api_access_token.rb
+ ../jobs/set_after_time_wizard.rb
+ ../lib/custom_wizard/actions.rb
+ ../lib/custom_wizard/builder.rb
+ ../lib/custom_wizard/field.rb
+ ../lib/custom_wizard/mapper.rb
+ ../lib/custom_wizard/step_updater.rb
+ ../lib/custom_wizard/template.rb
+ ../lib/custom_wizard/wizard.rb
+ ../lib/custom_wizard/api/api.rb
+ ../lib/custom_wizard/api/authorization.rb
+ ../lib/custom_wizard/api/endpoint.rb
+ ../lib/custom_wizard/api/log_entry.rb
+ ../lib/wizard/choice.rb
+ ../lib/wizard/field.rb
+ ../lib/wizard/step.rb
+ ../serializers/custom_wizard/api/authorization_serializer.rb
+ ../serializers/custom_wizard/api/basic_endpoint_serializer.rb
+ ../serializers/custom_wizard/api/endpoint_serializer.rb
+ ../serializers/custom_wizard/api/log_serializer.rb
+ ../serializers/custom_wizard/api_serializer.rb
+ ../serializers/custom_wizard/basic_api_serializer.rb
+ ../serializers/custom_wizard/wizard_field_serializer.rb
+ ../serializers/custom_wizard/wizard_step_serializer.rb
+ ../serializers/custom_wizard/wizard_serializer.rb
+ ../serializers/site_serializer.rb
].each do |path|
load File.expand_path(path, __FILE__)
end
@@ -85,13 +87,11 @@ after_initialize do
if user &&
user.first_seen_at.blank? &&
- wizard_id = CustomWizard::Wizard.after_signup
-
- wizard = CustomWizard::Wizard.create(user, wizard_id)
+ wizard = CustomWizard::Wizard.after_signup(user)
- if !wizard.completed? && wizard.permitted?
+ if !wizard.completed?
custom_redirect = true
- CustomWizard::Wizard.set_wizard_redirect(user, wizard_id)
+ CustomWizard::Wizard.set_wizard_redirect(user, wizard.id)
end
end
diff --git a/serializers/custom_wizard/wizard_field_serializer.rb b/serializers/custom_wizard/wizard_field_serializer.rb
index 820a1740..11bad2e9 100644
--- a/serializers/custom_wizard/wizard_field_serializer.rb
+++ b/serializers/custom_wizard/wizard_field_serializer.rb
@@ -7,7 +7,7 @@ class CustomWizardFieldSerializer < ::WizardFieldSerializer
:file_types,
:limit,
:property,
- :filter
+ :content
has_many :choices, serializer: WizardFieldChoiceSerializer, embed: :objects
@@ -49,7 +49,7 @@ class CustomWizardFieldSerializer < ::WizardFieldSerializer
object.property
end
- def filter
- object.filter
+ def content
+ object.content
end
end
\ No newline at end of file
diff --git a/serializers/custom_wizard/wizard_serializer.rb b/serializers/custom_wizard/wizard_serializer.rb
index 9ab3fcc6..afb7a2c7 100644
--- a/serializers/custom_wizard/wizard_serializer.rb
+++ b/serializers/custom_wizard/wizard_serializer.rb
@@ -7,7 +7,6 @@ class CustomWizardSerializer < ::WizardSerializer
:background,
:completed,
:required,
- :min_trust,
:permitted,
:uncategorized_category_id