decidim-forms 0.22.0 → 0.23.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (113) hide show
  1. checksums.yaml +4 -4
  2. data/app/assets/config/admin/decidim_forms_manifest.css +3 -0
  3. data/app/assets/config/decidim_forms_manifest.css +1 -0
  4. data/app/assets/javascripts/decidim/forms/admin/auto_select_options_from_url.component.js.es6 +40 -0
  5. data/app/assets/javascripts/decidim/forms/admin/forms.js.es6 +181 -25
  6. data/app/assets/javascripts/decidim/forms/autosortable_checkboxes.component.js.es6 +54 -34
  7. data/app/assets/javascripts/decidim/forms/display_conditions.component.js.es6 +204 -0
  8. data/app/assets/javascripts/decidim/forms/forms.js.es6 +8 -1
  9. data/app/assets/stylesheets/decidim/forms/questionnaire-answers-pdf.scss +69 -0
  10. data/app/commands/decidim/forms/admin/update_questionnaire.rb +25 -1
  11. data/app/commands/decidim/forms/answer_questionnaire.rb +1 -1
  12. data/app/controllers/decidim/forms/admin/concerns/has_questionnaire.rb +47 -1
  13. data/app/controllers/decidim/forms/admin/concerns/has_questionnaire_answers.rb +97 -0
  14. data/app/forms/decidim/forms/admin/display_condition_form.rb +100 -0
  15. data/app/forms/decidim/forms/admin/question_form.rb +1 -0
  16. data/app/forms/decidim/forms/answer_form.rb +15 -0
  17. data/app/helpers/decidim/forms/admin/application_helper.rb +21 -0
  18. data/app/helpers/decidim/forms/admin/concerns/has_questionnaire_answers_pagination_helper.rb +49 -0
  19. data/app/helpers/decidim/forms/admin/concerns/has_questionnaire_answers_url_helper.rb +40 -0
  20. data/app/helpers/decidim/forms/admin/questionnaire_answers_helper.rb +27 -0
  21. data/app/jobs/decidim/forms/export_questionnaire_answers_job.rb +19 -0
  22. data/app/models/decidim/forms/answer.rb +0 -3
  23. data/app/models/decidim/forms/answer_option.rb +14 -0
  24. data/app/models/decidim/forms/display_condition.rb +51 -0
  25. data/app/models/decidim/forms/question.rb +34 -0
  26. data/app/models/decidim/forms/question_matrix_row.rb +3 -0
  27. data/app/models/decidim/forms/questionnaire.rb +11 -1
  28. data/app/presenters/decidim/forms/admin/questionnaire_answer_presenter.rb +43 -0
  29. data/app/presenters/decidim/forms/admin/questionnaire_participant_presenter.rb +60 -0
  30. data/app/presenters/decidim/forms/answer_option_presenter.rb +20 -0
  31. data/app/presenters/decidim/forms/question_presenter.rb +16 -0
  32. data/app/queries/decidim/forms/questionnaire_participant.rb +35 -0
  33. data/app/queries/decidim/forms/questionnaire_participants.rb +43 -0
  34. data/app/views/decidim/forms/admin/questionnaires/_display_condition.html.erb +88 -0
  35. data/app/views/decidim/forms/admin/questionnaires/_display_condition_template.html.erb +7 -0
  36. data/app/views/decidim/forms/admin/questionnaires/_form.html.erb +30 -4
  37. data/app/views/decidim/forms/admin/questionnaires/_question.html.erb +19 -3
  38. data/app/views/decidim/forms/admin/questionnaires/answers/export/_answer.html.erb +31 -0
  39. data/app/views/decidim/forms/admin/questionnaires/answers/export/pdf.html.erb +13 -0
  40. data/app/views/decidim/forms/admin/questionnaires/answers/index.html.erb +53 -0
  41. data/app/views/decidim/forms/admin/questionnaires/answers/show.html.erb +48 -0
  42. data/app/views/decidim/forms/admin/questionnaires/edit.html.erb +9 -5
  43. data/app/views/decidim/forms/questionnaires/_answer.html.erb +1 -1
  44. data/app/views/decidim/forms/questionnaires/show.html.erb +22 -2
  45. data/app/views/layouts/decidim/forms/admin/questionnaires/questionnaire_answers.html.erb +12 -0
  46. data/config/initializers/wicked_pdf.rb +25 -0
  47. data/config/locales/am-ET.yml +1 -0
  48. data/config/locales/ar.yml +0 -1
  49. data/config/locales/bg-BG.yml +0 -2
  50. data/config/locales/bg.yml +14 -0
  51. data/config/locales/ca.yml +56 -4
  52. data/config/locales/cs.yml +54 -2
  53. data/config/locales/da.yml +1 -0
  54. data/config/locales/de.yml +51 -1
  55. data/config/locales/el.yml +0 -2
  56. data/config/locales/en.yml +54 -2
  57. data/config/locales/eo.yml +1 -0
  58. data/config/locales/es-MX.yml +53 -1
  59. data/config/locales/es-PY.yml +53 -1
  60. data/config/locales/es.yml +54 -2
  61. data/config/locales/et.yml +1 -0
  62. data/config/locales/eu.yml +0 -1
  63. data/config/locales/fi-plain.yml +53 -1
  64. data/config/locales/fi.yml +53 -1
  65. data/config/locales/fr-CA.yml +54 -2
  66. data/config/locales/fr.yml +54 -2
  67. data/config/locales/gl.yml +0 -1
  68. data/config/locales/hr.yml +1 -0
  69. data/config/locales/hu.yml +0 -2
  70. data/config/locales/id-ID.yml +0 -1
  71. data/config/locales/is.yml +1 -0
  72. data/config/locales/it.yml +53 -1
  73. data/config/locales/ja-JP.yml +51 -1
  74. data/config/locales/ja.yml +170 -0
  75. data/config/locales/ko-KR.yml +1 -0
  76. data/config/locales/ko.yml +1 -0
  77. data/config/locales/lt.yml +1 -0
  78. data/config/locales/{lv-LV.yml → lv.yml} +0 -1
  79. data/config/locales/mt.yml +1 -0
  80. data/config/locales/nl.yml +59 -7
  81. data/config/locales/no.yml +47 -2
  82. data/config/locales/om-ET.yml +1 -0
  83. data/config/locales/pl.yml +52 -3
  84. data/config/locales/pt-BR.yml +0 -1
  85. data/config/locales/pt.yml +51 -2
  86. data/config/locales/ro-RO.yml +51 -2
  87. data/config/locales/ru.yml +0 -1
  88. data/config/locales/sk.yml +0 -2
  89. data/config/locales/sl.yml +7 -0
  90. data/config/locales/so-SO.yml +1 -0
  91. data/config/locales/sv.yml +51 -2
  92. data/config/locales/ti-ER.yml +1 -0
  93. data/config/locales/tr-TR.yml +0 -1
  94. data/config/locales/vi-VN.yml +1 -0
  95. data/config/locales/vi.yml +1 -0
  96. data/config/locales/zh-CN.yml +172 -0
  97. data/config/locales/zh-TW.yml +1 -0
  98. data/db/migrate/20200130194123_create_decidim_forms_display_conditions.rb +20 -0
  99. data/lib/decidim/exporters/form_pdf.rb +33 -0
  100. data/lib/decidim/exporters/form_pdf_controller_helper.rb +11 -0
  101. data/lib/decidim/forms.rb +5 -0
  102. data/lib/decidim/forms/admin_engine.rb +1 -1
  103. data/lib/decidim/forms/test.rb +6 -0
  104. data/lib/decidim/forms/test/factories.rb +30 -0
  105. data/lib/decidim/forms/test/shared_examples/has_questionnaire.rb +619 -38
  106. data/lib/decidim/forms/test/shared_examples/manage_questionnaire_answers.rb +108 -0
  107. data/lib/decidim/forms/test/shared_examples/manage_questionnaires.rb +21 -894
  108. data/lib/decidim/forms/test/shared_examples/manage_questionnaires/add_display_conditions.rb +179 -0
  109. data/lib/decidim/forms/test/shared_examples/manage_questionnaires/add_questions.rb +463 -0
  110. data/lib/decidim/forms/test/shared_examples/manage_questionnaires/update_display_conditions.rb +93 -0
  111. data/lib/decidim/forms/test/shared_examples/manage_questionnaires/update_questions.rb +461 -0
  112. data/lib/decidim/forms/version.rb +1 -1
  113. metadata +91 -11
@@ -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,7 @@ 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]
14
15
  attribute :matrix_rows, Array[QuestionMatrixRowForm]
15
16
  attribute :max_choices, Integer
16
17
  attribute :deleted, Boolean, default: false
@@ -49,8 +49,23 @@ module Decidim
49
49
  choices.select(&:body)
50
50
  end
51
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
+
52
59
  private
53
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
+
54
69
  def grouped_choices
55
70
  selected_choices.group_by(&:matrix_row_id).values
56
71
  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
@@ -14,6 +16,10 @@ module Decidim
14
16
  "questionnaire_question_#{question.to_param}_answer_option_#{answer_option.to_param}"
15
17
  end
16
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
+
17
23
  def tabs_id_for_question_matrix_row(question, matrix_row)
18
24
  "questionnaire_question_#{question.to_param}_matrix_row_#{matrix_row.to_param}"
19
25
  end
@@ -29,6 +35,21 @@ module Decidim
29
35
  truncate translated_attribute(title), length: options[:max_length], omission: options[:omission]
30
36
  end
31
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
32
53
  end
33
54
  end
34
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
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Decidim
4
+ module Forms
5
+ module Admin
6
+ module Concerns
7
+ # Url helper for HasQuestionnaireAnswers controller concern
8
+ #
9
+ module HasQuestionnaireAnswersUrlHelper
10
+ def self.included(base)
11
+ base.helper_method :questionnaire_url, :questionnaire_participants_url,
12
+ :questionnaire_participant_answers_url, :questionnaire_export_response_url
13
+ end
14
+
15
+ # You can implement this method in your controller to change the URL
16
+ # where the questionnaire can be edited.
17
+ def questionnaire_url
18
+ url_for(questionnaire.questionnaire_for)
19
+ end
20
+
21
+ # You can implement this method in your controller to change the URL
22
+ # where the questionnaire participants' info will be shown.
23
+ def questionnaire_participants_url
24
+ url_for([:index, questionnaire.questionnaire_for, format: nil])
25
+ end
26
+
27
+ # You can implement this method in your controller to change the URL
28
+ # where the user's questionnaire answers will be shown.
29
+ def questionnaire_participant_answers_url(session_token)
30
+ url_for([:show, questionnaire.questionnaire_for, session_token: session_token])
31
+ end
32
+
33
+ def questionnaire_export_response_url(session_token)
34
+ url_for([:export_response, questionnaire.questionnaire_for, session_token: session_token, format: "pdf"])
35
+ end
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Decidim
4
+ module Forms
5
+ module Admin
6
+ # Custom helpers for questionnaire answers
7
+ #
8
+ module QuestionnaireAnswersHelper
9
+ def first_table_th(answer)
10
+ return translated_attribute answer.first_short_answer.question.body if answer.first_short_answer
11
+
12
+ t("session_token", scope: "decidim.forms.user_answers_serializer")
13
+ end
14
+
15
+ def first_table_td(answer)
16
+ return answer.first_short_answer.body if answer.first_short_answer
17
+
18
+ answer.session_token
19
+ end
20
+
21
+ def display_percentage(number)
22
+ number_to_percentage(number, precision: 0, strip_insignificant_zeros: true, locale: I18n.locale)
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Decidim
4
+ module Forms
5
+ class ExportQuestionnaireAnswersJob < ApplicationJob
6
+ queue_as :default
7
+
8
+ def perform(user, title, answers)
9
+ return if user&.email.blank?
10
+ return if answers&.blank?
11
+
12
+ serializer = Decidim::Forms::UserAnswersSerializer
13
+ export_data = Decidim::Exporters::FormPDF.new(answers, serializer).export
14
+
15
+ ExportMailer.export(user, title, export_data).deliver_now
16
+ end
17
+ end
18
+ end
19
+ end
@@ -17,9 +17,6 @@ module Decidim
17
17
  dependent: :destroy,
18
18
  inverse_of: :answer
19
19
 
20
- validates :body, presence: true, if: -> { question.mandatory_body? }
21
- validates :choices, presence: true, if: -> { question.mandatory_choices? }
22
-
23
20
  validate :user_questionnaire_same_organization
24
21
  validate :question_belongs_to_questionnaire
25
22
 
@@ -3,9 +3,23 @@
3
3
  module Decidim
4
4
  module Forms
5
5
  class AnswerOption < Forms::ApplicationRecord
6
+ include Decidim::TranslatableResource
7
+
6
8
  default_scope { order(arel_table[:id].asc) }
7
9
 
10
+ translatable_fields :body
11
+
8
12
  belongs_to :question, class_name: "Question", foreign_key: "decidim_question_id"
13
+
14
+ has_many :display_conditions,
15
+ class_name: "DisplayCondition",
16
+ foreign_key: "decidim_answer_option_id",
17
+ dependent: :nullify,
18
+ inverse_of: :answer_option
19
+
20
+ def translated_body
21
+ Decidim::Forms::AnswerOptionPresenter.new(self).translated_body
22
+ end
9
23
  end
10
24
  end
11
25
  end
@@ -0,0 +1,51 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Decidim
4
+ module Forms
5
+ # The data store for a DisplayCondition in the Decidim::Forms component.
6
+ # A display condition is associated to two questions. :question is the question
7
+ # that we want to display or hide based on some conditions, and :condition_question
8
+ # is the question the answers of which are checked against the conditions.
9
+ # Conditions can be whether the question is answered ("answered") or it is not ("not_answered"),
10
+ # if the selected answer option is ("equal") or is not ("not_equal") a given one, or whethe
11
+ # the text value of the answer matches a string ("match").
12
+ class DisplayCondition < Forms::ApplicationRecord
13
+ enum condition_type: [:answered, :not_answered, :equal, :not_equal, :match], _prefix: true
14
+
15
+ # Question which will be displayed or hidden
16
+ belongs_to :question, class_name: "Question", foreign_key: "decidim_question_id", inverse_of: :display_conditions
17
+
18
+ # Question the answers of which are checked against conditions
19
+ belongs_to :condition_question, class_name: "Question", foreign_key: "decidim_condition_question_id", inverse_of: :display_conditions_for_other_questions
20
+
21
+ # Answer option provided to check for "equal" or "not_equal" (optional)
22
+ belongs_to :answer_option, class_name: "AnswerOption", foreign_key: "decidim_answer_option_id", optional: true
23
+
24
+ def fulfilled?(answer)
25
+ case condition_type
26
+ when "answered"
27
+ answer.present?
28
+ when "not_answered"
29
+ answer.blank?
30
+ when "equal"
31
+ answer.present? ? answer.choices.pluck(:decidim_answer_option_id).include?(answer_option.id) : false
32
+ when "not_equal"
33
+ answer.present? ? !answer.choices.pluck(:decidim_answer_option_id).include?(answer_option.id) : true
34
+ when "match"
35
+ answer.present? ? condition_value.values.any? { |value| answer.body.match?(Regexp.new(value, Regexp::IGNORECASE)) } : false
36
+ end
37
+ end
38
+
39
+ def to_html_data
40
+ {
41
+ id: id,
42
+ type: condition_type,
43
+ condition: decidim_condition_question_id,
44
+ option: decidim_answer_option_id,
45
+ mandatory: mandatory,
46
+ value: condition_value&.dig(I18n.locale.to_s)
47
+ }.compact
48
+ end
49
+ end
50
+ end
51
+ end
@@ -4,10 +4,14 @@ module Decidim
4
4
  module Forms
5
5
  # The data store for a Question in the Decidim::Forms component.
6
6
  class Question < Forms::ApplicationRecord
7
+ include Decidim::TranslatableResource
8
+
7
9
  QUESTION_TYPES = %w(short_answer long_answer single_option multiple_option sorting matrix_single matrix_multiple).freeze
8
10
  SEPARATOR_TYPE = "separator"
9
11
  TYPES = (QUESTION_TYPES + [SEPARATOR_TYPE]).freeze
10
12
 
13
+ translatable_fields :body, :description
14
+
11
15
  belongs_to :questionnaire, class_name: "Questionnaire", foreign_key: "decidim_questionnaire_id"
12
16
 
13
17
  has_many :matrix_rows,
@@ -22,8 +26,34 @@ module Decidim
22
26
  dependent: :destroy,
23
27
  inverse_of: :question
24
28
 
29
+ # Conditions to display this question in questionnaire
30
+ has_many :display_conditions,
31
+ class_name: "DisplayCondition",
32
+ foreign_key: "decidim_question_id",
33
+ dependent: :destroy,
34
+ inverse_of: :question
35
+
36
+ # Conditions to display other questions based on the value of this question's answer
37
+ has_many :display_conditions_for_other_questions,
38
+ class_name: "DisplayCondition",
39
+ foreign_key: "decidim_condition_question_id",
40
+ dependent: :destroy,
41
+ inverse_of: :question
42
+
43
+ # Questions which have display conditions based on the value of this question's answer
44
+ has_many :conditioned_questions,
45
+ through: :display_conditions_for_other_questions,
46
+ foreign_key: "decidim_condition_question_id",
47
+ class_name: "Question"
48
+
25
49
  validates :question_type, inclusion: { in: TYPES }
26
50
 
51
+ scope :with_body, -> { where(question_type: %w(short_answer long_answer)) }
52
+ scope :with_choices, -> { where.not(question_type: %w(short_answer long_answer)) }
53
+
54
+ scope :conditioned, -> { includes(:display_conditions).where.not(decidim_forms_display_conditions: { id: nil }) }
55
+ scope :not_conditioned, -> { includes(:display_conditions).where(decidim_forms_display_conditions: { id: nil }) }
56
+
27
57
  def matrix?
28
58
  %w(matrix_single matrix_multiple).include?(question_type)
29
59
  end
@@ -44,6 +74,10 @@ module Decidim
44
74
  answer_options.size
45
75
  end
46
76
 
77
+ def translated_body
78
+ Decidim::Forms::QuestionPresenter.new(self).translated_body
79
+ end
80
+
47
81
  def separator?
48
82
  question_type.to_s == SEPARATOR_TYPE
49
83
  end