decidim-core 0.19.1 → 0.20.0
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of decidim-core might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/README.md +19 -0
- data/app/assets/javascripts/decidim.js.es6 +2 -1
- data/app/assets/javascripts/decidim/input_hashtags.js.es6 +1 -1
- data/app/assets/javascripts/decidim/input_mentions.js.es6 +112 -50
- data/app/assets/stylesheets/decidim/modules/_signup.scss +57 -0
- data/app/cells/decidim/coauthorships_cell.rb +2 -6
- data/app/cells/decidim/diff_cell.rb +3 -7
- data/app/controllers/decidim/application_controller.rb +0 -8
- data/app/controllers/decidim/devise/unlocks_controller.rb +25 -0
- data/app/controllers/decidim/searches_controller.rb +0 -1
- data/app/jobs/decidim/export_participatory_space_job.rb +20 -0
- data/app/models/decidim/category.rb +2 -0
- data/app/models/decidim/component.rb +2 -0
- data/app/models/decidim/user.rb +9 -2
- data/app/models/decidim/user_group.rb +7 -0
- data/app/presenters/decidim/attachment_presenter.rb +21 -0
- data/app/resolvers/decidim/core/user_resolver.rb +61 -0
- data/app/scrubbers/decidim/user_input_scrubber.rb +2 -2
- data/app/serializers/decidim/exporters/participatory_space_components_serializer.rb +46 -0
- data/{lib → app/serializers}/decidim/exporters/serializer.rb +0 -0
- data/app/serializers/decidim/importers/importer.rb +25 -0
- data/app/serializers/decidim/importers/participatory_space_components_importer.rb +67 -0
- data/app/services/decidim/open_data_exporter.rb +1 -1
- data/app/uploaders/decidim/banner_image_uploader.rb +0 -1
- data/app/views/decidim/devise/invitations/edit.html.erb +4 -2
- data/app/views/decidim/devise/registrations/new.html.erb +2 -2
- data/app/views/decidim/devise/unlocks/new.html.erb +33 -0
- data/app/views/decidim/searches/_filters.html.erb +3 -19
- data/app/views/decidim/searches/_resources_filter_block.html.erb +20 -0
- data/app/views/devise/mailer/unlock_instructions.html.erb +7 -0
- data/app/views/layouts/decidim/_main_footer.html.erb +26 -0
- data/app/views/layouts/decidim/_mini_footer.html.erb +21 -0
- data/app/views/layouts/decidim/_wrapper.html.erb +7 -50
- data/config/initializers/devise.rb +6 -6
- data/config/locales/en.yml +0 -10
- data/config/routes.rb +1 -0
- data/db/migrate/20191028135718_add_lockable_to_users.rb +10 -0
- data/db/migrate/20191118120529_add_weight_to_categories.rb +7 -0
- data/db/migrate/20191212102051_remove_continuity_badges.rb +13 -0
- data/db/seeds.rb +18 -0
- data/lib/decidim/acts_as_author.rb +21 -0
- data/lib/decidim/component_manifest.rb +45 -4
- data/lib/decidim/core.rb +1 -0
- data/lib/decidim/core/engine.rb +0 -4
- data/lib/decidim/core/test.rb +3 -0
- data/lib/decidim/core/test/factories.rb +5 -0
- data/lib/decidim/core/test/shared_examples/acts_as_author_examples.rb +12 -0
- data/lib/decidim/core/test/shared_examples/comments_examples.rb +41 -2
- data/lib/decidim/core/test/shared_examples/searchable_participatory_space_examples.rb +145 -0
- data/lib/decidim/core/test/shared_examples/searchable_resources_shared_context.rb +12 -0
- data/lib/decidim/core/version.rb +1 -1
- data/lib/decidim/exporters.rb +0 -1
- data/lib/decidim/exporters/export_manifest.rb +72 -0
- data/lib/decidim/faker/localized.rb +10 -0
- data/lib/decidim/form_builder.rb +2 -3
- data/lib/decidim/participatory_space_manifest.rb +33 -0
- data/lib/decidim/participatory_space_resourceable.rb +8 -0
- data/lib/decidim/query_extensions.rb +16 -0
- data/lib/decidim/resourceable.rb +1 -1
- data/lib/decidim/searchable.rb +19 -1
- data/vendor/assets/javascripts/tribute.js +1683 -1621
- metadata +29 -13
- data/app/assets/images/decidim/gamification/badges/continuity.svg +0 -73
- data/app/models/decidim/continuity_badge_status.rb +0 -9
- data/app/services/decidim/continuity_badge_tracker.rb +0 -64
- data/lib/decidim/components/export_manifest.rb +0 -63
@@ -9,6 +9,8 @@ module Decidim
|
|
9
9
|
belongs_to :parent, class_name: "Decidim::Category", foreign_key: "parent_id", inverse_of: :subcategories, optional: true
|
10
10
|
has_many :categorizations, foreign_key: "decidim_category_id", class_name: "Decidim::Categorization", dependent: :destroy
|
11
11
|
|
12
|
+
default_scope { order(arel_table[:parent_id].asc, arel_table[:weight].asc) }
|
13
|
+
|
12
14
|
validate :forbid_deep_nesting
|
13
15
|
before_validation :subcategories_have_same_participatory_space
|
14
16
|
|
data/app/models/decidim/user.rb
CHANGED
@@ -9,6 +9,7 @@ module Decidim
|
|
9
9
|
class User < UserBaseEntity
|
10
10
|
include Decidim::DataPortability
|
11
11
|
include Decidim::Searchable
|
12
|
+
include Decidim::ActsAsAuthor
|
12
13
|
|
13
14
|
OMNIAUTH_PROVIDERS = [:facebook, :twitter, :google_oauth2, (:developer if Rails.env.development?)].compact
|
14
15
|
|
@@ -19,8 +20,8 @@ module Decidim
|
|
19
20
|
end
|
20
21
|
|
21
22
|
devise :invitable, :database_authenticatable, :registerable, :confirmable, :timeoutable,
|
22
|
-
:recoverable, :rememberable, :trackable, :
|
23
|
-
:decidim_newsletterable,
|
23
|
+
:recoverable, :rememberable, :trackable, :lockable,
|
24
|
+
:decidim_validatable, :decidim_newsletterable,
|
24
25
|
:omniauthable, omniauth_providers: OMNIAUTH_PROVIDERS,
|
25
26
|
request_keys: [:env], reset_password_keys: [:decidim_organization_id, :email],
|
26
27
|
confirmation_keys: [:decidim_organization_id, :email]
|
@@ -81,6 +82,12 @@ module Decidim
|
|
81
82
|
# Returns a String.
|
82
83
|
attr_accessor :invitation_instructions
|
83
84
|
|
85
|
+
# Returns the presenter for this author, to be used in the views.
|
86
|
+
# Required by ActsAsAuthor.
|
87
|
+
def presenter
|
88
|
+
Decidim::UserPresenter.new(self)
|
89
|
+
end
|
90
|
+
|
84
91
|
def self.log_presenter_class_for(_log)
|
85
92
|
Decidim::AdminLog::UserPresenter
|
86
93
|
end
|
@@ -8,6 +8,7 @@ module Decidim
|
|
8
8
|
class UserGroup < UserBaseEntity
|
9
9
|
include Decidim::Traceable
|
10
10
|
include Decidim::DataPortability
|
11
|
+
include Decidim::ActsAsAuthor
|
11
12
|
|
12
13
|
has_many :memberships, class_name: "Decidim::UserGroupMembership", foreign_key: :decidim_user_group_id, dependent: :destroy
|
13
14
|
has_many :users, through: :memberships, class_name: "Decidim::User", foreign_key: :decidim_user_id
|
@@ -35,6 +36,12 @@ module Decidim
|
|
35
36
|
.where("extended_data->>'document_number' = ?", number)
|
36
37
|
end
|
37
38
|
|
39
|
+
# Returns the presenter for this author, to be used in the views.
|
40
|
+
# Required by ActsAsAuthor.
|
41
|
+
def presenter
|
42
|
+
Decidim::UserGroupPresenter.new(self)
|
43
|
+
end
|
44
|
+
|
38
45
|
def self.log_presenter_class_for(_log)
|
39
46
|
Decidim::AdminLog::UserGroupPresenter
|
40
47
|
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Decidim
|
4
|
+
#
|
5
|
+
# Decorator for attachments
|
6
|
+
#
|
7
|
+
class AttachmentPresenter < SimpleDelegator
|
8
|
+
include Rails.application.routes.mounted_helpers
|
9
|
+
include ActionView::Helpers::UrlHelper
|
10
|
+
|
11
|
+
delegate :url, to: :file, prefix: true
|
12
|
+
|
13
|
+
def attachment_file_url
|
14
|
+
URI.join(decidim.root_url(host: attachment.attached_to.organization.host), attachment.file_url).to_s
|
15
|
+
end
|
16
|
+
|
17
|
+
def attachment
|
18
|
+
__getobj__
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Decidim
|
4
|
+
module Core
|
5
|
+
# A GraphQL resolver to handle `user` queries
|
6
|
+
class UserResolver
|
7
|
+
#
|
8
|
+
# - organization: Decidim::Organization scoping
|
9
|
+
# - filters: hash of attr - value to filter results
|
10
|
+
#
|
11
|
+
def initialize(organization, filters = {})
|
12
|
+
@organization = organization
|
13
|
+
if filters.include? :wildcard
|
14
|
+
filters.delete(:name)
|
15
|
+
filters.delete(:nickname)
|
16
|
+
end
|
17
|
+
@filters = filters
|
18
|
+
end
|
19
|
+
|
20
|
+
def users
|
21
|
+
resolve
|
22
|
+
end
|
23
|
+
|
24
|
+
private
|
25
|
+
|
26
|
+
def resolve
|
27
|
+
return @records if @records
|
28
|
+
|
29
|
+
scope
|
30
|
+
global_filter
|
31
|
+
filter
|
32
|
+
@records.limit(50)
|
33
|
+
end
|
34
|
+
|
35
|
+
def scope
|
36
|
+
@records = Decidim::User
|
37
|
+
.where(organization: organization)
|
38
|
+
.confirmed
|
39
|
+
end
|
40
|
+
|
41
|
+
# Only key name attributes in Decidim::User will be applied
|
42
|
+
def filter
|
43
|
+
@filters.each do |key, value|
|
44
|
+
next unless Decidim::User.column_names.include? key.to_s
|
45
|
+
|
46
|
+
@records = @records.where("#{key} ilike ?", "%#{value}%")
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
# Special search key ":wildcard"
|
51
|
+
def global_filter
|
52
|
+
return unless @filters.include? :wildcard
|
53
|
+
|
54
|
+
term = "%#{@filters[:wildcard]}%"
|
55
|
+
@records = @records.where("name ilike ? or nickname ilike ?", term, term)
|
56
|
+
end
|
57
|
+
|
58
|
+
attr_reader :organization, :filters
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
@@ -21,11 +21,11 @@ module Decidim
|
|
21
21
|
private
|
22
22
|
|
23
23
|
def custom_allowed_attributes
|
24
|
-
Loofah::HTML5::
|
24
|
+
Loofah::HTML5::SafeList::ALLOWED_ATTRIBUTES + %w(frameborder allowfullscreen)
|
25
25
|
end
|
26
26
|
|
27
27
|
def custom_allowed_tags
|
28
|
-
Loofah::HTML5::
|
28
|
+
Loofah::HTML5::SafeList::ALLOWED_ELEMENTS_WITH_LIBXML2 + %w(iframe)
|
29
29
|
end
|
30
30
|
end
|
31
31
|
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Decidim
|
4
|
+
module Exporters
|
5
|
+
# This class serializes all components in a ParticipatorySpace so can be
|
6
|
+
# exported to CSV, JSON or other formats.
|
7
|
+
class ParticipatorySpaceComponentsSerializer < Decidim::Exporters::Serializer
|
8
|
+
include Decidim::ApplicationHelper
|
9
|
+
include Decidim::ResourceHelper
|
10
|
+
include Decidim::TranslationsHelper
|
11
|
+
|
12
|
+
# Public: Initializes the serializer with a participatory_space.
|
13
|
+
def initialize(participatory_space)
|
14
|
+
@participatory_space = participatory_space
|
15
|
+
end
|
16
|
+
|
17
|
+
# Public: Exports a hash with the serialized data for this participatory_space.
|
18
|
+
def serialize
|
19
|
+
participatory_space.components.collect do |component|
|
20
|
+
serialized = {
|
21
|
+
manifest_name: component.manifest_name,
|
22
|
+
id: component.id,
|
23
|
+
name: component.name,
|
24
|
+
participatory_space_id: component.participatory_space_id,
|
25
|
+
participatory_space_type: component.participatory_space_type,
|
26
|
+
settings: component.settings.as_json,
|
27
|
+
weight: component.weight,
|
28
|
+
permissions: component.permissions,
|
29
|
+
published_at: component.published_at
|
30
|
+
}
|
31
|
+
serialized[:specific_data] = serialize_component_specific_data(component) if component.serializes_specific_data?
|
32
|
+
serialized
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
private
|
37
|
+
|
38
|
+
attr_reader :participatory_space
|
39
|
+
|
40
|
+
def serialize_component_specific_data(component)
|
41
|
+
specific_serializer = component.manifest.specific_data_serializer_class.new(component)
|
42
|
+
specific_serializer.serialize
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
File without changes
|
@@ -0,0 +1,25 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Decidim
|
4
|
+
module Importers
|
5
|
+
# This class is an abstraction that defines a common and flexible interface
|
6
|
+
# in how importers should be called.
|
7
|
+
class Importer
|
8
|
+
# Imports the contents of the `serialized` argument.
|
9
|
+
#
|
10
|
+
# Importers that import JSON will normally accept a JSON valid value for
|
11
|
+
# the `serialized` argument.
|
12
|
+
# This values may be either: object, array, string, number, true, false
|
13
|
+
# or null.
|
14
|
+
#
|
15
|
+
# Returns: What has been imported.
|
16
|
+
#
|
17
|
+
# +serialized+: The serialized version of the resource to import.
|
18
|
+
# +user+: The Decidim::User that is importing.
|
19
|
+
# +opts+: Extra options that specific subclasses may require.
|
20
|
+
def import(_serialized, _user, _opts = {})
|
21
|
+
raise NotImplementedError, "Decidim::Importers::Importer should be subclassed."
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Decidim
|
4
|
+
module Importers
|
5
|
+
# This class parses and imports all components in a ParticipatorySpace.
|
6
|
+
# It currently supports JSON format.
|
7
|
+
class ParticipatorySpaceComponentsImporter < Decidim::Importers::Importer
|
8
|
+
# +participatory_space+: The ParticipatorySpace to which all components
|
9
|
+
# will belong to.
|
10
|
+
def initialize(participatory_space)
|
11
|
+
@participatory_space = participatory_space
|
12
|
+
end
|
13
|
+
|
14
|
+
# Parses an exported list of components and imports them into the
|
15
|
+
# platform.
|
16
|
+
#
|
17
|
+
# +participatory_space+: The ParticipatorySpace to which all components
|
18
|
+
# will belong to.
|
19
|
+
# +json_text+: A json document as a String.
|
20
|
+
# +user+: The Decidim::User that is importing.
|
21
|
+
def from_json(json_text, user)
|
22
|
+
json = JSON.parse(json_text)
|
23
|
+
import(json, user)
|
24
|
+
end
|
25
|
+
|
26
|
+
# For each component configuration in the json,
|
27
|
+
# creates a new Decidim::Component with that configuration.
|
28
|
+
#
|
29
|
+
# Returns: An Array with all components created.
|
30
|
+
#
|
31
|
+
# +json+: An array of json compatible Hashes with the configuration of Decidim::Components.
|
32
|
+
# +user+: The Decidim::User that is importing.
|
33
|
+
def import(json_ary, user)
|
34
|
+
ActiveRecord::Base.transaction do
|
35
|
+
json_ary.collect do |serialized|
|
36
|
+
attributes = serialized.with_indifferent_access
|
37
|
+
# we override the parent participatory sapce
|
38
|
+
attributes["participatory_space_id"] = @participatory_space.id
|
39
|
+
attributes["participatory_space_type"] = @participatory_space.class.name
|
40
|
+
import_component_from_attributes(attributes, user)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
private
|
46
|
+
|
47
|
+
# Returns a persisted Component instance build from the +attributes+ argument.
|
48
|
+
def import_component_from_attributes(attributes, user)
|
49
|
+
component = Decidim.traceability.perform_action!(:create,
|
50
|
+
Decidim::Component,
|
51
|
+
user) do
|
52
|
+
c = Decidim::Component.new(attributes.except(:id, :settings, :specific_data))
|
53
|
+
c[:settings] = attributes[:settings]
|
54
|
+
c.save!
|
55
|
+
c
|
56
|
+
end
|
57
|
+
import_component_specific_data(component, attributes, user) if component.serializes_specific_data?
|
58
|
+
component
|
59
|
+
end
|
60
|
+
|
61
|
+
def import_component_specific_data(component, serialized, user)
|
62
|
+
specific_importer = component.manifest.specific_data_importer_class.new(component)
|
63
|
+
specific_importer.import(serialized[:specific_data], user)
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
@@ -37,7 +37,7 @@ module Decidim
|
|
37
37
|
end
|
38
38
|
|
39
39
|
def data_for(export_manifest)
|
40
|
-
collection = components.where(manifest_name: export_manifest.
|
40
|
+
collection = components.where(manifest_name: export_manifest.manifest.name).find_each.flat_map do |component|
|
41
41
|
export_manifest.collection.call(component)
|
42
42
|
end
|
43
43
|
|
@@ -17,8 +17,10 @@
|
|
17
17
|
|
18
18
|
<%= f.hidden_field :invitation_token %>
|
19
19
|
|
20
|
-
<div class="
|
21
|
-
|
20
|
+
<div class="user-nickname">
|
21
|
+
<div class="field">
|
22
|
+
<%= f.text_field :nickname, help_text: t("devise.invitations.edit.nickname_help", organization: current_organization.name), required: "required", prefix: { value: "@", small: 1, large: 1 } %>
|
23
|
+
</div>
|
22
24
|
</div>
|
23
25
|
|
24
26
|
<% if f.object.class.require_password_on_accepting %>
|
@@ -34,9 +34,9 @@
|
|
34
34
|
</div>
|
35
35
|
</div>
|
36
36
|
|
37
|
-
<div class="user-
|
37
|
+
<div class="user-nickname">
|
38
38
|
<div class="field">
|
39
|
-
<%= f.text_field :nickname, help_text: t(".nickname_help", organization: current_organization.name) %>
|
39
|
+
<%= f.text_field :nickname, help_text: t(".nickname_help", organization: current_organization.name), prefix: { value: "@", small: 1, large: 1 } %>
|
40
40
|
</div>
|
41
41
|
</div>
|
42
42
|
|
@@ -0,0 +1,33 @@
|
|
1
|
+
<% add_decidim_page_title(t("devise.unlocks.new.resend_unlock_instructions")) %>
|
2
|
+
|
3
|
+
<main class="wrapper">
|
4
|
+
<div class="row collapse">
|
5
|
+
<div class="row collapse">
|
6
|
+
<div class="columns large-8 large-centered text-center page-title">
|
7
|
+
<h1><%= t("devise.unlocks.new.resend_unlock_instructions") %></h1>
|
8
|
+
</div>
|
9
|
+
</div>
|
10
|
+
|
11
|
+
<div class="row">
|
12
|
+
<div class="columns medium-7 large-5 medium-centered">
|
13
|
+
<div class="card">
|
14
|
+
<div class="card__content">
|
15
|
+
<%= decidim_form_for(resource, as: resource_name, url: unlock_path(resource_name), html: { method: :post, class: "register-form new_user" }) do |f| %>
|
16
|
+
<%= devise_error_messages! %>
|
17
|
+
|
18
|
+
<div class="field">
|
19
|
+
<%= f.email_field :email, autofocus: true, autocomplete: "email" %>
|
20
|
+
</div>
|
21
|
+
|
22
|
+
<div class="actions">
|
23
|
+
<%= f.submit t("devise.unlocks.new.resend_unlock_instructions"), class: "button expanded" %>
|
24
|
+
</div>
|
25
|
+
<% end %>
|
26
|
+
|
27
|
+
<%= render "decidim/devise/shared/links" %>
|
28
|
+
</div>
|
29
|
+
</div>
|
30
|
+
</div>
|
31
|
+
</div>
|
32
|
+
</div>
|
33
|
+
</main>
|
@@ -6,25 +6,9 @@
|
|
6
6
|
<% end %>
|
7
7
|
</p>
|
8
8
|
<% end %>
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
<div class="filters__section--general<%= " no-results" if results[:count].zero? %>">
|
13
|
-
<% if results[:count].positive? %>
|
14
|
-
<%= link_to search_path_by_resource_type(type), class: "flex--sbc" do %>
|
15
|
-
<strong class="section-title"><%= searchable_resource_human_name(type) %></strong>
|
16
|
-
<span class="muted"> <%= results[:count] %></span>
|
17
|
-
<% end %>
|
18
|
-
<% else %>
|
19
|
-
<div class="flex--sbc">
|
20
|
-
<strong class="section-title"><%= searchable_resource_human_name(type) %></strong>
|
21
|
-
<span class="muted"> <%= results[:count] %></span>
|
22
|
-
</div>
|
23
|
-
<% end %>
|
24
|
-
</div>
|
25
|
-
<% end %>
|
26
|
-
</div>
|
27
|
-
</div>
|
9
|
+
<%= render partial: "resources_filter_block", locals: { sections: @sections, types: Decidim::Searchable.searchable_resources_of_type_participant } %>
|
10
|
+
<%= render partial: "resources_filter_block", locals: { sections: @sections, types: Decidim::Searchable.searchable_resources_of_type_participatory_space } %>
|
11
|
+
<%= render partial: "resources_filter_block", locals: { sections: @sections, types: Decidim::Searchable.searchable_resources_of_type_component } %>
|
28
12
|
<div class="card card--secondary">
|
29
13
|
<%= filter_form_for filter do |form| %>
|
30
14
|
<%= scopes_picker_filter form, :decidim_scope_id %>
|
@@ -0,0 +1,20 @@
|
|
1
|
+
<% resources= @sections.select {|type, results| types.include?(type)} %>
|
2
|
+
<div class="card card--secondary">
|
3
|
+
<div class="filters">
|
4
|
+
<% resources.each do |type, results| %>
|
5
|
+
<div class="filters__section--general<%= " no-results" if results[:count].zero? %>">
|
6
|
+
<% if results[:count].positive? %>
|
7
|
+
<%= link_to search_path_by_resource_type(type), class: "flex--sbc" do %>
|
8
|
+
<strong class="section-title"><%= searchable_resource_human_name(type) %></strong>
|
9
|
+
<span class="muted"> <%= results[:count] %></span>
|
10
|
+
<% end %>
|
11
|
+
<% else %>
|
12
|
+
<div class="flex--sbc">
|
13
|
+
<strong class="section-title"><%= searchable_resource_human_name(type) %></strong>
|
14
|
+
<span class="muted"> <%= results[:count] %></span>
|
15
|
+
</div>
|
16
|
+
<% end %>
|
17
|
+
</div>
|
18
|
+
<% end %>
|
19
|
+
</div>
|
20
|
+
</div>
|