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,88 @@
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 create a plan.
7
+ class PlanForm < Decidim::Form
8
+ include OptionallyTranslatableAttributes
9
+ include Decidim::ApplicationHelper
10
+ mimic :plan
11
+
12
+ optionally_translatable_attribute :title, String
13
+
14
+ attribute :category_id, Integer
15
+ attribute :scope_id, Integer
16
+ attribute :attachments, Array[Plans::AttachmentForm]
17
+ attribute :contents, Array[Decidim::Plans::ContentForm]
18
+ attribute :proposal_ids, Array[Integer]
19
+
20
+ optionally_translatable_validate_presence :title
21
+
22
+ validates :proposals, presence: true
23
+ validates :category, presence: true, if: ->(form) { form.category_id.present? }
24
+ validates :scope, presence: true, if: ->(form) { form.scope_id.present? }
25
+
26
+ validate :scope_belongs_to_participatory_space_scope
27
+
28
+ delegate :categories, to: :current_component
29
+
30
+ def map_model(model)
31
+ self.contents = model.sections.map do |section|
32
+ ContentForm.from_model(
33
+ Content
34
+ .where(plan: model, section: section)
35
+ .first_or_initialize(plan: model, section: section)
36
+ )
37
+ end
38
+
39
+ return unless model.categorization
40
+
41
+ self.category_id = model.categorization.decidim_category_id
42
+ self.scope_id = model.decidim_scope_id
43
+ end
44
+
45
+ alias component current_component
46
+
47
+ # Finds the Category from the category_id.
48
+ #
49
+ # Returns a Decidim::Category
50
+ def category
51
+ @category ||= categories.find_by(id: category_id)
52
+ end
53
+
54
+ # Finds the Scope from the given decidim_scope_id, uses participatory space scope if missing.
55
+ #
56
+ # Returns a Decidim::Scope
57
+ def scope
58
+ @scope ||= @scope_id ? current_participatory_space.scopes.find_by(id: @scope_id) : current_participatory_space.scope
59
+ end
60
+
61
+ # Scope identifier
62
+ #
63
+ # Returns the scope identifier related to the plan
64
+ def scope_id
65
+ @scope_id || scope&.id
66
+ end
67
+
68
+ def author
69
+ current_organization
70
+ end
71
+
72
+ def proposals
73
+ @proposals ||= Decidim
74
+ .find_resource_manifest(:proposals)
75
+ .try(:resource_scope, current_component)
76
+ &.where(id: proposal_ids)
77
+ &.order(title: :asc)
78
+ end
79
+
80
+ private
81
+
82
+ def scope_belongs_to_participatory_space_scope
83
+ errors.add(:scope_id, :invalid) if current_participatory_space.out_of_scope?(scope)
84
+ end
85
+ end
86
+ end
87
+ end
88
+ end
@@ -0,0 +1,20 @@
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 create a plan.
7
+ class PlanSectionsForm < Decidim::Form
8
+ include Decidim::ApplicationHelper
9
+
10
+ attribute :sections, Array[SectionForm]
11
+
12
+ def map_model(sections)
13
+ self.sections = sections.map do |section|
14
+ SectionForm.from_model(section)
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,34 @@
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 create a plan.
7
+ class SectionForm < Decidim::Form
8
+ include TranslatableAttributes
9
+ include Decidim::ApplicationHelper
10
+
11
+ mimic :section
12
+
13
+ translatable_attribute :body, String
14
+ translatable_attribute :help, String
15
+ attribute :mandatory, Boolean, default: false
16
+ attribute :answer_length, Integer, default: 0
17
+ attribute :section_type, String
18
+ attribute :position, Integer
19
+ attribute :deleted, Boolean, default: false
20
+
21
+ validates :position, numericality: { greater_than_or_equal_to: 0 }
22
+ validates :body, translatable_presence: true, unless: :deleted
23
+ validates :answer_length, numericality: {
24
+ greater_than_or_equal_to: 0,
25
+ only_integer: true
26
+ }
27
+
28
+ def to_param
29
+ id || "section-id"
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Decidim
4
+ module Plans
5
+ class AttachmentForm < Decidim::Form
6
+ attribute :title, String
7
+ attribute :file
8
+ attribute :weight, Integer
9
+ attribute :deleted, Boolean, default: false
10
+
11
+ mimic :attachment
12
+
13
+ validates :title, presence: true, if: ->(form) { form.file.present? || form.id.present? }
14
+
15
+ def to_param
16
+ id || "attachment-id"
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,58 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Decidim
4
+ module Plans
5
+ # A form object to be used when admin users want to create or edit
6
+ # a plan content field.
7
+ class ContentForm < Decidim::Form
8
+ include OptionallyTranslatableAttributes
9
+ include Decidim::TranslationsHelper
10
+
11
+ mimic :content
12
+
13
+ alias component current_component
14
+
15
+ optionally_translatable_attribute :body, String
16
+ attribute :section_id, Integer
17
+ attribute :plan_id, Integer
18
+
19
+ optionally_translatable_validate_presence :body, if: :mandatory
20
+
21
+ attr_writer :section
22
+ attr_writer :plan
23
+
24
+ delegate :mandatory, to: :section
25
+
26
+ def section
27
+ @section ||= Decidim::Plans::Section.find(section_id)
28
+ end
29
+
30
+ def plan
31
+ @plan ||= Decidim::Plans::Plan.find(plan_id)
32
+ end
33
+
34
+ def label
35
+ translated_attribute(section.body)
36
+ end
37
+
38
+ def help
39
+ translated_attribute(section.help)
40
+ end
41
+
42
+ # Public: Map the correct fields.
43
+ #
44
+ # Returns nothing.
45
+ def map_model(model)
46
+ self.section_id = model.decidim_section_id
47
+ self.section = model.section
48
+
49
+ self.plan_id = model.decidim_plan_id
50
+ self.plan = model.plan
51
+ end
52
+
53
+ def deleted?
54
+ false
55
+ end
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,85 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Decidim
4
+ module Plans
5
+ # A form object to be used when public users want to create a Plan.
6
+ class PlanForm < Decidim::Form
7
+ include OptionallyTranslatableAttributes
8
+ mimic :plan
9
+
10
+ alias component current_component
11
+
12
+ optionally_translatable_attribute :title, String
13
+
14
+ attribute :user_group_id, Integer
15
+ attribute :category_id, Integer
16
+ attribute :scope_id, Integer
17
+ attribute :attachments, Array[Plans::AttachmentForm]
18
+ attribute :contents, Array[ContentForm]
19
+ attribute :proposal_ids, Array[Integer]
20
+
21
+ validates :proposals, presence: true
22
+ validates :category, presence: true, if: ->(form) { form.category_id.present? }
23
+ validates :scope, presence: true, if: ->(form) { form.scope_id.present? }
24
+ optionally_translatable_validate_presence :title
25
+
26
+ validate :scope_belongs_to_participatory_space_scope
27
+
28
+ delegate :categories, to: :current_component
29
+
30
+ # Finds the Category from the category_id.
31
+ #
32
+ # Returns a Decidim::Category
33
+ def category
34
+ @category ||= categories.find_by(id: category_id)
35
+ end
36
+
37
+ # Finds the Scope from the given decidim_scope_id, uses participatory space scope if missing.
38
+ #
39
+ # Returns a Decidim::Scope
40
+ def scope
41
+ @scope ||= @scope_id ? current_participatory_space.scopes.find_by(id: @scope_id) : current_participatory_space.scope
42
+ end
43
+
44
+ # Scope identifier
45
+ #
46
+ # Returns the scope identifier related to the plan
47
+ def scope_id
48
+ @scope_id || scope&.id
49
+ end
50
+
51
+ def map_model(model)
52
+ self.contents = model.sections.map do |section|
53
+ ContentForm.from_model(
54
+ Content
55
+ .where(plan: model, section: section)
56
+ .first_or_initialize(plan: model, section: section)
57
+ )
58
+ end
59
+
60
+ self.user_group_id = model.user_groups.first&.id
61
+ return unless model.categorization
62
+
63
+ self.category_id = model.categorization.decidim_category_id
64
+ end
65
+
66
+ def user_group
67
+ @user_group ||= Decidim::UserGroup.find user_group_id if user_group_id.present?
68
+ end
69
+
70
+ def proposals
71
+ @proposals ||= Decidim
72
+ .find_resource_manifest(:proposals)
73
+ .try(:resource_scope, current_component)
74
+ &.where(id: proposal_ids)
75
+ &.order(title: :asc)
76
+ end
77
+
78
+ private
79
+
80
+ def scope_belongs_to_participatory_space_scope
81
+ errors.add(:scope_id, :invalid) if current_participatory_space.out_of_scope?(scope)
82
+ end
83
+ end
84
+ end
85
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Decidim
4
+ module Plans
5
+ # A form object to be used when Plan editors rejects a request to acces it.
6
+ class RejectAccessToPlanForm < AccessToPlanForm
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Decidim
4
+ module Plans
5
+ # A form object to be used when public users want to request acces to a Plan.
6
+ class RequestAccessToPlanForm < Decidim::Form
7
+ mimic :plan
8
+
9
+ attribute :id, String
10
+ attribute :state, String
11
+
12
+ validates :id, presence: true
13
+ validates :state, presence: true, inclusion: { in: %w(open) }
14
+
15
+ def plan
16
+ @plan ||= Decidim::Plans::Plan.find id
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Decidim
4
+ module Plans
5
+ module Admin
6
+ # Custom helpers, scoped to the forms engine.
7
+ #
8
+ module ApplicationHelper
9
+ def tabs_id_for_section(section)
10
+ "section_#{section.to_param}"
11
+ end
12
+
13
+ def tabs_id_for_content(idx)
14
+ "content_#{idx}"
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,78 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Decidim
4
+ module Plans
5
+ # Custom helpers, scoped to the plans engine.
6
+ #
7
+ module ApplicationHelper
8
+ include Decidim::Comments::CommentsHelper
9
+ include PaginateHelper
10
+ include Decidim::MapHelper
11
+
12
+ # Public: The state of a plan in a way a human can understand.
13
+ #
14
+ # state - The String state of the plan.
15
+ #
16
+ # Returns a String.
17
+ def humanize_plan_state(state)
18
+ I18n.t(state, scope: "decidim.plans.answers", default: :not_answered)
19
+ end
20
+
21
+ # Public: The css class applied based on the plan state.
22
+ #
23
+ # state - The String state of the plan.
24
+ #
25
+ # Returns a String.
26
+ def plan_state_css_class(state)
27
+ case state
28
+ when "accepted"
29
+ "text-success"
30
+ when "rejected"
31
+ "text-alert"
32
+ when "evaluating"
33
+ "text-warning"
34
+ when "withdrawn"
35
+ "text-alert"
36
+ else
37
+ "text-info"
38
+ end
39
+ end
40
+
41
+ def current_user_plans
42
+ Plan.joins(:coauthorships).where(
43
+ component: current_component,
44
+ decidim_coauthorships: {
45
+ decidim_author_id: current_user.id,
46
+ decidim_author_type: "Decidim::UserBaseEntity"
47
+ }
48
+ )
49
+ end
50
+
51
+ def authors_for(plan)
52
+ plan.identities.map { |identity| present(identity) }
53
+ end
54
+
55
+ def filter_state_values
56
+ [
57
+ ["except_rejected", t("decidim.plans.application_helper.filter_state_values.except_rejected")],
58
+ ["accepted", t("decidim.plans.application_helper.filter_state_values.accepted")],
59
+ ["evaluating", t("decidim.plans.application_helper.filter_state_values.evaluating")],
60
+ ["rejected", t("decidim.plans.application_helper.filter_state_values.rejected")],
61
+ ["all", t("decidim.plans.application_helper.filter_state_values.all")]
62
+ ]
63
+ end
64
+
65
+ def filter_type_values
66
+ [
67
+ ["all", t("decidim.plans.application_helper.filter_type_values.all")],
68
+ ["plans", t("decidim.plans.application_helper.filter_type_values.plans")],
69
+ ["amendments", t("decidim.plans.application_helper.filter_type_values.amendments")]
70
+ ]
71
+ end
72
+
73
+ def tabs_id_for_content(idx)
74
+ "content_#{idx}"
75
+ end
76
+ end
77
+ end
78
+ end
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Decidim
4
+ module Plans
5
+ module AttachedProposalsHelper
6
+ include Decidim::ApplicationHelper
7
+
8
+ def attached_proposals_picker_field(form, name)
9
+ picker_options = {
10
+ id: "attached_proposals",
11
+ "class": "picker-multiple",
12
+ name: "proposal_ids",
13
+ multiple: true
14
+ }
15
+
16
+ prompt_params = {
17
+ url: plan_search_proposals_path(current_component, format: :html),
18
+ text: t("decidim.plans.attached_proposals_helper.attach_proposal")
19
+ }
20
+
21
+ form.data_picker(name, picker_options, prompt_params) do |item|
22
+ { url: plan_search_proposals_path(current_component, format: :json),
23
+ text: item.title }
24
+ end
25
+ end
26
+
27
+ def search_proposals
28
+ respond_to do |format|
29
+ format.html do
30
+ render partial: "decidim/plans/attached_proposals/proposals"
31
+ end
32
+ format.json do
33
+ query = Decidim
34
+ .find_resource_manifest(:proposals)
35
+ .try(:resource_scope, current_component)
36
+ &.order(title: :asc)
37
+ &.where("title ilike ?", "%#{params[:term]}%")
38
+ render json: query.all.collect { |p| [present(p).title, p.id] }
39
+ end
40
+ end
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,59 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Decidim
4
+ module Plans
5
+ module AttachmentsHelper
6
+ def tabs_id_for_attachment(attachment)
7
+ "attachment_#{attachment.to_param}"
8
+ end
9
+
10
+ def blank_attachment
11
+ @blank_attachment ||= Plans::AttachmentForm.new
12
+ end
13
+
14
+ # A modified version of the Decidim's own form builder `upload` method
15
+ # which is buggy for multiple file fields.
16
+ #
17
+ # See:
18
+ # Decidim::FormBuilder#upload
19
+ #
20
+ # form - The form object for which to create the field.
21
+ # attribute - The String/Symbol name of the attribute to build the
22
+ # field.
23
+ def upload_field(form, attribute)
24
+ file = form.object.send attribute
25
+ required = file.nil?
26
+
27
+ label_content = form.label_for(attribute)
28
+ label_content += required_tag if required
29
+ template = ""
30
+ template += form.label(attribute, label_content)
31
+ template += form.file_field attribute, label: false
32
+
33
+ if form.send(:file_is_image?, file)
34
+ template += if file.present?
35
+ content_tag :label, I18n.t("current_image", scope: "decidim.forms")
36
+ else
37
+ content_tag :label, I18n.t("default_image", scope: "decidim.forms")
38
+ end
39
+ template += link_to image_tag(file.url), file.url, target: "_blank"
40
+ elsif form.send(:file_is_present?, file)
41
+ template += label_tag I18n.t("current_file", scope: "decidim.forms")
42
+ template += link_to file.file.filename, file.url, target: "_blank"
43
+ end
44
+
45
+ template.html_safe
46
+ end
47
+
48
+ def required_tag
49
+ content_tag(
50
+ :abbr,
51
+ "*",
52
+ title: I18n.t("required", scope: "forms"),
53
+ data: { tooltip: true, disable_hover: false }, 'aria-haspopup': true,
54
+ class: "label-required"
55
+ ).html_safe
56
+ end
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Decidim
4
+ module Plans
5
+ module CellsHelper
6
+ def plans_controller?
7
+ context[:controller].class.to_s == "Decidim::Plans::PlansController"
8
+ end
9
+
10
+ def withdrawable?
11
+ return unless from_context
12
+ return unless plans_controller?
13
+ return if index_action?
14
+ from_context.withdrawable_by?(current_user)
15
+ end
16
+
17
+ def flagable?
18
+ return unless from_context
19
+ return unless plans_controller?
20
+ return if index_action?
21
+ return if from_context.try(:official?)
22
+ true
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,67 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Decidim
4
+ module Plans
5
+ # Custom helpers, scoped to the plans engine.
6
+ #
7
+ module PlanCellsHelper
8
+ include Decidim::Plans::ApplicationHelper
9
+ include Decidim::Plans::Engine.routes.url_helpers
10
+ include Decidim::LayoutHelper
11
+ include Decidim::ApplicationHelper
12
+ include Decidim::TranslationsHelper
13
+ include Decidim::ResourceReferenceHelper
14
+ include Decidim::TranslatableAttributes
15
+ include Decidim::CardHelper
16
+
17
+ delegate :title, :state, :answered?, :withdrawn?, to: :model
18
+
19
+ def has_actions?
20
+ false
21
+ end
22
+
23
+ def plans_controller?
24
+ context[:controller].class.to_s == "Decidim::Plans::PlansController"
25
+ end
26
+
27
+ def index_action?
28
+ context[:controller].action_name == "index"
29
+ end
30
+
31
+ def current_settings
32
+ model.component.current_settings
33
+ end
34
+
35
+ def component_settings
36
+ model.component.settings
37
+ end
38
+
39
+ def current_component
40
+ model.component
41
+ end
42
+
43
+ def from_context
44
+ @options[:from]
45
+ end
46
+
47
+ def badge_name
48
+ humanize_plan_state state
49
+ end
50
+
51
+ def state_classes
52
+ case state
53
+ when "accepted"
54
+ ["success"]
55
+ when "rejected"
56
+ ["alert"]
57
+ when "evaluating"
58
+ ["warning"]
59
+ when "withdrawn"
60
+ ["alert"]
61
+ else
62
+ ["muted"]
63
+ end
64
+ end
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Decidim
4
+ module Plans
5
+ module RemainingCharactersHelper
6
+ def remaining_characters(attribute, num_characters)
7
+ return unless block_given?
8
+
9
+ field_opts = {}
10
+ if num_characters.positive?
11
+ remaining_characters_id = "#{attribute}_remaining_characters"
12
+ field_opts = {
13
+ maxlength: num_characters,
14
+ data: {
15
+ remaining_characters: "##{remaining_characters_id}"
16
+ }
17
+ }
18
+ end
19
+
20
+ field = capture do
21
+ yield field_opts
22
+ end
23
+ chars_elem = render(
24
+ "decidim/plans/shared/remaining_characters_container",
25
+ remaining_characters_id: remaining_characters_id
26
+ )
27
+
28
+ field + chars_elem
29
+ end
30
+ end
31
+ end
32
+ end