decidim-forms 0.19.1 → 0.23.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +1 -1
- data/app/assets/config/admin/decidim_forms_manifest.css +3 -0
- data/app/assets/config/admin/decidim_forms_manifest.js +1 -0
- data/app/assets/config/decidim_forms_manifest.css +1 -0
- data/app/assets/images/decidim/surveys/icon.svg +1 -19
- data/app/assets/javascripts/decidim/forms/admin/auto_select_options_from_url.component.js.es6 +40 -0
- data/app/assets/javascripts/decidim/forms/admin/collapsible_questions.js.es6 +13 -0
- data/app/assets/javascripts/decidim/forms/admin/forms.js.es6 +260 -16
- data/app/assets/javascripts/decidim/forms/admin/live_text_update.component.js.es6 +52 -0
- data/app/assets/javascripts/decidim/forms/autosortable_checkboxes.component.js.es6 +54 -34
- data/app/assets/javascripts/decidim/forms/display_conditions.component.js.es6 +204 -0
- data/app/assets/javascripts/decidim/forms/forms.js.es6 +49 -1
- data/app/assets/javascripts/decidim/forms/max_choices_alert.component.js.es6 +44 -0
- data/app/assets/stylesheets/decidim/forms/forms.scss +39 -0
- data/app/assets/stylesheets/decidim/forms/questionnaire-answers-pdf.scss +69 -0
- data/app/cells/decidim/forms/matrix_readonly/show.erb +5 -0
- data/app/cells/decidim/forms/matrix_readonly_cell.rb +12 -0
- data/app/cells/decidim/forms/question_readonly/show.erb +5 -1
- data/app/cells/decidim/forms/question_readonly_cell.rb +5 -0
- data/app/cells/decidim/forms/step_navigation/show.erb +35 -0
- data/app/cells/decidim/forms/step_navigation_cell.rb +46 -0
- data/app/commands/decidim/forms/admin/update_questionnaire.rb +33 -1
- data/app/commands/decidim/forms/answer_questionnaire.rb +7 -2
- data/app/controllers/decidim/forms/admin/concerns/has_questionnaire.rb +52 -2
- data/app/controllers/decidim/forms/admin/concerns/has_questionnaire_answers.rb +97 -0
- data/app/controllers/decidim/forms/concerns/has_questionnaire.rb +58 -2
- data/app/forms/decidim/forms/admin/display_condition_form.rb +100 -0
- data/app/forms/decidim/forms/admin/question_form.rb +21 -1
- data/app/forms/decidim/forms/admin/question_matrix_row_form.rb +26 -0
- data/app/forms/decidim/forms/answer_choice_form.rb +1 -0
- data/app/forms/decidim/forms/answer_form.rb +31 -2
- data/app/forms/decidim/forms/questionnaire_form.rb +33 -3
- data/app/helpers/decidim/forms/admin/application_helper.rb +37 -0
- data/app/helpers/decidim/forms/admin/concerns/has_questionnaire_answers_pagination_helper.rb +49 -0
- data/app/helpers/decidim/forms/admin/concerns/has_questionnaire_answers_url_helper.rb +40 -0
- data/app/helpers/decidim/forms/admin/questionnaire_answers_helper.rb +27 -0
- data/app/jobs/decidim/forms/export_questionnaire_answers_job.rb +19 -0
- data/app/models/decidim/forms/answer.rb +2 -5
- data/app/models/decidim/forms/answer_choice.rb +7 -0
- data/app/models/decidim/forms/answer_option.rb +14 -0
- data/app/models/decidim/forms/display_condition.rb +51 -0
- data/app/models/decidim/forms/question.rb +52 -2
- data/app/models/decidim/forms/question_matrix_row.rb +15 -0
- data/app/models/decidim/forms/questionnaire.rb +13 -2
- data/app/presenters/decidim/forms/admin/questionnaire_answer_presenter.rb +43 -0
- data/app/presenters/decidim/forms/admin/questionnaire_participant_presenter.rb +60 -0
- data/app/presenters/decidim/forms/answer_option_presenter.rb +20 -0
- data/app/presenters/decidim/forms/question_presenter.rb +16 -0
- data/app/queries/decidim/forms/questionnaire_participant.rb +35 -0
- data/app/queries/decidim/forms/questionnaire_participants.rb +43 -0
- data/app/queries/decidim/forms/questionnaire_user_answers.rb +1 -1
- data/app/types/decidim/forms/answer_option_type.rb +14 -0
- data/app/types/decidim/forms/question_type.rb +23 -0
- data/app/types/decidim/forms/questionnaire_type.rb +22 -0
- data/app/views/decidim/forms/admin/questionnaires/_answer_option_template.html.erb +1 -1
- data/app/views/decidim/forms/admin/questionnaires/_display_condition.html.erb +88 -0
- data/app/views/decidim/forms/admin/questionnaires/_display_condition_template.html.erb +7 -0
- data/app/views/decidim/forms/admin/questionnaires/_form.html.erb +67 -8
- data/app/views/decidim/forms/admin/questionnaires/_matrix_row.html.erb +34 -0
- data/app/views/decidim/forms/admin/questionnaires/_matrix_row_template.html.erb +7 -0
- data/app/views/decidim/forms/admin/questionnaires/_question.html.erb +45 -6
- data/app/views/decidim/forms/admin/questionnaires/_separator.html.erb +41 -0
- data/app/views/decidim/forms/admin/questionnaires/answers/export/_answer.html.erb +31 -0
- data/app/views/decidim/forms/admin/questionnaires/answers/export/pdf.html.erb +13 -0
- data/app/views/decidim/forms/admin/questionnaires/answers/index.html.erb +53 -0
- data/app/views/decidim/forms/admin/questionnaires/answers/show.html.erb +48 -0
- data/app/views/decidim/forms/admin/questionnaires/edit.html.erb +9 -5
- data/app/views/decidim/forms/questionnaires/_answer.html.erb +28 -99
- data/app/views/decidim/forms/questionnaires/answers/_long_answer.html.erb +1 -0
- data/app/views/decidim/forms/questionnaires/answers/_matrix_multiple.html.erb +43 -0
- data/app/views/decidim/forms/questionnaires/answers/_matrix_single.html.erb +43 -0
- data/app/views/decidim/forms/questionnaires/answers/_multiple_option.html.erb +23 -0
- data/app/views/decidim/forms/questionnaires/answers/_separator.html.erb +1 -0
- data/app/views/decidim/forms/questionnaires/answers/_short_answer.html.erb +1 -0
- data/app/views/decidim/forms/questionnaires/answers/_single_option.html.erb +30 -0
- data/app/views/decidim/forms/questionnaires/answers/_sorting.html.erb +23 -0
- data/app/views/decidim/forms/questionnaires/show.html.erb +81 -28
- data/app/views/layouts/decidim/forms/admin/questionnaires/questionnaire_answers.html.erb +12 -0
- data/config/initializers/wicked_pdf.rb +25 -0
- data/config/locales/am-ET.yml +1 -0
- data/config/locales/ar.yml +12 -4
- data/config/locales/bg-BG.yml +14 -0
- data/config/locales/bg.yml +14 -0
- data/config/locales/ca.yml +98 -8
- data/config/locales/cs.yml +94 -4
- data/config/locales/da-DK.yml +1 -0
- data/config/locales/da.yml +1 -0
- data/config/locales/de.yml +92 -4
- data/config/locales/el-GR.yml +1 -0
- data/config/locales/el.yml +118 -0
- data/config/locales/en.yml +94 -4
- data/config/locales/eo.yml +1 -0
- data/config/locales/es-MX.yml +94 -4
- data/config/locales/es-PY.yml +94 -4
- data/config/locales/es.yml +94 -4
- data/config/locales/et-EE.yml +1 -0
- data/config/locales/et.yml +1 -0
- data/config/locales/eu.yml +7 -4
- data/config/locales/fi-plain.yml +94 -4
- data/config/locales/fi.yml +94 -4
- data/config/locales/fr-CA.yml +172 -0
- data/config/locales/fr.yml +94 -4
- data/config/locales/ga-IE.yml +1 -0
- data/config/locales/gl.yml +7 -4
- data/config/locales/hr-HR.yml +1 -0
- data/config/locales/hr.yml +1 -0
- data/config/locales/hu.yml +16 -4
- data/config/locales/id-ID.yml +7 -4
- data/config/locales/is-IS.yml +1 -0
- data/config/locales/is.yml +1 -0
- data/config/locales/it.yml +95 -5
- data/config/locales/ja-JP.yml +170 -0
- data/config/locales/ja.yml +170 -0
- data/config/locales/ko-KR.yml +1 -0
- data/config/locales/ko.yml +1 -0
- data/config/locales/lt-LT.yml +1 -0
- data/config/locales/lt.yml +1 -0
- data/config/locales/lv.yml +118 -0
- data/config/locales/mt-MT.yml +1 -0
- data/config/locales/mt.yml +1 -0
- data/config/locales/nl.yml +97 -7
- data/config/locales/no.yml +98 -1
- data/config/locales/om-ET.yml +1 -0
- data/config/locales/pl.yml +113 -26
- data/config/locales/pt-BR.yml +8 -5
- data/config/locales/pt.yml +111 -24
- data/config/locales/ro-RO.yml +167 -0
- data/config/locales/ru.yml +4 -2
- data/config/locales/sk-SK.yml +88 -0
- data/config/locales/sk.yml +90 -0
- data/config/locales/sl.yml +12 -0
- data/config/locales/so-SO.yml +1 -0
- data/config/locales/sr-CS.yml +1 -0
- data/config/locales/sv.yml +91 -4
- data/config/locales/ti-ER.yml +1 -0
- data/config/locales/tr-TR.yml +7 -4
- data/config/locales/vi-VN.yml +1 -0
- data/config/locales/vi.yml +1 -0
- data/config/locales/zh-CN.yml +172 -0
- data/config/locales/zh-TW.yml +1 -0
- data/db/migrate/20190315203056_add_session_token_to_decidim_forms_answers.rb +17 -0
- data/db/migrate/20190930094710_add_ip_hash_to_decidim_form_answers.rb +12 -0
- data/db/migrate/20200130194123_create_decidim_forms_display_conditions.rb +20 -0
- data/db/migrate/20200225123810_create_decidim_forms_question_matrix_rows.rb +11 -0
- data/db/migrate/20200304152939_add_matrix_row_id_to_decidim_forms_answer_choices.rb +11 -0
- data/lib/decidim/api/questionnaire_entity_interface.rb +18 -0
- data/lib/decidim/exporters/form_pdf.rb +33 -0
- data/lib/decidim/exporters/form_pdf_controller_helper.rb +11 -0
- data/lib/decidim/forms.rb +6 -0
- data/lib/decidim/forms/admin_engine.rb +1 -1
- data/lib/decidim/forms/api.rb +7 -0
- data/lib/decidim/forms/test.rb +6 -0
- data/lib/decidim/forms/test/factories.rb +80 -6
- data/lib/decidim/forms/test/shared_examples/has_questionnaire.rb +924 -66
- data/lib/decidim/forms/test/shared_examples/manage_questionnaire_answers.rb +108 -0
- data/lib/decidim/forms/test/shared_examples/manage_questionnaires.rb +33 -575
- data/lib/decidim/forms/test/shared_examples/manage_questionnaires/add_display_conditions.rb +179 -0
- data/lib/decidim/forms/test/shared_examples/manage_questionnaires/add_questions.rb +463 -0
- data/lib/decidim/forms/test/shared_examples/manage_questionnaires/update_display_conditions.rb +93 -0
- data/lib/decidim/forms/test/shared_examples/manage_questionnaires/update_questions.rb +461 -0
- data/lib/decidim/forms/user_answers_serializer.rb +23 -4
- data/lib/decidim/forms/version.rb +1 -1
- metadata +136 -9
@@ -16,7 +16,9 @@ module Decidim
|
|
16
16
|
helper Decidim::Forms::ApplicationHelper
|
17
17
|
include FormFactory
|
18
18
|
|
19
|
-
helper_method :questionnaire_for, :questionnaire, :allow_answers?, :update_url
|
19
|
+
helper_method :questionnaire_for, :questionnaire, :allow_answers?, :visitor_can_answer?, :visitor_already_answered?, :update_url, :form_path
|
20
|
+
|
21
|
+
invisible_captcha on_spam: :spam_detected
|
20
22
|
|
21
23
|
def show
|
22
24
|
@form = form(Decidim::Forms::QuestionnaireForm).from_model(questionnaire)
|
@@ -26,7 +28,7 @@ module Decidim
|
|
26
28
|
def answer
|
27
29
|
enforce_permission_to :answer, :questionnaire
|
28
30
|
|
29
|
-
@form = form(Decidim::Forms::QuestionnaireForm).from_params(params)
|
31
|
+
@form = form(Decidim::Forms::QuestionnaireForm).from_params(params, session_token: session_token, ip_hash: ip_hash)
|
30
32
|
|
31
33
|
Decidim::Forms::AnswerQuestionnaire.call(@form, current_user, questionnaire) do
|
32
34
|
on(:ok) do
|
@@ -49,6 +51,22 @@ module Decidim
|
|
49
51
|
raise "#{self.class.name} is expected to implement #allow_answers?"
|
50
52
|
end
|
51
53
|
|
54
|
+
# Public: Method to be implemented at the controller if needed. You need to
|
55
|
+
# return true if the questionnaire can receive answers by unregistered users
|
56
|
+
def allow_unregistered?
|
57
|
+
false
|
58
|
+
end
|
59
|
+
|
60
|
+
# Public: return true if the current user (if logged) can answer the questionnaire
|
61
|
+
def visitor_can_answer?
|
62
|
+
current_user || allow_unregistered?
|
63
|
+
end
|
64
|
+
|
65
|
+
# Public: return true if the current user (or session visitor) can answer the questionnaire
|
66
|
+
def visitor_already_answered?
|
67
|
+
questionnaire.answered_by?(current_user || tokenize(session[:session_id]))
|
68
|
+
end
|
69
|
+
|
52
70
|
# Public: Returns a String or Object that will be passed to `redirect_to` after
|
53
71
|
# answering the questionnaire. By default it redirects to the questionnaire_for.
|
54
72
|
#
|
@@ -63,6 +81,15 @@ module Decidim
|
|
63
81
|
url_for([questionnaire_for, action: :answer])
|
64
82
|
end
|
65
83
|
|
84
|
+
# Points to the shortest path accessing the current form. This will be
|
85
|
+
# used to detect whether a user is leaving the form with some partial
|
86
|
+
# answers, so that we can warn them.
|
87
|
+
#
|
88
|
+
# Overwrite this method at the controller.
|
89
|
+
def form_path
|
90
|
+
url_for([questionnaire_for, only_path: true])
|
91
|
+
end
|
92
|
+
|
66
93
|
# Public: Method to be implemented at the controller. You need to
|
67
94
|
# return the object that will hold the questionnaire.
|
68
95
|
def questionnaire_for
|
@@ -78,6 +105,35 @@ module Decidim
|
|
78
105
|
def questionnaire
|
79
106
|
@questionnaire ||= Questionnaire.includes(questions: :answer_options).find_by(questionnaire_for: questionnaire_for)
|
80
107
|
end
|
108
|
+
|
109
|
+
def spam_detected
|
110
|
+
enforce_permission_to :answer, :questionnaire
|
111
|
+
|
112
|
+
@form = form(Decidim::Forms::QuestionnaireForm).from_params(params)
|
113
|
+
|
114
|
+
flash.now[:alert] = I18n.t("answer.spam_detected", scope: i18n_flashes_scope)
|
115
|
+
render template: "decidim/forms/questionnaires/show"
|
116
|
+
end
|
117
|
+
|
118
|
+
def ip_hash
|
119
|
+
return nil unless request&.remote_ip
|
120
|
+
|
121
|
+
@ip_hash ||= tokenize(request&.remote_ip)
|
122
|
+
end
|
123
|
+
|
124
|
+
# token is used as a substitute of user_id if unregistered
|
125
|
+
def session_token
|
126
|
+
id = current_user&.id
|
127
|
+
session_id = request.session[:session_id] if request&.session
|
128
|
+
|
129
|
+
return nil unless id || session_id
|
130
|
+
|
131
|
+
@session_token ||= tokenize(id || session_id)
|
132
|
+
end
|
133
|
+
|
134
|
+
def tokenize(id)
|
135
|
+
Digest::MD5.hexdigest("#{id}-#{Rails.application.secrets.secret_key_base}")
|
136
|
+
end
|
81
137
|
end
|
82
138
|
end
|
83
139
|
end
|
@@ -0,0 +1,100 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Decidim
|
4
|
+
module Forms
|
5
|
+
module Admin
|
6
|
+
# This class holds a Form to update questionnaire questions from Decidim's admin panel.
|
7
|
+
class DisplayConditionForm < Decidim::Form
|
8
|
+
include TranslatableAttributes
|
9
|
+
|
10
|
+
attribute :decidim_question_id, Integer
|
11
|
+
attribute :decidim_condition_question_id, Integer
|
12
|
+
attribute :decidim_answer_option_id, Integer
|
13
|
+
attribute :condition_type, String
|
14
|
+
attribute :mandatory, Boolean, default: false
|
15
|
+
attribute :deleted, Boolean, default: false
|
16
|
+
|
17
|
+
translatable_attribute :condition_value, String
|
18
|
+
|
19
|
+
validates :question, presence: true, unless: :deleted
|
20
|
+
validates :condition_question, presence: true, unless: :deleted
|
21
|
+
validates :answer_option, presence: true, if: :answer_option_mandatory?
|
22
|
+
|
23
|
+
validates :condition_value, translatable_presence: true, if: :condition_value_mandatory?
|
24
|
+
validates :condition_type, presence: true, unless: :deleted
|
25
|
+
|
26
|
+
validate :condition_question_position, unless: :deleted
|
27
|
+
validate :valid_answer_option?, unless: :deleted
|
28
|
+
|
29
|
+
def to_param
|
30
|
+
return id if id.present?
|
31
|
+
|
32
|
+
"questionnaire-display-condition-id"
|
33
|
+
end
|
34
|
+
|
35
|
+
def answer_options
|
36
|
+
return [] if condition_question.blank?
|
37
|
+
|
38
|
+
condition_question.answer_options
|
39
|
+
end
|
40
|
+
|
41
|
+
def questions_for_select(questionnaire, id)
|
42
|
+
questionnaire.questions.map do |question|
|
43
|
+
[
|
44
|
+
question.translated_body,
|
45
|
+
question.id,
|
46
|
+
{
|
47
|
+
"disabled" => (question.question_type == "sorting" || question.id == id),
|
48
|
+
"data-type" => question.question_type
|
49
|
+
}
|
50
|
+
]
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
# Finds the Question from the given decidim_question_id
|
55
|
+
#
|
56
|
+
# Returns a Decidim::Forms::Question
|
57
|
+
def question
|
58
|
+
@question ||= Question.find_by(id: @decidim_question_id)
|
59
|
+
end
|
60
|
+
|
61
|
+
# Finds the Condition Question from the given decidim_condition_question_id
|
62
|
+
#
|
63
|
+
# Returns a Decidim::Forms::Question
|
64
|
+
def condition_question
|
65
|
+
@condition_question ||= Question.find_by(id: @decidim_condition_question_id)
|
66
|
+
end
|
67
|
+
|
68
|
+
# Finds the Answer Option from the given decidim_answer_option_id
|
69
|
+
#
|
70
|
+
# Returns a Decidim::Forms::AnswerOption
|
71
|
+
def answer_option
|
72
|
+
@answer_option ||= AnswerOption.find_by(id: @decidim_answer_option_id)
|
73
|
+
end
|
74
|
+
|
75
|
+
private
|
76
|
+
|
77
|
+
def condition_value_mandatory?
|
78
|
+
!deleted && condition_type == "match"
|
79
|
+
end
|
80
|
+
|
81
|
+
def answer_option_mandatory?
|
82
|
+
!deleted && %w(equal not_equal).include?(condition_type)
|
83
|
+
end
|
84
|
+
|
85
|
+
def valid_answer_option?
|
86
|
+
return unless answer_option_mandatory?
|
87
|
+
return if answer_option.blank?
|
88
|
+
|
89
|
+
errors.add(:decidim_answer_option_id, :invalid) if answer_option.question.id != decidim_condition_question_id
|
90
|
+
end
|
91
|
+
|
92
|
+
def condition_question_position
|
93
|
+
return if decidim_question_id.blank?
|
94
|
+
|
95
|
+
errors.add(:decidim_question_id, :invalid) unless question.position&.positive?
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
@@ -11,6 +11,8 @@ module Decidim
|
|
11
11
|
attribute :mandatory, Boolean, default: false
|
12
12
|
attribute :question_type, String
|
13
13
|
attribute :answer_options, Array[AnswerOptionForm]
|
14
|
+
attribute :display_conditions, Array[DisplayConditionForm]
|
15
|
+
attribute :matrix_rows, Array[QuestionMatrixRowForm]
|
14
16
|
attribute :max_choices, Integer
|
15
17
|
attribute :deleted, Boolean, default: false
|
16
18
|
|
@@ -20,7 +22,9 @@ module Decidim
|
|
20
22
|
validates :position, numericality: { greater_than_or_equal_to: 0 }
|
21
23
|
validates :question_type, inclusion: { in: Decidim::Forms::Question::TYPES }
|
22
24
|
validates :max_choices, numericality: { only_integer: true, greater_than: 1, less_than_or_equal_to: ->(form) { form.number_of_options } }, allow_blank: true
|
23
|
-
validates :body, translatable_presence: true,
|
25
|
+
validates :body, translatable_presence: true, if: :requires_body?
|
26
|
+
validates :matrix_rows, presence: true, if: :matrix?
|
27
|
+
validates :answer_options, presence: true, if: :matrix?
|
24
28
|
|
25
29
|
def to_param
|
26
30
|
return id if id.present?
|
@@ -31,6 +35,22 @@ module Decidim
|
|
31
35
|
def number_of_options
|
32
36
|
answer_options.size
|
33
37
|
end
|
38
|
+
|
39
|
+
def separator?
|
40
|
+
question_type == Decidim::Forms::Question::SEPARATOR_TYPE
|
41
|
+
end
|
42
|
+
|
43
|
+
private
|
44
|
+
|
45
|
+
def matrix?
|
46
|
+
question_type == "matrix_single" || question_type == "matrix_multiple"
|
47
|
+
end
|
48
|
+
|
49
|
+
def requires_body?
|
50
|
+
return false if separator?
|
51
|
+
|
52
|
+
!deleted
|
53
|
+
end
|
34
54
|
end
|
35
55
|
end
|
36
56
|
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Decidim
|
4
|
+
module Forms
|
5
|
+
module Admin
|
6
|
+
# This class holds a Form to update questionnaire question matrixes from Decidim's admin panel.
|
7
|
+
class QuestionMatrixRowForm < Decidim::Form
|
8
|
+
include TranslatableAttributes
|
9
|
+
|
10
|
+
attribute :position, Integer, default: 0
|
11
|
+
attribute :deleted, Boolean, default: false
|
12
|
+
|
13
|
+
translatable_attribute :body, String
|
14
|
+
|
15
|
+
validates :position, numericality: { greater_than_or_equal_to: 0 }
|
16
|
+
validates :body, translatable_presence: true, unless: :deleted
|
17
|
+
|
18
|
+
def to_param
|
19
|
+
return id if id.present?
|
20
|
+
|
21
|
+
"questionnaire-question-matrix-row-id"
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -9,14 +9,16 @@ module Decidim
|
|
9
9
|
attribute :question_id, String
|
10
10
|
attribute :body, String
|
11
11
|
attribute :choices, Array[AnswerChoiceForm]
|
12
|
+
attribute :matrix_choices, Array[AnswerChoiceForm]
|
12
13
|
|
13
14
|
validates :body, presence: true, if: :mandatory_body?
|
14
15
|
validates :selected_choices, presence: true, if: :mandatory_choices?
|
15
16
|
|
16
17
|
validate :max_choices, if: -> { question.max_choices }
|
17
18
|
validate :all_choices, if: -> { question.question_type == "sorting" }
|
19
|
+
validate :min_choices, if: -> { question.matrix? && question.mandatory? }
|
18
20
|
|
19
|
-
delegate :mandatory_body?, :mandatory_choices?, to: :question
|
21
|
+
delegate :mandatory_body?, :mandatory_choices?, :matrix?, to: :question
|
20
22
|
|
21
23
|
attr_writer :question
|
22
24
|
|
@@ -47,10 +49,37 @@ module Decidim
|
|
47
49
|
choices.select(&:body)
|
48
50
|
end
|
49
51
|
|
52
|
+
def display_conditions_fulfilled?
|
53
|
+
question.display_conditions.all? do |condition|
|
54
|
+
answer = question.questionnaire.answers.find_by(question: condition.condition_question)
|
55
|
+
condition.fulfilled?(answer)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
50
59
|
private
|
51
60
|
|
61
|
+
def mandatory_body?
|
62
|
+
question.mandatory_body? if display_conditions_fulfilled?
|
63
|
+
end
|
64
|
+
|
65
|
+
def mandatory_choices?
|
66
|
+
question.mandatory_choices? if display_conditions_fulfilled?
|
67
|
+
end
|
68
|
+
|
69
|
+
def grouped_choices
|
70
|
+
selected_choices.group_by(&:matrix_row_id).values
|
71
|
+
end
|
72
|
+
|
52
73
|
def max_choices
|
53
|
-
|
74
|
+
if matrix?
|
75
|
+
errors.add(:choices, :too_many) if grouped_choices.any? { |choices| choices.count > question.max_choices }
|
76
|
+
elsif selected_choices.size > question.max_choices
|
77
|
+
errors.add(:choices, :too_many)
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
def min_choices
|
82
|
+
errors.add(:choices, :missing) if grouped_choices.count != question.matrix_rows.count
|
54
83
|
end
|
55
84
|
|
56
85
|
def all_choices
|
@@ -4,20 +4,50 @@ module Decidim
|
|
4
4
|
module Forms
|
5
5
|
# This class holds a Form to answer a questionnaire from Decidim's public page.
|
6
6
|
class QuestionnaireForm < Decidim::Form
|
7
|
-
|
7
|
+
# as questionnaire uses "answers" for the database relationships is
|
8
|
+
# important not to use the same word here to avoid querying all the entries, resulting in a high performance penalty
|
9
|
+
attribute :responses, Array[AnswerForm]
|
8
10
|
attribute :user_group_id, Integer
|
9
11
|
|
10
12
|
attribute :tos_agreement, Boolean
|
13
|
+
|
11
14
|
validates :tos_agreement, allow_nil: false, acceptance: true
|
15
|
+
validate :session_token_in_context
|
12
16
|
|
13
|
-
# Private: Create the
|
17
|
+
# Private: Create the responses from the questionnaire questions
|
14
18
|
#
|
15
19
|
# Returns nothing.
|
16
20
|
def map_model(model)
|
17
|
-
self.
|
21
|
+
self.responses = model.questions.map do |question|
|
18
22
|
AnswerForm.from_model(Decidim::Forms::Answer.new(question: question))
|
19
23
|
end
|
20
24
|
end
|
25
|
+
|
26
|
+
# Public: Splits reponses by step, keeping the separator.
|
27
|
+
#
|
28
|
+
# Returns an array of steps. Each step is a list of the questions in that
|
29
|
+
# step, including the separator.
|
30
|
+
def responses_by_step
|
31
|
+
@responses_by_step ||=
|
32
|
+
begin
|
33
|
+
steps = responses.chunk_while do |a, b|
|
34
|
+
!a.question.separator? || b.question.separator?
|
35
|
+
end.to_a
|
36
|
+
|
37
|
+
steps = [[]] if steps == []
|
38
|
+
steps
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def total_steps
|
43
|
+
responses_by_step.count
|
44
|
+
end
|
45
|
+
|
46
|
+
def session_token_in_context
|
47
|
+
return if context&.session_token
|
48
|
+
|
49
|
+
errors.add(:tos_agreement, I18n.t("activemodel.errors.models.questionnaire.request_invalid"))
|
50
|
+
end
|
21
51
|
end
|
22
52
|
end
|
23
53
|
end
|
@@ -1,5 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
# i18n-tasks-use t('decidim.templates.admin.questionnaire_templates.form.title')
|
4
|
+
|
3
5
|
module Decidim
|
4
6
|
module Forms
|
5
7
|
module Admin
|
@@ -13,6 +15,41 @@ module Decidim
|
|
13
15
|
def tabs_id_for_question_answer_option(question, answer_option)
|
14
16
|
"questionnaire_question_#{question.to_param}_answer_option_#{answer_option.to_param}"
|
15
17
|
end
|
18
|
+
|
19
|
+
def tabs_id_for_question_display_condition(question, display_condition)
|
20
|
+
"questionnaire_question_#{question.to_param}_display_condition_#{display_condition.to_param}"
|
21
|
+
end
|
22
|
+
|
23
|
+
def tabs_id_for_question_matrix_row(question, matrix_row)
|
24
|
+
"questionnaire_question_#{question.to_param}_matrix_row_#{matrix_row.to_param}"
|
25
|
+
end
|
26
|
+
|
27
|
+
def dynamic_title(title, **options)
|
28
|
+
data = {
|
29
|
+
"max-length" => options[:max_length],
|
30
|
+
"omission" => options[:omission],
|
31
|
+
"placeholder" => options[:placeholder],
|
32
|
+
"locale" => I18n.locale
|
33
|
+
}
|
34
|
+
content_tag :span, class: options[:class], data: data do
|
35
|
+
truncate translated_attribute(title), length: options[:max_length], omission: options[:omission]
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def template?(questionnaire_for)
|
40
|
+
return unless defined? Decidim::Templates::Template
|
41
|
+
|
42
|
+
questionnaire_for.is_a? Decidim::Templates::Template
|
43
|
+
end
|
44
|
+
|
45
|
+
def templates_defined?
|
46
|
+
defined? Decidim::Templates::Admin::Concerns::Templatable
|
47
|
+
end
|
48
|
+
|
49
|
+
def title_for_questionnaire
|
50
|
+
scope = templates_defined? ? "decidim.templates.admin.questionnaire_templates" : "decidim.forms.admin.questionnaires"
|
51
|
+
t("form.title", scope: scope)
|
52
|
+
end
|
16
53
|
end
|
17
54
|
end
|
18
55
|
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Decidim
|
4
|
+
module Forms
|
5
|
+
module Admin
|
6
|
+
module Concerns
|
7
|
+
# Pagination helper for HasQuestionnaireAnswers controller concern
|
8
|
+
#
|
9
|
+
module HasQuestionnaireAnswersPaginationHelper
|
10
|
+
def self.included(base)
|
11
|
+
base.helper_method :prev_url, :next_url, :first?, :last?, :current_idx
|
12
|
+
end
|
13
|
+
|
14
|
+
def prev_url
|
15
|
+
return if first?
|
16
|
+
|
17
|
+
token = participant_ids[current_idx - 1]
|
18
|
+
questionnaire_participant_answers_url(token)
|
19
|
+
end
|
20
|
+
|
21
|
+
def next_url
|
22
|
+
return if last?
|
23
|
+
|
24
|
+
token = participant_ids[current_idx + 1]
|
25
|
+
questionnaire_participant_answers_url(token)
|
26
|
+
end
|
27
|
+
|
28
|
+
def first?
|
29
|
+
current_idx.zero?
|
30
|
+
end
|
31
|
+
|
32
|
+
def last?
|
33
|
+
current_idx == participant_ids.count - 1
|
34
|
+
end
|
35
|
+
|
36
|
+
private
|
37
|
+
|
38
|
+
def participant_ids
|
39
|
+
@participant_ids ||= collection.pluck(:session_token)
|
40
|
+
end
|
41
|
+
|
42
|
+
def current_idx
|
43
|
+
participant_ids.index(params[:session_token])
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|