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,79 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Decidim
|
4
|
+
module Plans
|
5
|
+
# A Helper to find and render the author of a version.
|
6
|
+
module TraceabilityHelper
|
7
|
+
include Decidim::TraceabilityHelper
|
8
|
+
|
9
|
+
# Caches a DiffRenderer instance for the `current_version`.
|
10
|
+
def item_diff_renderers
|
11
|
+
@item_diff_renderers ||= item_versions.map do |version|
|
12
|
+
renderer_for(version)
|
13
|
+
end.compact
|
14
|
+
end
|
15
|
+
|
16
|
+
def associated_diff_renderers
|
17
|
+
@associated_diff_renderers ||= associated_versions.map do |version|
|
18
|
+
renderer_for(version)
|
19
|
+
end.compact
|
20
|
+
end
|
21
|
+
|
22
|
+
def content_diff_renderers
|
23
|
+
@content_diff_renderers ||= content_versions.map do |version|
|
24
|
+
renderer_for(version)
|
25
|
+
end.compact
|
26
|
+
end
|
27
|
+
|
28
|
+
def diff_renderers
|
29
|
+
item_diff_renderers + associated_diff_renderers + content_diff_renderers
|
30
|
+
end
|
31
|
+
|
32
|
+
private
|
33
|
+
|
34
|
+
def renderer_for(version)
|
35
|
+
locale = current_locale unless component_settings.multilingual_answers?
|
36
|
+
|
37
|
+
renderer_klass =
|
38
|
+
case version.item.class.name
|
39
|
+
when "Decidim::Plans::Plan"
|
40
|
+
Decidim::Plans::DiffRenderer::Plan
|
41
|
+
when "Decidim::Plans::Content"
|
42
|
+
Decidim::Plans::DiffRenderer::Content
|
43
|
+
when "Decidim::Categorization"
|
44
|
+
Decidim::Plans::DiffRenderer::Categorization
|
45
|
+
end
|
46
|
+
|
47
|
+
renderer_klass.new(version, locale)
|
48
|
+
end
|
49
|
+
|
50
|
+
# Renders the given value in a user-friendly way based on the value class.
|
51
|
+
#
|
52
|
+
# value - an object to be rendered
|
53
|
+
#
|
54
|
+
# Returns an HTML-ready String.
|
55
|
+
def render_diff_value(value, type, action, options = {})
|
56
|
+
return "".html_safe if value.blank?
|
57
|
+
|
58
|
+
value_to_render = case type
|
59
|
+
when :date
|
60
|
+
l value, format: :long
|
61
|
+
when :percentage
|
62
|
+
number_to_percentage value, precision: 2
|
63
|
+
when :translatable
|
64
|
+
value.to_s
|
65
|
+
else
|
66
|
+
value
|
67
|
+
end
|
68
|
+
|
69
|
+
content_tag(:div, class: "card--list__item #{action}") do
|
70
|
+
content_tag(:div, class: "card--list__text") do
|
71
|
+
content_tag(:div, { class: "diff__value" }.merge(options)) do
|
72
|
+
value_to_render
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Decidim
|
4
|
+
module Plans
|
5
|
+
module UserGroupHelper
|
6
|
+
# Renders a user_group select field in a form.
|
7
|
+
# form - FormBuilder object
|
8
|
+
# name - attribute user_group_id
|
9
|
+
#
|
10
|
+
# Returns nothing.
|
11
|
+
def user_group_select_field(form, name)
|
12
|
+
selected = @form.user_group_id.presence
|
13
|
+
user_groups = Decidim::UserGroups::ManageableUserGroups.for(current_user).verified
|
14
|
+
form.select(
|
15
|
+
name,
|
16
|
+
user_groups.map { |g| [g.name, g.id] },
|
17
|
+
selected: selected,
|
18
|
+
include_blank: current_user.name
|
19
|
+
)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "active_support/concern"
|
4
|
+
|
5
|
+
require_dependency "paper_trail/frameworks/active_record"
|
6
|
+
|
7
|
+
module Decidim
|
8
|
+
module Plans
|
9
|
+
# A concern that adds traceabilty capability to the given model. Including
|
10
|
+
# this allows you the keep track of changes in the model attributes and
|
11
|
+
# changes authorship.
|
12
|
+
#
|
13
|
+
# Example:
|
14
|
+
#
|
15
|
+
# class MyModel < ApplicationRecord
|
16
|
+
# include Decidim::Plans::Traceable
|
17
|
+
# end
|
18
|
+
module Traceable
|
19
|
+
extend ActiveSupport::Concern
|
20
|
+
|
21
|
+
included do
|
22
|
+
# This is customized with the `:ignore` option since we don't want to
|
23
|
+
# store another version when the item is published.
|
24
|
+
has_paper_trail ignore: [:published_at],
|
25
|
+
class_name: "Decidim::Plans::PaperTrail::Version"
|
26
|
+
|
27
|
+
delegate :count, to: :versions, prefix: true
|
28
|
+
|
29
|
+
def last_whodunnit
|
30
|
+
versions.last.try(:whodunnit)
|
31
|
+
end
|
32
|
+
|
33
|
+
def last_editor
|
34
|
+
Decidim.traceability.version_editor(versions.last)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
# This is needed for the action logs to work properly. They are only
|
39
|
+
# stored against models that implement Decidim::Traceable. However, we
|
40
|
+
# cannot directly include that module because we want to modify its
|
41
|
+
# functionality.
|
42
|
+
def is_a?(klass)
|
43
|
+
return true if klass == Decidim::Traceable
|
44
|
+
super
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Decidim
|
4
|
+
module Plans
|
5
|
+
# This class holds the association between a Plan and a
|
6
|
+
# Decidim::Proposals::Proposal.
|
7
|
+
class AttachedProposal < ApplicationRecord
|
8
|
+
belongs_to :plan, class_name: "Decidim::Plans::Plan", foreign_key: "decidim_plan_id"
|
9
|
+
belongs_to :proposal, class_name: "Decidim::Proposals::Proposal", foreign_key: "decidim_proposal_id"
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Decidim
|
4
|
+
module Plans
|
5
|
+
# The content record is the actual content for each plan section for a
|
6
|
+
# single plan.
|
7
|
+
class Content < Plans::ApplicationRecord
|
8
|
+
include Decidim::Plans::Traceable
|
9
|
+
|
10
|
+
self.table_name = "decidim_plans_plan_contents"
|
11
|
+
|
12
|
+
belongs_to :plan, class_name: "Decidim::Plans::Plan", foreign_key: "decidim_plan_id"
|
13
|
+
belongs_to :section, class_name: "Decidim::Plans::Section", foreign_key: "decidim_section_id"
|
14
|
+
belongs_to :user, class_name: "Decidim::User", foreign_key: "decidim_user_id"
|
15
|
+
|
16
|
+
def title
|
17
|
+
section.body
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Decidim
|
4
|
+
module Plans
|
5
|
+
module PaperTrail
|
6
|
+
class Version < ::PaperTrail::Version
|
7
|
+
self.table_name = "versions"
|
8
|
+
|
9
|
+
# Since the test suite has test coverage for this, we want to declare
|
10
|
+
# the association when the test suite is running. This makes it pass
|
11
|
+
# when DB is not initialized prior to test runs such as when we run on
|
12
|
+
# Travis CI Ex. (there won't be a db in `spec/dummy_app/db/`).
|
13
|
+
has_many :version_associations,
|
14
|
+
class_name: "Decidim::Plans::PaperTrail::VersionAssociation",
|
15
|
+
dependent: :destroy
|
16
|
+
|
17
|
+
scope(:within_transaction, ->(id) { where transaction_id: id })
|
18
|
+
|
19
|
+
# Track the associations on the plans object
|
20
|
+
def track_associations?
|
21
|
+
true
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Decidim
|
4
|
+
module Plans
|
5
|
+
module PaperTrail
|
6
|
+
class VersionAssociation < ::ActiveRecord::Base
|
7
|
+
self.table_name = "version_associations"
|
8
|
+
|
9
|
+
belongs_to :version,
|
10
|
+
class_name: "Decidim::Plans::PaperTrail::Version"
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,206 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Decidim
|
4
|
+
module Plans
|
5
|
+
# A plan is similar to a proposal but allows the users to fill in multiple
|
6
|
+
# custom fields that the admin users define for each plans component. The
|
7
|
+
# fields are the plan sections and the contents are in plan contents.
|
8
|
+
class Plan < Plans::ApplicationRecord
|
9
|
+
include Decidim::Resourceable
|
10
|
+
include Decidim::Coauthorable
|
11
|
+
include Decidim::HasComponent
|
12
|
+
include Decidim::ScopableComponent
|
13
|
+
include Decidim::HasCategory
|
14
|
+
include Decidim::Reportable
|
15
|
+
include Decidim::HasAttachments
|
16
|
+
include Decidim::Followable
|
17
|
+
include Decidim::Comments::Commentable
|
18
|
+
include Decidim::Searchable
|
19
|
+
include Decidim::Plans::Traceable
|
20
|
+
include Decidim::Loggable
|
21
|
+
|
22
|
+
component_manifest_name "plans"
|
23
|
+
|
24
|
+
has_many :collaborator_requests,
|
25
|
+
class_name: "Decidim::Plans::PlanCollaboratorRequest",
|
26
|
+
foreign_key: :decidim_plan_id,
|
27
|
+
dependent: :destroy
|
28
|
+
|
29
|
+
has_many :requesters,
|
30
|
+
through: :collaborator_requests,
|
31
|
+
source: :user,
|
32
|
+
class_name: "Decidim::User",
|
33
|
+
foreign_key: :decidim_user_id
|
34
|
+
|
35
|
+
has_many :attached_proposals, foreign_key: :decidim_plan_id, dependent: :destroy
|
36
|
+
has_many :proposals, through: :attached_proposals
|
37
|
+
|
38
|
+
has_many :contents, foreign_key: :decidim_plan_id, dependent: :destroy
|
39
|
+
|
40
|
+
scope :open, -> { where(state: "open") }
|
41
|
+
scope :accepted, -> { where(state: "accepted") }
|
42
|
+
scope :rejected, -> { where(state: "rejected") }
|
43
|
+
scope :evaluating, -> { where(state: "evaluating") }
|
44
|
+
scope :withdrawn, -> { where(state: "withdrawn") }
|
45
|
+
scope :except_rejected, -> { where.not(state: "rejected").or(where(state: nil)) }
|
46
|
+
scope :except_withdrawn, -> { where.not(state: "withdrawn").or(where(state: nil)) }
|
47
|
+
scope :drafts, -> { where(published_at: nil) }
|
48
|
+
scope :published, -> { where.not(published_at: nil) }
|
49
|
+
|
50
|
+
acts_as_list scope: :decidim_component_id
|
51
|
+
|
52
|
+
searchable_fields(
|
53
|
+
{
|
54
|
+
scope_id: :decidim_scope_id,
|
55
|
+
participatory_space: { component: :participatory_space },
|
56
|
+
A: :search_title,
|
57
|
+
datetime: :published_at
|
58
|
+
},
|
59
|
+
index_on_create: ->(proposal) { proposal.visible? },
|
60
|
+
index_on_update: ->(proposal) { proposal.visible? }
|
61
|
+
)
|
62
|
+
|
63
|
+
def sections
|
64
|
+
Section.where(component: component).order(:position)
|
65
|
+
end
|
66
|
+
|
67
|
+
def self.order_randomly(seed)
|
68
|
+
transaction do
|
69
|
+
connection.execute("SELECT setseed(#{connection.quote(seed)})")
|
70
|
+
order(Arel.sql("RANDOM()")).load
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
def self.log_presenter_class_for(_log)
|
75
|
+
Decidim::Plans::AdminLog::PlanPresenter
|
76
|
+
end
|
77
|
+
|
78
|
+
# Returns a collection scoped by an author.
|
79
|
+
# Overrides this method in DataPortability to support Coauthorable.
|
80
|
+
def self.user_collection(author)
|
81
|
+
return unless author.is_a?(Decidim::User)
|
82
|
+
|
83
|
+
joins(:coauthorships)
|
84
|
+
.where("decidim_coauthorships.coauthorable_type = ?", name)
|
85
|
+
.where("decidim_coauthorships.decidim_author_id = ? AND decidim_coauthorships.decidim_author_type = ? ", author.id, author.class.base_class.name)
|
86
|
+
end
|
87
|
+
|
88
|
+
# Public: Checks if the plan is open for edits.
|
89
|
+
#
|
90
|
+
# Returns Boolean.
|
91
|
+
def open?
|
92
|
+
state == "open"
|
93
|
+
end
|
94
|
+
|
95
|
+
# Public: Checks if the plan has been published or not.
|
96
|
+
#
|
97
|
+
# Returns Boolean.
|
98
|
+
def published?
|
99
|
+
published_at.present?
|
100
|
+
end
|
101
|
+
|
102
|
+
# Public: Checks if the organization has given an answer for the plan.
|
103
|
+
#
|
104
|
+
# Returns Boolean.
|
105
|
+
def answered?
|
106
|
+
answered_at.present? && state.present?
|
107
|
+
end
|
108
|
+
|
109
|
+
# Public: Checks if the organization has accepted a plan.
|
110
|
+
#
|
111
|
+
# Returns Boolean.
|
112
|
+
def accepted?
|
113
|
+
answered? && state == "accepted"
|
114
|
+
end
|
115
|
+
|
116
|
+
# Public: Checks if the organization has rejected a plan.
|
117
|
+
#
|
118
|
+
# Returns Boolean.
|
119
|
+
def rejected?
|
120
|
+
answered? && state == "rejected"
|
121
|
+
end
|
122
|
+
|
123
|
+
# Public: Checks if the organization has marked the plan as evaluating it.
|
124
|
+
#
|
125
|
+
# Returns Boolean.
|
126
|
+
def evaluating?
|
127
|
+
answered? && state == "evaluating"
|
128
|
+
end
|
129
|
+
|
130
|
+
# Public: Checks if the author has withdrawn the plan.
|
131
|
+
#
|
132
|
+
# Returns Boolean.
|
133
|
+
def withdrawn?
|
134
|
+
state == "withdrawn"
|
135
|
+
end
|
136
|
+
|
137
|
+
# Public: Overrides the `reported_content_url` Reportable concern method.
|
138
|
+
def reported_content_url
|
139
|
+
ResourceLocatorPresenter.new(self).url
|
140
|
+
end
|
141
|
+
|
142
|
+
# Checks whether the user can edit the given plan.
|
143
|
+
#
|
144
|
+
# user - the user to check for authorship
|
145
|
+
def editable_by?(user)
|
146
|
+
!answered? && !copied_from_other_component? && authored_by?(user)
|
147
|
+
end
|
148
|
+
|
149
|
+
# Checks whether the user can withdraw the given plan.
|
150
|
+
#
|
151
|
+
# user - the user to check for withdrawability.
|
152
|
+
def withdrawable_by?(user)
|
153
|
+
user && !withdrawn? && creator_author == user && !copied_from_other_component?
|
154
|
+
end
|
155
|
+
|
156
|
+
# Public: Whether the plan is a draft or not.
|
157
|
+
def draft?
|
158
|
+
published_at.nil?
|
159
|
+
end
|
160
|
+
|
161
|
+
def commentable?
|
162
|
+
component.settings.comments_enabled?
|
163
|
+
end
|
164
|
+
|
165
|
+
def accepts_new_comments?
|
166
|
+
commentable? && !component.current_settings.comments_blocked
|
167
|
+
end
|
168
|
+
|
169
|
+
def comments_have_alignment?
|
170
|
+
true
|
171
|
+
end
|
172
|
+
|
173
|
+
def comments_have_votes?
|
174
|
+
true
|
175
|
+
end
|
176
|
+
|
177
|
+
def users_to_notify_on_comment_created
|
178
|
+
followers
|
179
|
+
end
|
180
|
+
|
181
|
+
# method for sort_link by number of comments
|
182
|
+
ransacker :commentable_comments_count do
|
183
|
+
query = <<-SQL
|
184
|
+
(SELECT COUNT(decidim_comments_comments.id)
|
185
|
+
FROM decidim_comments_comments
|
186
|
+
WHERE decidim_comments_comments.decidim_commentable_id = decidim_plans_plans.id
|
187
|
+
AND decidim_comments_comments.decidim_commentable_type = 'Decidim::Plans::Plan'
|
188
|
+
GROUP BY decidim_comments_comments.decidim_commentable_id
|
189
|
+
)
|
190
|
+
SQL
|
191
|
+
Arel.sql(query)
|
192
|
+
end
|
193
|
+
|
194
|
+
# Checks whether the plan is inside the time window to be editable or not once published.
|
195
|
+
def within_edit_time_limit?
|
196
|
+
true
|
197
|
+
end
|
198
|
+
|
199
|
+
private
|
200
|
+
|
201
|
+
def copied_from_other_component?
|
202
|
+
linked_resources(:plans, "copied_from_component").any?
|
203
|
+
end
|
204
|
+
end
|
205
|
+
end
|
206
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Decidim
|
4
|
+
module Plans
|
5
|
+
# A plan can accept requests to coauthor and contribute
|
6
|
+
class PlanCollaboratorRequest < Plans::ApplicationRecord
|
7
|
+
validates :plan, :user, presence: true
|
8
|
+
|
9
|
+
belongs_to :plan, class_name: "Decidim::Plans::Plan", foreign_key: :decidim_plan_id
|
10
|
+
belongs_to :user, class_name: "Decidim::User", foreign_key: :decidim_user_id
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Decidim
|
4
|
+
module Plans
|
5
|
+
# A plan section is a record that stores the question data and order of
|
6
|
+
# items that the users need to fill for each plan. The actual contents of
|
7
|
+
# the plans are stored in the Decidim::Plans::Content records.
|
8
|
+
class Section < Plans::ApplicationRecord
|
9
|
+
include Decidim::HasComponent
|
10
|
+
|
11
|
+
TYPES = %w(field_text_multiline field_text).freeze
|
12
|
+
|
13
|
+
validates :section_type, inclusion: { in: TYPES }
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Decidim
|
4
|
+
module Plans
|
5
|
+
module Admin
|
6
|
+
class Permissions < Decidim::DefaultPermissions
|
7
|
+
def permissions
|
8
|
+
return permission_action unless user
|
9
|
+
|
10
|
+
return permission_action if permission_action.scope != :admin
|
11
|
+
|
12
|
+
toggle_allow(admin_plan_answering_is_enabled?) if
|
13
|
+
permission_action.action == :create &&
|
14
|
+
permission_action.subject == :plan_answer
|
15
|
+
|
16
|
+
return permission_action if permission_action.subject != :plan &&
|
17
|
+
permission_action.subject != :plans &&
|
18
|
+
permission_action.subject != :section &&
|
19
|
+
permission_action.subject != :sections
|
20
|
+
|
21
|
+
case permission_action.action
|
22
|
+
when :create
|
23
|
+
permission_action.allow!
|
24
|
+
when :edit, :update, :destroy
|
25
|
+
permission_action.allow! if plan.present? || section.present?
|
26
|
+
end
|
27
|
+
|
28
|
+
permission_action
|
29
|
+
end
|
30
|
+
|
31
|
+
private
|
32
|
+
|
33
|
+
def plan
|
34
|
+
@plan ||= context.fetch(:plan, nil)
|
35
|
+
end
|
36
|
+
|
37
|
+
def admin_plan_answering_is_enabled?
|
38
|
+
current_settings.try(:plan_answering_enabled) &&
|
39
|
+
component_settings.try(:plan_answering_enabled)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Decidim
|
4
|
+
module Plans
|
5
|
+
class Permissions < Decidim::DefaultPermissions
|
6
|
+
def permissions
|
7
|
+
return permission_action unless user
|
8
|
+
|
9
|
+
# Delegate the admin permission checks to the admin permissions class
|
10
|
+
return Decidim::Plans::Admin::Permissions.new(user, permission_action, context).permissions if permission_action.scope == :admin
|
11
|
+
return permission_action if permission_action.scope != :public
|
12
|
+
|
13
|
+
if permission_action.subject == :plan
|
14
|
+
apply_plan_permissions(permission_action)
|
15
|
+
else
|
16
|
+
permission_action
|
17
|
+
end
|
18
|
+
|
19
|
+
permission_action
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
def plan
|
25
|
+
@plan ||= context.fetch(:plan, nil)
|
26
|
+
end
|
27
|
+
|
28
|
+
def apply_plan_permissions(permission_action)
|
29
|
+
case permission_action.action
|
30
|
+
when :create
|
31
|
+
can_create_plan?
|
32
|
+
when :edit
|
33
|
+
can_edit_plan?
|
34
|
+
when :withdraw
|
35
|
+
can_withdraw_plan?
|
36
|
+
when :publish
|
37
|
+
can_publish_plan?
|
38
|
+
when :request_access
|
39
|
+
can_request_access_plan?
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def can_create_plan?
|
44
|
+
toggle_allow(authorized?(:create) && current_settings&.creation_enabled?)
|
45
|
+
end
|
46
|
+
|
47
|
+
def can_edit_plan?
|
48
|
+
toggle_allow(plan.open? && plan.editable_by?(user))
|
49
|
+
end
|
50
|
+
|
51
|
+
def can_withdraw_plan?
|
52
|
+
toggle_allow(plan && plan.withdrawable_by?(user))
|
53
|
+
end
|
54
|
+
|
55
|
+
def can_publish_plan?
|
56
|
+
toggle_allow(plan.open? && plan.editable_by?(user))
|
57
|
+
end
|
58
|
+
|
59
|
+
def can_request_access_plan?
|
60
|
+
return toggle_allow(false) unless plan.open?
|
61
|
+
return toggle_allow(false) if plan.editable_by?(user)
|
62
|
+
return toggle_allow(false) if plan.requesters.include? user
|
63
|
+
toggle_allow(plan && !plan.editable_by?(user))
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Decidim
|
4
|
+
module Plans
|
5
|
+
module AdminLog
|
6
|
+
# This class holds the logic to present a `Decidim::Plans::Plan`
|
7
|
+
# for the `AdminLog` log.
|
8
|
+
#
|
9
|
+
# Usage should be automatic and you shouldn't need to call this class
|
10
|
+
# directly, but here's an example:
|
11
|
+
#
|
12
|
+
# action_log = Decidim::ActionLog.last
|
13
|
+
# view_helpers # => this comes from the views
|
14
|
+
# PlanPresenter.new(action_log, view_helpers).present
|
15
|
+
class PlanPresenter < Decidim::Log::BasePresenter
|
16
|
+
private
|
17
|
+
|
18
|
+
def resource_presenter
|
19
|
+
@resource_presenter ||= Decidim::Plans::Log::ResourcePresenter.new(action_log.resource, h, action_log.extra["resource"])
|
20
|
+
end
|
21
|
+
|
22
|
+
def diff_fields_mapping
|
23
|
+
{
|
24
|
+
state: "Decidim::Plans::AdminLog::ValueTypes::PlanStatePresenter",
|
25
|
+
answered_at: :date,
|
26
|
+
answer: :i18n
|
27
|
+
}
|
28
|
+
end
|
29
|
+
|
30
|
+
def action_string
|
31
|
+
case action
|
32
|
+
when "answer", "create", "update"
|
33
|
+
"decidim.plans.admin_log.plan.#{action}"
|
34
|
+
else
|
35
|
+
super
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def i18n_labels_scope
|
40
|
+
"activemodel.attributes.plan"
|
41
|
+
end
|
42
|
+
|
43
|
+
def has_diff?
|
44
|
+
action == "answer" || super
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Decidim
|
4
|
+
module Plans
|
5
|
+
module AdminLog
|
6
|
+
module ValueTypes
|
7
|
+
class PlanStatePresenter < Decidim::Log::ValueTypes::DefaultPresenter
|
8
|
+
def present
|
9
|
+
return unless value
|
10
|
+
h.t(value, scope: "decidim.plans.admin.plan_answers.edit", default: value)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Decidim
|
4
|
+
module Plans
|
5
|
+
#
|
6
|
+
# Decorator for contents
|
7
|
+
#
|
8
|
+
class ContentPresenter < SimpleDelegator
|
9
|
+
include Rails.application.routes.mounted_helpers
|
10
|
+
include TranslatableAttributes
|
11
|
+
|
12
|
+
def content
|
13
|
+
__getobj__
|
14
|
+
end
|
15
|
+
|
16
|
+
def title
|
17
|
+
translated_attribute(content.title).html_safe
|
18
|
+
end
|
19
|
+
|
20
|
+
def body
|
21
|
+
translated_attribute(content.body).html_safe
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|