decidim-plans 0.15.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (182) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE-AGPLv3.txt +661 -0
  3. data/README.md +157 -0
  4. data/Rakefile +48 -0
  5. data/app/assets/config/admin/decidim_plans_manifest.js +3 -0
  6. data/app/assets/config/decidim_plans_manifest.css +4 -0
  7. data/app/assets/config/decidim_plans_manifest.js +2 -0
  8. data/app/assets/images/decidim/plans/icon.svg +3 -0
  9. data/app/assets/javascripts/decidim/plans/admin/plans.js.es6 +3 -0
  10. data/app/assets/javascripts/decidim/plans/admin/sections.js.es6 +78 -0
  11. data/app/assets/javascripts/decidim/plans/multifield/component.js.es6 +101 -0
  12. data/app/assets/javascripts/decidim/plans/multifield.js.es6 +5 -0
  13. data/app/assets/javascripts/decidim/plans/plans_form.js.es6 +9 -0
  14. data/app/assets/javascripts/decidim/plans/proposal_picker.js.es6 +41 -0
  15. data/app/assets/javascripts/decidim/plans/remaining_characters.es6 +42 -0
  16. data/app/assets/javascripts/decidim/plans/social_share.js +2 -0
  17. data/app/assets/javascripts/decidim/plans/tab_focus.es6 +20 -0
  18. data/app/assets/stylesheets/decidim/plans/plans_form.scss +26 -0
  19. data/app/assets/stylesheets/decidim/plans/proposal_picker.scss +3 -0
  20. data/app/assets/stylesheets/decidim/plans/social_share.css.scss +22 -0
  21. data/app/cells/decidim/plans/author/flag.erb +5 -0
  22. data/app/cells/decidim/plans/author/withdraw.erb +6 -0
  23. data/app/cells/decidim/plans/author_cell.rb +9 -0
  24. data/app/cells/decidim/plans/coauthorships_cell.rb +23 -0
  25. data/app/cells/decidim/plans/collapsible_authors/show.erb +17 -0
  26. data/app/cells/decidim/plans/collapsible_authors_cell.rb +9 -0
  27. data/app/cells/decidim/plans/plan_activity_cell.rb +19 -0
  28. data/app/cells/decidim/plans/plan_cell.rb +59 -0
  29. data/app/cells/decidim/plans/plan_m/footer.erb +8 -0
  30. data/app/cells/decidim/plans/plan_m_cell.rb +62 -0
  31. data/app/commands/decidim/plans/accept_access_to_plan.rb +53 -0
  32. data/app/commands/decidim/plans/admin/answer_plan.rb +82 -0
  33. data/app/commands/decidim/plans/admin/create_plan.rb +102 -0
  34. data/app/commands/decidim/plans/admin/update_plan.rb +98 -0
  35. data/app/commands/decidim/plans/admin/update_sections.rb +54 -0
  36. data/app/commands/decidim/plans/attachment_methods.rb +78 -0
  37. data/app/commands/decidim/plans/create_plan.rb +90 -0
  38. data/app/commands/decidim/plans/destroy_plan.rb +33 -0
  39. data/app/commands/decidim/plans/nested_updater.rb +26 -0
  40. data/app/commands/decidim/plans/publish_plan.rb +79 -0
  41. data/app/commands/decidim/plans/reject_access_to_plan.rb +46 -0
  42. data/app/commands/decidim/plans/request_access_to_plan.rb +50 -0
  43. data/app/commands/decidim/plans/respond_to_access_request.rb +76 -0
  44. data/app/commands/decidim/plans/update_plan.rb +90 -0
  45. data/app/commands/decidim/plans/withdraw_plan.rb +50 -0
  46. data/app/controllers/concerns/decidim/plans/orderable.rb +53 -0
  47. data/app/controllers/decidim/plans/admin/application_controller.rb +16 -0
  48. data/app/controllers/decidim/plans/admin/plan_answers_controller.rb +40 -0
  49. data/app/controllers/decidim/plans/admin/plans_controller.rb +80 -0
  50. data/app/controllers/decidim/plans/admin/sections_controller.rb +75 -0
  51. data/app/controllers/decidim/plans/application_controller.rb +30 -0
  52. data/app/controllers/decidim/plans/plan_collaborator_requests_controller.rb +53 -0
  53. data/app/controllers/decidim/plans/plan_widgets_controller.rb +19 -0
  54. data/app/controllers/decidim/plans/plans_controller.rb +192 -0
  55. data/app/controllers/decidim/plans/versions_controller.rb +51 -0
  56. data/app/events/decidim/plans/accepted_plan_event.rb +9 -0
  57. data/app/events/decidim/plans/evaluating_plan_event.rb +8 -0
  58. data/app/events/decidim/plans/plan_access_accepted_event.rb +8 -0
  59. data/app/events/decidim/plans/plan_access_rejected_event.rb +8 -0
  60. data/app/events/decidim/plans/plan_access_request_event.rb +27 -0
  61. data/app/events/decidim/plans/plan_access_requested_event.rb +8 -0
  62. data/app/events/decidim/plans/plan_access_requester_accepted_event.rb +8 -0
  63. data/app/events/decidim/plans/plan_access_requester_rejected_event.rb +8 -0
  64. data/app/events/decidim/plans/publish_plan_event.rb +25 -0
  65. data/app/events/decidim/plans/rejected_plan_event.rb +9 -0
  66. data/app/forms/decidim/plans/accept_access_to_plan_form.rb +10 -0
  67. data/app/forms/decidim/plans/access_to_plan_form.rb +35 -0
  68. data/app/forms/decidim/plans/admin/plan_answer_form.rb +19 -0
  69. data/app/forms/decidim/plans/admin/plan_form.rb +88 -0
  70. data/app/forms/decidim/plans/admin/plan_sections_form.rb +20 -0
  71. data/app/forms/decidim/plans/admin/section_form.rb +34 -0
  72. data/app/forms/decidim/plans/attachment_form.rb +20 -0
  73. data/app/forms/decidim/plans/content_form.rb +58 -0
  74. data/app/forms/decidim/plans/plan_form.rb +85 -0
  75. data/app/forms/decidim/plans/reject_access_to_plan_form.rb +9 -0
  76. data/app/forms/decidim/plans/request_access_to_plan_form.rb +20 -0
  77. data/app/helpers/decidim/plans/admin/application_helper.rb +19 -0
  78. data/app/helpers/decidim/plans/application_helper.rb +78 -0
  79. data/app/helpers/decidim/plans/attached_proposals_helper.rb +44 -0
  80. data/app/helpers/decidim/plans/attachments_helper.rb +59 -0
  81. data/app/helpers/decidim/plans/cells_helper.rb +26 -0
  82. data/app/helpers/decidim/plans/plan_cells_helper.rb +67 -0
  83. data/app/helpers/decidim/plans/remaining_characters_helper.rb +32 -0
  84. data/app/helpers/decidim/plans/traceability_helper.rb +79 -0
  85. data/app/helpers/decidim/plans/user_group_helper.rb +23 -0
  86. data/app/models/concerns/decidim/plans/traceable.rb +48 -0
  87. data/app/models/decidim/plans/application_record.rb +10 -0
  88. data/app/models/decidim/plans/attached_proposal.rb +12 -0
  89. data/app/models/decidim/plans/content.rb +21 -0
  90. data/app/models/decidim/plans/paper_trail/version.rb +26 -0
  91. data/app/models/decidim/plans/paper_trail/version_association.rb +14 -0
  92. data/app/models/decidim/plans/plan.rb +206 -0
  93. data/app/models/decidim/plans/plan_collaborator_request.rb +13 -0
  94. data/app/models/decidim/plans/section.rb +16 -0
  95. data/app/permissions/decidim/plans/admin/permissions.rb +44 -0
  96. data/app/permissions/decidim/plans/permissions.rb +67 -0
  97. data/app/presenters/decidim/plans/admin_log/plan_presenter.rb +49 -0
  98. data/app/presenters/decidim/plans/admin_log/value_types/plan_state_presenter.rb +16 -0
  99. data/app/presenters/decidim/plans/content_presenter.rb +25 -0
  100. data/app/presenters/decidim/plans/log/resource_presenter.rb +18 -0
  101. data/app/presenters/decidim/plans/official_author_presenter.rb +38 -0
  102. data/app/presenters/decidim/plans/plan_presenter.rb +48 -0
  103. data/app/queries/decidim/plans/filtered_plans.rb +37 -0
  104. data/app/services/decidim/plans/diff_renderer/base.rb +108 -0
  105. data/app/services/decidim/plans/diff_renderer/categorization.rb +23 -0
  106. data/app/services/decidim/plans/diff_renderer/content.rb +33 -0
  107. data/app/services/decidim/plans/diff_renderer/plan.rb +25 -0
  108. data/app/services/decidim/plans/loggability.rb +19 -0
  109. data/app/services/decidim/plans/plan_builder.rb +68 -0
  110. data/app/services/decidim/plans/plan_search.rb +95 -0
  111. data/app/services/decidim/plans/tracer.rb +29 -0
  112. data/app/views/decidim/plans/admin/plan_answers/edit.html.erb +22 -0
  113. data/app/views/decidim/plans/admin/plans/_bulk-actions.html.erb +9 -0
  114. data/app/views/decidim/plans/admin/plans/_form.html.erb +62 -0
  115. data/app/views/decidim/plans/admin/plans/_js-callout.html.erb +6 -0
  116. data/app/views/decidim/plans/admin/plans/_plan-tr.html.erb +53 -0
  117. data/app/views/decidim/plans/admin/plans/edit.html.erb +7 -0
  118. data/app/views/decidim/plans/admin/plans/index.html.erb +64 -0
  119. data/app/views/decidim/plans/admin/plans/new.html.erb +7 -0
  120. data/app/views/decidim/plans/admin/sections/_bulk-actions.html.erb +7 -0
  121. data/app/views/decidim/plans/admin/sections/_form.html.erb +27 -0
  122. data/app/views/decidim/plans/admin/sections/_section.html.erb +66 -0
  123. data/app/views/decidim/plans/admin/sections/index.html.erb +7 -0
  124. data/app/views/decidim/plans/admin/shared/_info_plan.html.erb +17 -0
  125. data/app/views/decidim/plans/attached_proposals/_proposals.html.erb +12 -0
  126. data/app/views/decidim/plans/plan_widgets/show.html.erb +3 -0
  127. data/app/views/decidim/plans/plans/_accept_request_access_form.html.erb +8 -0
  128. data/app/views/decidim/plans/plans/_attached_proposals.html.erb +10 -0
  129. data/app/views/decidim/plans/plans/_collaborator_requests.html.erb +20 -0
  130. data/app/views/decidim/plans/plans/_content.html.erb +6 -0
  131. data/app/views/decidim/plans/plans/_contents.html.erb +8 -0
  132. data/app/views/decidim/plans/plans/_count.html.erb +1 -0
  133. data/app/views/decidim/plans/plans/_filters.html.erb +32 -0
  134. data/app/views/decidim/plans/plans/_filters_small_view.html.erb +18 -0
  135. data/app/views/decidim/plans/plans/_form.html.erb +57 -0
  136. data/app/views/decidim/plans/plans/_new_plan_button.html.erb +11 -0
  137. data/app/views/decidim/plans/plans/_plan.html.erb +1 -0
  138. data/app/views/decidim/plans/plans/_plan_preview.html.erb +1 -0
  139. data/app/views/decidim/plans/plans/_plans.html.erb +14 -0
  140. data/app/views/decidim/plans/plans/_reject_request_access_form.html.erb +10 -0
  141. data/app/views/decidim/plans/plans/_request_access_form.html.erb +7 -0
  142. data/app/views/decidim/plans/plans/edit.html.erb +21 -0
  143. data/app/views/decidim/plans/plans/index.html.erb +28 -0
  144. data/app/views/decidim/plans/plans/index.js.erb +10 -0
  145. data/app/views/decidim/plans/plans/new.html.erb +15 -0
  146. data/app/views/decidim/plans/plans/preview.html.erb +15 -0
  147. data/app/views/decidim/plans/plans/show.html.erb +133 -0
  148. data/app/views/decidim/plans/shared/_attachment_fields.html.erb +45 -0
  149. data/app/views/decidim/plans/shared/_attachments.html.erb +23 -0
  150. data/app/views/decidim/plans/shared/_remaining_characters_container.html.erb +12 -0
  151. data/app/views/decidim/plans/shared/_section_content_field.html.erb +39 -0
  152. data/app/views/decidim/plans/versions/_version.html.erb +20 -0
  153. data/app/views/decidim/plans/versions/index.html.erb +34 -0
  154. data/app/views/decidim/plans/versions/show.html.erb +41 -0
  155. data/config/locales/en.yml +341 -0
  156. data/config/locales/fi.yml +341 -0
  157. data/config/locales/sv.yml +341 -0
  158. data/db/migrate/20181230110225_enable_pg_trgm_extension_for_plans.rb +18 -0
  159. data/db/migrate/20181230110848_create_decidim_plans.rb +20 -0
  160. data/db/migrate/20181230111731_create_decidim_plans_sections.rb +13 -0
  161. data/db/migrate/20181230111931_create_decidim_plans_plan_contents.rb +14 -0
  162. data/db/migrate/20181230122035_create_decidim_plans_plan_collaborator_requests.rb +12 -0
  163. data/db/migrate/20190110125855_create_decidim_plans_attached_proposals.rb +12 -0
  164. data/db/migrate/20190129140441_add_help_to_decidim_plans_sections.rb +7 -0
  165. data/db/migrate/20190129143607_add_mandatory_to_decidim_plans_sections.rb +7 -0
  166. data/db/migrate/20190129172040_add_section_type_to_decidim_plans_sections.rb +12 -0
  167. data/db/migrate/20190129231439_add_answer_length_to_decidim_plans_sections.rb +11 -0
  168. data/db/migrate/20190202123044_create_version_associations.rb +25 -0
  169. data/db/migrate/20190202123045_add_transaction_id_column_to_versions.rb +15 -0
  170. data/db/migrate/20190202200716_add_update_token_to_decidim_plans.rb +7 -0
  171. data/lib/decidim/plans/admin.rb +10 -0
  172. data/lib/decidim/plans/admin_engine.rb +29 -0
  173. data/lib/decidim/plans/component.rb +163 -0
  174. data/lib/decidim/plans/engine.rb +43 -0
  175. data/lib/decidim/plans/locale_aware.rb +32 -0
  176. data/lib/decidim/plans/optionally_translatable_attributes.rb +85 -0
  177. data/lib/decidim/plans/paper_trail/record_trail.rb +24 -0
  178. data/lib/decidim/plans/paper_trail.rb +10 -0
  179. data/lib/decidim/plans/test/factories.rb +130 -0
  180. data/lib/decidim/plans/version.rb +8 -0
  181. data/lib/decidim/plans.rb +24 -0
  182. metadata +323 -0
@@ -0,0 +1,75 @@
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 SectionsController < Admin::ApplicationController
8
+ include Decidim::ApplicationHelper
9
+
10
+ helper Plans::ApplicationHelper
11
+
12
+ helper_method :blank_section, :section_types
13
+
14
+ def index
15
+ enforce_permission_to :create, :sections
16
+ @form = form(Admin::PlanSectionsForm).from_model(sections)
17
+ end
18
+
19
+ def new
20
+ enforce_permission_to :create, :sections
21
+ # TODO
22
+ end
23
+
24
+ def create
25
+ enforce_permission_to :create, :section
26
+ @form = form(Admin::PlanSectionsForm).from_params(params)
27
+
28
+ Admin::UpdateSections.call(@form, sections) do
29
+ on(:ok) do
30
+ flash[:notice] = I18n.t("update.success", scope: i18n_flashes_scope)
31
+ redirect_to sections_url
32
+ end
33
+
34
+ on(:invalid) do
35
+ flash.now[:alert] = I18n.t("update.invalid", scope: i18n_flashes_scope)
36
+ render template: "index"
37
+ end
38
+ end
39
+ end
40
+
41
+ def edit
42
+ enforce_permission_to :edit, :section, section: section
43
+ end
44
+
45
+ def update
46
+ enforce_permission_to :edit, :section, section: section
47
+ end
48
+
49
+ private
50
+
51
+ def i18n_flashes_scope
52
+ "decidim.plans.admin.sections"
53
+ end
54
+
55
+ def sections
56
+ @sections ||= Section.where(component: current_component).order(:position)
57
+ end
58
+
59
+ def section
60
+ @section ||= Section.where(component: current_component).find(params[:id])
61
+ end
62
+
63
+ def blank_section
64
+ @blank_section ||= Admin::SectionForm.new
65
+ end
66
+
67
+ def section_types
68
+ @section_types ||= Section::TYPES.map do |section_type|
69
+ [I18n.t("decidim.plans.section_types.#{section_type}"), section_type]
70
+ end
71
+ end
72
+ end
73
+ end
74
+ end
75
+ end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Decidim
4
+ module Plans
5
+ # This controller is the abstract class from which all other controllers of
6
+ # this engine inherit.
7
+ #
8
+ # Note that it inherits from `Decidim::Components::BaseController`, which
9
+ # override its layout and provide all kinds of useful methods.
10
+ class ApplicationController < Decidim::Components::BaseController
11
+ helper Decidim::Messaging::ConversationHelper
12
+ helper_method :plan_limit_reached?
13
+
14
+ def plan_limit
15
+ return nil if component_settings.plan_limit.zero?
16
+ component_settings.plan_limit
17
+ end
18
+
19
+ def plan_limit_reached?
20
+ return false unless plan_limit
21
+
22
+ plans.where(author: current_user).count >= plan_limit
23
+ end
24
+
25
+ def plans
26
+ Plan.where(component: current_component)
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,53 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Decidim
4
+ module Plans
5
+ # Exposes Plan Request actions for collaboration between
6
+ # participants on a resource.
7
+ class PlanCollaboratorRequestsController < Decidim::Plans::PlansController
8
+ before_action :retrieve_plan, only: [:request_access, :request_accept, :request_reject]
9
+
10
+ def request_access
11
+ @request_access_form = form(RequestAccessToPlanForm).from_params(params)
12
+ RequestAccessToPlan.call(@request_access_form, current_user) do
13
+ on(:ok) do |_plan|
14
+ flash[:notice] = t("access_requested.success", scope: "decidim.plans.requests")
15
+ end
16
+
17
+ on(:invalid) do
18
+ flash[:alert] = t("access_requested.error", scope: "decidim.plans.requests")
19
+ end
20
+ end
21
+ redirect_to Decidim::ResourceLocatorPresenter.new(@plan).path
22
+ end
23
+
24
+ def request_accept
25
+ @accept_request_form = form(AcceptAccessToPlanForm).from_params(params)
26
+ AcceptAccessToPlan.call(@accept_request_form, current_user) do
27
+ on(:ok) do |requester_user|
28
+ flash[:notice] = t("accepted_request.success", scope: "decidim.plans.requests", user: requester_user.nickname)
29
+ end
30
+
31
+ on(:invalid) do
32
+ flash[:alert] = t("accepted_request.error", scope: "decidim.plans.requests")
33
+ end
34
+ end
35
+ redirect_to Decidim::ResourceLocatorPresenter.new(@plan).path
36
+ end
37
+
38
+ def request_reject
39
+ @reject_request_form = form(RejectAccessToPlanForm).from_params(params)
40
+ RejectAccessToPlan.call(@reject_request_form, current_user) do
41
+ on(:ok) do |requester_user|
42
+ flash[:notice] = t("rejected_request.success", scope: "decidim.plans.requests", user: requester_user.nickname)
43
+ end
44
+
45
+ on(:invalid) do
46
+ flash.now[:alert] = t("rejected_request.error", scope: "decidim.plans.requests")
47
+ end
48
+ end
49
+ redirect_to Decidim::ResourceLocatorPresenter.new(@plan).path
50
+ end
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Decidim
4
+ module Plans
5
+ class PlanWidgetsController < Decidim::WidgetsController
6
+ helper Plans::ApplicationHelper
7
+
8
+ private
9
+
10
+ def model
11
+ @model ||= Plan.where(component: params[:component_id]).find(params[:plan_id])
12
+ end
13
+
14
+ def iframe_url
15
+ @iframe_url ||= plan_plan_widget_url(model)
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,192 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Decidim
4
+ module Plans
5
+ # Exposes the plan resource so users can view and create them.
6
+ class PlansController < Decidim::Plans::ApplicationController
7
+ helper Decidim::WidgetUrlsHelper
8
+ helper UserGroupHelper
9
+ helper TooltipHelper
10
+ helper Plans::AttachmentsHelper
11
+ helper Plans::RemainingCharactersHelper
12
+ include AttachedProposalsHelper
13
+ include FormFactory
14
+ include FilterResource
15
+ include Orderable
16
+ include Paginable
17
+
18
+ helper_method :attached_proposals_picker_field
19
+
20
+ before_action :authenticate_user!, only: [:new, :create, :edit, :update, :withdraw, :preview, :publish, :destroy]
21
+ before_action :check_draft, only: [:new]
22
+ before_action :retrieve_plan, only: [:show, :edit, :update, :withdraw, :preview, :publish, :destroy]
23
+ before_action :ensure_published!, only: [:show, :withdraw]
24
+
25
+ def index
26
+ @plans = search
27
+ .results
28
+ .published
29
+ .not_hidden
30
+ .includes(:category)
31
+ .includes(:scope)
32
+
33
+ @plans = paginate(@plans)
34
+ @plans = reorder(@plans)
35
+ end
36
+
37
+ def show
38
+ @report_form = form(Decidim::ReportForm).from_params(reason: "spam")
39
+ @request_access_form = form(RequestAccessToPlanForm).from_params({})
40
+ @accept_request_form = form(AcceptAccessToPlanForm).from_params({})
41
+ @reject_request_form = form(RejectAccessToPlanForm).from_params({})
42
+ end
43
+
44
+ def new
45
+ enforce_permission_to :create, :plan
46
+
47
+ @form = form(PlanForm).from_model(Plan.new(component: current_component))
48
+ end
49
+
50
+ def create
51
+ enforce_permission_to :create, :plan
52
+
53
+ @form = form(PlanForm).from_params(params, component: current_component)
54
+
55
+ CreatePlan.call(@form, current_user) do
56
+ on(:ok) do |plan|
57
+ flash[:notice] = I18n.t("plans.plans.create.success", scope: "decidim")
58
+ redirect_to Decidim::ResourceLocatorPresenter.new(plan).path + "/preview"
59
+ end
60
+
61
+ on(:invalid) do
62
+ flash.now[:alert] = I18n.t("plans.plans.create.error", scope: "decidim")
63
+ render :new
64
+ end
65
+ end
66
+ end
67
+
68
+ def edit
69
+ enforce_permission_to :edit, :plan, plan: @plan
70
+
71
+ @form = form(PlanForm).from_model(@plan)
72
+ end
73
+
74
+ def update
75
+ enforce_permission_to :edit, :plan, plan: @plan
76
+
77
+ @form = form(PlanForm).from_params(params, compontent: current_component)
78
+ UpdatePlan.call(@form, current_user, @plan) do
79
+ on(:ok) do |plan|
80
+ flash[:notice] = I18n.t("plans.plans.update.success", scope: "decidim")
81
+
82
+ return redirect_to Decidim::ResourceLocatorPresenter.new(plan).path if plan.published?
83
+
84
+ redirect_to Decidim::ResourceLocatorPresenter.new(plan).path + "/preview"
85
+ end
86
+
87
+ on(:invalid) do
88
+ flash.now[:alert] = I18n.t("plans.plans.update.error", scope: "decidim")
89
+ render :edit
90
+ end
91
+ end
92
+ end
93
+
94
+ def destroy
95
+ enforce_permission_to :edit, :plan, plan: @plan
96
+
97
+ # Form needed in case destroing fails
98
+ @form = form(PlanForm).from_model(@plan)
99
+
100
+ DestroyPlan.call(@plan, current_user) do
101
+ on(:ok) do
102
+ flash[:notice] = I18n.t("plans.plans.destroy.success", scope: "decidim")
103
+ redirect_to new_plan_path
104
+ end
105
+
106
+ on(:invalid) do
107
+ flash.now[:alert] = I18n.t("plans.plans.destroy.error", scope: "decidim")
108
+ render :edit
109
+ end
110
+ end
111
+ end
112
+
113
+ def withdraw
114
+ raise ActionController::RoutingError, "Not Found" if @plan.withdrawn?
115
+ enforce_permission_to :withdraw, :plan, plan: @plan
116
+
117
+ WithdrawPlan.call(@plan, current_user) do
118
+ on(:ok) do
119
+ flash[:notice] = t("withdraw.success", scope: "decidim.plans.plans")
120
+ end
121
+
122
+ on(:invalid) do
123
+ flash.now[:alert] = t("withdraw.error", scope: "decidim.plans.plans")
124
+ end
125
+ end
126
+ redirect_to Decidim::ResourceLocatorPresenter.new(@plan).path
127
+ end
128
+
129
+ def preview; end
130
+
131
+ def publish
132
+ PublishPlan.call(@plan, current_user) do
133
+ on(:ok) do |plan|
134
+ flash[:notice] = I18n.t("publish.success", scope: "decidim.plans.plans.plan")
135
+ redirect_to Decidim::ResourceLocatorPresenter.new(plan).path
136
+ end
137
+
138
+ on(:invalid) do
139
+ flash.now[:alert] = t("publish.error", scope: "decidim.plans.plans.plan")
140
+ redirect_to Decidim::ResourceLocatorPresenter.new(@plan).path
141
+ end
142
+ end
143
+ end
144
+
145
+ private
146
+
147
+ def check_draft
148
+ redirect_to edit_plan_path(plan_draft) if plan_draft.present?
149
+ end
150
+
151
+ def plan_draft
152
+ Plan.drafts.from_all_author_identities(current_user).not_hidden.where(component: current_component)
153
+ end
154
+
155
+ def retrieve_plan
156
+ @plan = Plan.where(component: current_component).find(params[:id])
157
+ end
158
+
159
+ def ensure_published!
160
+ return unless @plan
161
+ return if @plan.published?
162
+
163
+ raise ActionController::RoutingError, "Not Found" unless @plan.editable_by?(current_user)
164
+
165
+ redirect_to preview_plan_path(@plan)
166
+ end
167
+
168
+ def search_klass
169
+ PlanSearch
170
+ end
171
+
172
+ def default_filter_params
173
+ {
174
+ search_text: "",
175
+ origin: "all",
176
+ activity: "",
177
+ category_id: "",
178
+ state: "except_rejected",
179
+ scope_id: nil,
180
+ related_to: ""
181
+ }
182
+ end
183
+
184
+ def context_params
185
+ {
186
+ component: current_component,
187
+ organization: current_organization
188
+ }
189
+ end
190
+ end
191
+ end
192
+ end
@@ -0,0 +1,51 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Decidim
4
+ module Plans
5
+ # Exposes Plan versions so users can see how a Plan
6
+ # has been updated through time.
7
+ class VersionsController < Decidim::Plans::ApplicationController
8
+ helper Decidim::Plans::TraceabilityHelper
9
+ helper_method :current_version, :item_versions, :associated_versions,
10
+ :content_versions, :item
11
+
12
+ private
13
+
14
+ def item
15
+ @item ||= Plan.where(component: current_component).find(params[:plan_id])
16
+ end
17
+
18
+ def current_version
19
+ return nil if params[:id].to_i < 1
20
+ @current_version ||= item.versions[params[:id].to_i - 1]
21
+ end
22
+
23
+ def item_versions
24
+ # There may be multiple updates on the item during the same transaction
25
+ Decidim::Plans::PaperTrail::Version.where(
26
+ transaction_id: current_version.transaction_id,
27
+ item_type: "Decidim::Plans::Plan"
28
+ ).order(:created_at)
29
+ end
30
+
31
+ def associated_versions
32
+ @associated_versions ||= Decidim::Plans::PaperTrail::Version.where(
33
+ transaction_id: current_version.transaction_id
34
+ ).where.not(
35
+ item_type: ["Decidim::Plans::Plan", "Decidim::Plans::Content"]
36
+ )
37
+ end
38
+
39
+ def content_versions
40
+ @content_versions ||= item.sections.map do |section|
41
+ content = item.contents.find_by(section: section)
42
+ next unless content
43
+
44
+ content.versions.find_by(
45
+ transaction_id: current_version.transaction_id
46
+ )
47
+ end.compact
48
+ end
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,9 @@
1
+ # frozen-string_literal: true
2
+
3
+ module Decidim
4
+ module Plans
5
+ class AcceptedPlanEvent < Decidim::Events::SimpleEvent
6
+ include Decidim::Events::AuthorEvent
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,8 @@
1
+ # frozen-string_literal: true
2
+
3
+ module Decidim
4
+ module Plans
5
+ class EvaluatingPlanEvent < Decidim::Events::SimpleEvent
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,8 @@
1
+ # frozen-string_literal: true
2
+
3
+ module Decidim
4
+ module Plans
5
+ class PlanAccessAcceptedEvent < PlanAccessRequestEvent
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,8 @@
1
+ # frozen-string_literal: true
2
+
3
+ module Decidim
4
+ module Plans
5
+ class PlanAccessRejectedEvent < PlanAccessRequestEvent
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,27 @@
1
+ # frozen-string_literal: true
2
+
3
+ module Decidim
4
+ module Plans
5
+ class PlanAccessRequestEvent < Decidim::Events::SimpleEvent
6
+ i18n_attributes :requester_name, :requester_path, :requester_nickname
7
+
8
+ delegate :name, to: :requester, prefix: true
9
+
10
+ delegate :nickname, to: :requester, prefix: true
11
+
12
+ def requester_path
13
+ requester.profile_path
14
+ end
15
+
16
+ private
17
+
18
+ def requester
19
+ @requester ||= Decidim::UserPresenter.new(rejected_requester_user)
20
+ end
21
+
22
+ def rejected_requester_user
23
+ @rejected_requester_user ||= Decidim::User.find_by(id: extra[:requester_id])
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,8 @@
1
+ # frozen-string_literal: true
2
+
3
+ module Decidim
4
+ module Plans
5
+ class PlanAccessRequestedEvent < PlanAccessRequestEvent
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,8 @@
1
+ # frozen-string_literal: true
2
+
3
+ module Decidim
4
+ module Plans
5
+ class PlanAccessRequesterAcceptedEvent < Decidim::Events::SimpleEvent
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,8 @@
1
+ # frozen-string_literal: true
2
+
3
+ module Decidim
4
+ module Plans
5
+ class PlanAccessRequesterRejectedEvent < Decidim::Events::SimpleEvent
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,25 @@
1
+ # frozen-string_literal: true
2
+
3
+ module Decidim
4
+ module Plans
5
+ class PublishPlanEvent < Decidim::Events::SimpleEvent
6
+ include Decidim::Events::CoauthorEvent
7
+
8
+ def resource_text
9
+ # TODO
10
+ end
11
+
12
+ private
13
+
14
+ def i18n_scope
15
+ return super unless participatory_space_event?
16
+
17
+ "decidim.events.plans.plan_published_for_space"
18
+ end
19
+
20
+ def participatory_space_event?
21
+ extra.dig(:participatory_space)
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,9 @@
1
+ # frozen-string_literal: true
2
+
3
+ module Decidim
4
+ module Plans
5
+ class RejectedPlanEvent < Decidim::Events::SimpleEvent
6
+ include Decidim::Events::AuthorEvent
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Decidim
4
+ module Plans
5
+ # A form object to be used when Plan editors accept a request to
6
+ # access it as coauthors.
7
+ class AcceptAccessToPlanForm < AccessToPlanForm
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Decidim
4
+ module Plans
5
+ # A form object common to accept and reject actions requesters of Plans.
6
+ class AccessToPlanForm < Decidim::Form
7
+ mimic :plan
8
+
9
+ attribute :id, String
10
+ attribute :requester_user_id, String
11
+ attribute :state, String
12
+
13
+ validates :id, :requester_user_id, presence: true
14
+ validates :state, presence: true, inclusion: { in: %w(open) }
15
+
16
+ validate :existence_of_requester_in_requesters
17
+
18
+ def plan
19
+ @plan ||= Decidim::Plans::Plan.find id if id
20
+ end
21
+
22
+ def requester_user
23
+ @requester_user ||= Decidim::User.find_by(id: requester_user_id, organization: current_organization) if requester_user_id
24
+ end
25
+
26
+ private
27
+
28
+ def existence_of_requester_in_requesters
29
+ if plan
30
+ errors.add(:requester_user_id, :invalid) unless plan.requesters.exists? requester_user_id
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Decidim
4
+ module Plans
5
+ module Admin
6
+ # A form object to be used when admin users want to answer a plan.
7
+ class PlanAnswerForm < Decidim::Form
8
+ include TranslatableAttributes
9
+ mimic :plan_answer
10
+
11
+ translatable_attribute :answer, String
12
+ attribute :state, String
13
+
14
+ validates :state, presence: true, inclusion: { in: %w(accepted rejected evaluating) }
15
+ validates :answer, translatable_presence: true, if: ->(form) { form.state == "rejected" }
16
+ end
17
+ end
18
+ end
19
+ end