decidim-core 0.27.0 → 0.27.2

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.

Files changed (121) hide show
  1. checksums.yaml +4 -4
  2. data/app/cells/decidim/amendable/announcement_cell.rb +1 -1
  3. data/app/cells/decidim/card_m_cell.rb +1 -1
  4. data/app/cells/decidim/newsletter_templates/base_cell.rb +8 -0
  5. data/app/cells/decidim/newsletter_templates/basic_only_text/show.erb +4 -4
  6. data/app/cells/decidim/newsletter_templates/image_text_cta/show.erb +4 -4
  7. data/app/cells/decidim/upload_modal_cell.rb +12 -7
  8. data/app/commands/decidim/unendorse_resource.rb +1 -1
  9. data/app/controllers/decidim/devise/invitations_controller.rb +9 -2
  10. data/app/controllers/decidim/groups_controller.rb +5 -0
  11. data/app/controllers/decidim/last_activities_controller.rb +5 -2
  12. data/app/controllers/decidim/links_controller.rb +4 -2
  13. data/app/controllers/decidim/profiles_controller.rb +1 -1
  14. data/app/forms/decidim/account_form.rb +2 -2
  15. data/app/forms/decidim/amendable/form.rb +2 -1
  16. data/app/forms/decidim/registration_form.rb +2 -2
  17. data/app/forms/decidim/upload_validation_form.rb +51 -7
  18. data/app/helpers/decidim/icon_helper.rb +3 -3
  19. data/app/helpers/decidim/layout_helper.rb +12 -4
  20. data/app/helpers/decidim/newsletters_helper.rb +1 -0
  21. data/app/helpers/decidim/sanitize_helper.rb +1 -1
  22. data/app/mailers/decidim/newsletter_mailer.rb +10 -3
  23. data/app/mailers/decidim/notification_mailer.rb +1 -0
  24. data/app/mailers/decidim/notifications_digest_mailer.rb +1 -0
  25. data/app/models/decidim/newsletter.rb +28 -0
  26. data/app/models/decidim/user.rb +0 -2
  27. data/app/models/decidim/user_base_entity.rb +2 -0
  28. data/app/models/decidim/user_block.rb +2 -2
  29. data/app/models/decidim/user_group.rb +1 -1
  30. data/app/packs/src/decidim/editor/clipboard_override.js +143 -0
  31. data/app/packs/src/decidim/editor/clipboard_utilities.js +119 -0
  32. data/app/packs/src/decidim/editor/linebreak_module.js +0 -8
  33. data/app/packs/src/decidim/editor.js +9 -2
  34. data/app/packs/src/decidim/form_filter.component.test.js +148 -5
  35. data/app/packs/src/decidim/form_filter.js +26 -4
  36. data/app/packs/stylesheets/decidim/_editor.scss +129 -0
  37. data/app/packs/stylesheets/decidim/email.scss +7 -0
  38. data/app/packs/stylesheets/decidim/extras/_quill.scss +0 -6
  39. data/app/presenters/decidim/admin_log/user_group_presenter.rb +1 -1
  40. data/app/presenters/decidim/admin_log/user_moderation_presenter.rb +1 -1
  41. data/app/presenters/decidim/home_stats_presenter.rb +11 -4
  42. data/app/presenters/decidim/push_notification_presenter.rb +1 -1
  43. data/app/presenters/decidim/stats_presenter.rb +7 -8
  44. data/app/presenters/decidim/user_presenter.rb +9 -4
  45. data/app/queries/decidim/public_activities.rb +1 -0
  46. data/app/uploaders/decidim/application_uploader.rb +1 -1
  47. data/app/uploaders/decidim/avatar_uploader.rb +2 -2
  48. data/app/validators/etiquette_validator.rb +7 -3
  49. data/app/validators/file_content_type_validator.rb +103 -0
  50. data/app/validators/passthru_validator.rb +11 -0
  51. data/app/validators/uploader_content_type_validator.rb +22 -0
  52. data/app/views/decidim/messaging/conversations/_conversation.html.erb +1 -1
  53. data/app/views/decidim/newsletter_mailer/newsletter.html.erb +3 -3
  54. data/app/views/decidim/newsletters/show.html.erb +1 -1
  55. data/app/views/decidim/notification_mailer/event_received.html.erb +1 -1
  56. data/app/views/decidim/notifications_digest_mailer/_email_content.html.erb +1 -1
  57. data/app/views/layouts/decidim/_mailer_logo.html.erb +2 -2
  58. data/app/views/layouts/decidim/newsletter_base.html.erb +2 -2
  59. data/config/locales/ar.yml +5 -17
  60. data/config/locales/bg.yml +5 -17
  61. data/config/locales/ca.yml +20 -24
  62. data/config/locales/cs.yml +12 -17
  63. data/config/locales/de.yml +2 -18
  64. data/config/locales/el.yml +4 -18
  65. data/config/locales/en.yml +11 -15
  66. data/config/locales/es-MX.yml +13 -17
  67. data/config/locales/es-PY.yml +13 -17
  68. data/config/locales/es.yml +22 -26
  69. data/config/locales/eu.yml +28 -35
  70. data/config/locales/fi-plain.yml +11 -15
  71. data/config/locales/fi.yml +12 -16
  72. data/config/locales/fr-CA.yml +11 -18
  73. data/config/locales/fr.yml +11 -18
  74. data/config/locales/ga-IE.yml +0 -2
  75. data/config/locales/gl.yml +2 -17
  76. data/config/locales/gn-PY.yml +1 -0
  77. data/config/locales/hu.yml +4 -18
  78. data/config/locales/id-ID.yml +5 -17
  79. data/config/locales/is-IS.yml +0 -1
  80. data/config/locales/it.yml +1 -18
  81. data/config/locales/ja.yml +25 -29
  82. data/config/locales/ka-GE.yml +1 -0
  83. data/config/locales/lb.yml +0 -17
  84. data/config/locales/lo-LA.yml +1 -0
  85. data/config/locales/lt.yml +0 -17
  86. data/config/locales/lv.yml +5 -17
  87. data/config/locales/nl.yml +0 -17
  88. data/config/locales/no.yml +2 -19
  89. data/config/locales/pl.yml +4 -18
  90. data/config/locales/pt-BR.yml +0 -17
  91. data/config/locales/pt.yml +0 -17
  92. data/config/locales/ro-RO.yml +49 -16
  93. data/config/locales/ru.yml +5 -3
  94. data/config/locales/sk.yml +5 -17
  95. data/config/locales/sv.yml +22 -18
  96. data/config/locales/tr-TR.yml +4 -18
  97. data/config/locales/uk.yml +5 -1
  98. data/config/locales/zh-CN.yml +3 -17
  99. data/lib/decidim/api/types/localized_string_type.rb +9 -0
  100. data/lib/decidim/api/types/translated_field_type.rb +20 -5
  101. data/lib/decidim/asset_router/pipeline.rb +93 -0
  102. data/lib/decidim/asset_router/storage.rb +82 -0
  103. data/lib/decidim/asset_router.rb +3 -75
  104. data/lib/decidim/attribute_object/form.rb +9 -0
  105. data/lib/decidim/attributes/localized_date.rb +1 -1
  106. data/lib/decidim/attributes/time_with_zone.rb +5 -2
  107. data/lib/decidim/core/engine.rb +7 -5
  108. data/lib/decidim/core/test/factories.rb +13 -6
  109. data/lib/decidim/core/test/shared_examples/comments_examples.rb +1 -1
  110. data/lib/decidim/core/test/shared_examples/editor_shared_examples.rb +30 -0
  111. data/lib/decidim/core/test/shared_examples/mcell_examples.rb +17 -0
  112. data/lib/decidim/core/test.rb +2 -0
  113. data/lib/decidim/core/version.rb +1 -1
  114. data/lib/decidim/dependency_resolver.rb +14 -8
  115. data/lib/decidim/file_validator_humanizer.rb +1 -1
  116. data/lib/decidim/form_builder.rb +11 -4
  117. data/lib/decidim/participatory_space_resourceable.rb +7 -1
  118. data/lib/decidim/resourceable.rb +5 -4
  119. data/lib/decidim/settings_manifest.rb +1 -1
  120. metadata +17 -8
  121. data/app/packs/images/decidim/gamification/badges/decidim_gamification_badges_invitations.svg +0 -1
@@ -0,0 +1,93 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Decidim
4
+ module AssetRouter
5
+ # The pipeline asset router provides global access to the asset routers for
6
+ # assets that are precompiled through the Rails assets pipeline (or webpack)
7
+ # and stored locally or in a remote CDN. This handles the configuration
8
+ # options for the asset routes so that they don't have to be always manually
9
+ # created.
10
+ class Pipeline
11
+ # Initializes the router.
12
+ #
13
+ # @param asset [String] The asset to route to
14
+ # @param model [ActiveRecord::Base, nil] The model that provides the
15
+ # organizational context. When nil, the host will be included in the URL
16
+ # when it is available through other configurations. Otherwise, the host
17
+ # will not be part of the URL.
18
+ def initialize(asset, model: nil)
19
+ @asset = asset
20
+ @model = model
21
+ end
22
+
23
+ # Generates the correct URL to the asset with the provided options.
24
+ #
25
+ # @param options [Hash] The options for the URL that are the normal route
26
+ # options Rails route helpers accept
27
+ # @return [String] The full URL to the asset or when host cannot be
28
+ # resolved, the asset path.
29
+ def url(**options)
30
+ path = ActionController::Base.helpers.asset_pack_path(asset, **options)
31
+ "#{asset_host}#{path}"
32
+ end
33
+
34
+ private
35
+
36
+ attr_reader :asset, :model
37
+
38
+ # Fetches the organization from the model or returns the model itself if
39
+ # it is an organization.
40
+ #
41
+ # @return [Decidim::Organization]
42
+ def organization
43
+ @organization ||=
44
+ if model.is_a?(Decidim::Organization)
45
+ model
46
+ else
47
+ model.try(:organization)
48
+ end
49
+ end
50
+
51
+ # Resolves the full asset host with the resolved options and also adds the
52
+ # port at the end of the URL unless it is the default port 80 or 443.
53
+ #
54
+ # @return [String] The hostname with protocol and port or an empty string
55
+ # when the host cannot be resolved.
56
+ def asset_host
57
+ return "" if default_options[:host].blank?
58
+
59
+ base_host = ActionController::Base.helpers.compute_asset_host("", **default_options)
60
+ return "" if base_host.blank?
61
+ return base_host if option_resolver.default_port?
62
+
63
+ "#{base_host}:#{option_resolver.port}"
64
+ end
65
+
66
+ # Determines the default options to be passed to the route helper.
67
+ #
68
+ # @return [Hash] The default options hash to pass to the route helper
69
+ def default_options
70
+ @default_options ||= option_resolver.options.tap do |opts|
71
+ opts[:host] = default_host if default_host
72
+ end
73
+ end
74
+
75
+ # Determines the default host for the pipeline assets. Either the
76
+ # configured assets host or the organization host when available. When the
77
+ # default host is not available, the host returned by the
78
+ # UrlOptionResolver will be used.
79
+ #
80
+ # @return [String, nil]
81
+ def default_host
82
+ @default_host ||= Rails.configuration.action_controller.asset_host || organization&.host
83
+ end
84
+
85
+ # Stores an instance of UrlOptionResolver for convenience.
86
+ #
87
+ # @return [Decidim::UrlOptionResolver]
88
+ def option_resolver
89
+ @option_resolver ||= UrlOptionResolver.new
90
+ end
91
+ end
92
+ end
93
+ end
@@ -0,0 +1,82 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Decidim
4
+ module AssetRouter
5
+ # Storage asset router provides global access to the asset routes for assets
6
+ # saved through ActiveStorage. This handles the different cases for routing
7
+ # to the remote routes when using an assets CDN or to local routes when
8
+ # using the local disk storage driver.
9
+ class Storage
10
+ # Initializes the router.
11
+ #
12
+ # @param [ActiveStorage::Attached, ActiveStorage::Blob] The asset to route
13
+ # to
14
+ def initialize(asset)
15
+ @asset = asset
16
+ end
17
+
18
+ # Generates the correct URL to the asset with the provided options.
19
+ #
20
+ # @param options The options for the URL that are the normal route options
21
+ # Rails route helpers accept
22
+ def url(**options)
23
+ if asset.is_a? ActiveStorage::Attached
24
+ routes.rails_blob_url(asset.blob, **default_options.merge(options))
25
+ else
26
+ routes.rails_representation_url(asset, **default_options.merge(options))
27
+ end
28
+ end
29
+
30
+ private
31
+
32
+ attr_reader :asset
33
+
34
+ # Provides the route helpers depending on whether the URL is generated to
35
+ # the local host or an external CDN (remote).
36
+ #
37
+ # @return [Module, Decidim::EngineRouter] The correct route helpers based
38
+ # on the configuration
39
+ def routes
40
+ @routes ||=
41
+ if remote?
42
+ Rails.application.routes.url_helpers
43
+ else
44
+ EngineRouter.new("main_app", {})
45
+ end
46
+ end
47
+
48
+ # Determines whether the assets call should be to a remote CDN or to the
49
+ # local server based on the storage options.
50
+ #
51
+ # @return [Boolean] A boolean indicating whether the assets are served
52
+ # through a remote CDN
53
+ def remote?
54
+ remote_storage_options.present?
55
+ end
56
+
57
+ # Determines the default options to be passed to the route helper. For the
58
+ # remote storage, returns the remote storage options and for the local
59
+ # disk storage returns an empty hash.
60
+ #
61
+ # @return [Hash] The default options hash to pass to the route helper
62
+ def default_options
63
+ @default_options ||=
64
+ if remote?
65
+ remote_storage_options
66
+ else
67
+ {}
68
+ end
69
+ end
70
+
71
+ # The remote storage options when using a remote CDN. An empty hash in
72
+ # case using the local disk storage.
73
+ #
74
+ # @return [Hash] The remote storage options hash
75
+ def remote_storage_options
76
+ @remote_storage_options ||= {
77
+ host: Rails.application.secrets.dig(:storage, :cdn_host)
78
+ }.compact
79
+ end
80
+ end
81
+ end
82
+ end
@@ -1,80 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Decidim
4
- # Asset router provides global access to the asset routes for assets saved
5
- # through ActiveStorage. This handles the different cases for routing to the
6
- # remote routes when using an assets CDN or to local routes when using the
7
- # local disk storage driver.
8
- class AssetRouter
9
- # Initializes the router.
10
- #
11
- # @param [ActiveStorage::Attached, ActiveStorage::Blob] The asset to route
12
- # to
13
- def initialize(asset)
14
- @asset = asset
15
- end
16
-
17
- # Generates the correct URL to the asset with the provided options.
18
- #
19
- # @param options The options for the URL that are the normal route options
20
- # Rails route helpers accept
21
- def url(**options)
22
- if asset.is_a? ActiveStorage::Attached
23
- routes.rails_blob_url(asset.blob, **default_options.merge(options))
24
- else
25
- routes.rails_representation_url(asset, **default_options.merge(options))
26
- end
27
- end
28
-
29
- private
30
-
31
- attr_reader :asset
32
-
33
- # Provides the route helpers depending on whether the URL is generated to
34
- # the local host or an external CDN (remote).
35
- #
36
- # @return [Module, Decidim::EngineRouter] The correct route helpers based
37
- # on the configuration
38
- def routes
39
- @routes ||=
40
- if remote?
41
- Rails.application.routes.url_helpers
42
- else
43
- EngineRouter.new("main_app", {})
44
- end
45
- end
46
-
47
- # Determines whether the assets call should be to a remote CDN or to the
48
- # local server based on the storage options.
49
- #
50
- # @return [Boolean] A boolean indicating whether the assets are served
51
- # through a remote CDN
52
- def remote?
53
- remote_storage_options.present?
54
- end
55
-
56
- # Determines the default options to be passed to the route helper. For the
57
- # remote storage, returns the remote storage options and for the local disk
58
- # storage returns an empty hash.
59
- #
60
- # @return [Hash] The default options hash to pass to the route helper
61
- def default_options
62
- @default_options ||=
63
- if remote?
64
- remote_storage_options
65
- else
66
- {}
67
- end
68
- end
69
-
70
- # The remote storage options when using a remote CDN. An empty hash in case
71
- # using the local disk storage.
72
- #
73
- # @return [Hash] The remote storage options hash
74
- def remote_storage_options
75
- @remote_storage_options ||= {
76
- host: Rails.application.secrets.dig(:storage, :cdn_host)
77
- }.compact
78
- end
4
+ module AssetRouter
5
+ autoload :Pipeline, "decidim/asset_router/pipeline"
6
+ autoload :Storage, "decidim/asset_router/storage"
79
7
  end
80
8
  end
@@ -48,6 +48,15 @@ module Decidim
48
48
  value.attachment.try(:blob)
49
49
  when ActiveStorage::Attached::Many
50
50
  value.attachments.map(&:blob)
51
+ when ActiveRecord::Associations::CollectionProxy, ActiveRecord::Relation, Array
52
+ if attribute_types[key].type == :array
53
+ value
54
+ else
55
+ # This is a sub-form that needs to read the properties directly
56
+ # from the original model. We cannot pass an array here as it
57
+ # would be passed to the form constructor causing an error.
58
+ model
59
+ end
51
60
  else
52
61
  value
53
62
  end
@@ -16,7 +16,7 @@ module Decidim
16
16
 
17
17
  Date.strptime(value, I18n.t("date.formats.decidim_short"))
18
18
  rescue ArgumentError
19
- nil
19
+ super
20
20
  end
21
21
  end
22
22
  end
@@ -4,7 +4,7 @@ module Decidim
4
4
  module Attributes
5
5
  # Custom attributes value to parse a String representing a Time using
6
6
  # the app TimeZone.
7
- class TimeWithZone < ActiveModel::Type::Time
7
+ class TimeWithZone < ActiveModel::Type::DateTime
8
8
  def type
9
9
  :"decidim/attributes/time_with_zone"
10
10
  end
@@ -16,7 +16,10 @@ module Decidim
16
16
 
17
17
  Time.zone.strptime(value, I18n.t("time.formats.decidim_short"))
18
18
  rescue ArgumentError
19
- nil
19
+ fallback = super
20
+ return fallback unless fallback.is_a?(Time)
21
+
22
+ ActiveSupport::TimeWithZone.new(fallback, Time.zone)
20
23
  end
21
24
  end
22
25
  end
@@ -253,6 +253,13 @@ module Decidim
253
253
  end
254
254
  end
255
255
 
256
+ initializer "decidim.validators" do
257
+ config.to_prepare do
258
+ # Decidim overrides to the file content type validator
259
+ require "file_content_type_validator"
260
+ end
261
+ end
262
+
256
263
  initializer "decidim.content_processors" do |_app|
257
264
  Decidim.configure do |config|
258
265
  config.content_processors += [:user, :user_group, :hashtag, :link]
@@ -542,11 +549,6 @@ module Decidim
542
549
  end
543
550
 
544
551
  initializer "decidim.core.add_badges" do
545
- Decidim::Gamification.register_badge(:invitations) do |badge|
546
- badge.levels = [1, 5, 10, 30, 50]
547
- badge.reset = ->(user) { Decidim::User.where(invited_by: user.id).count }
548
- end
549
-
550
552
  Decidim::Gamification.register_badge(:followers) do |badge|
551
553
  badge.levels = [1, 15, 30, 60, 100]
552
554
  badge.reset = ->(user) { user.followers.count }
@@ -234,13 +234,20 @@ FactoryBot.define do
234
234
  confirmed_at { Time.current }
235
235
  end
236
236
 
237
+ trait :blocked do
238
+ blocked { true }
239
+ blocked_at { Time.current }
240
+ extended_data { { user_name: generate(:name) } }
241
+ name { "Blocked user group" }
242
+ end
243
+
237
244
  after(:build) do |user_group, evaluator|
238
- user_group.extended_data = {
239
- document_number: evaluator.document_number,
240
- phone: evaluator.phone,
241
- rejected_at: evaluator.rejected_at,
242
- verified_at: evaluator.verified_at
243
- }
245
+ user_group.extended_data = user_group.extended_data.merge({
246
+ document_number: evaluator.document_number,
247
+ phone: evaluator.phone,
248
+ rejected_at: evaluator.rejected_at,
249
+ verified_at: evaluator.verified_at
250
+ })
244
251
  end
245
252
 
246
253
  after(:create) do |user_group, evaluator|
@@ -761,7 +761,7 @@ shared_examples "comments" do
761
761
 
762
762
  it "replaces the mention with a link to the user's profile" do
763
763
  expect(page).to have_comment_from(user, "A valid user mention: @#{mentioned_user.nickname}", wait: 20)
764
- expect(page).to have_link "@#{mentioned_user.nickname}", href: "http://#{mentioned_user.organization.host}/profiles/#{mentioned_user.nickname}"
764
+ expect(page).to have_link "@#{mentioned_user.nickname}", href: "http://#{mentioned_user.organization.host}:#{Capybara.server_port}/profiles/#{mentioned_user.nickname}"
765
765
  end
766
766
  end
767
767
 
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ shared_examples_for "has embedded video in description" do |description_attribute_name|
4
+ let(description_attribute_name) { { en: %(Description <iframe class="ql-video" allowfullscreen="true" src="#{iframe_src}" frameborder="0"></iframe>) } }
5
+ let(:iframe_src) { "http://www.example.org" }
6
+
7
+ context "when cookies are rejected" do
8
+ before do
9
+ click_link "Cookie settings"
10
+ click_button "Accept only essential"
11
+ end
12
+
13
+ it "disables iframe" do
14
+ expect(page).to have_content("You need to enable all cookies in order to see this content")
15
+ expect(page).not_to have_selector("iframe")
16
+ end
17
+ end
18
+
19
+ context "when cookies are accepted" do
20
+ before do
21
+ click_link "Cookie settings"
22
+ click_button "Accept all"
23
+ end
24
+
25
+ it "shows iframe" do
26
+ expect(page).not_to have_content("You need to enable all cookies in order to see this content")
27
+ expect(page).to have_selector("iframe", count: 1)
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "spec_helper"
4
+
5
+ shared_examples_for "m-cell" do |model_name|
6
+ context "with decorated title" do
7
+ let(:cell_model) { send(model_name) }
8
+
9
+ before do
10
+ cell_model.update!(title: { en: "Model <strong>decorated title</strong>" })
11
+ end
12
+
13
+ it "renders the escaped title correctly" do
14
+ expect(cell_html.to_s).to include("Model &lt;strong&gt;decorated title&lt;/strong&gt;")
15
+ end
16
+ end
17
+ end
@@ -4,6 +4,7 @@ require "decidim/core/test/shared_examples/acts_as_author_examples"
4
4
  require "decidim/core/test/shared_examples/admin_log_presenter_examples"
5
5
  require "decidim/core/test/shared_examples/authorable"
6
6
  require "decidim/core/test/shared_examples/coauthorable"
7
+ require "decidim/core/test/shared_examples/editor_shared_examples"
7
8
  require "decidim/core/test/shared_examples/endorsable"
8
9
  require "decidim/core/test/shared_examples/publicable"
9
10
  require "decidim/core/test/shared_examples/localised_email"
@@ -73,3 +74,4 @@ require "decidim/core/test/shared_examples/translated_event_examples"
73
74
  require "decidim/core/test/shared_examples/conversations_examples"
74
75
  require "decidim/core/test/shared_examples/resource_endorsed_event_examples"
75
76
  require "decidim/core/test/shared_examples/versions_controller_examples"
77
+ require "decidim/core/test/shared_examples/mcell_examples"
@@ -4,7 +4,7 @@ module Decidim
4
4
  # This holds the decidim-core version.
5
5
  module Core
6
6
  def self.version
7
- "0.27.0"
7
+ "0.27.2"
8
8
  end
9
9
  end
10
10
  end
@@ -28,11 +28,9 @@ module Decidim
28
28
  # specs which means any gems available in the gem install folder.
29
29
  #
30
30
  # @param gem [String] The name for the gem to be looked up.
31
- # @return [Bundler::LazySpecification, Gem::Specification, nil] When bundler
32
- # is available, returns `Bundler::LazySpecification`. When bundler is not
33
- # available, returns `Gem::Specification`. Both of these implement the
34
- # necessary methods for the other checks. In both cases returns nil if
35
- # the gem is not found.
31
+ # @return [Gem::Specification, nil] Returns the gem specification for the
32
+ # given gem name. The specification implements the necessary methods for
33
+ # the other checks. In both cases returns nil if the gem is not found.
36
34
  def lookup(gem)
37
35
  # In case the lookup method is called with a spec definition, return the
38
36
  # definition itself.
@@ -217,10 +215,18 @@ module Decidim
217
215
  # Gemfile, e.g. when the Decidim gems are installed through git.
218
216
  #
219
217
  # @param name [String] The name of the gem to find.
220
- # @return [Bundler::LazySpecification, nil] The specification for the gem
221
- # or nil if the gem is not listed in the locked gems.
218
+ # @return [Gem::Specification, nil] The specification for the gem
219
+ # or nil if the gem is not listed in the locked gems or nil when the
220
+ # returned spec is not installed in the current gem environment.
222
221
  def spec(name)
223
- Bundler.definition.locked_gems.specs.find { |s| s.name == name }
222
+ sp = Bundler.definition.locked_gems.specs.find { |s| s.name == name }
223
+
224
+ # Fetching the gem through Gem.loaded_specs ensures we are not returning
225
+ # a lazy specification which does not respond to `#full_gem_path` which
226
+ # is needed for the resolver.
227
+ return Gem.loaded_specs[sp.name] if sp
228
+
229
+ nil
224
230
  end
225
231
 
226
232
  private
@@ -49,7 +49,7 @@ module Decidim
49
49
  if (extensions = extension_allowlist)
50
50
  messages << I18n.t(
51
51
  "allowed_file_extensions",
52
- extensions: extensions.join(" "),
52
+ extensions: extensions.sort.join(" "),
53
53
  scope: "decidim.forms.file_validation"
54
54
  )
55
55
  end
@@ -47,7 +47,7 @@ module Decidim
47
47
  # rubocop:enable Metrics/ParameterLists
48
48
 
49
49
  def create_language_selector(locales, tabs_id, name)
50
- if Decidim.available_locales.count > 4
50
+ if locales.count > 4
51
51
  language_selector_select(locales, tabs_id, name)
52
52
  else
53
53
  language_tabs(locales, tabs_id, name)
@@ -387,7 +387,14 @@ module Decidim
387
387
  def datetime_field(attribute, options = {})
388
388
  value = object.send(attribute)
389
389
  data = { datepicker: "", timepicker: "" }
390
- data[:startdate] = I18n.l(value, format: :decidim_short) if value.present? && value.is_a?(ActiveSupport::TimeWithZone)
390
+ if value.present?
391
+ case value
392
+ when ActiveSupport::TimeWithZone
393
+ data[:startdate] = I18n.l(value, format: :decidim_short)
394
+ when Time, DateTime
395
+ data[:startdate] = I18n.l(value.in_time_zone(Time.zone), format: :decidim_short)
396
+ end
397
+ end
391
398
  datepicker_format = ruby_format_to_datepicker(I18n.t("time.formats.decidim_short"))
392
399
  data[:"date-format"] = datepicker_format
393
400
 
@@ -417,7 +424,7 @@ module Decidim
417
424
  max_file_size: max_file_size(record, :file),
418
425
  label: I18n.t("decidim.forms.upload.labels.add_attachment"),
419
426
  button_edit_label: I18n.t("decidim.forms.upload.labels.edit_image"),
420
- extension_allowlist: Decidim.organization_settings(Decidim::Attachment).upload_allowed_file_extensions
427
+ extension_allowlist: Decidim.organization_settings(record).upload_allowed_file_extensions
421
428
  }.merge(options)
422
429
 
423
430
  # Upload help uses extension allowlist from the options so we need to call this AFTER setting the defaults.
@@ -844,7 +851,7 @@ module Decidim
844
851
  end
845
852
 
846
853
  def extension_allowlist_help(extension_allowlist)
847
- ["#{I18n.t("extension_allowlist", scope: "decidim.forms.files")} #{extension_allowlist.map { |ext| ext }.join(", ")}"]
854
+ [I18n.t("extension_allowlist", scope: "decidim.forms.files", extensions: extension_allowlist.map { |ext| ext }.join(", "))]
848
855
  end
849
856
 
850
857
  def image_dimensions_help(dimensions_info)
@@ -47,7 +47,13 @@ module Decidim
47
47
  .joins(:participatory_space_resource_links_to)
48
48
  .where(decidim_participatory_space_links: { name: link_name, from_id: id, from_type: self.class.name })
49
49
 
50
- klass.where(id: from).or(klass.where(id: to))
50
+ query = klass.where(id: from).or(klass.where(id: to)).published
51
+
52
+ if klass.column_names.include?("weight")
53
+ query.order(:weight)
54
+ else
55
+ query.order(created_at: :desc)
56
+ end
51
57
  end
52
58
 
53
59
  def participatory_space_sibling_scope(participatory_space_name)
@@ -36,8 +36,8 @@ module Decidim
36
36
  # link_name - The String name of the link between this model and the target resource.
37
37
  #
38
38
  # Returns an ActiveRecord::Relation.
39
- def linked_resources(resource_name, link_name)
40
- scope = sibling_scope(resource_name)
39
+ def linked_resources(resource_name, link_name, component_published: true)
40
+ scope = sibling_scope(resource_name, component_published: component_published)
41
41
 
42
42
  from = scope
43
43
  .joins(:resource_links_from)
@@ -56,14 +56,15 @@ module Decidim
56
56
  # resource_name - The String name of the resource manifest exposed by a component.
57
57
  #
58
58
  # Returns an ActiveRecord::Relation.
59
- def sibling_scope(resource_name)
59
+ def sibling_scope(resource_name, component_published: true)
60
60
  manifest = Decidim.find_resource_manifest(resource_name)
61
61
  return self.class.none unless manifest
62
62
 
63
63
  scope = manifest.resource_scope(component)
64
64
  scope = scope.where("#{self.class.table_name}.id != ?", id) if manifest.model_class == self.class
65
65
  scope = scope.not_hidden if manifest.model_class.respond_to?(:not_hidden)
66
- scope.includes(:component).where.not(decidim_components: { published_at: nil })
66
+ scope = scope.includes(:component).where.not(decidim_components: { published_at: nil }) if component_published
67
+ scope
67
68
  end
68
69
 
69
70
  # Links the given resources to this model, replaces any previous links with the same name.
@@ -97,7 +97,7 @@ module Decidim
97
97
  enum: { klass: String, default: nil },
98
98
  select: { klass: String, default: nil },
99
99
  scope: { klass: Integer, default: nil },
100
- time: { klass: DateTime, default: nil }
100
+ time: { klass: Decidim::Attributes::TimeWithZone, default: nil }
101
101
  }.freeze
102
102
 
103
103
  attribute :type, Symbol, default: :boolean