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.
Files changed (158) hide show
  1. checksums.yaml +4 -4
  2. data/app/assets/config/decidim_core_manifest.js +1 -0
  3. data/app/assets/images/decidim/process.svg +10 -0
  4. data/app/assets/javascripts/decidim.js.es6 +2 -0
  5. data/app/assets/javascripts/decidim/editor.js.es6 +2 -2
  6. data/app/assets/javascripts/decidim/filters.js.es6 +1 -1
  7. data/app/assets/javascripts/decidim/form_filter.component.js.es6 +26 -5
  8. data/app/assets/javascripts/decidim/form_filter.component.test.js +1 -18
  9. data/app/assets/javascripts/decidim/foundation.js.es6 +1 -0
  10. data/app/assets/javascripts/decidim/history.js.es6 +35 -0
  11. data/app/assets/javascripts/decidim/orders.js.es6 +28 -0
  12. data/app/assets/stylesheets/decidim/_decidim.scss +1 -0
  13. data/app/assets/stylesheets/decidim/email.css +21 -3
  14. data/app/assets/stylesheets/decidim/extras/_leaflet.scss +4 -2
  15. data/app/assets/stylesheets/decidim/extras/_quill.scss +8 -0
  16. data/app/assets/stylesheets/decidim/extras/_register_form.scss +9 -0
  17. data/app/assets/stylesheets/decidim/modules/_buttons.scss +19 -3
  18. data/app/assets/stylesheets/decidim/utils/_mixins.scss +8 -4
  19. data/app/commands/decidim/authorize_user.rb +9 -4
  20. data/app/commands/decidim/create_omniauth_registration.rb +4 -2
  21. data/app/commands/decidim/create_registration.rb +4 -1
  22. data/app/commands/decidim/invite_user.rb +3 -1
  23. data/app/commands/decidim/update_notifications_settings.rb +31 -0
  24. data/app/constraints/decidim/current_feature.rb +16 -15
  25. data/app/controllers/concerns/decidim/action_authorization.rb +73 -0
  26. data/app/controllers/concerns/decidim/feature_settings.rb +2 -5
  27. data/app/controllers/concerns/decidim/needs_authorization.rb +5 -1
  28. data/app/controllers/concerns/decidim/user_profile.rb +1 -0
  29. data/app/controllers/decidim/application_controller.rb +12 -0
  30. data/app/controllers/decidim/authorizations_controller.rb +10 -12
  31. data/app/controllers/decidim/cookie_policy_controller.rb +15 -0
  32. data/app/controllers/decidim/devise/confirmations_controller.rb +4 -0
  33. data/app/controllers/decidim/devise/invitations_controller.rb +4 -0
  34. data/app/controllers/decidim/devise/omniauth_registrations_controller.rb +10 -3
  35. data/app/controllers/decidim/devise/passwords_controller.rb +4 -0
  36. data/app/controllers/decidim/devise/registrations_controller.rb +21 -0
  37. data/app/controllers/decidim/devise/sessions_controller.rb +4 -0
  38. data/app/controllers/decidim/notifications_settings_controller.rb +31 -0
  39. data/app/controllers/decidim/pages_controller.rb +17 -6
  40. data/app/controllers/decidim/participatory_processes_controller.rb +5 -5
  41. data/app/forms/decidim/notifications_settings_form.rb +17 -0
  42. data/app/forms/decidim/registration_form.rb +2 -1
  43. data/app/helpers/decidim/action_authorization_helper.rb +82 -0
  44. data/app/helpers/decidim/application_helper.rb +2 -0
  45. data/app/helpers/decidim/authorization_form_helper.rb +1 -1
  46. data/app/helpers/decidim/cookies_helper.rb +11 -0
  47. data/app/helpers/decidim/decidim_form_helper.rb +18 -0
  48. data/app/helpers/decidim/language_chooser_helper.rb +18 -0
  49. data/app/helpers/decidim/layout_helper.rb +0 -5
  50. data/app/helpers/decidim/localized_locales_helper.rb +1 -1
  51. data/app/helpers/decidim/meta_tags_helper.rb +104 -0
  52. data/app/helpers/decidim/orders_helper.rb +28 -0
  53. data/app/helpers/decidim/resource_helper.rb +46 -6
  54. data/app/mailers/decidim/application_mailer.rb +2 -2
  55. data/app/mailers/decidim/decidim_devise_mailer.rb +0 -1
  56. data/app/mailers/decidim/newsletter_mailer.rb +24 -0
  57. data/app/models/decidim/abilities/everyone.rb +2 -0
  58. data/app/models/decidim/feature.rb +30 -0
  59. data/app/models/decidim/newsletter.rb +24 -0
  60. data/app/models/decidim/organization.rb +2 -0
  61. data/app/models/decidim/user.rb +3 -2
  62. data/app/queries/decidim/highlighted_participatory_processes.rb +10 -0
  63. data/app/queries/decidim/promoted_participatory_processes.rb +9 -0
  64. data/app/queries/decidim/public_participatory_processes.rb +10 -0
  65. data/app/services/decidim/action_authorizer.rb +102 -0
  66. data/app/services/decidim/resource_search.rb +1 -1
  67. data/app/uploaders/decidim/official_image_footer_uploader.rb +12 -0
  68. data/app/uploaders/decidim/official_image_header_uploader.rb +12 -0
  69. data/app/validators/etiquette_validator.rb +42 -0
  70. data/app/views/decidim/account/show.html.erb +1 -1
  71. data/app/views/decidim/authorizations/index.html.erb +4 -4
  72. data/app/views/decidim/authorizations/new.html.erb +2 -2
  73. data/app/views/decidim/cookie_policy/accept.js.erb +3 -0
  74. data/app/views/decidim/devise/confirmations/new.html.erb +3 -1
  75. data/app/views/decidim/devise/invitations/edit.html.erb +1 -1
  76. data/app/views/decidim/devise/omniauth_registrations/new.html.erb +1 -1
  77. data/app/views/decidim/devise/passwords/edit.html.erb +1 -1
  78. data/app/views/decidim/devise/passwords/new.html.erb +3 -1
  79. data/app/views/decidim/devise/registrations/edit.html.erb +2 -1
  80. data/app/views/decidim/devise/registrations/new.html.erb +13 -1
  81. data/app/views/decidim/devise/sessions/new.html.erb +3 -1
  82. data/app/views/decidim/newsletter_mailer/newsletter.html.erb +5 -0
  83. data/app/views/decidim/notifications_settings/show.html.erb +26 -0
  84. data/app/views/decidim/pages/index.html.erb +34 -0
  85. data/app/views/decidim/participatory_process_steps/index.html.erb +1 -1
  86. data/app/views/decidim/participatory_processes/_participatory_process.html.erb +4 -2
  87. data/app/views/decidim/participatory_processes/_promoted_process.html.erb +1 -1
  88. data/app/views/decidim/participatory_processes/index.html.erb +3 -3
  89. data/app/views/decidim/participatory_processes/show.html.erb +30 -9
  90. data/app/views/decidim/shared/_action_authorization_modal.html.erb +55 -0
  91. data/app/views/decidim/shared/_login_modal.html.erb +2 -3
  92. data/app/views/decidim/shared/_orders.html.erb +15 -0
  93. data/app/views/decidim/shared/_share_modal.html.erb +33 -0
  94. data/app/views/devise/mailer/organization_admin_invitation_instructions.html.erb +1 -1
  95. data/app/views/layouts/decidim/_application.html.erb +12 -0
  96. data/app/views/layouts/decidim/_cookie_warning.html.erb +8 -0
  97. data/app/views/layouts/decidim/_footer.html.erb +6 -0
  98. data/app/views/layouts/decidim/_header.html.erb +7 -9
  99. data/app/views/layouts/decidim/_language_chooser.html.erb +3 -3
  100. data/app/views/layouts/decidim/_logo.html.erb +1 -1
  101. data/app/views/layouts/decidim/_mailer_logo.html.erb +31 -0
  102. data/app/views/layouts/decidim/_main_nav.html.erb +11 -0
  103. data/app/views/layouts/decidim/_process_header.html.erb +9 -7
  104. data/app/views/layouts/decidim/_social_media_links.html.erb +39 -0
  105. data/app/views/layouts/decidim/_social_meta.html.erb +8 -8
  106. data/app/views/layouts/decidim/mailer.html.erb +110 -62
  107. data/app/views/layouts/decidim/participatory_process.html.erb +14 -3
  108. data/app/views/layouts/decidim/user_profile.html.erb +1 -0
  109. data/app/views/pages/decidim_page.html.erb +5 -0
  110. data/app/views/pages/home.html.erb +4 -0
  111. data/app/views/pages/home/_extended.html.erb +2 -2
  112. data/app/views/pages/home/_footer_sub_hero.html.erb +12 -0
  113. data/app/views/pages/home/_hero.html.erb +2 -2
  114. data/app/views/pages/home/_highlighted_processes.html.erb +2 -2
  115. data/config/i18n-tasks.yml +6 -2
  116. data/config/initializers/invisible_captcha.rb +10 -0
  117. data/config/initializers/mail_previews.rb +4 -0
  118. data/config/locales/ca.yml +129 -23
  119. data/config/locales/en.yml +98 -13
  120. data/config/locales/es.yml +128 -22
  121. data/config/locales/eu.yml +5 -0
  122. data/config/routes.rb +13 -5
  123. data/db/migrate/20170131134349_add_action_permissions_to_decidim_features.rb +5 -0
  124. data/db/migrate/20170202084913_add_comments_and_replies_notifications_to_users.rb +6 -0
  125. data/db/migrate/20170203150545_add_newsletter_notifications_to_users.rb +5 -0
  126. data/db/migrate/20170206083118_rename_extra_info_on_processes.rb +12 -0
  127. data/db/migrate/20170206142116_add_published_at_to_decidim_features.rb +6 -0
  128. data/db/migrate/20170207091021_add_social_media_handlers_to_organization.rb +8 -0
  129. data/db/migrate/20170207093048_add_organization_logo_and_url.rb +7 -0
  130. data/db/migrate/20170213081133_create_decidim_newsletters.rb +15 -0
  131. data/db/seeds.rb +29 -4
  132. data/lib/decidim/attributes.rb +6 -0
  133. data/lib/decidim/attributes/time_with_zone.rb +13 -0
  134. data/lib/decidim/authorable.rb +2 -2
  135. data/lib/decidim/core.rb +11 -0
  136. data/lib/decidim/core/api.rb +11 -0
  137. data/lib/decidim/core/api/author_interface.rb +11 -0
  138. data/lib/decidim/core/api/localized_string_type.rb +11 -0
  139. data/{app/types/decidim → lib/decidim/core/api}/process_step_type.rb +2 -2
  140. data/{app/types/decidim → lib/decidim/core/api}/process_type.rb +1 -1
  141. data/{app/types/decidim → lib/decidim/core/api}/session_type.rb +0 -0
  142. data/lib/decidim/core/api/translated_field_type.rb +42 -0
  143. data/{app/types/decidim → lib/decidim/core/api}/user_group_type.rb +1 -1
  144. data/{app/types/decidim → lib/decidim/core/api}/user_type.rb +1 -1
  145. data/lib/decidim/core/engine.rb +7 -1
  146. data/lib/decidim/core/test.rb +1 -0
  147. data/lib/decidim/core/test/factories.rb +41 -8
  148. data/lib/decidim/core/test/shared_examples/localised_email.rb +24 -0
  149. data/lib/decidim/core/version.rb +1 -1
  150. data/lib/decidim/feature_manifest.rb +11 -0
  151. data/{app/validators → lib/decidim}/feature_validator.rb +1 -3
  152. data/lib/decidim/features/base_controller.rb +6 -1
  153. data/lib/decidim/form_builder.rb +166 -1
  154. data/lib/decidim/has_feature.rb +2 -1
  155. data/lib/decidim/resourceable.rb +26 -0
  156. data/vendor/assets/javascripts/morphdom.js +679 -0
  157. metadata +174 -49
  158. data/lib/decidim/has_attachment.rb +0 -32
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+ module Decidim
3
+ module Attributes
4
+ autoload :TimeWithZone, "decidim/attributes/time_with_zone"
5
+ end
6
+ end
@@ -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
@@ -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
@@ -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, Api::TranslatedFieldType, "The title of this step"
16
+ field :title, TranslatedFieldType, "The title of this step"
17
17
 
18
18
  field :shortDescription do
19
- type Api::TranslatedFieldType
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, Api::TranslatedFieldType, "The title of this process."
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."
@@ -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
@@ -6,7 +6,7 @@ module Decidim
6
6
  description "A user group"
7
7
 
8
8
  interfaces [
9
- Decidim::Api::AuthorInterface
9
+ Decidim::AuthorInterface
10
10
  ]
11
11
 
12
12
  field :id, !types.ID, "The user group's id"
@@ -6,7 +6,7 @@ module Decidim
6
6
  description "A user"
7
7
 
8
8
  interfaces [
9
- Decidim::Api::AuthorInterface
9
+ Decidim::AuthorInterface
10
10
  ]
11
11
 
12
12
  field :name, !types.String, "The user's name"
@@ -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
@@ -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 { Decidim::Faker::Localized.word }
55
- domain { Decidim::Faker::Localized.word }
56
- developer_group { Faker::Company.name }
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
@@ -2,7 +2,7 @@
2
2
  # This holds Decidim's version and the Rails version on which it depends.
3
3
  module Decidim
4
4
  def self.version
5
- "0.0.3"
5
+ "0.0.5"
6
6
  end
7
7
 
8
8
  def self.rails_version
@@ -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
- manifest_name = options[:manifest].respond_to?(:call) ? options[:manifest].call : options[:manifest].to_s
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
@@ -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(locale, scope: "locales")
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]