decidim-core 0.0.3 → 0.0.5
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/app/assets/config/decidim_core_manifest.js +1 -0
- data/app/assets/images/decidim/process.svg +10 -0
- data/app/assets/javascripts/decidim.js.es6 +2 -0
- data/app/assets/javascripts/decidim/editor.js.es6 +2 -2
- data/app/assets/javascripts/decidim/filters.js.es6 +1 -1
- data/app/assets/javascripts/decidim/form_filter.component.js.es6 +26 -5
- data/app/assets/javascripts/decidim/form_filter.component.test.js +1 -18
- data/app/assets/javascripts/decidim/foundation.js.es6 +1 -0
- data/app/assets/javascripts/decidim/history.js.es6 +35 -0
- data/app/assets/javascripts/decidim/orders.js.es6 +28 -0
- data/app/assets/stylesheets/decidim/_decidim.scss +1 -0
- data/app/assets/stylesheets/decidim/email.css +21 -3
- data/app/assets/stylesheets/decidim/extras/_leaflet.scss +4 -2
- data/app/assets/stylesheets/decidim/extras/_quill.scss +8 -0
- data/app/assets/stylesheets/decidim/extras/_register_form.scss +9 -0
- data/app/assets/stylesheets/decidim/modules/_buttons.scss +19 -3
- data/app/assets/stylesheets/decidim/utils/_mixins.scss +8 -4
- data/app/commands/decidim/authorize_user.rb +9 -4
- data/app/commands/decidim/create_omniauth_registration.rb +4 -2
- data/app/commands/decidim/create_registration.rb +4 -1
- data/app/commands/decidim/invite_user.rb +3 -1
- data/app/commands/decidim/update_notifications_settings.rb +31 -0
- data/app/constraints/decidim/current_feature.rb +16 -15
- data/app/controllers/concerns/decidim/action_authorization.rb +73 -0
- data/app/controllers/concerns/decidim/feature_settings.rb +2 -5
- data/app/controllers/concerns/decidim/needs_authorization.rb +5 -1
- data/app/controllers/concerns/decidim/user_profile.rb +1 -0
- data/app/controllers/decidim/application_controller.rb +12 -0
- data/app/controllers/decidim/authorizations_controller.rb +10 -12
- data/app/controllers/decidim/cookie_policy_controller.rb +15 -0
- data/app/controllers/decidim/devise/confirmations_controller.rb +4 -0
- data/app/controllers/decidim/devise/invitations_controller.rb +4 -0
- data/app/controllers/decidim/devise/omniauth_registrations_controller.rb +10 -3
- data/app/controllers/decidim/devise/passwords_controller.rb +4 -0
- data/app/controllers/decidim/devise/registrations_controller.rb +21 -0
- data/app/controllers/decidim/devise/sessions_controller.rb +4 -0
- data/app/controllers/decidim/notifications_settings_controller.rb +31 -0
- data/app/controllers/decidim/pages_controller.rb +17 -6
- data/app/controllers/decidim/participatory_processes_controller.rb +5 -5
- data/app/forms/decidim/notifications_settings_form.rb +17 -0
- data/app/forms/decidim/registration_form.rb +2 -1
- data/app/helpers/decidim/action_authorization_helper.rb +82 -0
- data/app/helpers/decidim/application_helper.rb +2 -0
- data/app/helpers/decidim/authorization_form_helper.rb +1 -1
- data/app/helpers/decidim/cookies_helper.rb +11 -0
- data/app/helpers/decidim/decidim_form_helper.rb +18 -0
- data/app/helpers/decidim/language_chooser_helper.rb +18 -0
- data/app/helpers/decidim/layout_helper.rb +0 -5
- data/app/helpers/decidim/localized_locales_helper.rb +1 -1
- data/app/helpers/decidim/meta_tags_helper.rb +104 -0
- data/app/helpers/decidim/orders_helper.rb +28 -0
- data/app/helpers/decidim/resource_helper.rb +46 -6
- data/app/mailers/decidim/application_mailer.rb +2 -2
- data/app/mailers/decidim/decidim_devise_mailer.rb +0 -1
- data/app/mailers/decidim/newsletter_mailer.rb +24 -0
- data/app/models/decidim/abilities/everyone.rb +2 -0
- data/app/models/decidim/feature.rb +30 -0
- data/app/models/decidim/newsletter.rb +24 -0
- data/app/models/decidim/organization.rb +2 -0
- data/app/models/decidim/user.rb +3 -2
- data/app/queries/decidim/highlighted_participatory_processes.rb +10 -0
- data/app/queries/decidim/promoted_participatory_processes.rb +9 -0
- data/app/queries/decidim/public_participatory_processes.rb +10 -0
- data/app/services/decidim/action_authorizer.rb +102 -0
- data/app/services/decidim/resource_search.rb +1 -1
- data/app/uploaders/decidim/official_image_footer_uploader.rb +12 -0
- data/app/uploaders/decidim/official_image_header_uploader.rb +12 -0
- data/app/validators/etiquette_validator.rb +42 -0
- data/app/views/decidim/account/show.html.erb +1 -1
- data/app/views/decidim/authorizations/index.html.erb +4 -4
- data/app/views/decidim/authorizations/new.html.erb +2 -2
- data/app/views/decidim/cookie_policy/accept.js.erb +3 -0
- data/app/views/decidim/devise/confirmations/new.html.erb +3 -1
- data/app/views/decidim/devise/invitations/edit.html.erb +1 -1
- data/app/views/decidim/devise/omniauth_registrations/new.html.erb +1 -1
- data/app/views/decidim/devise/passwords/edit.html.erb +1 -1
- data/app/views/decidim/devise/passwords/new.html.erb +3 -1
- data/app/views/decidim/devise/registrations/edit.html.erb +2 -1
- data/app/views/decidim/devise/registrations/new.html.erb +13 -1
- data/app/views/decidim/devise/sessions/new.html.erb +3 -1
- data/app/views/decidim/newsletter_mailer/newsletter.html.erb +5 -0
- data/app/views/decidim/notifications_settings/show.html.erb +26 -0
- data/app/views/decidim/pages/index.html.erb +34 -0
- data/app/views/decidim/participatory_process_steps/index.html.erb +1 -1
- data/app/views/decidim/participatory_processes/_participatory_process.html.erb +4 -2
- data/app/views/decidim/participatory_processes/_promoted_process.html.erb +1 -1
- data/app/views/decidim/participatory_processes/index.html.erb +3 -3
- data/app/views/decidim/participatory_processes/show.html.erb +30 -9
- data/app/views/decidim/shared/_action_authorization_modal.html.erb +55 -0
- data/app/views/decidim/shared/_login_modal.html.erb +2 -3
- data/app/views/decidim/shared/_orders.html.erb +15 -0
- data/app/views/decidim/shared/_share_modal.html.erb +33 -0
- data/app/views/devise/mailer/organization_admin_invitation_instructions.html.erb +1 -1
- data/app/views/layouts/decidim/_application.html.erb +12 -0
- data/app/views/layouts/decidim/_cookie_warning.html.erb +8 -0
- data/app/views/layouts/decidim/_footer.html.erb +6 -0
- data/app/views/layouts/decidim/_header.html.erb +7 -9
- data/app/views/layouts/decidim/_language_chooser.html.erb +3 -3
- data/app/views/layouts/decidim/_logo.html.erb +1 -1
- data/app/views/layouts/decidim/_mailer_logo.html.erb +31 -0
- data/app/views/layouts/decidim/_main_nav.html.erb +11 -0
- data/app/views/layouts/decidim/_process_header.html.erb +9 -7
- data/app/views/layouts/decidim/_social_media_links.html.erb +39 -0
- data/app/views/layouts/decidim/_social_meta.html.erb +8 -8
- data/app/views/layouts/decidim/mailer.html.erb +110 -62
- data/app/views/layouts/decidim/participatory_process.html.erb +14 -3
- data/app/views/layouts/decidim/user_profile.html.erb +1 -0
- data/app/views/pages/decidim_page.html.erb +5 -0
- data/app/views/pages/home.html.erb +4 -0
- data/app/views/pages/home/_extended.html.erb +2 -2
- data/app/views/pages/home/_footer_sub_hero.html.erb +12 -0
- data/app/views/pages/home/_hero.html.erb +2 -2
- data/app/views/pages/home/_highlighted_processes.html.erb +2 -2
- data/config/i18n-tasks.yml +6 -2
- data/config/initializers/invisible_captcha.rb +10 -0
- data/config/initializers/mail_previews.rb +4 -0
- data/config/locales/ca.yml +129 -23
- data/config/locales/en.yml +98 -13
- data/config/locales/es.yml +128 -22
- data/config/locales/eu.yml +5 -0
- data/config/routes.rb +13 -5
- data/db/migrate/20170131134349_add_action_permissions_to_decidim_features.rb +5 -0
- data/db/migrate/20170202084913_add_comments_and_replies_notifications_to_users.rb +6 -0
- data/db/migrate/20170203150545_add_newsletter_notifications_to_users.rb +5 -0
- data/db/migrate/20170206083118_rename_extra_info_on_processes.rb +12 -0
- data/db/migrate/20170206142116_add_published_at_to_decidim_features.rb +6 -0
- data/db/migrate/20170207091021_add_social_media_handlers_to_organization.rb +8 -0
- data/db/migrate/20170207093048_add_organization_logo_and_url.rb +7 -0
- data/db/migrate/20170213081133_create_decidim_newsletters.rb +15 -0
- data/db/seeds.rb +29 -4
- data/lib/decidim/attributes.rb +6 -0
- data/lib/decidim/attributes/time_with_zone.rb +13 -0
- data/lib/decidim/authorable.rb +2 -2
- data/lib/decidim/core.rb +11 -0
- data/lib/decidim/core/api.rb +11 -0
- data/lib/decidim/core/api/author_interface.rb +11 -0
- data/lib/decidim/core/api/localized_string_type.rb +11 -0
- data/{app/types/decidim → lib/decidim/core/api}/process_step_type.rb +2 -2
- data/{app/types/decidim → lib/decidim/core/api}/process_type.rb +1 -1
- data/{app/types/decidim → lib/decidim/core/api}/session_type.rb +0 -0
- data/lib/decidim/core/api/translated_field_type.rb +42 -0
- data/{app/types/decidim → lib/decidim/core/api}/user_group_type.rb +1 -1
- data/{app/types/decidim → lib/decidim/core/api}/user_type.rb +1 -1
- data/lib/decidim/core/engine.rb +7 -1
- data/lib/decidim/core/test.rb +1 -0
- data/lib/decidim/core/test/factories.rb +41 -8
- data/lib/decidim/core/test/shared_examples/localised_email.rb +24 -0
- data/lib/decidim/core/version.rb +1 -1
- data/lib/decidim/feature_manifest.rb +11 -0
- data/{app/validators → lib/decidim}/feature_validator.rb +1 -3
- data/lib/decidim/features/base_controller.rb +6 -1
- data/lib/decidim/form_builder.rb +166 -1
- data/lib/decidim/has_feature.rb +2 -1
- data/lib/decidim/resourceable.rb +26 -0
- data/vendor/assets/javascripts/morphdom.js +679 -0
- metadata +174 -49
- data/lib/decidim/has_attachment.rb +0 -32
@@ -0,0 +1,13 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
module Decidim
|
3
|
+
module Attributes
|
4
|
+
# Custom Virtus value to parse a String representing a Time using
|
5
|
+
# the app TimeZone.
|
6
|
+
class TimeWithZone < Virtus::Attribute
|
7
|
+
def coerce(value)
|
8
|
+
return value unless value.is_a?(String)
|
9
|
+
Time.zone.parse(value)
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
data/lib/decidim/authorable.rb
CHANGED
@@ -5,8 +5,8 @@ module Decidim
|
|
5
5
|
extend ActiveSupport::Concern
|
6
6
|
|
7
7
|
included do
|
8
|
-
belongs_to :author, foreign_key: "decidim_author_id", class_name: Decidim::User
|
9
|
-
belongs_to :user_group, foreign_key: "decidim_user_group_id", class_name: Decidim::UserGroup
|
8
|
+
belongs_to :author, foreign_key: "decidim_author_id", class_name: "Decidim::User"
|
9
|
+
belongs_to :user_group, foreign_key: "decidim_user_group_id", class_name: "Decidim::UserGroup"
|
10
10
|
|
11
11
|
validate :verified_user_group, :user_group_membership
|
12
12
|
validate :author_belongs_to_organization
|
data/lib/decidim/core.rb
CHANGED
@@ -1,6 +1,8 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
1
2
|
# frozen_string_literal: true
|
2
3
|
require "decidim/core/engine"
|
3
4
|
require "decidim/core/version"
|
5
|
+
require "decidim/core/api"
|
4
6
|
|
5
7
|
# Decidim configuration.
|
6
8
|
module Decidim
|
@@ -15,9 +17,11 @@ module Decidim
|
|
15
17
|
autoload :Authorable, "decidim/authorable"
|
16
18
|
autoload :Features, "decidim/features"
|
17
19
|
autoload :HasAttachments, "decidim/has_attachments"
|
20
|
+
autoload :FeatureValidator, "decidim/feature_validator"
|
18
21
|
autoload :HasFeature, "decidim/has_feature"
|
19
22
|
autoload :HasScope, "decidim/has_scope"
|
20
23
|
autoload :HasCategory, "decidim/has_category"
|
24
|
+
autoload :Attributes, "decidim/attributes"
|
21
25
|
|
22
26
|
include ActiveSupport::Configurable
|
23
27
|
|
@@ -58,6 +62,13 @@ module Decidim
|
|
58
62
|
[]
|
59
63
|
end
|
60
64
|
|
65
|
+
# Exposes a configuration option: an Array of `cancancan`'s Ability classes
|
66
|
+
# that will be automatically included to the `Decidim::Admin::Abilities::Base`
|
67
|
+
# class.
|
68
|
+
config_accessor :admin_abilities do
|
69
|
+
[]
|
70
|
+
end
|
71
|
+
|
61
72
|
# Exposes a configuration option: an Array of classes that can be used as
|
62
73
|
# AuthorizaionHandlers so users can be verified against different systems.
|
63
74
|
config_accessor :authorization_handlers do
|
@@ -0,0 +1,11 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
module Decidim
|
3
|
+
autoload :AuthorInterface, "decidim/core/api/author_interface"
|
4
|
+
autoload :TranslatedFieldType, "decidim/core/api/translated_field_type"
|
5
|
+
autoload :LocalizedStringType, "decidim/core/api/localized_string_type"
|
6
|
+
autoload :ProcessStepType, "decidim/core/api/process_step_type"
|
7
|
+
autoload :ProcessType, "decidim/core/api/process_type"
|
8
|
+
autoload :SessionType, "decidim/core/api/session_type"
|
9
|
+
autoload :UserGroupType, "decidim/core/api/user_group_type"
|
10
|
+
autoload :UserType, "decidim/core/api/user_type"
|
11
|
+
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
module Decidim
|
3
|
+
# This interface represents an author who owns a resource.
|
4
|
+
AuthorInterface = GraphQL::InterfaceType.define do
|
5
|
+
name "Author"
|
6
|
+
description "An author"
|
7
|
+
|
8
|
+
field :name, !types.String, "The author's name"
|
9
|
+
field :avatarUrl, !types.String, "The author's avatar url"
|
10
|
+
end
|
11
|
+
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
module Decidim
|
3
|
+
# This type represents a localized string in a single language.
|
4
|
+
LocalizedStringType = GraphQL::ObjectType.define do
|
5
|
+
name "LocalizedString"
|
6
|
+
description "Represents a particular translation of a LocalizedStringType"
|
7
|
+
|
8
|
+
field :locale, !types.String, "The standard locale of this translation."
|
9
|
+
field :text, types.String, "The content of this translation."
|
10
|
+
end
|
11
|
+
end
|
@@ -13,10 +13,10 @@ module Decidim
|
|
13
13
|
property :participatory_process
|
14
14
|
end
|
15
15
|
|
16
|
-
field :title,
|
16
|
+
field :title, TranslatedFieldType, "The title of this step"
|
17
17
|
|
18
18
|
field :shortDescription do
|
19
|
-
type
|
19
|
+
type TranslatedFieldType
|
20
20
|
description "A short description of the step."
|
21
21
|
property :short_description
|
22
22
|
end
|
@@ -7,7 +7,7 @@ module Decidim
|
|
7
7
|
|
8
8
|
field :id, !types.ID, "The Process' unique ID"
|
9
9
|
|
10
|
-
field :title,
|
10
|
+
field :title, TranslatedFieldType, "The title of this process."
|
11
11
|
|
12
12
|
connection :steps, ProcessStepType.connection_type do
|
13
13
|
description "All the steps of this process."
|
File without changes
|
@@ -0,0 +1,42 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
module Decidim
|
3
|
+
# This type represents a translated field in multiple languages.
|
4
|
+
TranslatedFieldType = GraphQL::ObjectType.define do
|
5
|
+
name "TranslatedField"
|
6
|
+
description "A translated field"
|
7
|
+
|
8
|
+
field :locales do
|
9
|
+
type types[types.String]
|
10
|
+
description "Lists all the locales in which this translation is available"
|
11
|
+
resolve ->(obj, _args, _ctx) { obj.keys }
|
12
|
+
end
|
13
|
+
|
14
|
+
field :translations do
|
15
|
+
type !types[LocalizedStringType]
|
16
|
+
description "All the localized strings for this translation."
|
17
|
+
|
18
|
+
argument :locales do
|
19
|
+
type types[types.String]
|
20
|
+
description "A list of locales to scope the translations to."
|
21
|
+
end
|
22
|
+
|
23
|
+
resolve ->(obj, args, _ctx) {
|
24
|
+
translations = obj.stringify_keys
|
25
|
+
translations = translations.slice(*args["locales"]) if args["locales"]
|
26
|
+
|
27
|
+
translations.map { |locale, text| OpenStruct.new(locale: locale, text: text) }
|
28
|
+
}
|
29
|
+
end
|
30
|
+
|
31
|
+
field :translation do
|
32
|
+
type types.String
|
33
|
+
description "Returns a single translation given a locale."
|
34
|
+
argument :locale, !types.String, "A locale to search for"
|
35
|
+
|
36
|
+
resolve ->(obj, args, _ctx) {
|
37
|
+
translations = obj.stringify_keys
|
38
|
+
translations[args["locale"]]
|
39
|
+
}
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
data/lib/decidim/core/engine.rb
CHANGED
@@ -14,7 +14,6 @@ require "foundation-rails"
|
|
14
14
|
require "foundation_rails_helper"
|
15
15
|
require "active_link_to"
|
16
16
|
require "rectify"
|
17
|
-
require "roadie-rails"
|
18
17
|
require "carrierwave"
|
19
18
|
require "high_voltage"
|
20
19
|
require "rails-i18n"
|
@@ -27,6 +26,9 @@ require "omniauth"
|
|
27
26
|
require "omniauth-facebook"
|
28
27
|
require "omniauth-twitter"
|
29
28
|
require "omniauth-google-oauth2"
|
29
|
+
require "invisible_captcha"
|
30
|
+
require "premailer/rails"
|
31
|
+
require "nokogiri"
|
30
32
|
|
31
33
|
require "decidim/api"
|
32
34
|
|
@@ -85,6 +87,10 @@ module Decidim
|
|
85
87
|
initializer "decidim.query_extensions" do
|
86
88
|
QueryExtensions.extend!(Decidim::Api::QueryType)
|
87
89
|
end
|
90
|
+
|
91
|
+
initializer "decidim.i18n_exceptions" do
|
92
|
+
ActionView::Base.raise_on_missing_translations = true unless Rails.env.production?
|
93
|
+
end
|
88
94
|
end
|
89
95
|
end
|
90
96
|
end
|
data/lib/decidim/core/test.rb
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
require "decidim/core/test/shared_examples/authorable"
|
3
|
+
require "decidim/core/test/shared_examples/localised_email"
|
3
4
|
require "decidim/core/test/shared_examples/has_attachments"
|
4
5
|
require "decidim/core/test/shared_examples/has_feature"
|
5
6
|
require "decidim/core/test/shared_examples/has_scope"
|
@@ -30,15 +30,22 @@ FactoryGirl.define do
|
|
30
30
|
end
|
31
31
|
|
32
32
|
factory :organization, class: Decidim::Organization do
|
33
|
-
name { Faker::Company.name }
|
33
|
+
name { Faker::Company.unique.name }
|
34
34
|
twitter_handler { Faker::Hipster.word }
|
35
|
+
facebook_handler { Faker::Hipster.word }
|
36
|
+
instagram_handler { Faker::Hipster.word }
|
37
|
+
youtube_handler { Faker::Hipster.word }
|
38
|
+
github_handler { Faker::Hipster.word }
|
35
39
|
sequence(:host) { |n| "#{n}.lvh.me" }
|
36
40
|
description { Decidim::Faker::Localized.wrapped("<p>", "</p>") { Decidim::Faker::Localized.sentence(2) } }
|
37
41
|
welcome_text { Decidim::Faker::Localized.wrapped("<p>", "</p>") { Decidim::Faker::Localized.sentence(2) } }
|
38
42
|
homepage_image { test_file("city.jpeg", "image/jpeg") }
|
39
43
|
favicon { test_file("icon.png", "image/png") }
|
40
|
-
default_locale I18n.default_locale
|
41
|
-
available_locales Decidim.available_locales
|
44
|
+
default_locale { I18n.default_locale }
|
45
|
+
available_locales { Decidim.available_locales }
|
46
|
+
official_img_header { test_file("avatar.jpg", "image/jpeg") }
|
47
|
+
official_img_footer { test_file("avatar.jpg", "image/jpeg") }
|
48
|
+
official_url { Faker::Internet.url }
|
42
49
|
end
|
43
50
|
|
44
51
|
factory :participatory_process, class: Decidim::ParticipatoryProcess do
|
@@ -51,11 +58,13 @@ FactoryGirl.define do
|
|
51
58
|
banner_image { test_file("city2.jpeg", "image/jpeg") }
|
52
59
|
published_at { Time.current }
|
53
60
|
organization
|
54
|
-
scope
|
55
|
-
|
56
|
-
|
61
|
+
scope { Decidim::Faker::Localized.word }
|
62
|
+
developer_group { Decidim::Faker::Localized.sentence(1) }
|
63
|
+
local_area { Decidim::Faker::Localized.sentence(2) }
|
64
|
+
target { Decidim::Faker::Localized.sentence(3) }
|
65
|
+
participatory_scope { Decidim::Faker::Localized.sentence(1) }
|
66
|
+
participatory_structure { Decidim::Faker::Localized.sentence(2) }
|
57
67
|
end_date 2.month.from_now.at_midnight
|
58
|
-
|
59
68
|
trait :promoted do
|
60
69
|
promoted true
|
61
70
|
end
|
@@ -100,6 +109,8 @@ FactoryGirl.define do
|
|
100
109
|
locale { organization.default_locale }
|
101
110
|
tos_agreement "1"
|
102
111
|
avatar { test_file("avatar.jpg", "image/jpeg") }
|
112
|
+
comments_notifications true
|
113
|
+
replies_notifications true
|
103
114
|
|
104
115
|
trait :confirmed do
|
105
116
|
confirmed_at { Time.current }
|
@@ -116,6 +127,10 @@ FactoryGirl.define do
|
|
116
127
|
trait :official do
|
117
128
|
roles ["official"]
|
118
129
|
end
|
130
|
+
|
131
|
+
trait :collaborator do
|
132
|
+
roles ["collaborator"]
|
133
|
+
end
|
119
134
|
end
|
120
135
|
|
121
136
|
factory :user_group, class: Decidim::UserGroup do
|
@@ -184,6 +199,15 @@ FactoryGirl.define do
|
|
184
199
|
name { Decidim::Faker::Localized.sentence(3) }
|
185
200
|
participatory_process
|
186
201
|
manifest_name "dummy"
|
202
|
+
published_at { Time.now }
|
203
|
+
|
204
|
+
trait :unpublished do
|
205
|
+
published_at { nil }
|
206
|
+
end
|
207
|
+
|
208
|
+
trait :published do
|
209
|
+
published_at { Time.current }
|
210
|
+
end
|
187
211
|
end
|
188
212
|
|
189
213
|
factory :scope, class: Decidim::Scope do
|
@@ -193,7 +217,8 @@ FactoryGirl.define do
|
|
193
217
|
|
194
218
|
factory :dummy_resource, class: Decidim::DummyResource do
|
195
219
|
title { generate(:name) }
|
196
|
-
feature
|
220
|
+
feature { create(:feature, manifest_name: "dummy") }
|
221
|
+
author { create(:user, :confirmed, organization: feature.organization) }
|
197
222
|
end
|
198
223
|
|
199
224
|
factory :resource_link, class: Decidim::ResourceLink do
|
@@ -201,6 +226,14 @@ FactoryGirl.define do
|
|
201
226
|
to { build(:dummy_resource) }
|
202
227
|
from { build(:dummy_resource, feature: to.feature) }
|
203
228
|
end
|
229
|
+
|
230
|
+
factory :newsletter, class: Decidim::Newsletter do
|
231
|
+
author { build(:user, :confirmed, organization: organization) }
|
232
|
+
organization
|
233
|
+
|
234
|
+
subject { Decidim::Faker::Localized.sentence(3) }
|
235
|
+
body { Decidim::Faker::Localized.wrapped("<p>", "</p>") { Decidim::Faker::Localized.sentence(4) } }
|
236
|
+
end
|
204
237
|
end
|
205
238
|
|
206
239
|
def test_file(filename, content_type)
|
@@ -0,0 +1,24 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require "spec_helper"
|
3
|
+
|
4
|
+
shared_examples "localised email" do
|
5
|
+
let(:user) { build(:user, locale: locale) }
|
6
|
+
|
7
|
+
context "when the user has a custom locale" do
|
8
|
+
let(:locale) { "ca" }
|
9
|
+
|
10
|
+
it "uses the user's locale" do
|
11
|
+
expect(mail.subject).to eq(subject)
|
12
|
+
expect(mail.body.encoded).to match(body)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
context "otherwise" do
|
17
|
+
let(:locale) { nil }
|
18
|
+
|
19
|
+
it "uses the default locale" do
|
20
|
+
expect(mail.subject).to eq(default_subject)
|
21
|
+
expect(mail.body.encoded).to match(default_body)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
data/lib/decidim/core/version.rb
CHANGED
@@ -26,6 +26,17 @@ module Decidim
|
|
26
26
|
# engine's assets path.
|
27
27
|
attribute :icon, String
|
28
28
|
|
29
|
+
# Actions are used to validate permissions of a feature against particular
|
30
|
+
# authorizations or potentially other authorization rules.
|
31
|
+
#
|
32
|
+
# An example would be `vote` on participatory processes, or `create_meeting`
|
33
|
+
# on meetings.
|
34
|
+
#
|
35
|
+
# A Feature can expose as many actions as it wants and the admin panel will
|
36
|
+
# generate a UI to handle them. There's a set of controller helpers available
|
37
|
+
# as well that allows checking for those permissions.
|
38
|
+
attribute :actions, Array[String]
|
39
|
+
|
29
40
|
validates :name, presence: true
|
30
41
|
|
31
42
|
# Public: Registers a hook to this manifest. Hooks get fired when some
|
@@ -16,8 +16,6 @@ class FeatureValidator < ActiveModel::EachValidator
|
|
16
16
|
return
|
17
17
|
end
|
18
18
|
|
19
|
-
|
20
|
-
|
21
|
-
record.errors[attribute] << :invalid if feature.manifest_name.to_s != manifest_name
|
19
|
+
record.errors[attribute] << :invalid if feature.manifest_name.to_s != options[:manifest].to_s
|
22
20
|
end
|
23
21
|
end
|
@@ -9,9 +9,13 @@ module Decidim
|
|
9
9
|
layout "layouts/decidim/participatory_process"
|
10
10
|
include NeedsParticipatoryProcess
|
11
11
|
include FeatureSettings
|
12
|
+
include ActionAuthorization
|
13
|
+
|
12
14
|
helper Decidim::TranslationsHelper
|
13
15
|
helper Decidim::ParticipatoryProcessHelper
|
14
16
|
helper Decidim::ResourceHelper
|
17
|
+
helper Decidim::ActionAuthorizationHelper
|
18
|
+
helper Decidim::MetaTagsHelper
|
15
19
|
|
16
20
|
helper_method :current_feature,
|
17
21
|
:current_manifest
|
@@ -20,6 +24,7 @@ module Decidim
|
|
20
24
|
|
21
25
|
before_action do
|
22
26
|
authorize! :read, current_participatory_process
|
27
|
+
authorize! :read, current_feature
|
23
28
|
end
|
24
29
|
|
25
30
|
def current_feature
|
@@ -27,7 +32,7 @@ module Decidim
|
|
27
32
|
end
|
28
33
|
|
29
34
|
def current_manifest
|
30
|
-
current_feature.manifest
|
35
|
+
@current_manifest ||= current_feature.manifest
|
31
36
|
end
|
32
37
|
|
33
38
|
def current_participatory_process
|
data/lib/decidim/form_builder.rb
CHANGED
@@ -7,6 +7,23 @@ module Decidim
|
|
7
7
|
class FormBuilder < FoundationRailsHelper::FormBuilder
|
8
8
|
include ActionView::Context
|
9
9
|
|
10
|
+
# Public: generates a check boxes input from a collection and adds help
|
11
|
+
# text and errors.
|
12
|
+
#
|
13
|
+
# attribute - the name of the field
|
14
|
+
# collection - the collection from which we will render the check boxes
|
15
|
+
# value_attribute - a Symbol or a Proc defining how to find the value
|
16
|
+
# attribute
|
17
|
+
# text_attribute - a Symbol or a Proc defining how to find the text
|
18
|
+
# attribute
|
19
|
+
# options - a Hash with options
|
20
|
+
# html_options - a Hash with options
|
21
|
+
#
|
22
|
+
# Renders a collection of check boxes.
|
23
|
+
def collection_check_boxes(attribute, collection, value_attribute, text_attribute, options = {}, html_options = {})
|
24
|
+
super + error_and_help_text(attribute, options)
|
25
|
+
end
|
26
|
+
|
10
27
|
# Public: Generates an form field for each locale.
|
11
28
|
#
|
12
29
|
# type - The form field's type, like `text_area` or `text_input`
|
@@ -30,7 +47,7 @@ module Decidim
|
|
30
47
|
tabs_panels = content_tag(:ul, class: "tabs", id: "#{name}-tabs", data: { tabs: true }) do
|
31
48
|
locales.each_with_index.inject("".html_safe) do |string, (locale, index)|
|
32
49
|
string + content_tag(:li, class: tab_element_class_for("title", index)) do
|
33
|
-
title = I18n.t(
|
50
|
+
title = I18n.with_locale(locale) { I18n.t("name", scope: "locale") }
|
34
51
|
element_class = ""
|
35
52
|
element_class += "alert" if error?(name_with_locale(name, locale))
|
36
53
|
content_tag(:a, title, href: "##{name}-panel-#{index}", class: element_class)
|
@@ -103,8 +120,156 @@ module Decidim
|
|
103
120
|
select(name, @template.options_for_select(categories, selected: selected, disabled: disabled), options)
|
104
121
|
end
|
105
122
|
|
123
|
+
# Public: Override so checkboxes are rendered before the label.
|
124
|
+
def check_box(attribute, options = {}, checked_value = "1", unchecked_value = "0")
|
125
|
+
custom_label(attribute, options[:label], options[:label_options], true) do
|
126
|
+
options.delete(:label)
|
127
|
+
options.delete(:label_options)
|
128
|
+
@template.check_box(@object_name, attribute, objectify_options(options), checked_value, unchecked_value)
|
129
|
+
end + error_and_help_text(attribute, options)
|
130
|
+
end
|
131
|
+
|
106
132
|
private
|
107
133
|
|
134
|
+
# Private: Override from FoundationRailsHelper in order to render
|
135
|
+
# inputs inside the label and to automatically inject validations
|
136
|
+
# from the object.
|
137
|
+
#
|
138
|
+
# attribute - The String name of the attribute to buidl the field.
|
139
|
+
# options - A Hash with options to build the field.
|
140
|
+
# html_options - An optional Hash with options to pass to the html element.
|
141
|
+
#
|
142
|
+
# Returns a String
|
143
|
+
def field(attribute, options, html_options = nil, &block)
|
144
|
+
label = options.delete(:label)
|
145
|
+
label_options = options.delete(:label_options)
|
146
|
+
custom_label(attribute, label, label_options) do
|
147
|
+
field_with_validations(attribute, options, html_options, &block)
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
# Private: Builds a form field and detects validations from
|
152
|
+
# the form object.
|
153
|
+
#
|
154
|
+
# attribute - The String name of the attribute to build the field.
|
155
|
+
# options - A Hash with options to build the field.
|
156
|
+
# html_options - An optional Hash with options to pass to the html element.
|
157
|
+
#
|
158
|
+
# Returns a String.
|
159
|
+
def field_with_validations(attribute, options, html_options)
|
160
|
+
class_options = html_options || options
|
161
|
+
|
162
|
+
if error?(attribute)
|
163
|
+
class_options[:class] = class_options[:class].to_s
|
164
|
+
class_options[:class] += " is-invalid-input"
|
165
|
+
end
|
166
|
+
|
167
|
+
help_text = options.delete(:help_text)
|
168
|
+
prefix = options.delete(:prefix)
|
169
|
+
postfix = options.delete(:postfix)
|
170
|
+
|
171
|
+
class_options = extract_validations(attribute, options).merge(class_options)
|
172
|
+
|
173
|
+
content = yield(class_options)
|
174
|
+
content += abide_error_element(attribute) if class_options[:pattern] || class_options[:required]
|
175
|
+
content = content.html_safe
|
176
|
+
|
177
|
+
html = wrap_prefix_and_postfix(content, prefix, postfix)
|
178
|
+
html + error_and_help_text(attribute, options.merge(help_text: help_text))
|
179
|
+
end
|
180
|
+
|
181
|
+
# Private: Builds a Hash of options to be injected at the HTML output as
|
182
|
+
# HTML5 validations.
|
183
|
+
#
|
184
|
+
# attribute - The String name of the attribute to extract the validations.
|
185
|
+
# options - A Hash of options to extract validations.
|
186
|
+
#
|
187
|
+
# Returns a Hash.
|
188
|
+
def extract_validations(attribute, options)
|
189
|
+
min_length = options.delete(:minlength) || length_for_attribute(attribute, :minimum) || 0
|
190
|
+
max_length = options.delete(:maxlength) || length_for_attribute(attribute, :maximum)
|
191
|
+
|
192
|
+
validation_options = {}
|
193
|
+
validation_options[:pattern] = "^(.){#{min_length},#{max_length}}$" if min_length.to_i.positive? || max_length.to_i.positive?
|
194
|
+
validation_options[:required] = options[:required] || attribute_required?(attribute)
|
195
|
+
validation_options
|
196
|
+
end
|
197
|
+
|
198
|
+
# Private: Tries to find if an attribute is required in the form object.
|
199
|
+
#
|
200
|
+
# Returns Boolean.
|
201
|
+
def attribute_required?(attribute)
|
202
|
+
validator = find_validator(attribute, ActiveModel::Validations::PresenceValidator)
|
203
|
+
|
204
|
+
validator && validator.options.blank?
|
205
|
+
end
|
206
|
+
|
207
|
+
# Private: Tries to find a length validator in the form object.
|
208
|
+
#
|
209
|
+
# attribute - The attribute to look for the validations.
|
210
|
+
# type - A Symbol for the type of length to fetch. Currently only :minimum & :maximum are supported.
|
211
|
+
#
|
212
|
+
# Returns an Integer or Nil.
|
213
|
+
def length_for_attribute(attribute, type)
|
214
|
+
length_validator = find_validator(attribute, ActiveModel::Validations::LengthValidator)
|
215
|
+
return unless length_validator
|
216
|
+
|
217
|
+
length_validator.options[type]
|
218
|
+
end
|
219
|
+
|
220
|
+
# Private: Finds a validator.
|
221
|
+
#
|
222
|
+
# attribute - The attribute to validate.
|
223
|
+
# klass - The Class of the validator to find.
|
224
|
+
#
|
225
|
+
# Returns a klass object.
|
226
|
+
def find_validator(attribute, klass)
|
227
|
+
return unless object.respond_to?(:_validators)
|
228
|
+
object._validators[attribute].find { |validator| validator.class == klass }
|
229
|
+
end
|
230
|
+
|
231
|
+
# Private: Override method from FoundationRailsHelper to render the text of the
|
232
|
+
# label before the input, instead of after.
|
233
|
+
#
|
234
|
+
# attribute - The String name of the attribute we're build the label.
|
235
|
+
# text - The String text to use as label.
|
236
|
+
# options - An optional Hash to build the label.
|
237
|
+
#
|
238
|
+
# Returns a String.
|
239
|
+
def custom_label(attribute, text, options, field_before_label = false)
|
240
|
+
return block_given? ? yield.html_safe : "".html_safe if text == false
|
241
|
+
|
242
|
+
text = default_label_text(object, attribute) if text.nil? || text == true
|
243
|
+
|
244
|
+
text = if field_before_label && block_given?
|
245
|
+
safe_join([yield, text.html_safe])
|
246
|
+
elsif block_given?
|
247
|
+
safe_join([text.html_safe, yield])
|
248
|
+
end
|
249
|
+
|
250
|
+
label(attribute, text, options || {})
|
251
|
+
end
|
252
|
+
|
253
|
+
# Private: Builds a span to be shown when there's a validation error in a field.
|
254
|
+
# It looks for the text that will be the content in a similar way `human_attribute_name`
|
255
|
+
# does it.
|
256
|
+
#
|
257
|
+
# attribute - The name of the attribute of the field.
|
258
|
+
#
|
259
|
+
# Returns a String.
|
260
|
+
def abide_error_element(attribute)
|
261
|
+
defaults = []
|
262
|
+
defaults << :"decidim.forms.errors.#{object.class.model_name.i18n_key}.#{attribute}"
|
263
|
+
defaults << :"decidim.forms.errors.#{attribute}"
|
264
|
+
defaults << :"forms.errors.#{attribute}"
|
265
|
+
defaults << :"decidim.forms.errors.error"
|
266
|
+
|
267
|
+
options = { count: 1, default: defaults }
|
268
|
+
|
269
|
+
text = I18n.t(defaults.shift, options)
|
270
|
+
content_tag(:span, text, class: "form-error")
|
271
|
+
end
|
272
|
+
|
108
273
|
def categories_for_select(scope)
|
109
274
|
sorted_main_categories = scope.first_class.includes(:subcategories).sort_by do |category|
|
110
275
|
category.name[I18n.locale.to_s]
|