decidim-forms 0.23.1 → 0.24.0.rc2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (78) hide show
  1. checksums.yaml +4 -4
  2. data/app/assets/javascripts/decidim/forms/forms.js.es6 +4 -4
  3. data/app/commands/decidim/forms/admin/update_questionnaire.rb +2 -1
  4. data/app/commands/decidim/forms/answer_questionnaire.rb +43 -1
  5. data/app/controllers/decidim/forms/admin/concerns/has_questionnaire.rb +10 -2
  6. data/app/controllers/decidim/forms/concerns/has_questionnaire.rb +13 -6
  7. data/app/forms/decidim/forms/admin/question_form.rb +2 -0
  8. data/app/forms/decidim/forms/answer_form.rb +27 -0
  9. data/app/helpers/decidim/forms/admin/application_helper.rb +1 -6
  10. data/app/helpers/decidim/forms/admin/concerns/has_questionnaire_answers_url_helper.rb +3 -3
  11. data/app/helpers/decidim/forms/admin/questionnaire_answers_helper.rb +5 -2
  12. data/app/jobs/decidim/forms/export_questionnaire_answers_job.rb +1 -1
  13. data/app/models/decidim/forms/answer.rb +8 -0
  14. data/app/models/decidim/forms/question.rb +9 -3
  15. data/app/models/decidim/forms/questionnaire.rb +11 -0
  16. data/app/presenters/decidim/forms/admin/questionnaire_answer_presenter.rb +20 -0
  17. data/app/presenters/decidim/forms/admin/questionnaire_participant_presenter.rb +4 -4
  18. data/app/queries/decidim/forms/questionnaire_user_answers.rb +2 -1
  19. data/app/views/decidim/forms/admin/questionnaires/_form.html.erb +3 -3
  20. data/app/views/decidim/forms/admin/questionnaires/_question.html.erb +11 -0
  21. data/app/views/decidim/forms/admin/questionnaires/edit.html.erb +1 -1
  22. data/app/views/decidim/forms/questionnaires/_answer.html.erb +1 -1
  23. data/app/views/decidim/forms/questionnaires/answers/_files.html.erb +1 -0
  24. data/app/views/decidim/forms/questionnaires/answers/_long_answer.html.erb +1 -1
  25. data/app/views/decidim/forms/questionnaires/answers/_matrix_multiple.html.erb +2 -1
  26. data/app/views/decidim/forms/questionnaires/answers/_matrix_single.html.erb +2 -1
  27. data/app/views/decidim/forms/questionnaires/answers/_multiple_option.html.erb +2 -1
  28. data/app/views/decidim/forms/questionnaires/answers/_short_answer.html.erb +1 -1
  29. data/app/views/decidim/forms/questionnaires/answers/_single_option.html.erb +2 -1
  30. data/app/views/decidim/forms/questionnaires/show.html.erb +1 -1
  31. data/config/initializers/wicked_pdf.rb +2 -0
  32. data/config/locales/ca.yml +8 -2
  33. data/config/locales/cs.yml +10 -4
  34. data/config/locales/de.yml +10 -2
  35. data/config/locales/el.yml +1 -1
  36. data/config/locales/en.yml +8 -2
  37. data/config/locales/es-MX.yml +8 -2
  38. data/config/locales/es-PY.yml +8 -2
  39. data/config/locales/es.yml +8 -2
  40. data/config/locales/fi-plain.yml +8 -2
  41. data/config/locales/fi.yml +8 -2
  42. data/config/locales/fr-CA.yml +8 -2
  43. data/config/locales/fr.yml +10 -4
  44. data/config/locales/it.yml +1 -2
  45. data/config/locales/ja.yml +3 -3
  46. data/config/locales/lv.yml +1 -1
  47. data/config/locales/nl.yml +4 -5
  48. data/config/locales/no.yml +1 -1
  49. data/config/locales/pl.yml +11 -2
  50. data/config/locales/pt.yml +1 -2
  51. data/config/locales/ro-RO.yml +1 -2
  52. data/config/locales/si-LK.yml +1 -0
  53. data/config/locales/sv.yml +1 -2
  54. data/config/locales/sw-KE.yml +1 -0
  55. data/config/locales/tr-TR.yml +88 -1
  56. data/config/locales/zh-CN.yml +1 -2
  57. data/db/migrate/20201110152921_add_salt_to_decidim_forms_questionnaires.rb +16 -0
  58. data/db/migrate/20210208094442_add_max_characters_to_decidim_forms_questions.rb +7 -0
  59. data/lib/decidim/api/answer_option_type.rb +13 -0
  60. data/lib/decidim/api/question_type.rb +21 -0
  61. data/lib/decidim/api/questionnaire_entity_interface.rb +5 -5
  62. data/lib/decidim/api/questionnaire_type.rb +19 -0
  63. data/lib/decidim/exporters/form_pdf_controller_helper.rb +2 -0
  64. data/lib/decidim/forms.rb +1 -1
  65. data/lib/decidim/forms/api.rb +3 -0
  66. data/lib/decidim/forms/data_portability_user_answers_serializer.rb +7 -1
  67. data/lib/decidim/forms/test/factories.rb +13 -2
  68. data/lib/decidim/forms/test/shared_examples/has_questionnaire.rb +38 -5
  69. data/lib/decidim/forms/test/shared_examples/manage_questionnaires.rb +5 -4
  70. data/lib/decidim/forms/test/shared_examples/manage_questionnaires/add_display_conditions.rb +7 -3
  71. data/lib/decidim/forms/test/shared_examples/manage_questionnaires/add_questions.rb +1 -1
  72. data/lib/decidim/forms/test/shared_examples/manage_questionnaires/update_questions.rb +5 -0
  73. data/lib/decidim/forms/user_answers_serializer.rb +10 -2
  74. data/lib/decidim/forms/version.rb +1 -1
  75. metadata +23 -18
  76. data/app/types/decidim/forms/answer_option_type.rb +0 -14
  77. data/app/types/decidim/forms/question_type.rb +0 -23
  78. data/app/types/decidim/forms/questionnaire_type.rb +0 -22
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 00b6fa084292af8d4b343f801ccc5725a7983e3390b41dcd00f2b84b34afc914
4
- data.tar.gz: 9f037814dca4fd315e6425074dc2d8c42f8c144d1bb8c5042897dd8ab5410bdf
3
+ metadata.gz: 6a5539df9d1d2dd15d72a9d05c48ac67a4724c144f2e7e05eab201eacc5bf512
4
+ data.tar.gz: 2ebd85d3dc62b8fabb7ad1858be62b9a70070c36d8cd67be84d39a021ca9b1ab
5
5
  SHA512:
6
- metadata.gz: fc48b056500a941f30b72dd044e9614008b8a0e84111e10af937ce00bab58d13a2e8938a4fa7a8dea9660dbb6c00ffc5b897330f42a9f73a2203edd5c86dd6d9
7
- data.tar.gz: a0dbe8818ba0efbe2d7d8b1f9503c5b07cde7023437d81ab5ee1c1695a16631d26e054cc8dd072460c97c652101ef34ef5489e6bfb596cc6376d58f3e53fd184
6
+ metadata.gz: 56e81b75394541cd766d6b1b9a20cfb0545c04ff6c6d509acbea6633ca91d4c18f90241c0d4342d56f28dde94ce3f55c4675c8238e2493145a87403b4b5076e6
7
+ data.tar.gz: be237ce6925bb2dbc3551578f8e9ef3c2b780b0cb08e4945632107653ae2c43af304cb98f7526432abd58d83c688761ba1f0c31a81c1a582372b4dd49cd8f797
@@ -53,16 +53,16 @@
53
53
  window.exitUrl = event.currentTarget.action;
54
54
  });
55
55
 
56
- window.onbeforeunload = () => {
56
+ window.addEventListener("beforeunload", (event) => {
57
57
  const exitUrl = window.exitUrl;
58
58
  const hasChanged = $form.data("changed");
59
59
  window.exitUrl = null;
60
60
 
61
61
  if (!hasChanged || (exitUrl && exitUrl.includes(safePath))) {
62
- return null;
62
+ return;
63
63
  }
64
64
 
65
- return "";
66
- }
65
+ event.returnValue = true;
66
+ });
67
67
  }
68
68
  })(window);
@@ -48,7 +48,8 @@ module Decidim
48
48
  position: form_question.position,
49
49
  mandatory: form_question.mandatory,
50
50
  question_type: form_question.question_type,
51
- max_choices: form_question.max_choices
51
+ max_choices: form_question.max_choices,
52
+ max_characters: form_question.max_characters
52
53
  }
53
54
 
54
55
  update_nested_model(form_question, question_attributes, @questionnaire.questions) do |question|
@@ -4,6 +4,8 @@ module Decidim
4
4
  module Forms
5
5
  # This command is executed when the user answers a Questionnaire.
6
6
  class AnswerQuestionnaire < Rectify::Command
7
+ include ::Decidim::MultipleAttachmentsMethods
8
+
7
9
  # Initializes a AnswerQuestionnaire Command.
8
10
  #
9
11
  # form - The form from which to get the data.
@@ -21,14 +23,34 @@ module Decidim
21
23
  return broadcast(:invalid) if @form.invalid?
22
24
 
23
25
  answer_questionnaire
24
- broadcast(:ok)
26
+
27
+ if @errors
28
+ reset_form_attachments
29
+ broadcast(:invalid)
30
+ else
31
+ broadcast(:ok)
32
+ end
25
33
  end
26
34
 
27
35
  attr_reader :form
28
36
 
29
37
  private
30
38
 
39
+ # This method will add an error to the `add_documents` field only if there's
40
+ # any error in any other field or an error in another answer in the
41
+ # questionnaire. This is needed because when the form has
42
+ # an error, the attachments are lost, so we need a way to inform the user
43
+ # of this problem.
44
+ def reset_form_attachments
45
+ @form.responses.each do |answer|
46
+ answer.errors.add(:add_documents, :needs_to_be_reattached) if answer.has_attachments?
47
+ end
48
+ end
49
+
31
50
  def answer_questionnaire
51
+ @main_form = @form
52
+ @errors = nil
53
+
32
54
  Answer.transaction do
33
55
  form.responses_by_step.flatten.select(&:display_conditions_fulfilled?).each do |form_answer|
34
56
  answer = Answer.new(
@@ -51,7 +73,27 @@ module Decidim
51
73
  end
52
74
 
53
75
  answer.save!
76
+
77
+ next unless form_answer.question.has_attachments?
78
+
79
+ # The attachments module expects `@form` to be the form with the
80
+ # attachments
81
+ @form = form_answer
82
+ @attached_to = answer
83
+
84
+ build_attachments
85
+
86
+ if attachments_invalid?
87
+ @errors = true
88
+ next
89
+ end
90
+
91
+ create_attachments if process_attachments?
92
+ document_cleanup!
54
93
  end
94
+
95
+ @form = @main_form
96
+ raise ActiveRecord::Rollback if @errors
55
97
  end
56
98
  end
57
99
  end
@@ -15,8 +15,10 @@ module Decidim
15
15
 
16
16
  included do
17
17
  helper Decidim::Forms::Admin::ApplicationHelper
18
+ include Decidim::TranslatableAttributes
19
+
18
20
  helper_method :questionnaire_for, :questionnaire, :blank_question, :blank_answer_option, :blank_matrix_row,
19
- :blank_display_condition, :question_types, :display_condition_types, :update_url, :public_url, :answer_options_url
21
+ :blank_display_condition, :question_types, :display_condition_types, :update_url, :public_url, :answer_options_url, :edit_questionnaire_title
20
22
 
21
23
  if defined? Decidim::Templates::Admin
22
24
  include Decidim::Templates::Admin::Concerns::Templatable
@@ -97,7 +99,13 @@ module Decidim
97
99
  # Returns the url to get the answer options json (for the display conditions form)
98
100
  # for the question with id = params[:id]
99
101
  def answer_options_url(params)
100
- url_for([questionnaire.questionnaire_for, action: :answer_options, format: :json, **params])
102
+ url_for([questionnaire.questionnaire_for, { action: :answer_options, format: :json, **params }])
103
+ end
104
+
105
+ # Implement this method in your controller to set the title
106
+ # of the edit form.
107
+ def edit_questionnaire_title
108
+ t(:title, scope: "decidim.forms.admin.questionnaires.form", questionnaire_for: translated_attribute(questionnaire_for.try(:title)))
101
109
  end
102
110
 
103
111
  private
@@ -26,7 +26,7 @@ module Decidim
26
26
  end
27
27
 
28
28
  def answer
29
- enforce_permission_to :answer, :questionnaire
29
+ enforce_permission_to_answer_questionnaire
30
30
 
31
31
  @form = form(Decidim::Forms::QuestionnaireForm).from_params(params, session_token: session_token, ip_hash: ip_hash)
32
32
 
@@ -78,7 +78,7 @@ module Decidim
78
78
  # You can implement this method in your controller to change the URL
79
79
  # where the questionnaire will be submitted.
80
80
  def update_url
81
- url_for([questionnaire_for, action: :answer])
81
+ url_for([questionnaire_for, { action: :answer }])
82
82
  end
83
83
 
84
84
  # Points to the shortest path accessing the current form. This will be
@@ -87,7 +87,7 @@ module Decidim
87
87
  #
88
88
  # Overwrite this method at the controller.
89
89
  def form_path
90
- url_for([questionnaire_for, only_path: true])
90
+ url_for([questionnaire_for, { only_path: true }])
91
91
  end
92
92
 
93
93
  # Public: Method to be implemented at the controller. You need to
@@ -107,7 +107,7 @@ module Decidim
107
107
  end
108
108
 
109
109
  def spam_detected
110
- enforce_permission_to :answer, :questionnaire
110
+ enforce_permission_to_answer_questionnaire
111
111
 
112
112
  @form = form(Decidim::Forms::QuestionnaireForm).from_params(params)
113
113
 
@@ -115,6 +115,12 @@ module Decidim
115
115
  render template: "decidim/forms/questionnaires/show"
116
116
  end
117
117
 
118
+ # You can implement this method in your controller to change the
119
+ # enforce_permission_to arguments.
120
+ def enforce_permission_to_answer_questionnaire
121
+ enforce_permission_to :answer, :questionnaire
122
+ end
123
+
118
124
  def ip_hash
119
125
  return nil unless request&.remote_ip
120
126
 
@@ -131,8 +137,9 @@ module Decidim
131
137
  @session_token ||= tokenize(id || session_id)
132
138
  end
133
139
 
134
- def tokenize(id)
135
- Digest::MD5.hexdigest("#{id}-#{Rails.application.secrets.secret_key_base}")
140
+ def tokenize(id, length: 10)
141
+ tokenizer = Decidim::Tokenizer.new(salt: questionnaire.salt || questionnaire.id, length: length)
142
+ tokenizer.int_digest(id).to_s
136
143
  end
137
144
  end
138
145
  end
@@ -14,6 +14,7 @@ module Decidim
14
14
  attribute :display_conditions, Array[DisplayConditionForm]
15
15
  attribute :matrix_rows, Array[QuestionMatrixRowForm]
16
16
  attribute :max_choices, Integer
17
+ attribute :max_characters, Integer, default: 0
17
18
  attribute :deleted, Boolean, default: false
18
19
 
19
20
  translatable_attribute :body, String
@@ -22,6 +23,7 @@ module Decidim
22
23
  validates :position, numericality: { greater_than_or_equal_to: 0 }
23
24
  validates :question_type, inclusion: { in: Decidim::Forms::Question::TYPES }
24
25
  validates :max_choices, numericality: { only_integer: true, greater_than: 1, less_than_or_equal_to: ->(form) { form.number_of_options } }, allow_blank: true
26
+ validates :max_characters, numericality: { only_integer: true, greater_than_or_equal_to: 0 }
25
27
  validates :body, translatable_presence: true, if: :requires_body?
26
28
  validates :matrix_rows, presence: true, if: :matrix?
27
29
  validates :answer_options, presence: true, if: :matrix?
@@ -5,18 +5,23 @@ module Decidim
5
5
  # This class holds a Form to save the questionnaire answers from Decidim's public page
6
6
  class AnswerForm < Decidim::Form
7
7
  include Decidim::TranslationsHelper
8
+ include Decidim::AttachmentAttributes
8
9
 
9
10
  attribute :question_id, String
10
11
  attribute :body, String
11
12
  attribute :choices, Array[AnswerChoiceForm]
12
13
  attribute :matrix_choices, Array[AnswerChoiceForm]
13
14
 
15
+ attachments_attribute :documents
16
+
14
17
  validates :body, presence: true, if: :mandatory_body?
15
18
  validates :selected_choices, presence: true, if: :mandatory_choices?
16
19
 
17
20
  validate :max_choices, if: -> { question.max_choices }
18
21
  validate :all_choices, if: -> { question.question_type == "sorting" }
19
22
  validate :min_choices, if: -> { question.matrix? && question.mandatory? }
23
+ validate :documents_present, if: -> { question.question_type == "files" && question.mandatory? }
24
+ validate :max_characters, if: -> { question.max_characters.positive? }
20
25
 
21
26
  delegate :mandatory_body?, :mandatory_choices?, :matrix?, to: :question
22
27
 
@@ -49,6 +54,10 @@ module Decidim
49
54
  choices.select(&:body)
50
55
  end
51
56
 
57
+ def custom_choices
58
+ choices.select(&:custom_body)
59
+ end
60
+
52
61
  def display_conditions_fulfilled?
53
62
  question.display_conditions.all? do |condition|
54
63
  answer = context.responses&.find { |r| r.question_id&.to_i == condition.condition_question.id }
@@ -56,6 +65,10 @@ module Decidim
56
65
  end
57
66
  end
58
67
 
68
+ def has_attachments?
69
+ question.has_attachments? && errors[:add_documents].empty? && add_documents.present?
70
+ end
71
+
59
72
  private
60
73
 
61
74
  def mandatory_body?
@@ -78,6 +91,16 @@ module Decidim
78
91
  end
79
92
  end
80
93
 
94
+ def max_characters
95
+ if body.present?
96
+ errors.add(:body, :too_long) if body.size > question.max_characters
97
+ elsif custom_choices.any?
98
+ custom_choices.each do |choice|
99
+ errors.add(:body, :too_long) if choice.custom_body.size > question.max_characters
100
+ end
101
+ end
102
+ end
103
+
81
104
  def min_choices
82
105
  errors.add(:choices, :missing) if grouped_choices.count != question.matrix_rows.count
83
106
  end
@@ -93,6 +116,10 @@ module Decidim
93
116
  def max_choices_label
94
117
  I18n.t("questionnaires.question.max_choices", scope: "decidim.forms", n: question.max_choices)
95
118
  end
119
+
120
+ def documents_present
121
+ errors.add(:add_documents, :blank) if add_documents.empty? && errors[:add_documents].empty?
122
+ end
96
123
  end
97
124
  end
98
125
  end
@@ -31,7 +31,7 @@ module Decidim
31
31
  "placeholder" => options[:placeholder],
32
32
  "locale" => I18n.locale
33
33
  }
34
- content_tag :span, class: options[:class], data: data do
34
+ tag.span(class: options[:class], data: data) do
35
35
  truncate translated_attribute(title), length: options[:max_length], omission: options[:omission]
36
36
  end
37
37
  end
@@ -45,11 +45,6 @@ module Decidim
45
45
  def templates_defined?
46
46
  defined? Decidim::Templates::Admin::Concerns::Templatable
47
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
53
48
  end
54
49
  end
55
50
  end
@@ -21,17 +21,17 @@ module Decidim
21
21
  # You can implement this method in your controller to change the URL
22
22
  # where the questionnaire participants' info will be shown.
23
23
  def questionnaire_participants_url
24
- url_for([:index, questionnaire.questionnaire_for, format: nil])
24
+ url_for([:index, questionnaire.questionnaire_for, { format: nil }])
25
25
  end
26
26
 
27
27
  # You can implement this method in your controller to change the URL
28
28
  # where the user's questionnaire answers will be shown.
29
29
  def questionnaire_participant_answers_url(session_token)
30
- url_for([:show, questionnaire.questionnaire_for, session_token: session_token])
30
+ url_for([:show, questionnaire.questionnaire_for, { session_token: session_token }])
31
31
  end
32
32
 
33
33
  def questionnaire_export_response_url(session_token)
34
- url_for([:export_response, questionnaire.questionnaire_for, session_token: session_token, format: "pdf"])
34
+ url_for([:export_response, questionnaire.questionnaire_for, { session_token: session_token, format: "pdf" }])
35
35
  end
36
36
  end
37
37
  end
@@ -7,13 +7,16 @@ module Decidim
7
7
  #
8
8
  module QuestionnaireAnswersHelper
9
9
  def first_table_th(answer)
10
- return translated_attribute answer.first_short_answer.question.body if answer.first_short_answer
10
+ if answer.first_short_answer
11
+ @first_short_answer = answer.first_short_answer
12
+ return translated_attribute @first_short_answer.question.body
13
+ end
11
14
 
12
15
  t("session_token", scope: "decidim.forms.user_answers_serializer")
13
16
  end
14
17
 
15
18
  def first_table_td(answer)
16
- return answer.first_short_answer.body if answer.first_short_answer
19
+ return answer.first_short_answer&.body if @first_short_answer
17
20
 
18
21
  answer.session_token
19
22
  end
@@ -7,7 +7,7 @@ module Decidim
7
7
 
8
8
  def perform(user, title, answers)
9
9
  return if user&.email.blank?
10
- return if answers&.blank?
10
+ return if answers.blank?
11
11
 
12
12
  serializer = Decidim::Forms::UserAnswersSerializer
13
13
  export_data = Decidim::Exporters::FormPDF.new(answers, serializer).export
@@ -6,6 +6,7 @@ module Decidim
6
6
  class Answer < Forms::ApplicationRecord
7
7
  include Decidim::DataPortability
8
8
  include Decidim::NewsletterParticipant
9
+ include Decidim::HasAttachments
9
10
 
10
11
  belongs_to :user, class_name: "Decidim::User", foreign_key: "decidim_user_id", optional: true
11
12
  belongs_to :questionnaire, class_name: "Questionnaire", foreign_key: "decidim_questionnaire_id"
@@ -20,6 +21,8 @@ module Decidim
20
21
  validate :user_questionnaire_same_organization
21
22
  validate :question_belongs_to_questionnaire
22
23
 
24
+ scope :not_separator, -> { joins(:question).where.not(decidim_forms_questions: { question_type: Decidim::Forms::Question::SEPARATOR_TYPE }) }
25
+
23
26
  def self.user_collection(user)
24
27
  where(decidim_user_id: user.id)
25
28
  end
@@ -39,6 +42,11 @@ module Decidim
39
42
  answers.pluck(:decidim_user_id).flatten.compact.uniq
40
43
  end
41
44
 
45
+ def organization
46
+ user.organization if user.present?
47
+ questionnaire&.questionnaire_for.try(:organization)
48
+ end
49
+
42
50
  private
43
51
 
44
52
  def user_questionnaire_same_organization
@@ -6,7 +6,7 @@ module Decidim
6
6
  class Question < Forms::ApplicationRecord
7
7
  include Decidim::TranslatableResource
8
8
 
9
- QUESTION_TYPES = %w(short_answer long_answer single_option multiple_option sorting matrix_single matrix_multiple).freeze
9
+ QUESTION_TYPES = %w(short_answer long_answer single_option multiple_option sorting files matrix_single matrix_multiple).freeze
10
10
  SEPARATOR_TYPE = "separator"
11
11
  TYPES = (QUESTION_TYPES + [SEPARATOR_TYPE]).freeze
12
12
 
@@ -48,6 +48,8 @@ module Decidim
48
48
 
49
49
  validates :question_type, inclusion: { in: TYPES }
50
50
 
51
+ scope :not_separator, -> { where.not(question_type: SEPARATOR_TYPE) }
52
+
51
53
  scope :with_body, -> { where(question_type: %w(short_answer long_answer)) }
52
54
  scope :with_choices, -> { where.not(question_type: %w(short_answer long_answer)) }
53
55
 
@@ -63,11 +65,11 @@ module Decidim
63
65
  end
64
66
 
65
67
  def mandatory_body?
66
- mandatory? && !multiple_choice?
68
+ mandatory? && !multiple_choice? && !has_attachments?
67
69
  end
68
70
 
69
71
  def mandatory_choices?
70
- mandatory? && multiple_choice?
72
+ mandatory? && multiple_choice? && !has_attachments?
71
73
  end
72
74
 
73
75
  def number_of_options
@@ -81,6 +83,10 @@ module Decidim
81
83
  def separator?
82
84
  question_type.to_s == SEPARATOR_TYPE
83
85
  end
86
+
87
+ def has_attachments?
88
+ question_type.to_s == "files"
89
+ end
84
90
  end
85
91
  end
86
92
  end
@@ -14,6 +14,8 @@ module Decidim
14
14
  has_many :questions, -> { order(:position) }, class_name: "Question", foreign_key: "decidim_questionnaire_id", dependent: :destroy
15
15
  has_many :answers, class_name: "Answer", foreign_key: "decidim_questionnaire_id", dependent: :destroy
16
16
 
17
+ after_initialize :set_default_salt
18
+
17
19
  # Public: returns whether the questionnaire questions can be modified or not.
18
20
  def questions_editable?
19
21
  has_component = questionnaire_for.respond_to? :component
@@ -29,6 +31,15 @@ module Decidim
29
31
  def pristine?
30
32
  created_at.to_i == updated_at.to_i && questions.empty?
31
33
  end
34
+
35
+ private
36
+
37
+ # salt is used to generate secure hash in anonymous answers
38
+ def set_default_salt
39
+ return unless defined?(salt)
40
+
41
+ self.salt ||= Tokenizer.random_salt
42
+ end
32
43
  end
33
44
  end
34
45
  end