decidim-core 0.4.4 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (136) hide show
  1. checksums.yaml +4 -4
  2. data/app/assets/config/decidim_core_manifest.js +1 -0
  3. data/app/assets/images/decidim/icons.svg +1 -1
  4. data/app/assets/javascripts/decidim.js.es6 +3 -0
  5. data/app/assets/javascripts/decidim/form_filter.component.js.es6 +39 -6
  6. data/app/assets/javascripts/decidim/form_filter.component.test.js +13 -10
  7. data/app/assets/javascripts/decidim/impersonation.js.es6 +16 -0
  8. data/app/assets/javascripts/decidim/select2.field.js.es6 +47 -0
  9. data/app/assets/javascripts/decidim/select2.js.es6 +8 -0
  10. data/app/assets/stylesheets/decidim/_decidim.scss +3 -0
  11. data/app/assets/stylesheets/decidim/extras/_announcement.scss +3 -0
  12. data/app/assets/stylesheets/decidim/extras/_callout.scss +1 -1
  13. data/app/assets/stylesheets/decidim/extras/_impersonation-bar.scss +22 -0
  14. data/app/assets/stylesheets/decidim/extras/_process_stats.scss +1 -1
  15. data/app/assets/stylesheets/decidim/extras/_proposal_form.scss +7 -0
  16. data/app/assets/stylesheets/decidim/extras/_reference.scss +1 -1
  17. data/app/assets/stylesheets/decidim/extras/_register_form.scss +1 -1
  18. data/app/assets/stylesheets/decidim/extras/_social_icons_mini.scss +1 -1
  19. data/app/assets/stylesheets/decidim/layouts/_home.scss +1 -1
  20. data/app/assets/stylesheets/decidim/layouts/_view.scss +1 -1
  21. data/app/assets/stylesheets/decidim/map.css +1 -1
  22. data/app/assets/stylesheets/decidim/modules/_author-avatar.scss +1 -1
  23. data/app/assets/stylesheets/decidim/modules/_callout.scss +1 -1
  24. data/app/assets/stylesheets/decidim/modules/_datepicker.scss +1 -1
  25. data/app/assets/stylesheets/decidim/modules/_definition-data.scss +1 -1
  26. data/app/assets/stylesheets/decidim/modules/_extra.scss +1 -1
  27. data/app/assets/stylesheets/decidim/modules/_flag.scss +1 -1
  28. data/app/assets/stylesheets/decidim/modules/_footer.scss +1 -1
  29. data/app/assets/stylesheets/decidim/modules/_forms.scss +1 -1
  30. data/app/assets/stylesheets/decidim/modules/_help.scss +1 -1
  31. data/app/assets/stylesheets/decidim/modules/_pagination.scss +1 -1
  32. data/app/assets/stylesheets/decidim/modules/_process-info.scss +1 -1
  33. data/app/assets/stylesheets/decidim/modules/_reference.scss +1 -1
  34. data/app/assets/stylesheets/decidim/modules/_reveal.scss +1 -1
  35. data/app/assets/stylesheets/decidim/modules/_signup.scss +1 -1
  36. data/app/assets/stylesheets/decidim/modules/_static-pages.scss +1 -1
  37. data/app/assets/stylesheets/decidim/modules/_tags.scss +1 -1
  38. data/app/assets/stylesheets/decidim/modules/_title-action.scss +1 -1
  39. data/app/assets/stylesheets/decidim/modules/_user-form.scss +1 -1
  40. data/app/assets/stylesheets/decidim/modules/_video.scss +1 -1
  41. data/app/assets/stylesheets/decidim/plugins/_select2.scss +63 -0
  42. data/app/assets/stylesheets/decidim/utils/_toggle-expand.scss +1 -1
  43. data/app/commands/decidim/invite_user.rb +5 -2
  44. data/app/controllers/concerns/decidim/devise_controllers.rb +6 -0
  45. data/app/controllers/concerns/decidim/impersonate_users.rb +67 -0
  46. data/app/controllers/concerns/decidim/needs_authorization.rb +0 -4
  47. data/app/controllers/decidim/application_controller.rb +7 -0
  48. data/app/controllers/decidim/devise/invitations_controller.rb +7 -0
  49. data/app/controllers/decidim/errors_controller.rb +1 -1
  50. data/app/controllers/decidim/features/base_controller.rb +1 -1
  51. data/app/controllers/decidim/participatory_process_widgets_controller.rb +0 -2
  52. data/app/controllers/decidim/scopes_controller.rb +29 -0
  53. data/app/forms/decidim/attachment_form.rb +14 -0
  54. data/app/forms/decidim/invite_user_form.rb +12 -1
  55. data/app/helpers/decidim/application_helper.rb +1 -0
  56. data/app/helpers/decidim/attachments_helper.rb +11 -0
  57. data/app/helpers/decidim/decidim_form_helper.rb +9 -8
  58. data/app/helpers/decidim/icon_helper.rb +12 -2
  59. data/app/helpers/decidim/participatory_process_helper.rb +0 -24
  60. data/app/helpers/decidim/scopes_helper.rb +15 -0
  61. data/app/helpers/decidim/translations_helper.rb +1 -1
  62. data/app/mailers/decidim/reported_mailer.rb +1 -1
  63. data/app/models/decidim/abilities/admin_ability.rb +1 -0
  64. data/app/models/decidim/abilities/everyone_ability.rb +2 -0
  65. data/app/models/decidim/abilities/user_manager_ability.rb +35 -0
  66. data/app/models/decidim/impersonation_log.rb +35 -0
  67. data/app/models/decidim/organization.rb +9 -0
  68. data/app/models/decidim/participatory_process.rb +1 -4
  69. data/app/models/decidim/scope.rb +65 -1
  70. data/app/models/decidim/scope_type.rb +16 -0
  71. data/app/models/decidim/user.rb +35 -4
  72. data/app/presenters/decidim/participatory_process_stats_presenter.rb +1 -1
  73. data/app/queries/decidim/freetext_scopes.rb +39 -0
  74. data/app/services/decidim/resource_search.rb +7 -6
  75. data/app/uploaders/decidim/attachment_uploader.rb +1 -1
  76. data/app/views/decidim/account/delete.html.erb +1 -1
  77. data/app/views/decidim/application/_documents.html.erb +4 -2
  78. data/app/views/decidim/participatory_processes/_statistics.html.erb +1 -1
  79. data/app/views/decidim/participatory_processes/show.html.erb +4 -0
  80. data/app/views/decidim/reported_mailer/hide.html.erb +1 -1
  81. data/app/views/decidim/reported_mailer/report.html.erb +1 -1
  82. data/app/views/decidim/shared/_announcement.html.erb +5 -0
  83. data/app/views/decidim/shared/_feature_announcement.html.erb +5 -0
  84. data/app/views/decidim/shared/_flag_modal.html.erb +1 -1
  85. data/app/views/decidim/shared/_tags.html.erb +10 -0
  86. data/app/views/devise/mailer/invitation_instructions.html.erb +13 -0
  87. data/app/views/devise/mailer/invitation_instructions.text.erb +11 -0
  88. data/app/views/layouts/decidim/_application.html.erb +1 -0
  89. data/app/views/layouts/decidim/_impersonation_warning.html.erb +10 -0
  90. data/app/views/layouts/decidim/_wrapper.html.erb +1 -1
  91. data/app/views/pages/home/_statistics.html.erb +1 -1
  92. data/app/views/pages/home/_sub_hero.html.erb +1 -1
  93. data/config/locales/ca.yml +10 -1
  94. data/config/locales/en.yml +9 -0
  95. data/config/locales/es.yml +10 -1
  96. data/config/locales/eu.yml +22 -1
  97. data/config/locales/fi.yml +1 -1
  98. data/config/locales/fr.yml +60 -0
  99. data/config/locales/it.yml +1 -1
  100. data/config/locales/nl.yml +1 -1
  101. data/config/locales/pl.yml +91 -0
  102. data/config/routes.rb +2 -0
  103. data/db/migrate/20170605162500_add_hierarchy_to_scopes.rb +70 -0
  104. data/db/migrate/20170713131206_add_admin_to_users.rb +6 -1
  105. data/db/migrate/20170713131308_migrate_user_roles_to_participatory_process_roles.rb +5 -1
  106. data/db/migrate/20170720135441_add_managed_to_users.rb +7 -0
  107. data/db/migrate/20170720140610_set_email_unique_in_organization_condition_for_managed_users.rb +12 -0
  108. data/db/migrate/20170724130558_create_impersonation_logs.rb +15 -0
  109. data/db/migrate/20170727125445_add_roles_to_users.rb +7 -0
  110. data/db/migrate/20170804125402_attachment_description_nullable.rb +7 -0
  111. data/db/migrate/20170808080905_add_announcement_to_participatory_processes.rb +7 -0
  112. data/db/migrate/20170809084005_add_scopes_enabled_to_participatory_processes.rb +7 -0
  113. data/db/seeds.rb +34 -10
  114. data/lib/decidim/core.rb +11 -5
  115. data/lib/decidim/core/engine.rb +1 -0
  116. data/lib/decidim/core/test.rb +2 -0
  117. data/lib/decidim/core/test/factories.rb +59 -22
  118. data/lib/decidim/core/test/shared_examples/announcements_examples.rb +58 -0
  119. data/lib/decidim/core/test/shared_examples/comments_examples.rb +15 -29
  120. data/lib/decidim/core/test/shared_examples/errors.rb +1 -1
  121. data/lib/decidim/core/test/shared_examples/has_attachments.rb +4 -9
  122. data/lib/decidim/core/test/shared_examples/manage_moderations_examples.rb +1 -8
  123. data/lib/decidim/core/test/shared_examples/paginated_resource_examples.rb +2 -2
  124. data/lib/decidim/core/test/shared_examples/process_announcements_examples.rb +33 -0
  125. data/lib/decidim/core/test/shared_examples/reports_examples.rb +1 -2
  126. data/lib/decidim/core/version.rb +6 -2
  127. data/lib/decidim/faker/localized.rb +4 -4
  128. data/lib/decidim/filter_form_builder.rb +7 -0
  129. data/lib/decidim/form_builder.rb +34 -5
  130. data/lib/decidim/scopable.rb +33 -0
  131. data/lib/decidim/settings_manifest.rb +10 -2
  132. data/lib/decidim/translatable_attributes.rb +0 -1
  133. metadata +61 -17
  134. data/app/helpers/decidim/organization_scopes_helper.rb +0 -42
  135. data/db/seeds/city3.jpeg +0 -0
  136. data/lib/tasks/factory_girl.rake +0 -17
@@ -10,8 +10,18 @@ module Decidim
10
10
  #
11
11
  # Returns an HTML tag with the icon.
12
12
  def feature_icon(feature)
13
- if feature.manifest.icon
14
- external_icon feature.manifest.icon
13
+ feature_manifest_icon(feature.manifest)
14
+ end
15
+
16
+ # Public: Returns an icon given an instance of a Feature Manifest. It defaults to
17
+ # a question mark when no icon is found.
18
+ #
19
+ # feature_manifest - The feature manifest to generate the icon for.
20
+ #
21
+ # Returns an HTML tag with the icon.
22
+ def feature_manifest_icon(feature_manifest)
23
+ if feature_manifest.icon
24
+ external_icon feature_manifest.icon
15
25
  else
16
26
  icon "question-mark"
17
27
  end
@@ -13,29 +13,5 @@ module Decidim
13
13
  dates = [participatory_process_step.start_date, participatory_process_step.end_date]
14
14
  dates.map { |date| date ? localize(date.to_date, format: :default) : "?" }.join(" - ")
15
15
  end
16
-
17
- # Public: Returns an icon given an instance of a Feature. It defaults to
18
- # a question mark when no icon is found.
19
- #
20
- # feature - The feature to generate the icon for.
21
- #
22
- # Returns an HTML tag with the icon.
23
- def feature_icon(feature)
24
- feature_manifest_icon(feature.manifest)
25
- end
26
-
27
- # Public: Returns an icon given an instance of a Feature Manifest. It defaults to
28
- # a question mark when no icon is found.
29
- #
30
- # feature_manifest - The feature manifest to generate the icon for.
31
- #
32
- # Returns an HTML tag with the icon.
33
- def feature_manifest_icon(feature_manifest)
34
- if feature_manifest.icon
35
- external_icon feature_manifest.icon
36
- else
37
- icon "question-mark"
38
- end
39
- end
40
16
  end
41
17
  end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Decidim
4
+ # A Helper to render scopes, including a global scope, for forms.
5
+ module ScopesHelper
6
+ Option = Struct.new(:id, :name)
7
+
8
+ # Check whether the resource has a visible scope or not.
9
+ #
10
+ # Returns boolean.
11
+ def has_visible_scopes?(resource)
12
+ current_participatory_process.scopes_enabled? && !current_participatory_process.scope.present? && resource.scope.present?
13
+ end
14
+ end
15
+ end
@@ -22,7 +22,7 @@ module Decidim
22
22
  # available locales by default.
23
23
  #
24
24
  # Returns a Hash with the locales as keys and the translations as values.
25
- def multi_translation(key, locales = I18n.available_locales)
25
+ def multi_translation(key, locales = Decidim.available_locales)
26
26
  locales.each_with_object({}) do |locale, result|
27
27
  I18n.with_locale(locale) do
28
28
  result[locale.to_sym] = I18n.t(key)
@@ -36,7 +36,7 @@ module Decidim
36
36
  end
37
37
 
38
38
  def manage_moderations_url
39
- @manage_moderations_url ||= decidim_admin.participatory_process_moderations_url(@participatory_process.id, host: @organization.host)
39
+ @manage_moderations_url ||= decidim_admin.moderations_url(@participatory_process.id, host: @organization.host)
40
40
  end
41
41
  end
42
42
  end
@@ -19,6 +19,7 @@ module Decidim
19
19
  can :read, :admin_dashboard
20
20
  can :read, ParticipatoryProcess
21
21
  can :read, Feature
22
+ can :impersonate, :managed_users
22
23
  end
23
24
 
24
25
  def admin?
@@ -15,6 +15,8 @@ module Decidim
15
15
 
16
16
  can :read, Feature, &:published?
17
17
 
18
+ can :search, Scope
19
+
18
20
  can :manage, User do |other|
19
21
  other == user
20
22
  end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Decidim
4
+ module Abilities
5
+ # Defines the abilities for an user with role 'user_manager'.
6
+ # Intended to be used with `cancancan`.
7
+ class UserManagerAbility
8
+ include CanCan::Ability
9
+
10
+ attr_reader :user
11
+
12
+ def initialize(user, context)
13
+ @user = user
14
+ @context = context
15
+
16
+ define_abilities if not_admin? && user_manager?
17
+ end
18
+
19
+ def define_abilities
20
+ can :read, :admin_dashboard
21
+ can :impersonate, :managed_users
22
+ end
23
+
24
+ # Whether the user is an admin or not.
25
+ def not_admin?
26
+ @user && !@user.admin?
27
+ end
28
+
29
+ # Whether the user has the user_manager role or not.
30
+ def user_manager?
31
+ @user.role? "user_manager"
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Decidim
4
+ # ImpersonationLogs are created whenever an admin impersonate a managed user
5
+ class ImpersonationLog < ApplicationRecord
6
+ SESSION_TIME_IN_MINUTES = 30
7
+
8
+ belongs_to :admin, foreign_key: "decidim_admin_id", class_name: "Decidim::User"
9
+ belongs_to :user, foreign_key: "decidim_user_id", class_name: "Decidim::User"
10
+
11
+ validate :same_organization, :non_active_impersonation
12
+
13
+ scope :active, -> { where(ended_at: nil) }
14
+
15
+ def ended?
16
+ ended_at.present?
17
+ end
18
+
19
+ def expired?
20
+ expired_at.present?
21
+ end
22
+
23
+ private
24
+
25
+ def same_organization
26
+ return if admin&.organization == user&.organization
27
+ errors.add(:admin, :invalid)
28
+ end
29
+
30
+ def non_active_impersonation
31
+ return if ended? || expired?
32
+ errors.add(:admin, :invalid) if Decidim::ImpersonationLog.where(admin: admin).active.any?
33
+ end
34
+ end
35
+ end
@@ -9,7 +9,9 @@ module Decidim
9
9
 
10
10
  has_many :static_pages, foreign_key: "decidim_organization_id", class_name: "Decidim::StaticPage", inverse_of: :organization
11
11
  has_many :scopes, -> { order(name: :asc) }, foreign_key: "decidim_organization_id", class_name: "Decidim::Scope", inverse_of: :organization
12
+ has_many :scope_types, -> { order(name: :asc) }, foreign_key: "decidim_organization_id", class_name: "Decidim::ScopeType", inverse_of: :organization
12
13
  has_many :admins, -> { where(admin: true) }, foreign_key: "decidim_organization_id", class_name: "Decidim::User"
14
+ has_many :users_with_any_role, -> { where.not(roles: []) }, foreign_key: "decidim_organization_id", class_name: "Decidim::User"
13
15
  has_many :users, foreign_key: "decidim_organization_id", class_name: "Decidim::User"
14
16
 
15
17
  validates :name, :host, uniqueness: true
@@ -21,6 +23,13 @@ module Decidim
21
23
  mount_uploader :logo, Decidim::OrganizationLogoUploader
22
24
  mount_uploader :favicon, Decidim::OrganizationFaviconUploader
23
25
 
26
+ # Returns top level scopes for this organization.
27
+ #
28
+ # Returns an ActiveRecord::Relation.
29
+ def top_scopes
30
+ @top_scopes ||= scopes.top_level
31
+ end
32
+
24
33
  def homepage_big_url
25
34
  homepage_image.big.url
26
35
  end
@@ -9,6 +9,7 @@ module Decidim
9
9
  class ParticipatoryProcess < ApplicationRecord
10
10
  include Decidim::HasAttachments
11
11
  include Decidim::Publicable
12
+ include Decidim::Scopable
12
13
 
13
14
  belongs_to :organization,
14
15
  foreign_key: "decidim_organization_id",
@@ -18,10 +19,6 @@ module Decidim
18
19
  class_name: "Decidim::ParticipatoryProcessGroup",
19
20
  inverse_of: :participatory_processes,
20
21
  optional: true
21
- belongs_to :scope,
22
- foreign_key: "decidim_scope_id",
23
- class_name: "Decidim::Scope",
24
- optional: true
25
22
  has_many :steps,
26
23
  -> { order(position: :asc) },
27
24
  foreign_key: "decidim_participatory_process_id",
@@ -10,6 +10,70 @@ module Decidim
10
10
  class_name: "Decidim::Organization",
11
11
  inverse_of: :scopes
12
12
 
13
- validates :name, presence: true, uniqueness: { scope: :organization }
13
+ belongs_to :scope_type,
14
+ foreign_key: "scope_type_id",
15
+ class_name: "Decidim::ScopeType",
16
+ inverse_of: :scopes,
17
+ optional: true
18
+
19
+ belongs_to :parent,
20
+ foreign_key: "parent_id",
21
+ class_name: "Decidim::Scope",
22
+ inverse_of: :children,
23
+ optional: true
24
+
25
+ has_many :children,
26
+ foreign_key: "parent_id",
27
+ class_name: "Decidim::Scope",
28
+ inverse_of: :parent
29
+
30
+ before_validation :update_part_of, on: :update
31
+
32
+ validates :name, :code, :organization, presence: true
33
+ validates :code, uniqueness: { scope: :organization }
34
+ validate :forbid_cycles
35
+
36
+ after_create_commit :create_part_of
37
+
38
+ # Scope to return only the top level scopes.
39
+ #
40
+ # Returns an ActiveRecord::Relation.
41
+ def self.top_level
42
+ where parent_id: nil
43
+ end
44
+
45
+ def descendants
46
+ organization.scopes.where("? = ANY(decidim_scopes.part_of)", id)
47
+ end
48
+
49
+ # Gets the scopes from the part_of list
50
+ #
51
+ # Returns an ActiveRecord::Relation.
52
+ def part_of_scopes
53
+ organization.scopes.where(id: part_of)
54
+ end
55
+
56
+ private
57
+
58
+ def forbid_cycles
59
+ errors.add(:parent_id, :cycle_detected) if parent && parent.part_of.include?(id)
60
+ end
61
+
62
+ def create_part_of
63
+ self[:part_of] = calculate_part_of
64
+ save if changed?
65
+ end
66
+
67
+ def update_part_of
68
+ self[:part_of] = calculate_part_of
69
+ end
70
+
71
+ def calculate_part_of
72
+ if parent
73
+ [id] + parent.part_of
74
+ else
75
+ [id]
76
+ end
77
+ end
14
78
  end
15
79
  end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Decidim
4
+ # Scope types allows to use different types of scopes in participatory process
5
+ # (municipalities, provinces, states, countries, etc.)
6
+ class ScopeType < ApplicationRecord
7
+ belongs_to :organization,
8
+ foreign_key: "decidim_organization_id",
9
+ class_name: "Decidim::Organization",
10
+ inverse_of: :scope_types
11
+
12
+ has_many :scopes, foreign_key: "decidim_scope_type_id", class_name: "Decidim::Scope", inverse_of: :scope_type
13
+
14
+ validates :name, presence: true
15
+ end
16
+ end
@@ -6,6 +6,7 @@ module Decidim
6
6
  # A User is a citizen that wants to join the platform to participate.
7
7
  class User < ApplicationRecord
8
8
  MAXIMUM_AVATAR_FILE_SIZE = 5.megabytes
9
+ ROLES = %w(admin user_manager).freeze
9
10
 
10
11
  devise :invitable, :database_authenticatable, :registerable, :confirmable,
11
12
  :recoverable, :rememberable, :trackable, :decidim_validatable,
@@ -19,13 +20,16 @@ module Decidim
19
20
  has_many :user_groups, through: :memberships, class_name: "Decidim::UserGroup", foreign_key: :decidim_user_group_id
20
21
 
21
22
  validates :name, presence: true, unless: -> { deleted? }
22
- validates :locale, inclusion: { in: I18n.available_locales.map(&:to_s) }, allow_blank: true
23
+ validates :locale, inclusion: { in: Decidim.available_locales.map(&:to_s) }, allow_blank: true
23
24
  validates :tos_agreement, acceptance: true, allow_nil: false, on: :create
24
25
  validates :avatar, file_size: { less_than_or_equal_to: MAXIMUM_AVATAR_FILE_SIZE }
25
- validates :email, uniqueness: { scope: :organization }, unless: -> { deleted? }
26
+ validates :email, uniqueness: { scope: :organization }, unless: -> { deleted? || managed? }
27
+ validate :all_roles_are_valid
28
+
26
29
  mount_uploader :avatar, Decidim::AvatarUploader
27
30
 
28
31
  scope :not_deleted, -> { where(deleted_at: nil) }
32
+ scope :managed, -> { where(managed: true) }
29
33
 
30
34
  # Public: Allows customizing the invitation instruction email content when
31
35
  # inviting a user.
@@ -33,6 +37,21 @@ module Decidim
33
37
  # Returns a String.
34
38
  attr_accessor :invitation_instructions
35
39
 
40
+ # Checks if the user has the given `role` or not.
41
+ #
42
+ # role - a String or a Symbol that represents the role that is being
43
+ # checked
44
+ #
45
+ # Returns a boolean.
46
+ def role?(role)
47
+ roles.include?(role.to_s)
48
+ end
49
+
50
+ # Public: Returns the active role of the user
51
+ def active_role
52
+ admin ? "admin" : roles.first
53
+ end
54
+
36
55
  # Public: returns the user's name or the default one
37
56
  def name
38
57
  super || I18n.t("decidim.anonymous_user")
@@ -60,9 +79,17 @@ module Decidim
60
79
  protected
61
80
 
62
81
  # Overrides devise email required validation.
63
- # If the user has been deleted the email field is not required anymore.
82
+ # If the user has been deleted or it is managed the email field is not required anymore.
64
83
  def email_required?
65
- !deleted?
84
+ return false if deleted? || managed?
85
+ super
86
+ end
87
+
88
+ # Overrides devise password required validation.
89
+ # If the user is managed the password field is not required anymore.
90
+ def password_required?
91
+ return false if managed?
92
+ super
66
93
  end
67
94
 
68
95
  private
@@ -71,5 +98,9 @@ module Decidim
71
98
  def send_devise_notification(notification, *args)
72
99
  devise_mailer.send(notification, self, *args).deliver_later
73
100
  end
101
+
102
+ def all_roles_are_valid
103
+ errors.add(:roles, :invalid) unless roles.all? { |role| ROLES.include?(role) }
104
+ end
74
105
  end
75
106
  end
@@ -4,7 +4,7 @@ module Decidim
4
4
  # A presenter to render statistics in the homepage.
5
5
  class ParticipatoryProcessStatsPresenter < Rectify::Presenter
6
6
  attribute :participatory_process, Decidim::ParticipatoryProcess
7
- include Decidim::ParticipatoryProcessHelper
7
+ include Decidim::IconHelper
8
8
 
9
9
  # Public: Render a collection of primary stats.
10
10
  def highlighted
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Decidim
4
+ # This query searches scopes by name.
5
+ class FreetextScopes < Rectify::Query
6
+ # Syntactic sugar to initialize the class and return the queried objects.
7
+ #
8
+ # organization - an Organization context for the scope search
9
+ # lang - the language code to be used for the search
10
+ # text - the text to be searched in scopes names
11
+ # root - root scope
12
+ def self.for(organization, lang, text, root = nil)
13
+ new(organization, lang, text, root).query
14
+ end
15
+
16
+ # Initializes the class.
17
+ #
18
+ # organization - an Organization context for the scope search
19
+ # lang - the language code to be used for the search
20
+ # text - the text to be searched in scopes names
21
+ def initialize(organization, lang, text, root = nil)
22
+ @organization = organization
23
+ @lang = lang
24
+ @text = text
25
+ @root = root
26
+ end
27
+
28
+ # Finds scopes in the given organization and language whose name begins with the indicated text.
29
+ #
30
+ # Returns an ActiveRecord::Relation.
31
+ def query
32
+ if @root
33
+ @root.descendants.where("name->>? ilike ?", @lang, @text + "%")
34
+ else
35
+ @organization.scopes.where("name->>? ilike ?", @lang, @text + "%")
36
+ end
37
+ end
38
+ end
39
+ end