decidim-initiatives 0.16.1 → 0.17.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (126) hide show
  1. checksums.yaml +4 -4
  2. data/app/assets/javascripts/decidim/initiatives/scoped_type.js +13 -10
  3. data/app/assets/stylesheet/decidim/initiatives/initiatives-votes.css.scss +97 -0
  4. data/app/cells/decidim/initiatives/initiative_m_cell.rb +1 -1
  5. data/app/cells/decidim/initiatives_votes/vote/show.erb +29 -0
  6. data/app/cells/decidim/initiatives_votes/vote_cell.rb +51 -0
  7. data/app/commands/decidim/initiatives/admin/create_initiative_type.rb +8 -1
  8. data/app/commands/decidim/initiatives/admin/update_initiative.rb +7 -6
  9. data/app/commands/decidim/initiatives/admin/update_initiative_answer.rb +76 -0
  10. data/app/commands/decidim/initiatives/admin/update_initiative_type.rb +17 -1
  11. data/app/commands/decidim/initiatives/create_initiative.rb +12 -0
  12. data/app/commands/decidim/initiatives/spawn_committee_request.rb +8 -7
  13. data/app/commands/decidim/initiatives/validate_mobile_phone.rb +61 -0
  14. data/app/commands/decidim/initiatives/validate_sms_code.rb +40 -0
  15. data/app/commands/decidim/initiatives/vote_initiative.rb +23 -7
  16. data/app/controllers/concerns/decidim/initiatives/needs_initiative.rb +12 -1
  17. data/app/controllers/decidim/initiatives/admin/answers_controller.rb +46 -0
  18. data/app/controllers/decidim/initiatives/admin/initiatives_controller.rb +31 -5
  19. data/app/controllers/decidim/initiatives/admin/initiatives_types_controller.rb +1 -0
  20. data/app/controllers/decidim/initiatives/admin/initiatives_types_permissions_controller.rb +20 -0
  21. data/app/controllers/decidim/initiatives/admin/moderations_controller.rb +16 -0
  22. data/app/controllers/decidim/initiatives/application_controller.rb +0 -2
  23. data/app/controllers/decidim/initiatives/authorization_sign_modals_controller.rb +26 -0
  24. data/app/controllers/decidim/initiatives/committee_requests_controller.rb +4 -1
  25. data/app/controllers/decidim/initiatives/create_initiative_controller.rb +16 -5
  26. data/app/controllers/decidim/initiatives/initiative_signatures_controller.rb +207 -0
  27. data/app/controllers/decidim/initiatives/initiative_votes_controller.rb +3 -1
  28. data/app/controllers/decidim/initiatives/initiatives_type_signature_types_controller.rb +21 -0
  29. data/app/forms/decidim/initiatives/admin/initiative_answer_form.rb +29 -0
  30. data/app/forms/decidim/initiatives/admin/initiative_form.rb +25 -7
  31. data/app/forms/decidim/initiatives/admin/initiative_type_form.rb +14 -0
  32. data/app/forms/decidim/initiatives/committee_member_form.rb +30 -0
  33. data/app/forms/decidim/initiatives/initiative_form.rb +5 -0
  34. data/app/forms/decidim/initiatives/vote_form.rb +151 -0
  35. data/app/helpers/decidim/initiatives/create_initiative_helper.rb +25 -12
  36. data/app/helpers/decidim/initiatives/initiative_helper.rb +18 -0
  37. data/app/models/decidim/initiative.rb +54 -3
  38. data/app/models/decidim/initiatives_committee_member.rb +1 -0
  39. data/app/models/decidim/initiatives_type.rb +30 -0
  40. data/app/permissions/decidim/initiatives/admin/permissions.rb +12 -3
  41. data/app/permissions/decidim/initiatives/permissions.rb +37 -8
  42. data/app/queries/decidim/initiatives/admin/admin_users.rb +39 -0
  43. data/app/services/decidim/initiatives/data_encryptor.rb +26 -0
  44. data/app/services/decidim/initiatives/dummy_timestamp.rb +22 -0
  45. data/app/services/decidim/initiatives/pdf_signature_example.rb +121 -0
  46. data/app/views/decidim/initiatives/admin/answers/_info_initiative.html.erb +23 -0
  47. data/app/views/decidim/initiatives/admin/answers/edit.html.erb +35 -0
  48. data/app/views/decidim/initiatives/admin/initiatives/_form.html.erb +17 -16
  49. data/app/views/decidim/initiatives/admin/initiatives/edit.html.erb +10 -3
  50. data/app/views/decidim/initiatives/admin/initiatives/export_pdf_signatures.pdf.erb +35 -0
  51. data/app/views/decidim/initiatives/admin/initiatives/index.html.erb +6 -0
  52. data/app/views/decidim/initiatives/admin/initiatives_types/_form.html.erb +36 -0
  53. data/app/views/decidim/initiatives/admin/initiatives_types/index.html.erb +2 -0
  54. data/app/views/decidim/initiatives/create_initiative/fill_data.html.erb +3 -3
  55. data/app/views/decidim/initiatives/create_initiative/finish.html.erb +2 -2
  56. data/app/views/decidim/initiatives/create_initiative/previous_form.html.erb +2 -2
  57. data/app/views/decidim/initiatives/create_initiative/promotal_committee.html.erb +3 -3
  58. data/app/views/decidim/initiatives/create_initiative/select_initiative_type.html.erb +37 -22
  59. data/app/views/decidim/initiatives/initiative_signatures/_wizard_steps.html.erb +19 -0
  60. data/app/views/decidim/initiatives/initiative_signatures/fill_personal_data.html.erb +43 -0
  61. data/app/views/decidim/initiatives/initiative_signatures/finish.html.erb +17 -0
  62. data/app/views/decidim/initiatives/initiative_signatures/sms_code.html.erb +22 -0
  63. data/app/views/decidim/initiatives/initiative_signatures/sms_phone_number.html.erb +22 -0
  64. data/app/views/decidim/initiatives/initiative_signatures/update_buttons_and_counters.js.erb +21 -0
  65. data/app/views/decidim/initiatives/initiative_votes/update_buttons_and_counters.js.erb +1 -1
  66. data/app/views/decidim/initiatives/initiatives/_author.html.erb +1 -1
  67. data/app/views/decidim/initiatives/initiatives/_interactions.html.erb +11 -0
  68. data/app/views/decidim/initiatives/initiatives/_progress_bar.html.erb +9 -0
  69. data/app/views/decidim/initiatives/initiatives/_result.html.erb +3 -3
  70. data/app/views/decidim/initiatives/initiatives/_vote_button.html.erb +39 -18
  71. data/app/views/decidim/initiatives/initiatives/_vote_cabin.html.erb +9 -9
  72. data/app/views/decidim/initiatives/initiatives/show.html.erb +12 -12
  73. data/app/views/decidim/initiatives/initiatives/signature_identities.html.erb +19 -9
  74. data/app/views/decidim/initiatives/initiatives_type_signature_types/search.html.erb +1 -0
  75. data/app/views/layouts/decidim/_initiative_creation_header.html.erb +24 -34
  76. data/app/views/layouts/decidim/_initiative_signature_creation_header.html.erb +27 -0
  77. data/app/views/layouts/decidim/admin/initiative.html.erb +22 -15
  78. data/app/views/layouts/decidim/admin/initiatives_votes.pdf.erb +11 -0
  79. data/app/views/layouts/decidim/initiative_creation.html.erb +15 -3
  80. data/app/views/layouts/decidim/initiative_signature_creation.html.erb +12 -0
  81. data/config/initializers/wicked_pdf.rb +23 -0
  82. data/config/locales/ar-SA.yml +138 -7
  83. data/config/locales/ca.yml +120 -28
  84. data/config/locales/cs-CZ.yml +103 -9
  85. data/config/locales/cs.yml +494 -0
  86. data/config/locales/de.yml +101 -9
  87. data/config/locales/en.yml +133 -40
  88. data/config/locales/es-MX.yml +101 -9
  89. data/config/locales/es-PY.yml +101 -9
  90. data/config/locales/es.yml +124 -32
  91. data/config/locales/eu.yml +101 -9
  92. data/config/locales/fi-pl.yml +101 -9
  93. data/config/locales/fi-plain.yml +478 -0
  94. data/config/locales/fi.yml +117 -25
  95. data/config/locales/fr.yml +102 -10
  96. data/config/locales/gl.yml +101 -9
  97. data/config/locales/hu.yml +102 -10
  98. data/config/locales/id-ID.yml +100 -9
  99. data/config/locales/it.yml +101 -9
  100. data/config/locales/nl.yml +101 -9
  101. data/config/locales/pl.yml +104 -10
  102. data/config/locales/pt-BR.yml +101 -9
  103. data/config/locales/pt.yml +101 -9
  104. data/config/locales/ru.yml +4 -9
  105. data/config/locales/sv.yml +101 -9
  106. data/config/locales/tr-TR.yml +101 -9
  107. data/config/locales/uk.yml +4 -9
  108. data/db/migrate/20181212154456_add_collect_extra_user_fields_to_initiatives_types.rb +7 -0
  109. data/db/migrate/20181212155125_add_online_signature_enabled_to_initiative_type.rb +7 -0
  110. data/db/migrate/20181212155740_add_extra_fields_legal_information_to_initiatives_types.rb +7 -0
  111. data/db/migrate/20181213184712_add_min_committee_members_to_initiative_type.rb +7 -0
  112. data/db/migrate/20181220134322_add_encrypted_metadata_to_decidim_initiatives_votes.rb +7 -0
  113. data/db/migrate/20181224100803_add_timestamp_to_decidim_initiatives_votes.rb +7 -0
  114. data/db/migrate/20181224101041_add_hash_id_to_decidim_initiatives_votes.rb +7 -0
  115. data/db/migrate/20190124170442_add_validate_sms_code_on_votes_to_initiatives_types.rb +7 -0
  116. data/db/migrate/20190125131847_add_document_number_authorization_handler_to_initiatives_types.rb +7 -0
  117. data/db/migrate/20190213184301_add_undo_online_signatures_enabled_to_initiatives_types.rb +7 -0
  118. data/lib/decidim/initiatives.rb +12 -0
  119. data/lib/decidim/initiatives/admin_engine.rb +12 -0
  120. data/lib/decidim/initiatives/engine.rb +3 -0
  121. data/lib/decidim/initiatives/participatory_space.rb +6 -1
  122. data/lib/decidim/initiatives/test/factories.rb +39 -0
  123. data/lib/decidim/initiatives/version.rb +1 -1
  124. metadata +115 -14
  125. data/app/views/decidim/initiatives/initiatives/_statistics.html.erb +0 -21
  126. data/app/views/decidim/initiatives/initiatives/_votes_count.html.erb +0 -12
@@ -0,0 +1,207 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Decidim
4
+ module Initiatives
5
+ require "wicked"
6
+
7
+ class InitiativeSignaturesController < Decidim::Initiatives::ApplicationController
8
+ layout "layouts/decidim/initiative_signature_creation"
9
+
10
+ include Wicked::Wizard
11
+ include Decidim::Initiatives::NeedsInitiative
12
+ include Decidim::FormFactory
13
+
14
+ prepend_before_action :set_wizard_steps
15
+ before_action :authenticate_user!
16
+
17
+ helper InitiativeHelper
18
+
19
+ helper_method :initiative_type, :extra_data_legal_information
20
+
21
+ # GET /initiatives/:initiative_id/initiative_signatures/:step
22
+ def show
23
+ group_id = params[:group_id] || (session[:initiative_vote_form] ||= {})["group_id"]
24
+ enforce_permission_to :sign_initiative, :initiative, initiative: current_initiative, group_id: group_id, signature_has_steps: signature_has_steps?
25
+ send("#{step}_step", initiative_vote_form: session[:initiative_vote_form])
26
+ end
27
+
28
+ # PUT /initiatives/:initiative_id/initiative_signatures/:step
29
+ def update
30
+ group_id = params.dig(:initiatives_vote, :group_id) || session[:initiative_vote_form]["group_id"]
31
+ enforce_permission_to :sign_initiative, :initiative, initiative: current_initiative, group_id: group_id, signature_has_steps: signature_has_steps?
32
+ send("#{step}_step", params)
33
+ end
34
+
35
+ # POST /initiatives/:initiative_id/initiative_signatures
36
+ def create
37
+ group_id = params[:group_id] || session[:initiative_vote_form]&.dig("group_id")
38
+ enforce_permission_to :vote, :initiative, initiative: current_initiative, group_id: group_id
39
+ @form = form(Decidim::Initiatives::VoteForm)
40
+ .from_params(
41
+ initiative_id: current_initiative.id,
42
+ author_id: current_user.id,
43
+ group_id: group_id
44
+ )
45
+
46
+ VoteInitiative.call(@form, current_user) do
47
+ on(:ok) do
48
+ current_initiative.reload
49
+ render :update_buttons_and_counters
50
+ end
51
+
52
+ on(:invalid) do
53
+ render json: {
54
+ error: I18n.t("create.error", scope: "decidim.initiatives.initiative_votes")
55
+ }, status: 422
56
+ end
57
+ end
58
+ end
59
+
60
+ private
61
+
62
+ def fill_personal_data_step(_unused)
63
+ @form = form(Decidim::Initiatives::VoteForm)
64
+ .from_params(
65
+ initiative_id: current_initiative.id,
66
+ author_id: current_user.id,
67
+ group_id: params[:group_id]
68
+ )
69
+ session[:initiative_vote_form] = { group_id: @form.group_id }
70
+ skip_step unless initiative_type.collect_user_extra_fields
71
+ render_wizard
72
+ end
73
+
74
+ def sms_phone_number_step(parameters)
75
+ if parameters.has_key?(:initiatives_vote) || !fill_personal_data_step?
76
+ build_vote_form(parameters)
77
+ else
78
+ check_session_personal_data
79
+ end
80
+ clear_session_sms_code
81
+
82
+ if @vote_form.invalid?
83
+ flash[:alert] = I18n.t("personal_data.invalid", scope: "decidim.initiatives.initiative_votes")
84
+ jump_to(previous_step)
85
+ end
86
+
87
+ @form = Decidim::Verifications::Sms::MobilePhoneForm.new
88
+ render_wizard
89
+ end
90
+
91
+ def sms_code_step(parameters)
92
+ check_session_personal_data if fill_personal_data_step?
93
+ @phone_form = Decidim::Verifications::Sms::MobilePhoneForm.from_params(parameters.merge(user: current_user))
94
+ @form = Decidim::Verifications::Sms::ConfirmationForm.new
95
+ render_wizard && return if session_sms_code.present?
96
+
97
+ ValidateMobilePhone.call(@phone_form, current_user) do
98
+ on(:ok) do |metadata|
99
+ store_session_sms_code(metadata)
100
+ render_wizard
101
+ end
102
+
103
+ on(:invalid) do
104
+ flash[:alert] = I18n.t("sms_phone.invalid", scope: "decidim.initiatives.initiative_votes")
105
+ redirect_to wizard_path(:sms_phone_number)
106
+ end
107
+ end
108
+ end
109
+
110
+ def finish_step(parameters)
111
+ if parameters.has_key?(:initiatives_vote) || !fill_personal_data_step?
112
+ build_vote_form(parameters)
113
+ else
114
+ check_session_personal_data
115
+ end
116
+
117
+ if sms_step?
118
+ @confirmation_code_form = Decidim::Verifications::Sms::ConfirmationForm.from_params(parameters)
119
+
120
+ ValidateSmsCode.call(@confirmation_code_form, session_sms_code) do
121
+ on(:ok) { clear_session_sms_code }
122
+
123
+ on(:invalid) do
124
+ flash[:alert] = I18n.t("sms_code.invalid", scope: "decidim.initiatives.initiative_votes")
125
+ jump_to :sms_code
126
+ render_wizard && return
127
+ end
128
+ end
129
+ end
130
+
131
+ VoteInitiative.call(@vote_form, current_user) do
132
+ on(:ok) do
133
+ session[:initiative_vote_form] = {}
134
+ end
135
+
136
+ on(:invalid) do |vote|
137
+ logger.fatal "Failed creating signature: #{vote.errors.full_messages.join(", ")}" if vote
138
+ flash[:alert] = I18n.t("create.invalid", scope: "decidim.initiatives.initiative_votes")
139
+ jump_to previous_step
140
+ end
141
+ end
142
+ render_wizard
143
+ end
144
+
145
+ def build_vote_form(parameters)
146
+ @vote_form = form(Decidim::Initiatives::VoteForm).from_params(parameters).tap do |form|
147
+ form.initiative_id = current_initiative.id
148
+ form.author_id = current_user.id
149
+ end
150
+
151
+ session[:initiative_vote_form] = session[:initiative_vote_form].merge(@vote_form.attributes_with_values)
152
+ end
153
+
154
+ def session_vote_form
155
+ raw_birth_date = session[:initiative_vote_form]["date_of_birth"]
156
+ return unless raw_birth_date
157
+
158
+ @vote_form = form(Decidim::Initiatives::VoteForm).from_params(
159
+ session[:initiative_vote_form].merge("date_of_birth" => Date.parse(raw_birth_date))
160
+ )
161
+ end
162
+
163
+ def initiative_type
164
+ @initiative_type ||= current_initiative&.scoped_type&.type
165
+ end
166
+
167
+ def extra_data_legal_information
168
+ @extra_data_legal_information ||= initiative_type.extra_fields_legal_information
169
+ end
170
+
171
+ def check_session_personal_data
172
+ return if session[:initiative_vote_form].present? && session_vote_form&.valid?
173
+
174
+ flash[:alert] = I18n.t("create.error", scope: "decidim.initiatives.initiative_votes")
175
+ jump_to(:fill_personal_data)
176
+ end
177
+
178
+ def store_session_sms_code(metadata)
179
+ session[:initiative_sms_code] = metadata
180
+ end
181
+
182
+ def session_sms_code
183
+ session[:initiative_sms_code]
184
+ end
185
+
186
+ def clear_session_sms_code
187
+ session[:initiative_sms_code] = {}
188
+ end
189
+
190
+ def sms_step?
191
+ current_initiative.validate_sms_code_on_votes?
192
+ end
193
+
194
+ def fill_personal_data_step?
195
+ initiative_type.collect_user_extra_fields?
196
+ end
197
+
198
+ def set_wizard_steps
199
+ initial_wizard_steps = [:finish]
200
+ initial_wizard_steps.unshift(:sms_phone_number, :sms_code) if sms_step?
201
+ initial_wizard_steps.unshift(:fill_personal_data) if fill_personal_data_step?
202
+
203
+ self.steps = initial_wizard_steps
204
+ end
205
+ end
206
+ end
207
+ end
@@ -5,6 +5,7 @@ module Decidim
5
5
  # Exposes the initiative vote resource so users can vote initiatives.
6
6
  class InitiativeVotesController < Decidim::Initiatives::ApplicationController
7
7
  include Decidim::Initiatives::NeedsInitiative
8
+ include Decidim::FormFactory
8
9
 
9
10
  before_action :authenticate_user!
10
11
 
@@ -13,7 +14,8 @@ module Decidim
13
14
  # POST /initiatives/:initiative_id/initiative_vote
14
15
  def create
15
16
  enforce_permission_to :vote, :initiative, initiative: current_initiative, group_id: params[:group_id]
16
- VoteInitiative.call(current_initiative, current_user, params[:group_id]) do
17
+ @form = form(Decidim::Initiatives::VoteForm).from_params(initiative_id: current_initiative.id, author_id: current_user.id, group_id: params[:group_id])
18
+ VoteInitiative.call(@form, current_user) do
17
19
  on(:ok) do
18
20
  current_initiative.reload
19
21
  render :update_buttons_and_counters
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Decidim
4
+ module Initiatives
5
+ class InitiativesTypeSignatureTypesController < Decidim::Initiatives::ApplicationController
6
+ helper_method :allowed_signature_types_for_initiatives
7
+
8
+ # GET /initiative_type_signature_types/search
9
+ def search
10
+ enforce_permission_to :search, :initiative_type_signature_types
11
+ render layout: false
12
+ end
13
+
14
+ private
15
+
16
+ def allowed_signature_types_for_initiatives
17
+ @allowed_signature_types_for_initiatives ||= InitiativesType.find(params[:type_id]).allowed_signature_types_for_initiatives
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Decidim
4
+ module Initiatives
5
+ module Admin
6
+ # A form object used to manage the initiative answer in the
7
+ # administration panel.
8
+ class InitiativeAnswerForm < Form
9
+ include TranslatableAttributes
10
+
11
+ mimic :initiative
12
+
13
+ translatable_attribute :answer, String
14
+ attribute :answer_url, String
15
+ attribute :signature_start_date, Decidim::Attributes::LocalizedDate
16
+ attribute :signature_end_date, Decidim::Attributes::LocalizedDate
17
+
18
+ validates :signature_start_date, :signature_end_date, presence: true, if: :signature_dates_required?
19
+ validates :signature_end_date, date: { after: :signature_start_date }, if: lambda { |form|
20
+ form.signature_start_date.present? && form.signature_end_date.present?
21
+ }
22
+
23
+ def signature_dates_required?
24
+ @signature_dates_required ||= context.initiative.state == "published"
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
@@ -19,21 +19,16 @@ module Decidim
19
19
  attribute :signature_end_date, Decidim::Attributes::LocalizedDate
20
20
  attribute :hashtag, String
21
21
  attribute :offline_votes, Integer
22
-
23
- translatable_attribute :answer, String
24
- attribute :answer_url, String
22
+ attribute :state, String
25
23
 
26
24
  validates :title, :description, presence: true
27
- validates :signature_type, presence: true
25
+ validates :signature_type, presence: true, if: :signature_type_updatable?
28
26
  validates :signature_start_date, presence: true, if: ->(form) { form.context.initiative.published? }
29
27
  validates :signature_end_date, presence: true, if: ->(form) { form.context.initiative.published? }
30
28
  validates :signature_end_date, date: { after: :signature_start_date }, if: lambda { |form|
31
29
  form.signature_start_date.present? && form.signature_end_date.present?
32
30
  }
33
31
 
34
- validates :answer, translatable_presence: true, if: ->(form) { form.context.initiative.accepted? }
35
- validates :answer_url, presence: true, if: ->(form) { form.context.initiative.accepted? }
36
-
37
32
  validates :offline_votes,
38
33
  numericality: {
39
34
  only_integer: true,
@@ -44,6 +39,29 @@ module Decidim
44
39
  self.type_id = model.type.id
45
40
  self.decidim_scope_id = model.scope.id
46
41
  end
42
+
43
+ def signature_type_updatable?
44
+ @signature_type_updatable ||= begin
45
+ state ||= context.initiative.state
46
+ state == "validating" && context.current_user.admin? || state == "created"
47
+ end
48
+ end
49
+
50
+ def state_updatable?
51
+ false
52
+ end
53
+
54
+ def scoped_type_id
55
+ return unless type && decidim_scope_id
56
+
57
+ type.scopes.find_by!(decidim_scopes_id: decidim_scope_id).id
58
+ end
59
+
60
+ private
61
+
62
+ def type
63
+ @type ||= type_id ? Decidim::InitiativesType.find(type_id) : context.initiative.type
64
+ end
47
65
  end
48
66
  end
49
67
  end
@@ -12,11 +12,25 @@ module Decidim
12
12
  translatable_attribute :title, String
13
13
  translatable_attribute :description, String
14
14
  attribute :banner_image, String
15
+ attribute :online_signature_enabled, Boolean
16
+ attribute :undo_online_signatures_enabled, Boolean
17
+ attribute :minimum_committee_members, Integer
18
+ attribute :collect_user_extra_fields, Boolean
19
+ translatable_attribute :extra_fields_legal_information, String
20
+ attribute :validate_sms_code_on_votes, Boolean
21
+ attribute :document_number_authorization_handler, String
15
22
 
16
23
  validates :title, :description, translatable_presence: true
24
+ validates :online_signature_enabled, inclusion: { in: [true, false] }
25
+ validates :undo_online_signatures_enabled, inclusion: { in: [true, false] }
26
+ validates :minimum_committee_members, numericality: { only_integer: true }, allow_nil: true
17
27
  validates :banner_image, presence: true, if: lambda { |form|
18
28
  form.context.initiative_type.nil?
19
29
  }
30
+
31
+ def minimum_committee_members=(value)
32
+ super(value.presence)
33
+ end
20
34
  end
21
35
  end
22
36
  end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Decidim
4
+ module Initiatives
5
+ # A form object used to collect the data for a new initiative committee
6
+ # member.
7
+ class CommitteeMemberForm < Form
8
+ mimic :initiatives_committee_member
9
+
10
+ attribute :initiative_id, Integer
11
+ attribute :user_id, Integer
12
+ attribute :state, String
13
+
14
+ validates :initiative_id, presence: true
15
+ validates :user_id, presence: true
16
+ validates :state, inclusion: { in: %w(requested rejected accepted) }, unless: :user_is_author?
17
+ validates :state, inclusion: { in: %w(rejected accepted) }, if: :user_is_author?
18
+
19
+ def user_is_author?
20
+ initiative&.decidim_author_id == user_id
21
+ end
22
+
23
+ private
24
+
25
+ def initiative
26
+ @initiative ||= Decidim::Initiative.find_by(id: initiative_id)
27
+ end
28
+ end
29
+ end
30
+ end
@@ -14,6 +14,7 @@ module Decidim
14
14
  attribute :scope_id, Integer
15
15
  attribute :decidim_user_group_id, Integer
16
16
  attribute :signature_type, String
17
+ attribute :state, String
17
18
 
18
19
  validates :title, :description, presence: true
19
20
  validates :title, length: { maximum: 150 }
@@ -25,6 +26,10 @@ module Decidim
25
26
  self.type_id = model.type.id
26
27
  self.scope_id = model.scope.id
27
28
  end
29
+
30
+ def signature_type_updatable?
31
+ state == "created" || state.nil?
32
+ end
28
33
  end
29
34
  end
30
35
  end
@@ -0,0 +1,151 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "virtus/multiparams"
4
+
5
+ module Decidim
6
+ module Initiatives
7
+ # A form object used to collect the data for a new initiative.
8
+ class VoteForm < Form
9
+ include TranslatableAttributes
10
+ include Virtus::Multiparams
11
+
12
+ mimic :initiatives_vote
13
+
14
+ attribute :name_and_surname, String
15
+ attribute :document_number, String
16
+ attribute :date_of_birth, Date
17
+
18
+ attribute :postal_code, String
19
+ attribute :encrypted_metadata, String
20
+ attribute :hash_id, String
21
+
22
+ attribute :initiative_id, Integer
23
+ attribute :author_id, Integer
24
+ attribute :group_id, Integer
25
+
26
+ validates :name_and_surname, :document_number, :date_of_birth, :postal_code, presence: true, if: :required_personal_data?
27
+ validates :encrypted_metadata, presence: true, if: :required_personal_data?
28
+ validates :initiative_id, presence: true
29
+ validates :author_id, presence: true
30
+
31
+ validate :document_number_authorized, if: :required_personal_data?
32
+ validate :document_number_uniqueness, if: :required_personal_data?
33
+ validate :personal_data_consistent_with_metadata, if: :required_personal_data?
34
+
35
+ def initiative
36
+ @initiative ||= Decidim::Initiative.find_by(id: initiative_id)
37
+ end
38
+
39
+ delegate :scope, to: :initiative
40
+
41
+ def metadata
42
+ { name_and_surname: name_and_surname,
43
+ document_number: document_number,
44
+ date_of_birth: date_of_birth,
45
+ postal_code: postal_code }
46
+ end
47
+
48
+ def encrypted_metadata
49
+ @encrypted_metadata ||= encrypt_metadata
50
+ end
51
+
52
+ def hash_id
53
+ Digest::MD5.hexdigest(
54
+ "#{initiative_id}-#{document_number || author_id}-#{Rails.application.secrets.secret_key_base}"
55
+ )
56
+ end
57
+
58
+ def decrypted_metadata
59
+ return unless encrypted_metadata
60
+
61
+ encryptor.decrypt(encrypted_metadata)
62
+ end
63
+
64
+ protected
65
+
66
+ def required_personal_data?
67
+ initiative_type&.collect_user_extra_fields?
68
+ end
69
+
70
+ def initiative_type
71
+ @initiative_type ||= initiative&.scoped_type&.type
72
+ end
73
+
74
+ def encryptor
75
+ @encryptor ||= DataEncryptor.new(secret: "personal user metadata")
76
+ end
77
+
78
+ def encrypt_metadata
79
+ return unless required_personal_data?
80
+
81
+ encryptor.encrypt(metadata)
82
+ end
83
+
84
+ def document_number_authorized
85
+ return if initiative.document_number_authorization_handler.blank?
86
+
87
+ errors.add(:document_number, :invalid) unless authorized? && authorization_handler && authorization.unique_id == authorization_handler.unique_id
88
+ end
89
+
90
+ def document_number_uniqueness
91
+ errors.add(:document_number, :taken) if initiative.votes.where(hash_id: hash_id).exists?
92
+ end
93
+
94
+ def personal_data_consistent_with_metadata
95
+ return if initiative.document_number_authorization_handler.blank?
96
+
97
+ errors.add(:base, :invalid) unless authorized? &&
98
+ authorization_handler &&
99
+ authorization_handler_metadata_variations.any? { |variation| authorization.metadata.symbolize_keys == variation.symbolize_keys }
100
+ end
101
+
102
+ def author
103
+ @author ||= current_organization.users.find_by(id: author_id)
104
+ end
105
+
106
+ def authorization
107
+ return unless author && handler_name
108
+
109
+ @authorization ||= Verifications::Authorizations.new(organization: author.organization, user: author, name: handler_name).first
110
+ end
111
+
112
+ def authorization_status
113
+ return unless authorization
114
+
115
+ Decidim::Verifications::Adapter.from_element(handler_name).authorize(authorization, {}, nil, nil)
116
+ end
117
+
118
+ def authorized?
119
+ authorization_status&.first == :ok
120
+ end
121
+
122
+ def handler_name
123
+ initiative.document_number_authorization_handler
124
+ end
125
+
126
+ def authorization_handler
127
+ return unless document_number && handler_name
128
+
129
+ @authorization_handler ||= Decidim::AuthorizationHandler.handler_for(handler_name,
130
+ document_number: document_number,
131
+ name_and_surname: name_and_surname,
132
+ date_of_birth: date_of_birth,
133
+ postal_code: postal_code,
134
+ scope_id: scope&.id)
135
+ end
136
+
137
+ def authorization_handler_metadata_variations
138
+ return [] unless authorization_handler && scope.present?
139
+
140
+ scope.children.map do |child_scope|
141
+ Decidim::AuthorizationHandler.handler_for(handler_name,
142
+ document_number: document_number,
143
+ name_and_surname: name_and_surname,
144
+ date_of_birth: date_of_birth,
145
+ postal_code: postal_code,
146
+ scope_id: child_scope&.id)
147
+ end.unshift(authorization_handler).map(&:metadata)
148
+ end
149
+ end
150
+ end
151
+ end