decidim-plans 0.15.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/LICENSE-AGPLv3.txt +661 -0
- data/README.md +157 -0
- data/Rakefile +48 -0
- data/app/assets/config/admin/decidim_plans_manifest.js +3 -0
- data/app/assets/config/decidim_plans_manifest.css +4 -0
- data/app/assets/config/decidim_plans_manifest.js +2 -0
- data/app/assets/images/decidim/plans/icon.svg +3 -0
- data/app/assets/javascripts/decidim/plans/admin/plans.js.es6 +3 -0
- data/app/assets/javascripts/decidim/plans/admin/sections.js.es6 +78 -0
- data/app/assets/javascripts/decidim/plans/multifield/component.js.es6 +101 -0
- data/app/assets/javascripts/decidim/plans/multifield.js.es6 +5 -0
- data/app/assets/javascripts/decidim/plans/plans_form.js.es6 +9 -0
- data/app/assets/javascripts/decidim/plans/proposal_picker.js.es6 +41 -0
- data/app/assets/javascripts/decidim/plans/remaining_characters.es6 +42 -0
- data/app/assets/javascripts/decidim/plans/social_share.js +2 -0
- data/app/assets/javascripts/decidim/plans/tab_focus.es6 +20 -0
- data/app/assets/stylesheets/decidim/plans/plans_form.scss +26 -0
- data/app/assets/stylesheets/decidim/plans/proposal_picker.scss +3 -0
- data/app/assets/stylesheets/decidim/plans/social_share.css.scss +22 -0
- data/app/cells/decidim/plans/author/flag.erb +5 -0
- data/app/cells/decidim/plans/author/withdraw.erb +6 -0
- data/app/cells/decidim/plans/author_cell.rb +9 -0
- data/app/cells/decidim/plans/coauthorships_cell.rb +23 -0
- data/app/cells/decidim/plans/collapsible_authors/show.erb +17 -0
- data/app/cells/decidim/plans/collapsible_authors_cell.rb +9 -0
- data/app/cells/decidim/plans/plan_activity_cell.rb +19 -0
- data/app/cells/decidim/plans/plan_cell.rb +59 -0
- data/app/cells/decidim/plans/plan_m/footer.erb +8 -0
- data/app/cells/decidim/plans/plan_m_cell.rb +62 -0
- data/app/commands/decidim/plans/accept_access_to_plan.rb +53 -0
- data/app/commands/decidim/plans/admin/answer_plan.rb +82 -0
- data/app/commands/decidim/plans/admin/create_plan.rb +102 -0
- data/app/commands/decidim/plans/admin/update_plan.rb +98 -0
- data/app/commands/decidim/plans/admin/update_sections.rb +54 -0
- data/app/commands/decidim/plans/attachment_methods.rb +78 -0
- data/app/commands/decidim/plans/create_plan.rb +90 -0
- data/app/commands/decidim/plans/destroy_plan.rb +33 -0
- data/app/commands/decidim/plans/nested_updater.rb +26 -0
- data/app/commands/decidim/plans/publish_plan.rb +79 -0
- data/app/commands/decidim/plans/reject_access_to_plan.rb +46 -0
- data/app/commands/decidim/plans/request_access_to_plan.rb +50 -0
- data/app/commands/decidim/plans/respond_to_access_request.rb +76 -0
- data/app/commands/decidim/plans/update_plan.rb +90 -0
- data/app/commands/decidim/plans/withdraw_plan.rb +50 -0
- data/app/controllers/concerns/decidim/plans/orderable.rb +53 -0
- data/app/controllers/decidim/plans/admin/application_controller.rb +16 -0
- data/app/controllers/decidim/plans/admin/plan_answers_controller.rb +40 -0
- data/app/controllers/decidim/plans/admin/plans_controller.rb +80 -0
- data/app/controllers/decidim/plans/admin/sections_controller.rb +75 -0
- data/app/controllers/decidim/plans/application_controller.rb +30 -0
- data/app/controllers/decidim/plans/plan_collaborator_requests_controller.rb +53 -0
- data/app/controllers/decidim/plans/plan_widgets_controller.rb +19 -0
- data/app/controllers/decidim/plans/plans_controller.rb +192 -0
- data/app/controllers/decidim/plans/versions_controller.rb +51 -0
- data/app/events/decidim/plans/accepted_plan_event.rb +9 -0
- data/app/events/decidim/plans/evaluating_plan_event.rb +8 -0
- data/app/events/decidim/plans/plan_access_accepted_event.rb +8 -0
- data/app/events/decidim/plans/plan_access_rejected_event.rb +8 -0
- data/app/events/decidim/plans/plan_access_request_event.rb +27 -0
- data/app/events/decidim/plans/plan_access_requested_event.rb +8 -0
- data/app/events/decidim/plans/plan_access_requester_accepted_event.rb +8 -0
- data/app/events/decidim/plans/plan_access_requester_rejected_event.rb +8 -0
- data/app/events/decidim/plans/publish_plan_event.rb +25 -0
- data/app/events/decidim/plans/rejected_plan_event.rb +9 -0
- data/app/forms/decidim/plans/accept_access_to_plan_form.rb +10 -0
- data/app/forms/decidim/plans/access_to_plan_form.rb +35 -0
- data/app/forms/decidim/plans/admin/plan_answer_form.rb +19 -0
- data/app/forms/decidim/plans/admin/plan_form.rb +88 -0
- data/app/forms/decidim/plans/admin/plan_sections_form.rb +20 -0
- data/app/forms/decidim/plans/admin/section_form.rb +34 -0
- data/app/forms/decidim/plans/attachment_form.rb +20 -0
- data/app/forms/decidim/plans/content_form.rb +58 -0
- data/app/forms/decidim/plans/plan_form.rb +85 -0
- data/app/forms/decidim/plans/reject_access_to_plan_form.rb +9 -0
- data/app/forms/decidim/plans/request_access_to_plan_form.rb +20 -0
- data/app/helpers/decidim/plans/admin/application_helper.rb +19 -0
- data/app/helpers/decidim/plans/application_helper.rb +78 -0
- data/app/helpers/decidim/plans/attached_proposals_helper.rb +44 -0
- data/app/helpers/decidim/plans/attachments_helper.rb +59 -0
- data/app/helpers/decidim/plans/cells_helper.rb +26 -0
- data/app/helpers/decidim/plans/plan_cells_helper.rb +67 -0
- data/app/helpers/decidim/plans/remaining_characters_helper.rb +32 -0
- data/app/helpers/decidim/plans/traceability_helper.rb +79 -0
- data/app/helpers/decidim/plans/user_group_helper.rb +23 -0
- data/app/models/concerns/decidim/plans/traceable.rb +48 -0
- data/app/models/decidim/plans/application_record.rb +10 -0
- data/app/models/decidim/plans/attached_proposal.rb +12 -0
- data/app/models/decidim/plans/content.rb +21 -0
- data/app/models/decidim/plans/paper_trail/version.rb +26 -0
- data/app/models/decidim/plans/paper_trail/version_association.rb +14 -0
- data/app/models/decidim/plans/plan.rb +206 -0
- data/app/models/decidim/plans/plan_collaborator_request.rb +13 -0
- data/app/models/decidim/plans/section.rb +16 -0
- data/app/permissions/decidim/plans/admin/permissions.rb +44 -0
- data/app/permissions/decidim/plans/permissions.rb +67 -0
- data/app/presenters/decidim/plans/admin_log/plan_presenter.rb +49 -0
- data/app/presenters/decidim/plans/admin_log/value_types/plan_state_presenter.rb +16 -0
- data/app/presenters/decidim/plans/content_presenter.rb +25 -0
- data/app/presenters/decidim/plans/log/resource_presenter.rb +18 -0
- data/app/presenters/decidim/plans/official_author_presenter.rb +38 -0
- data/app/presenters/decidim/plans/plan_presenter.rb +48 -0
- data/app/queries/decidim/plans/filtered_plans.rb +37 -0
- data/app/services/decidim/plans/diff_renderer/base.rb +108 -0
- data/app/services/decidim/plans/diff_renderer/categorization.rb +23 -0
- data/app/services/decidim/plans/diff_renderer/content.rb +33 -0
- data/app/services/decidim/plans/diff_renderer/plan.rb +25 -0
- data/app/services/decidim/plans/loggability.rb +19 -0
- data/app/services/decidim/plans/plan_builder.rb +68 -0
- data/app/services/decidim/plans/plan_search.rb +95 -0
- data/app/services/decidim/plans/tracer.rb +29 -0
- data/app/views/decidim/plans/admin/plan_answers/edit.html.erb +22 -0
- data/app/views/decidim/plans/admin/plans/_bulk-actions.html.erb +9 -0
- data/app/views/decidim/plans/admin/plans/_form.html.erb +62 -0
- data/app/views/decidim/plans/admin/plans/_js-callout.html.erb +6 -0
- data/app/views/decidim/plans/admin/plans/_plan-tr.html.erb +53 -0
- data/app/views/decidim/plans/admin/plans/edit.html.erb +7 -0
- data/app/views/decidim/plans/admin/plans/index.html.erb +64 -0
- data/app/views/decidim/plans/admin/plans/new.html.erb +7 -0
- data/app/views/decidim/plans/admin/sections/_bulk-actions.html.erb +7 -0
- data/app/views/decidim/plans/admin/sections/_form.html.erb +27 -0
- data/app/views/decidim/plans/admin/sections/_section.html.erb +66 -0
- data/app/views/decidim/plans/admin/sections/index.html.erb +7 -0
- data/app/views/decidim/plans/admin/shared/_info_plan.html.erb +17 -0
- data/app/views/decidim/plans/attached_proposals/_proposals.html.erb +12 -0
- data/app/views/decidim/plans/plan_widgets/show.html.erb +3 -0
- data/app/views/decidim/plans/plans/_accept_request_access_form.html.erb +8 -0
- data/app/views/decidim/plans/plans/_attached_proposals.html.erb +10 -0
- data/app/views/decidim/plans/plans/_collaborator_requests.html.erb +20 -0
- data/app/views/decidim/plans/plans/_content.html.erb +6 -0
- data/app/views/decidim/plans/plans/_contents.html.erb +8 -0
- data/app/views/decidim/plans/plans/_count.html.erb +1 -0
- data/app/views/decidim/plans/plans/_filters.html.erb +32 -0
- data/app/views/decidim/plans/plans/_filters_small_view.html.erb +18 -0
- data/app/views/decidim/plans/plans/_form.html.erb +57 -0
- data/app/views/decidim/plans/plans/_new_plan_button.html.erb +11 -0
- data/app/views/decidim/plans/plans/_plan.html.erb +1 -0
- data/app/views/decidim/plans/plans/_plan_preview.html.erb +1 -0
- data/app/views/decidim/plans/plans/_plans.html.erb +14 -0
- data/app/views/decidim/plans/plans/_reject_request_access_form.html.erb +10 -0
- data/app/views/decidim/plans/plans/_request_access_form.html.erb +7 -0
- data/app/views/decidim/plans/plans/edit.html.erb +21 -0
- data/app/views/decidim/plans/plans/index.html.erb +28 -0
- data/app/views/decidim/plans/plans/index.js.erb +10 -0
- data/app/views/decidim/plans/plans/new.html.erb +15 -0
- data/app/views/decidim/plans/plans/preview.html.erb +15 -0
- data/app/views/decidim/plans/plans/show.html.erb +133 -0
- data/app/views/decidim/plans/shared/_attachment_fields.html.erb +45 -0
- data/app/views/decidim/plans/shared/_attachments.html.erb +23 -0
- data/app/views/decidim/plans/shared/_remaining_characters_container.html.erb +12 -0
- data/app/views/decidim/plans/shared/_section_content_field.html.erb +39 -0
- data/app/views/decidim/plans/versions/_version.html.erb +20 -0
- data/app/views/decidim/plans/versions/index.html.erb +34 -0
- data/app/views/decidim/plans/versions/show.html.erb +41 -0
- data/config/locales/en.yml +341 -0
- data/config/locales/fi.yml +341 -0
- data/config/locales/sv.yml +341 -0
- data/db/migrate/20181230110225_enable_pg_trgm_extension_for_plans.rb +18 -0
- data/db/migrate/20181230110848_create_decidim_plans.rb +20 -0
- data/db/migrate/20181230111731_create_decidim_plans_sections.rb +13 -0
- data/db/migrate/20181230111931_create_decidim_plans_plan_contents.rb +14 -0
- data/db/migrate/20181230122035_create_decidim_plans_plan_collaborator_requests.rb +12 -0
- data/db/migrate/20190110125855_create_decidim_plans_attached_proposals.rb +12 -0
- data/db/migrate/20190129140441_add_help_to_decidim_plans_sections.rb +7 -0
- data/db/migrate/20190129143607_add_mandatory_to_decidim_plans_sections.rb +7 -0
- data/db/migrate/20190129172040_add_section_type_to_decidim_plans_sections.rb +12 -0
- data/db/migrate/20190129231439_add_answer_length_to_decidim_plans_sections.rb +11 -0
- data/db/migrate/20190202123044_create_version_associations.rb +25 -0
- data/db/migrate/20190202123045_add_transaction_id_column_to_versions.rb +15 -0
- data/db/migrate/20190202200716_add_update_token_to_decidim_plans.rb +7 -0
- data/lib/decidim/plans/admin.rb +10 -0
- data/lib/decidim/plans/admin_engine.rb +29 -0
- data/lib/decidim/plans/component.rb +163 -0
- data/lib/decidim/plans/engine.rb +43 -0
- data/lib/decidim/plans/locale_aware.rb +32 -0
- data/lib/decidim/plans/optionally_translatable_attributes.rb +85 -0
- data/lib/decidim/plans/paper_trail/record_trail.rb +24 -0
- data/lib/decidim/plans/paper_trail.rb +10 -0
- data/lib/decidim/plans/test/factories.rb +130 -0
- data/lib/decidim/plans/version.rb +8 -0
- data/lib/decidim/plans.rb +24 -0
- metadata +323 -0
@@ -0,0 +1,33 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Decidim
|
4
|
+
module Plans
|
5
|
+
# A command with all the business logic when a user destroys a draft plan.
|
6
|
+
class DestroyPlan < Rectify::Command
|
7
|
+
# Public: Initializes the command.
|
8
|
+
#
|
9
|
+
# plan - The plan to destroy.
|
10
|
+
# current_user - The current user.
|
11
|
+
def initialize(plan, current_user)
|
12
|
+
@plan = plan
|
13
|
+
@current_user = current_user
|
14
|
+
end
|
15
|
+
|
16
|
+
# Executes the command. Broadcasts these events:
|
17
|
+
#
|
18
|
+
# - :ok when everything is valid and the plan is deleted.
|
19
|
+
# - :invalid if the plan is not a draft.
|
20
|
+
# - :invalid if the plan's author is not the current user.
|
21
|
+
#
|
22
|
+
# Returns nothing.
|
23
|
+
def call
|
24
|
+
return broadcast(:invalid) unless @plan.draft?
|
25
|
+
return broadcast(:invalid) unless @plan.authored_by?(@current_user)
|
26
|
+
|
27
|
+
@plan.destroy!
|
28
|
+
|
29
|
+
broadcast(:ok, @plan)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Decidim
|
4
|
+
module Plans
|
5
|
+
# Updater for nested models.
|
6
|
+
module NestedUpdater
|
7
|
+
private
|
8
|
+
|
9
|
+
def update_nested_model(form, attributes, parent_association)
|
10
|
+
record = parent_association.find_by(id: form.id) || parent_association.build(attributes)
|
11
|
+
|
12
|
+
yield record if block_given?
|
13
|
+
|
14
|
+
if record.persisted?
|
15
|
+
if form.deleted?
|
16
|
+
record.destroy!
|
17
|
+
else
|
18
|
+
record.update!(attributes)
|
19
|
+
end
|
20
|
+
elsif !form.deleted?
|
21
|
+
record.save!
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,79 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Decidim
|
4
|
+
module Plans
|
5
|
+
# A command with all the business logic when a user creates a new plan.
|
6
|
+
class PublishPlan < Rectify::Command
|
7
|
+
# Public: Initializes the command.
|
8
|
+
#
|
9
|
+
# plan - The plan to publish.
|
10
|
+
# current_user - The current user.
|
11
|
+
def initialize(plan, current_user)
|
12
|
+
@plan = plan
|
13
|
+
@current_user = current_user
|
14
|
+
end
|
15
|
+
|
16
|
+
# Executes the command. Broadcasts these events:
|
17
|
+
#
|
18
|
+
# - :ok when everything is valid and the plan is published.
|
19
|
+
# - :invalid if the plan's author is not the current user.
|
20
|
+
#
|
21
|
+
# Returns nothing.
|
22
|
+
def call
|
23
|
+
return broadcast(:invalid) unless @plan.authored_by?(@current_user)
|
24
|
+
|
25
|
+
transaction do
|
26
|
+
publish_plan
|
27
|
+
send_notification
|
28
|
+
send_notification_to_participatory_space
|
29
|
+
end
|
30
|
+
|
31
|
+
broadcast(:ok, @plan)
|
32
|
+
end
|
33
|
+
|
34
|
+
private
|
35
|
+
|
36
|
+
def publish_plan
|
37
|
+
Decidim.traceability.perform_action!(
|
38
|
+
"publish",
|
39
|
+
@plan,
|
40
|
+
@current_user,
|
41
|
+
visibility: "public-only"
|
42
|
+
) do
|
43
|
+
@plan.update published_at: Time.current
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def send_notification
|
48
|
+
return if @plan.coauthorships.empty?
|
49
|
+
|
50
|
+
Decidim::EventsManager.publish(
|
51
|
+
event: "decidim.events.plans.plan_published",
|
52
|
+
event_class: Decidim::Plans::PublishPlanEvent,
|
53
|
+
resource: @plan,
|
54
|
+
recipient_ids: coauthors_followers
|
55
|
+
)
|
56
|
+
end
|
57
|
+
|
58
|
+
def send_notification_to_participatory_space
|
59
|
+
Decidim::EventsManager.publish(
|
60
|
+
event: "decidim.events.plans.plan_published",
|
61
|
+
event_class: Decidim::Plans::PublishPlanEvent,
|
62
|
+
resource: @plan,
|
63
|
+
recipient_ids: @plan.participatory_space.followers.pluck(:id) - coauthors_followers,
|
64
|
+
extra: {
|
65
|
+
participatory_space: true
|
66
|
+
}
|
67
|
+
)
|
68
|
+
end
|
69
|
+
|
70
|
+
def coauthors_followers
|
71
|
+
followers_ids = []
|
72
|
+
@plan.authors.each do |author|
|
73
|
+
followers_ids += author.followers.pluck(:id)
|
74
|
+
end
|
75
|
+
followers_ids
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Decidim
|
4
|
+
module Plans
|
5
|
+
# A command with all the business logic to reject a user request to
|
6
|
+
# contribute to a plan.
|
7
|
+
class RejectAccessToPlan < RespondToAccessRequest
|
8
|
+
# Executes the command. Broadcasts these events:
|
9
|
+
#
|
10
|
+
# - :ok when everything is valid.
|
11
|
+
# - :invalid if it wasn't valid and we couldn't proceed.
|
12
|
+
#
|
13
|
+
# Returns nothing.
|
14
|
+
def call
|
15
|
+
return broadcast(:invalid) if @form.invalid?
|
16
|
+
return broadcast(:invalid) if @current_user.nil?
|
17
|
+
|
18
|
+
@plan.requesters.delete @requester_user
|
19
|
+
|
20
|
+
notify_plan_requester
|
21
|
+
notify_plan_authors
|
22
|
+
broadcast(:ok, @requester_user)
|
23
|
+
end
|
24
|
+
|
25
|
+
def recipient_ids
|
26
|
+
@plan.authors.pluck(:id)
|
27
|
+
end
|
28
|
+
|
29
|
+
def authors_event
|
30
|
+
"decidim.events.plans.plan_access_rejected"
|
31
|
+
end
|
32
|
+
|
33
|
+
def authors_event_class
|
34
|
+
Decidim::Plans::PlanAccessRejectedEvent
|
35
|
+
end
|
36
|
+
|
37
|
+
def requester_event
|
38
|
+
"decidim.events.plans.plan_access_requester_rejected"
|
39
|
+
end
|
40
|
+
|
41
|
+
def requester_event_class
|
42
|
+
Decidim::Plans::PlanAccessRequesterRejectedEvent
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Decidim
|
4
|
+
module Plans
|
5
|
+
# A command with all the business logic when a user requests
|
6
|
+
# access to edit a plan.
|
7
|
+
class RequestAccessToPlan < Rectify::Command
|
8
|
+
# Public: Initializes the command.
|
9
|
+
#
|
10
|
+
# form - A form object with the params.
|
11
|
+
# plan - A Decidim::Plans::Plan object.
|
12
|
+
# current_user - The current user and requester user
|
13
|
+
def initialize(form, current_user)
|
14
|
+
@form = form
|
15
|
+
@plan = form.plan
|
16
|
+
@current_user = current_user
|
17
|
+
end
|
18
|
+
|
19
|
+
# Executes the command. Broadcasts these events:
|
20
|
+
#
|
21
|
+
# - :ok when everything is valid.
|
22
|
+
# - :invalid if it wasn't valid and we couldn't proceed.
|
23
|
+
#
|
24
|
+
# Returns nothing.
|
25
|
+
def call
|
26
|
+
return broadcast(:invalid) if @form.invalid?
|
27
|
+
return broadcast(:invalid) if @current_user.nil?
|
28
|
+
|
29
|
+
@plan.collaborator_requests.create!(user: @current_user)
|
30
|
+
notify_plan_authors
|
31
|
+
broadcast(:ok, @plan)
|
32
|
+
end
|
33
|
+
|
34
|
+
private
|
35
|
+
|
36
|
+
def notify_plan_authors
|
37
|
+
recipient_ids = @plan.authors.pluck(:id)
|
38
|
+
Decidim::EventsManager.publish(
|
39
|
+
event: "decidim.events.plans.plan_access_requested",
|
40
|
+
event_class: Decidim::Plans::PlanAccessRequestedEvent,
|
41
|
+
resource: @plan,
|
42
|
+
recipient_ids: recipient_ids.uniq,
|
43
|
+
extra: {
|
44
|
+
requester_id: @current_user.id
|
45
|
+
}
|
46
|
+
)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,76 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Decidim
|
4
|
+
module Plans
|
5
|
+
# Common functionality for {Accept,Reject}AccessToPlan.
|
6
|
+
class RespondToAccessRequest < Rectify::Command
|
7
|
+
# Public: Initializes the command.
|
8
|
+
#
|
9
|
+
# form - A form object with the params.
|
10
|
+
# plan - A Decidim::Plans::Plan object.
|
11
|
+
# current_user - The current user.
|
12
|
+
# requester_user - The user that requested to collaborate.
|
13
|
+
def initialize(form, current_user)
|
14
|
+
@form = form
|
15
|
+
@plan = form.plan
|
16
|
+
@current_user = current_user
|
17
|
+
@requester_user = form.requester_user
|
18
|
+
end
|
19
|
+
|
20
|
+
# Executes the command. Broadcasts these events:
|
21
|
+
#
|
22
|
+
# - :ok when everything is valid.
|
23
|
+
# - :invalid if it wasn't valid and we couldn't proceed.
|
24
|
+
#
|
25
|
+
# Returns nothing.
|
26
|
+
def call
|
27
|
+
return broadcast(:invalid) if @form.invalid?
|
28
|
+
return broadcast(:invalid) if @current_user.nil?
|
29
|
+
|
30
|
+
transaction do
|
31
|
+
@plan.requesters.delete @requester_user
|
32
|
+
|
33
|
+
Decidim::Coauthorship.create(
|
34
|
+
coauthorable: @plan,
|
35
|
+
author: @requester_user
|
36
|
+
)
|
37
|
+
end
|
38
|
+
|
39
|
+
notify_plan_requester
|
40
|
+
notify_plan_authors
|
41
|
+
broadcast(:ok, @requester_user)
|
42
|
+
end
|
43
|
+
|
44
|
+
def notify_plan_authors
|
45
|
+
Decidim::EventsManager.publish(
|
46
|
+
event: authors_event,
|
47
|
+
event_class: authors_event_class,
|
48
|
+
resource: @plan,
|
49
|
+
recipient_ids: recipient_ids.uniq,
|
50
|
+
extra: {
|
51
|
+
requester_id: @requester_user.id
|
52
|
+
}
|
53
|
+
)
|
54
|
+
end
|
55
|
+
|
56
|
+
def notify_plan_requester
|
57
|
+
Decidim::EventsManager.publish(
|
58
|
+
event: requester_event,
|
59
|
+
event_class: requester_event_class,
|
60
|
+
resource: @plan,
|
61
|
+
recipient_ids: [@requester_user.id]
|
62
|
+
)
|
63
|
+
end
|
64
|
+
|
65
|
+
def recipient_ids; end
|
66
|
+
|
67
|
+
def authors_event; end
|
68
|
+
|
69
|
+
def authors_event_class; end
|
70
|
+
|
71
|
+
def requester_event; end
|
72
|
+
|
73
|
+
def requester_event_class; end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
@@ -0,0 +1,90 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Decidim
|
4
|
+
module Plans
|
5
|
+
# A command with all the business logic when a user updates a plan.
|
6
|
+
class UpdatePlan < Rectify::Command
|
7
|
+
include AttachmentMethods
|
8
|
+
include NestedUpdater
|
9
|
+
|
10
|
+
# Public: Initializes the command.
|
11
|
+
#
|
12
|
+
# form - A form object with the params.
|
13
|
+
# current_user - The current user.
|
14
|
+
# plan - the plan to update.
|
15
|
+
def initialize(form, current_user, plan)
|
16
|
+
@form = form
|
17
|
+
@current_user = current_user
|
18
|
+
@plan = plan
|
19
|
+
@attached_to = plan
|
20
|
+
end
|
21
|
+
|
22
|
+
# Executes the command. Broadcasts these events:
|
23
|
+
#
|
24
|
+
# - :ok when everything is valid, together with the plan.
|
25
|
+
# - :invalid if the form wasn't valid and we couldn't proceed.
|
26
|
+
#
|
27
|
+
# Returns nothing.
|
28
|
+
def call
|
29
|
+
return broadcast(:invalid) unless plan.editable_by?(current_user)
|
30
|
+
|
31
|
+
if process_attachments?
|
32
|
+
prepare_attachments
|
33
|
+
return broadcast(:invalid) if attachments_invalid?
|
34
|
+
end
|
35
|
+
|
36
|
+
if form.invalid?
|
37
|
+
mark_attachment_reattachment
|
38
|
+
return broadcast(:invalid)
|
39
|
+
end
|
40
|
+
|
41
|
+
Decidim::Plans.tracer.trace!(@current_user) do
|
42
|
+
transaction do
|
43
|
+
update_plan
|
44
|
+
update_plan_contents
|
45
|
+
update_attachments if process_attachments?
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
broadcast(:ok, plan)
|
50
|
+
end
|
51
|
+
|
52
|
+
private
|
53
|
+
|
54
|
+
attr_reader :form, :plan, :current_user, :attachments
|
55
|
+
|
56
|
+
def update_plan
|
57
|
+
Decidim::Plans.loggability.update!(
|
58
|
+
@plan,
|
59
|
+
@current_user,
|
60
|
+
attributes
|
61
|
+
)
|
62
|
+
end
|
63
|
+
|
64
|
+
def update_plan_contents
|
65
|
+
@form.contents.each do |content|
|
66
|
+
update_nested_model(
|
67
|
+
content,
|
68
|
+
{ body: content.body,
|
69
|
+
section: content.section,
|
70
|
+
user: @form.current_user },
|
71
|
+
@plan.contents
|
72
|
+
)
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
def attributes
|
77
|
+
{
|
78
|
+
title: @form.title,
|
79
|
+
category: @form.category,
|
80
|
+
scope: @form.scope,
|
81
|
+
proposals: @form.proposals,
|
82
|
+
# The update token ensures a new version is always created even if
|
83
|
+
# the other attributes have not changed. This is needed to force a new
|
84
|
+
# version to show the changes to associated models.
|
85
|
+
update_token: Time.now.to_f
|
86
|
+
}
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Decidim
|
4
|
+
module Plans
|
5
|
+
# A command with all the business logic when a user withdraws a plan.
|
6
|
+
class WithdrawPlan < Rectify::Command
|
7
|
+
# Public: Initializes the command.
|
8
|
+
#
|
9
|
+
# plan - The plan to withdraw.
|
10
|
+
# current_user - The current user.
|
11
|
+
def initialize(plan, current_user)
|
12
|
+
@plan = plan
|
13
|
+
@current_user = current_user
|
14
|
+
end
|
15
|
+
|
16
|
+
# Executes the command. Broadcasts these events:
|
17
|
+
#
|
18
|
+
# - :ok when everything is valid, together with the proposal.
|
19
|
+
# - :invalid if the proposal already has supports or does not belong to current user.
|
20
|
+
#
|
21
|
+
# Returns nothing.
|
22
|
+
def call
|
23
|
+
# return broadcast(:invalid) if @plan.votes.any?
|
24
|
+
|
25
|
+
transaction do
|
26
|
+
change_plan_state_to_withdrawn
|
27
|
+
# reject_emendations_if_any # 0.16+
|
28
|
+
end
|
29
|
+
|
30
|
+
broadcast(:ok, @plan)
|
31
|
+
end
|
32
|
+
|
33
|
+
private
|
34
|
+
|
35
|
+
def change_plan_state_to_withdrawn
|
36
|
+
@plan.update state: "withdrawn"
|
37
|
+
end
|
38
|
+
|
39
|
+
def reject_emendations_if_any
|
40
|
+
return if @plan.emendations.empty?
|
41
|
+
|
42
|
+
@plan.emendations.each do |emendation|
|
43
|
+
@form = form(Decidim::Amendable::RejectForm).from_params(id: emendation.amendment.id)
|
44
|
+
result = Decidim::Amendable::Reject.call(@form)
|
45
|
+
return result[:ok] if result[:ok]
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "active_support/concern"
|
4
|
+
|
5
|
+
module Decidim
|
6
|
+
module Plans
|
7
|
+
# Common logic to ordering resources
|
8
|
+
module Orderable
|
9
|
+
extend ActiveSupport::Concern
|
10
|
+
|
11
|
+
included do
|
12
|
+
helper_method :order, :available_orders, :random_seed
|
13
|
+
|
14
|
+
private
|
15
|
+
|
16
|
+
# Gets how the plans should be ordered based on the choice made by the user.
|
17
|
+
def order
|
18
|
+
@order ||= detect_order(params[:order]) || default_order
|
19
|
+
end
|
20
|
+
|
21
|
+
# Available orders based on enabled settings
|
22
|
+
def available_orders
|
23
|
+
@available_orders ||= begin
|
24
|
+
available_orders = %w(random recent)
|
25
|
+
available_orders
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def default_order
|
30
|
+
"random"
|
31
|
+
end
|
32
|
+
|
33
|
+
# Returns: A random float number between -1 and 1 to be used as a random seed at the database.
|
34
|
+
def random_seed
|
35
|
+
@random_seed ||= (params[:random_seed] ? params[:random_seed].to_f : (rand * 2 - 1))
|
36
|
+
end
|
37
|
+
|
38
|
+
def detect_order(candidate)
|
39
|
+
available_orders.detect { |order| order == candidate }
|
40
|
+
end
|
41
|
+
|
42
|
+
def reorder(plans)
|
43
|
+
case order
|
44
|
+
when "random"
|
45
|
+
plans.order_randomly(random_seed)
|
46
|
+
when "recent"
|
47
|
+
plans.order(created_at: :desc)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Decidim
|
4
|
+
module Plans
|
5
|
+
module Admin
|
6
|
+
# This controller is the abstract class from which all other controllers of
|
7
|
+
# this engine inherit.
|
8
|
+
#
|
9
|
+
# Note that it inherits from `Decidim::Admin::Components::BaseController`, which
|
10
|
+
# override its layout and provide all kinds of useful methods.
|
11
|
+
class ApplicationController < Decidim::Admin::Components::BaseController
|
12
|
+
helper Decidim::ApplicationHelper
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Decidim
|
4
|
+
module Plans
|
5
|
+
module Admin
|
6
|
+
# This controller allows admins to answer plans in a participatory process.
|
7
|
+
class PlanAnswersController < Admin::ApplicationController
|
8
|
+
helper_method :plan
|
9
|
+
|
10
|
+
def edit
|
11
|
+
enforce_permission_to :create, :plan_answer
|
12
|
+
@form = form(Admin::PlanAnswerForm).from_model(plan)
|
13
|
+
end
|
14
|
+
|
15
|
+
def update
|
16
|
+
enforce_permission_to :create, :plan_answer
|
17
|
+
@form = form(Admin::PlanAnswerForm).from_params(params)
|
18
|
+
|
19
|
+
Admin::AnswerPlan.call(@form, plan) do
|
20
|
+
on(:ok) do
|
21
|
+
flash[:notice] = I18n.t("plans.answer.success", scope: "decidim.plans.admin")
|
22
|
+
redirect_to plans_path
|
23
|
+
end
|
24
|
+
|
25
|
+
on(:invalid) do
|
26
|
+
flash.now[:alert] = I18n.t("plans.answer.invalid", scope: "decidim.plans.admin")
|
27
|
+
render action: "edit"
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
private
|
33
|
+
|
34
|
+
def plan
|
35
|
+
@plan ||= Plan.where(component: current_component).find(params[:id])
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,80 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Decidim
|
4
|
+
module Plans
|
5
|
+
module Admin
|
6
|
+
# This controller allows admins to manage plans in a participatory process.
|
7
|
+
class PlansController < Admin::ApplicationController
|
8
|
+
include Decidim::ApplicationHelper
|
9
|
+
include Plans::AttachedProposalsHelper
|
10
|
+
|
11
|
+
helper Plans::ApplicationHelper
|
12
|
+
helper Plans::AttachmentsHelper
|
13
|
+
helper Plans::RemainingCharactersHelper
|
14
|
+
helper_method :plans, :query, :form_presenter, :attached_proposals_picker_field
|
15
|
+
|
16
|
+
def new
|
17
|
+
enforce_permission_to :create, :plans
|
18
|
+
@form = form(Admin::PlanForm).from_model(Plan.new(component: current_component))
|
19
|
+
end
|
20
|
+
|
21
|
+
def create
|
22
|
+
enforce_permission_to :create, :plan
|
23
|
+
@form = form(Admin::PlanForm).from_params(params)
|
24
|
+
|
25
|
+
Admin::CreatePlan.call(@form) do
|
26
|
+
on(:ok) do
|
27
|
+
flash[:notice] = I18n.t("plans.create.success", scope: "decidim")
|
28
|
+
redirect_to plans_path
|
29
|
+
end
|
30
|
+
|
31
|
+
on(:invalid) do
|
32
|
+
flash.now[:alert] = I18n.t("plans.create.invalid", scope: "decidim")
|
33
|
+
render action: "new"
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def edit
|
39
|
+
enforce_permission_to :edit, :plan, plan: plan
|
40
|
+
@form = form(Admin::PlanForm).from_model(plan)
|
41
|
+
end
|
42
|
+
|
43
|
+
def update
|
44
|
+
enforce_permission_to :edit, :plan, plan: plan
|
45
|
+
|
46
|
+
@form = form(Admin::PlanForm).from_params(params)
|
47
|
+
Admin::UpdatePlan.call(@form, @plan) do
|
48
|
+
on(:ok) do
|
49
|
+
flash[:notice] = I18n.t("plans.update.success", scope: "decidim")
|
50
|
+
redirect_to plans_path
|
51
|
+
end
|
52
|
+
|
53
|
+
on(:invalid) do
|
54
|
+
flash.now[:alert] = I18n.t("plans.update.invalid", scope: "decidim")
|
55
|
+
render :edit
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
private
|
61
|
+
|
62
|
+
def query
|
63
|
+
@query ||= Plan.where(component: current_component).ransack(params[:q])
|
64
|
+
end
|
65
|
+
|
66
|
+
def plans
|
67
|
+
@plans ||= query.result.page(params[:page]).per(15)
|
68
|
+
end
|
69
|
+
|
70
|
+
def plan
|
71
|
+
@plan ||= Plan.where(component: current_component).find(params[:id])
|
72
|
+
end
|
73
|
+
|
74
|
+
def form_presenter
|
75
|
+
@form_presenter ||= present(@form, presenter_class: Decidim::Plans::PlanPresenter)
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|