decidim-initiatives 0.20.0 → 0.23.1.rc1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +22 -0
- data/app/assets/images/decidim/gamification/badges/initiatives.svg +1 -87
- data/app/assets/images/decidim/initiatives/icon.svg +1 -3
- data/app/assets/javascripts/decidim/initiatives/admin/initiatives_types.js.es6 +18 -0
- data/app/assets/stylesheet/decidim/initiatives/initiatives-votes.css.scss +0 -1
- data/app/assets/stylesheet/decidim/initiatives/initiatives.scss +6 -8
- data/app/assets/stylesheet/decidim/initiatives/popularity_item.css.scss +0 -1
- data/app/assets/stylesheet/decidim/initiatives/print-initiative.css.scss +0 -3
- data/app/cells/decidim/initiatives/content_blocks/highlighted_initiatives/show.erb +4 -3
- data/app/cells/decidim/initiatives/initiative_m_cell.rb +25 -0
- data/app/cells/decidim/initiatives_votes/vote/show.erb +12 -9
- data/app/cells/decidim/initiatives_votes/vote_cell.rb +7 -0
- data/app/commands/decidim/initiatives/admin/create_initiative_type.rb +6 -1
- data/app/commands/decidim/initiatives/admin/send_initiative_to_technical_validation.rb +17 -0
- data/app/commands/decidim/initiatives/admin/update_initiative.rb +29 -10
- data/app/commands/decidim/initiatives/admin/update_initiative_type.rb +6 -1
- data/app/commands/decidim/initiatives/attachment_methods.rb +37 -0
- data/app/commands/decidim/initiatives/create_initiative.rb +25 -3
- data/app/commands/decidim/initiatives/unvote_initiative.rb +4 -10
- data/app/commands/decidim/initiatives/vote_initiative.rb +47 -31
- data/app/controllers/concerns/decidim/initiatives/admin/filterable.rb +51 -0
- data/app/controllers/concerns/decidim/initiatives/orderable.rb +3 -1
- data/app/controllers/concerns/decidim/initiatives/single_initiative_type.rb +26 -0
- data/app/controllers/decidim/initiatives/admin/answers_controller.rb +2 -3
- data/app/controllers/decidim/initiatives/admin/initiatives_controller.rb +28 -15
- data/app/controllers/decidim/initiatives/create_initiative_controller.rb +36 -6
- data/app/controllers/decidim/initiatives/initiative_signatures_controller.rb +19 -23
- data/app/controllers/decidim/initiatives/initiative_votes_controller.rb +11 -5
- data/app/controllers/decidim/initiatives/initiatives_controller.rb +28 -13
- data/app/controllers/decidim/initiatives/initiatives_type_scopes_controller.rb +9 -1
- data/app/controllers/decidim/initiatives/versions_controller.rb +20 -0
- data/app/controllers/decidim/initiatives/{initiative_widgets_controller.rb → widgets_controller.rb} +2 -2
- data/app/events/decidim/initiatives/admin/initiative_sent_to_technical_validation_event.rb +21 -0
- data/app/events/decidim/initiatives/admin/support_threshold_reached_event.rb +13 -0
- data/app/forms/decidim/initiatives/admin/initiative_form.rb +49 -7
- data/app/forms/decidim/initiatives/admin/initiative_type_form.rb +8 -1
- data/app/forms/decidim/initiatives/initiative_form.rb +56 -1
- data/app/forms/decidim/initiatives/vote_form.rb +133 -76
- data/app/helpers/decidim/initiatives/application_helper.rb +104 -0
- data/app/helpers/decidim/initiatives/initiative_helper.rb +13 -0
- data/app/helpers/decidim/initiatives/initiatives_helper.rb +10 -0
- data/app/jobs/decidim/initiatives/export_initiatives_job.rb +25 -0
- data/app/mailers/decidim/initiatives/initiatives_mailer.rb +0 -21
- data/app/models/concerns/decidim/initiatives/has_area.rb +30 -0
- data/app/models/decidim/initiative.rb +184 -44
- data/app/models/decidim/initiatives_type.rb +5 -2
- data/app/models/decidim/initiatives_type_scope.rb +5 -1
- data/app/models/decidim/initiatives_vote.rb +19 -23
- data/app/permissions/decidim/initiatives/admin/permissions.rb +19 -8
- data/app/permissions/decidim/initiatives/permissions.rb +37 -14
- data/app/presenters/decidim/initiatives/initiative_stats_presenter.rb +1 -5
- data/app/queries/decidim/initiatives/admin/manageable_initiatives.rb +7 -39
- data/app/serializers/decidim/initiatives/initiative_serializer.rb +32 -0
- data/app/services/decidim/initiatives/diff_renderer.rb +18 -0
- data/app/services/decidim/initiatives/initiative_search.rb +59 -15
- data/app/services/decidim/initiatives/status_change_notifier.rb +4 -5
- data/app/types/decidim/initiatives/initiative_api_type.rb +26 -0
- data/app/types/decidim/initiatives/initiative_committee_member_type.rb +18 -0
- data/app/types/decidim/initiatives/initiative_type.rb +42 -0
- data/app/views/decidim/initiatives/admin/answers/_info_initiative.html.erb +1 -1
- data/app/views/decidim/initiatives/admin/exports/_dropdown.html.erb +8 -0
- data/app/views/decidim/initiatives/admin/initiatives/_form.html.erb +59 -14
- data/app/views/decidim/initiatives/admin/initiatives/_initiative_attachments.erb +43 -0
- data/app/views/decidim/initiatives/admin/initiatives/export_pdf_signatures.pdf.erb +12 -9
- data/app/views/decidim/initiatives/admin/initiatives/index.html.erb +10 -45
- data/app/views/decidim/initiatives/admin/initiatives_types/_form.html.erb +47 -10
- data/app/views/decidim/initiatives/admin/initiatives_types/_initiative_type_scopes.html.erb +28 -25
- data/app/views/decidim/initiatives/create_initiative/fill_data.html.erb +46 -10
- data/app/views/decidim/initiatives/create_initiative/finish.html.erb +17 -10
- data/app/views/decidim/initiatives/create_initiative/previous_form.html.erb +2 -1
- data/app/views/decidim/initiatives/create_initiative/promotal_committee.html.erb +1 -1
- data/app/views/decidim/initiatives/create_initiative/select_initiative_type.html.erb +1 -2
- data/app/views/decidim/initiatives/create_initiative/show_similar_initiatives.html.erb +1 -1
- data/app/views/decidim/initiatives/initiative_signatures/fill_personal_data.html.erb +1 -1
- data/app/views/decidim/initiatives/initiative_signatures/update_buttons_and_counters.js.erb +1 -1
- data/app/views/decidim/initiatives/initiative_votes/update_buttons_and_counters.js.erb +1 -1
- data/app/views/decidim/initiatives/initiatives/_author.html.erb +1 -1
- data/app/views/decidim/initiatives/initiatives/_filters.html.erb +16 -28
- data/app/views/decidim/initiatives/initiatives/_index_header.html.erb +39 -5
- data/app/views/decidim/initiatives/initiatives/_initiatives.html.erb +11 -1
- data/app/views/decidim/initiatives/initiatives/_interactions.html.erb +2 -3
- data/app/views/decidim/initiatives/initiatives/_progress_bar.html.erb +24 -9
- data/app/views/decidim/initiatives/initiatives/_tags.html.erb +3 -0
- data/app/views/decidim/initiatives/initiatives/_vote_cabin.html.erb +1 -13
- data/app/views/decidim/initiatives/initiatives/index.html.erb +1 -1
- data/app/views/decidim/initiatives/initiatives/show.html.erb +2 -3
- data/app/views/decidim/initiatives/versions/index.html.erb +8 -0
- data/app/views/decidim/initiatives/versions/show.html.erb +10 -0
- data/app/views/layouts/decidim/_initiative_creation_header.html.erb +2 -1
- data/app/views/layouts/decidim/_initiative_header.html.erb +2 -1
- data/app/views/layouts/decidim/_initiative_signature_creation_header.html.erb +1 -1
- data/app/views/layouts/decidim/initiative.html.erb +1 -0
- data/app/views/layouts/decidim/initiative_creation.html.erb +1 -2
- data/app/views/layouts/decidim/initiative_signature_creation.html.erb +2 -2
- data/config/locales/am-ET.yml +1 -0
- data/config/locales/ar.yml +17 -21
- data/config/locales/bg-BG.yml +13 -0
- data/config/locales/bg.yml +13 -0
- data/config/locales/ca.yml +100 -22
- data/config/locales/cs.yml +110 -32
- data/config/locales/da-DK.yml +1 -0
- data/config/locales/da.yml +1 -0
- data/config/locales/de.yml +75 -21
- data/config/locales/el-GR.yml +1 -0
- data/config/locales/el.yml +525 -0
- data/config/locales/en.yml +102 -24
- data/config/locales/eo.yml +1 -0
- data/config/locales/es-MX.yml +100 -21
- data/config/locales/es-PY.yml +100 -21
- data/config/locales/es.yml +104 -25
- data/config/locales/et-EE.yml +1 -0
- data/config/locales/et.yml +1 -0
- data/config/locales/eu.yml +4 -21
- data/config/locales/fi-plain.yml +99 -21
- data/config/locales/fi.yml +117 -39
- data/config/locales/fr-CA.yml +557 -0
- data/config/locales/fr.yml +101 -23
- data/config/locales/ga-IE.yml +1 -0
- data/config/locales/gl.yml +4 -21
- data/config/locales/hr-HR.yml +1 -0
- data/config/locales/hr.yml +1 -0
- data/config/locales/hu.yml +30 -24
- data/config/locales/id-ID.yml +4 -21
- data/config/locales/is-IS.yml +251 -0
- data/config/locales/is.yml +251 -0
- data/config/locales/it.yml +116 -61
- data/config/locales/ja-JP.yml +529 -0
- data/config/locales/ja.yml +549 -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 +525 -0
- data/config/locales/mt-MT.yml +1 -0
- data/config/locales/mt.yml +1 -0
- data/config/locales/nl.yml +76 -21
- data/config/locales/no.yml +368 -9
- data/config/locales/om-ET.yml +1 -0
- data/config/locales/pl.yml +260 -189
- data/config/locales/pt-BR.yml +5 -22
- data/config/locales/pt.yml +231 -179
- data/config/locales/ro-RO.yml +533 -0
- data/config/locales/ru.yml +4 -21
- data/config/locales/sk-SK.yml +468 -0
- data/config/locales/sk.yml +458 -0
- data/config/locales/sl.yml +24 -0
- data/config/locales/so-SO.yml +1 -0
- data/config/locales/sr-CS.yml +8 -0
- data/config/locales/sv.yml +102 -31
- data/config/locales/ti-ER.yml +1 -0
- data/config/locales/tr-TR.yml +4 -21
- data/config/locales/uk.yml +4 -21
- data/config/locales/vi-VN.yml +1 -0
- data/config/locales/vi.yml +1 -0
- data/config/locales/zh-CN.yml +549 -0
- data/config/locales/zh-TW.yml +1 -0
- data/db/migrate/20191106144259_add_settings_to_initiatives_types.rb +8 -0
- data/db/migrate/20191107134847_add_scopes_to_initiatives_votes.rb +28 -0
- data/db/migrate/20191116170841_allow_multiple_initiative_votes_counter_caches.rb +32 -0
- data/db/migrate/20191118105634_allow_multiple_offline_votes.rb +34 -0
- data/db/migrate/20200320105920_index_foreign_keys_in_decidim_initiatives.rb +8 -0
- data/db/migrate/20200320105921_index_foreign_keys_in_decidim_initiatives_votes.rb +8 -0
- data/db/migrate/20200417120551_add_custom_signature_end_time_option.rb +7 -0
- data/db/migrate/20200424110930_add_attachments_enabled_option.rb +7 -0
- data/db/migrate/20200514085422_add_area_to_initiatives.rb +7 -0
- data/db/migrate/20200514102631_add_area_enabled_option_to_initiatives.rb +7 -0
- data/db/migrate/20200528151456_remove_user_groups_from_initiative_votes.rb +7 -0
- data/db/migrate/20200827154214_add_commentable_counter_cache_to_initiatives.rb +9 -0
- data/db/seeds/city.jpeg +0 -0
- data/db/seeds/city2.jpeg +0 -0
- data/lib/decidim/api/initiative_type_interface.rb +13 -0
- data/lib/decidim/initiatives/admin_engine.rb +5 -0
- data/lib/decidim/initiatives/api.rb +7 -0
- data/lib/decidim/initiatives/engine.rb +11 -2
- data/lib/decidim/initiatives/participatory_space.rb +18 -1
- data/lib/decidim/initiatives/query_extensions.rb +40 -0
- data/lib/decidim/initiatives/test/factories.rb +57 -10
- data/lib/decidim/initiatives/version.rb +1 -1
- data/lib/tasks/decidim_initiatives.rake +1 -3
- metadata +95 -22
- data/app/views/decidim/initiatives/initiative_widgets/show.html.erb +0 -4
- data/app/views/decidim/initiatives/initiatives/_supports.html.erb +0 -22
- data/app/views/decidim/initiatives/initiatives/signature_identities.html.erb +0 -42
- data/app/views/decidim/initiatives/initiatives_mailer/notify_validating_request.html.erb +0 -3
@@ -14,6 +14,11 @@ module Decidim
|
|
14
14
|
attribute :banner_image, String
|
15
15
|
attribute :signature_type, String
|
16
16
|
attribute :undo_online_signatures_enabled, Boolean
|
17
|
+
attribute :attachments_enabled, Boolean
|
18
|
+
attribute :custom_signature_end_date_enabled, Boolean
|
19
|
+
attribute :area_enabled, Boolean
|
20
|
+
attribute :child_scope_threshold_enabled, Boolean
|
21
|
+
attribute :only_global_scope_enabled, Boolean
|
17
22
|
attribute :promoting_committee_enabled, Boolean
|
18
23
|
attribute :minimum_committee_members, Integer
|
19
24
|
attribute :collect_user_extra_fields, Boolean
|
@@ -22,9 +27,11 @@ module Decidim
|
|
22
27
|
attribute :document_number_authorization_handler, String
|
23
28
|
|
24
29
|
validates :title, :description, translatable_presence: true
|
25
|
-
validates :
|
30
|
+
validates :attachments_enabled, :undo_online_signatures_enabled, :custom_signature_end_date_enabled,
|
31
|
+
:area_enabled, :promoting_committee_enabled, inclusion: { in: [true, false] }
|
26
32
|
validates :minimum_committee_members, numericality: { only_integer: true }, allow_nil: true
|
27
33
|
validates :banner_image, presence: true, if: ->(form) { form.context.initiative_type.nil? }
|
34
|
+
validates :document_number_authorization_handler, presence: true, if: ->(form) { form.collect_user_extra_fields? }
|
28
35
|
|
29
36
|
def minimum_committee_members=(value)
|
30
37
|
super(value.presence)
|
@@ -12,15 +12,24 @@ module Decidim
|
|
12
12
|
attribute :description, String
|
13
13
|
attribute :type_id, Integer
|
14
14
|
attribute :scope_id, Integer
|
15
|
+
attribute :area_id, Integer
|
15
16
|
attribute :decidim_user_group_id, Integer
|
16
17
|
attribute :signature_type, String
|
18
|
+
attribute :signature_end_date, Date
|
17
19
|
attribute :state, String
|
20
|
+
attribute :attachment, AttachmentForm
|
18
21
|
|
19
22
|
validates :title, :description, presence: true
|
20
23
|
validates :title, length: { maximum: 150 }
|
21
24
|
validates :signature_type, presence: true
|
22
25
|
validates :type_id, presence: true
|
26
|
+
validates :area, presence: true, if: ->(form) { form.area_id.present? }
|
23
27
|
validate :scope_exists
|
28
|
+
validate :notify_missing_attachment_if_errored
|
29
|
+
validate :trigger_attachment_errors
|
30
|
+
validates :signature_end_date, date: { after: Date.current }, if: lambda { |form|
|
31
|
+
form.context.initiative_type.custom_signature_end_date_enabled? && form.signature_end_date.present?
|
32
|
+
}
|
24
33
|
|
25
34
|
def map_model(model)
|
26
35
|
self.type_id = model.type.id
|
@@ -32,15 +41,61 @@ module Decidim
|
|
32
41
|
end
|
33
42
|
|
34
43
|
def scope_id
|
44
|
+
return nil if initiative_type.only_global_scope_enabled?
|
45
|
+
|
35
46
|
super.presence
|
36
47
|
end
|
37
48
|
|
49
|
+
def area
|
50
|
+
@area ||= current_organization.areas.find_by(id: area_id)
|
51
|
+
end
|
52
|
+
|
53
|
+
def initiative_type
|
54
|
+
@initiative_type ||= InitiativesType.find(type_id)
|
55
|
+
end
|
56
|
+
|
57
|
+
def available_scopes
|
58
|
+
@available_scopes ||= if initiative_type.only_global_scope_enabled?
|
59
|
+
initiative_type.scopes.where(scope: nil)
|
60
|
+
else
|
61
|
+
initiative_type.scopes
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
def scope
|
66
|
+
@scope ||= Scope.find(scope_id) if scope_id.present?
|
67
|
+
end
|
68
|
+
|
38
69
|
private
|
39
70
|
|
40
71
|
def scope_exists
|
41
72
|
return if scope_id.blank?
|
42
73
|
|
43
|
-
errors.add(:scope_id, :invalid) unless InitiativesTypeScope.where(
|
74
|
+
errors.add(:scope_id, :invalid) unless InitiativesTypeScope.where(type: initiative_type, scope: scope).exists?
|
75
|
+
end
|
76
|
+
|
77
|
+
# This method will add an error to the `attachment` field only if there's
|
78
|
+
# any error in any other field. This is needed because when the form has
|
79
|
+
# an error, the attachment is lost, so we need a way to inform the user of
|
80
|
+
# this problem.
|
81
|
+
def notify_missing_attachment_if_errored
|
82
|
+
return if attachment.blank?
|
83
|
+
|
84
|
+
errors.add(:attachment, :needs_to_be_reattached) if errors.any?
|
85
|
+
end
|
86
|
+
|
87
|
+
def trigger_attachment_errors
|
88
|
+
return if attachment.blank?
|
89
|
+
return if attachment.valid?
|
90
|
+
|
91
|
+
attachment.errors.each { |error| errors.add(:attachment, error) }
|
92
|
+
|
93
|
+
attachment = Attachment.new(
|
94
|
+
attached_to: attachment.try(:attached_to),
|
95
|
+
file: attachment.try(:file)
|
96
|
+
)
|
97
|
+
|
98
|
+
errors.add(:attachment, :file) if !attachment.save && attachment.errors.has_key?(:file)
|
44
99
|
end
|
45
100
|
end
|
46
101
|
end
|
@@ -19,110 +19,163 @@ module Decidim
|
|
19
19
|
attribute :encrypted_metadata, String
|
20
20
|
attribute :hash_id, String
|
21
21
|
|
22
|
-
attribute :
|
23
|
-
attribute :
|
24
|
-
attribute :group_id, Integer
|
22
|
+
attribute :initiative, Decidim::Initiative
|
23
|
+
attribute :signer, Decidim::User
|
25
24
|
|
26
|
-
validates :
|
27
|
-
validates :encrypted_metadata, presence: true, if: :required_personal_data?
|
28
|
-
validates :initiative_id, presence: true
|
29
|
-
validates :author_id, presence: true
|
25
|
+
validates :initiative, :signer, presence: true
|
30
26
|
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
def initiative
|
36
|
-
@initiative ||= Decidim::Initiative.find_by(id: initiative_id)
|
27
|
+
with_options if: :required_personal_data? do
|
28
|
+
validates :name_and_surname, :document_number, :date_of_birth, :postal_code, :encrypted_metadata, :hash_id, presence: true
|
29
|
+
validate :document_number_authorized?
|
30
|
+
validate :already_voted?
|
37
31
|
end
|
38
32
|
|
39
33
|
delegate :scope, to: :initiative
|
40
34
|
|
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
35
|
def encrypted_metadata
|
49
|
-
|
36
|
+
return unless required_personal_data?
|
37
|
+
|
38
|
+
@encrypted_metadata ||= encryptor.encrypt(metadata)
|
50
39
|
end
|
51
40
|
|
41
|
+
# Public: The hash to uniquely identify an initiative vote. It uses the
|
42
|
+
# initiative scope as a default.
|
43
|
+
#
|
44
|
+
# Returns a String.
|
52
45
|
def hash_id
|
53
|
-
|
54
|
-
|
46
|
+
return unless initiative && (document_number || signer)
|
47
|
+
|
48
|
+
@hash_id ||= Digest::MD5.hexdigest(
|
49
|
+
[
|
50
|
+
initiative.id,
|
51
|
+
document_number || signer.id,
|
52
|
+
Rails.application.secrets.secret_key_base
|
53
|
+
].compact.join("-")
|
55
54
|
)
|
56
55
|
end
|
57
56
|
|
58
|
-
|
59
|
-
|
57
|
+
# Public: Builds the list of scopes where the user is authorized to vote in. This is used when
|
58
|
+
# the initiative allows also voting on child scopes, not only the main scope.
|
59
|
+
#
|
60
|
+
# Instead of just listing the children of the main scope, we just want to select the ones that
|
61
|
+
# have been added to the InitiativeType with its voting settings.
|
62
|
+
#
|
63
|
+
def authorized_scopes
|
64
|
+
initiative.votable_initiative_type_scopes.select do |initiative_type_scope|
|
65
|
+
initiative_type_scope.global_scope? ||
|
66
|
+
initiative_type_scope.scope == user_authorized_scope ||
|
67
|
+
initiative_type_scope.scope.ancestor_of?(user_authorized_scope)
|
68
|
+
end.flat_map(&:scope)
|
69
|
+
end
|
70
|
+
|
71
|
+
# Public: Finds the scope the user has an authorization for, this way the user can vote
|
72
|
+
# on that scope and its parents.
|
73
|
+
#
|
74
|
+
# This is can be used to allow users that are authorized with a children
|
75
|
+
# scope to sign an initiative with a parent scope.
|
76
|
+
#
|
77
|
+
# As an example: A city (global scope) has many districts (scopes with
|
78
|
+
# parent nil), and each district has different neighbourhoods (with its
|
79
|
+
# parent as a district). If we setup the authorization handler to match
|
80
|
+
# a neighbourhood, the same authorization can be used to participate
|
81
|
+
# in district, neighbourhoods or city initiatives.
|
82
|
+
#
|
83
|
+
# Returns a Decidim::Scope.
|
84
|
+
def user_authorized_scope
|
85
|
+
return scope if handler_name.blank?
|
86
|
+
return unless authorized?
|
87
|
+
|
88
|
+
@user_authorized_scope ||= authorized_scope_candidates.find do |scope|
|
89
|
+
scope&.id == authorization.metadata.symbolize_keys[:scope_id]
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
# Public: Builds a list of Decidim::Scopes where the user could have a
|
94
|
+
# valid authorization.
|
95
|
+
#
|
96
|
+
# If the intiative is set with a global scope (meaning the scope is nil),
|
97
|
+
# all the scopes in the organizaton are valid.
|
98
|
+
#
|
99
|
+
# Returns an array of Decidim::Scopes.
|
100
|
+
def authorized_scope_candidates
|
101
|
+
authorized_scope_candidates = [initiative.scope]
|
102
|
+
authorized_scope_candidates += if initiative.scope.present?
|
103
|
+
initiative.scope.descendants
|
104
|
+
else
|
105
|
+
initiative.organization.scopes
|
106
|
+
end
|
107
|
+
authorized_scope_candidates.uniq
|
108
|
+
end
|
60
109
|
|
61
|
-
|
110
|
+
def metadata
|
111
|
+
{
|
112
|
+
name_and_surname: name_and_surname,
|
113
|
+
document_number: document_number,
|
114
|
+
date_of_birth: date_of_birth,
|
115
|
+
postal_code: postal_code
|
116
|
+
}
|
62
117
|
end
|
63
118
|
|
64
119
|
protected
|
65
120
|
|
121
|
+
# Private: Whether the personal data given when signing the initiative should
|
122
|
+
# be stored together with the vote or not.
|
123
|
+
#
|
124
|
+
# Returns a Boolean.
|
66
125
|
def required_personal_data?
|
67
|
-
|
126
|
+
@required_personal_data ||= initiative&.type&.collect_user_extra_fields?
|
68
127
|
end
|
69
128
|
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
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
|
129
|
+
# Private: Checks that the unique hash computed from the authorization
|
130
|
+
# and the user provided data match.
|
131
|
+
#
|
132
|
+
# This prevents users that know partial data from another user to sign
|
133
|
+
# initiatives with someone elses identity.
|
134
|
+
def document_number_authorized?
|
85
135
|
return if initiative.document_number_authorization_handler.blank?
|
86
136
|
|
87
137
|
errors.add(:document_number, :invalid) unless authorized? && authorization_handler && authorization.unique_id == authorization_handler.unique_id
|
88
138
|
end
|
89
139
|
|
90
|
-
|
91
|
-
|
92
|
-
|
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 }
|
140
|
+
# Private: Checks if there's any existing vote that matches the user's data.
|
141
|
+
def already_voted?
|
142
|
+
errors.add(:document_number, :taken) if initiative.votes.where(hash_id: hash_id, scope: scope).exists?
|
100
143
|
end
|
101
144
|
|
102
145
|
def author
|
103
146
|
@author ||= current_organization.users.find_by(id: author_id)
|
104
147
|
end
|
105
148
|
|
149
|
+
# Private: Finds an authorization for the user signing the initiative and
|
150
|
+
# the configured handler.
|
106
151
|
def authorization
|
107
|
-
return unless
|
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
|
152
|
+
return unless signer && handler_name
|
114
153
|
|
115
|
-
|
154
|
+
@authorization ||= Verifications::Authorizations.new(
|
155
|
+
organization: signer.organization,
|
156
|
+
user: signer,
|
157
|
+
name: handler_name
|
158
|
+
).first
|
116
159
|
end
|
117
160
|
|
161
|
+
# Private: Checks if the authorization hasn't expired or is invalid.
|
118
162
|
def authorized?
|
119
163
|
authorization_status&.first == :ok
|
120
164
|
end
|
121
165
|
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
166
|
+
# Private: Builds an authorization handler with the data the user provided
|
167
|
+
# when signing the initiative.
|
168
|
+
#
|
169
|
+
# This is currently tied to authorization handlers that have, at least, these attributes:
|
170
|
+
# * document_number
|
171
|
+
# * name_and_surname
|
172
|
+
# * date_of_birth
|
173
|
+
# * postal_code
|
174
|
+
#
|
175
|
+
# Once we have the authorization handler we can use is to compute the
|
176
|
+
# unique_id and compare it to an existing authorization.
|
177
|
+
#
|
178
|
+
# Returns a Decidim::AuthorizationHandler.
|
126
179
|
def authorization_handler
|
127
180
|
return unless document_number && handler_name
|
128
181
|
|
@@ -130,21 +183,25 @@ module Decidim
|
|
130
183
|
document_number: document_number,
|
131
184
|
name_and_surname: name_and_surname,
|
132
185
|
date_of_birth: date_of_birth,
|
133
|
-
postal_code: postal_code
|
134
|
-
scope_id: scope&.id)
|
186
|
+
postal_code: postal_code)
|
135
187
|
end
|
136
188
|
|
137
|
-
|
138
|
-
|
189
|
+
# Private: The AuthorizationHandler name used to verify the user's
|
190
|
+
# document number.
|
191
|
+
#
|
192
|
+
# Returns a String.
|
193
|
+
def handler_name
|
194
|
+
initiative.document_number_authorization_handler
|
195
|
+
end
|
196
|
+
|
197
|
+
def authorization_status
|
198
|
+
return unless authorization
|
139
199
|
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
postal_code: postal_code,
|
146
|
-
scope_id: child_scope&.id)
|
147
|
-
end.unshift(authorization_handler).map(&:metadata)
|
200
|
+
Decidim::Verifications::Adapter.from_element(handler_name).authorize(authorization, {}, nil, nil)
|
201
|
+
end
|
202
|
+
|
203
|
+
def encryptor
|
204
|
+
@encryptor ||= DataEncryptor.new(secret: "personal user metadata")
|
148
205
|
end
|
149
206
|
end
|
150
207
|
end
|
@@ -0,0 +1,104 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Decidim
|
4
|
+
module Initiatives
|
5
|
+
# Custom helpers, scoped to the initiatives engine.
|
6
|
+
#
|
7
|
+
module ApplicationHelper
|
8
|
+
include Decidim::CheckBoxesTreeHelper
|
9
|
+
|
10
|
+
def filter_states_values
|
11
|
+
TreeNode.new(
|
12
|
+
TreePoint.new("", t("decidim.initiatives.application_helper.filter_state_values.all")),
|
13
|
+
[
|
14
|
+
TreePoint.new("open", t("decidim.initiatives.application_helper.filter_state_values.open")),
|
15
|
+
TreeNode.new(
|
16
|
+
TreePoint.new("closed", t("decidim.initiatives.application_helper.filter_state_values.closed")),
|
17
|
+
[
|
18
|
+
TreePoint.new("accepted", t("decidim.initiatives.application_helper.filter_state_values.accepted")),
|
19
|
+
TreePoint.new("rejected", t("decidim.initiatives.application_helper.filter_state_values.rejected"))
|
20
|
+
]
|
21
|
+
),
|
22
|
+
TreePoint.new("answered", t("decidim.initiatives.application_helper.filter_state_values.answered"))
|
23
|
+
]
|
24
|
+
)
|
25
|
+
end
|
26
|
+
|
27
|
+
def filter_scopes_values
|
28
|
+
main_scopes = current_organization.scopes.top_level
|
29
|
+
|
30
|
+
scopes_values = main_scopes.includes(:scope_type, :children).flat_map do |scope|
|
31
|
+
TreeNode.new(
|
32
|
+
TreePoint.new(scope.id.to_s, translated_attribute(scope.name, current_organization)),
|
33
|
+
scope_children_to_tree(scope)
|
34
|
+
)
|
35
|
+
end
|
36
|
+
|
37
|
+
scopes_values.prepend(TreePoint.new("global", t("decidim.scopes.global")))
|
38
|
+
|
39
|
+
TreeNode.new(
|
40
|
+
TreePoint.new("", t("decidim.initiatives.application_helper.filter_scope_values.all")),
|
41
|
+
scopes_values
|
42
|
+
)
|
43
|
+
end
|
44
|
+
|
45
|
+
def scope_children_to_tree(scope)
|
46
|
+
return unless scope.children.any?
|
47
|
+
|
48
|
+
scope.children.includes(:scope_type, :children).flat_map do |child|
|
49
|
+
TreeNode.new(
|
50
|
+
TreePoint.new(child.id.to_s, translated_attribute(child.name, current_organization)),
|
51
|
+
scope_children_to_tree(child)
|
52
|
+
)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def filter_types_values
|
57
|
+
types_values = Decidim::InitiativesType.where(organization: current_organization).map do |type|
|
58
|
+
TreeNode.new(
|
59
|
+
TreePoint.new(type.id.to_s, type.title[I18n.locale.to_s])
|
60
|
+
)
|
61
|
+
end
|
62
|
+
|
63
|
+
TreeNode.new(
|
64
|
+
TreePoint.new("", t("decidim.initiatives.application_helper.filter_type_values.all")),
|
65
|
+
types_values
|
66
|
+
)
|
67
|
+
end
|
68
|
+
|
69
|
+
def filter_areas_values
|
70
|
+
areas_or_types = areas_for_select(current_organization)
|
71
|
+
|
72
|
+
areas_values = if areas_or_types.first.is_a?(Decidim::Area)
|
73
|
+
filter_areas(areas_or_types)
|
74
|
+
else
|
75
|
+
filter_areas_and_types(areas_or_types)
|
76
|
+
end
|
77
|
+
|
78
|
+
TreeNode.new(
|
79
|
+
TreePoint.new("", t("decidim.initiatives.application_helper.filter_area_values.all")),
|
80
|
+
areas_values
|
81
|
+
)
|
82
|
+
end
|
83
|
+
|
84
|
+
def filter_areas(areas)
|
85
|
+
areas.map do |area|
|
86
|
+
TreeNode.new(
|
87
|
+
TreePoint.new(area.id.to_s, area.name[I18n.locale.to_s])
|
88
|
+
)
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
def filter_areas_and_types(area_types)
|
93
|
+
area_types.map do |area_type|
|
94
|
+
TreeNode.new(
|
95
|
+
TreePoint.new(area_type.area_ids.join("_"), area_type.name[I18n.locale.to_s]),
|
96
|
+
area_type.areas.map do |area|
|
97
|
+
TreePoint.new(area.id.to_s, area.name[I18n.locale.to_s])
|
98
|
+
end
|
99
|
+
)
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|