decidim-core 0.27.1 → 0.27.3

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 (130) hide show
  1. checksums.yaml +4 -4
  2. data/app/cells/decidim/newsletter_templates/base_cell.rb +8 -0
  3. data/app/cells/decidim/newsletter_templates/basic_only_text/show.erb +4 -4
  4. data/app/cells/decidim/newsletter_templates/image_text_cta/show.erb +4 -4
  5. data/app/cells/decidim/upload_modal/files.erb +1 -0
  6. data/app/cells/decidim/upload_modal_cell.rb +26 -11
  7. data/app/commands/decidim/attachment_methods.rb +20 -2
  8. data/app/commands/decidim/create_registration.rb +1 -0
  9. data/app/commands/decidim/gallery_methods.rb +1 -1
  10. data/app/commands/decidim/unendorse_resource.rb +1 -1
  11. data/app/commands/decidim/update_account.rb +1 -0
  12. data/app/commands/decidim/update_password.rb +2 -0
  13. data/app/controllers/decidim/devise/sessions_controller.rb +18 -2
  14. data/app/controllers/decidim/groups_controller.rb +5 -0
  15. data/app/controllers/decidim/links_controller.rb +10 -11
  16. data/app/controllers/decidim/profiles_controller.rb +1 -1
  17. data/app/helpers/decidim/cells_helper.rb +1 -0
  18. data/app/helpers/decidim/external_domain_helper.rb +14 -3
  19. data/app/helpers/decidim/icon_helper.rb +3 -3
  20. data/app/helpers/decidim/newsletters_helper.rb +1 -0
  21. data/app/helpers/decidim/sanitize_helper.rb +3 -2
  22. data/app/mailers/decidim/newsletter_mailer.rb +10 -3
  23. data/app/models/decidim/newsletter.rb +28 -0
  24. data/app/models/decidim/scope_type.rb +24 -0
  25. data/app/models/decidim/user.rb +0 -2
  26. data/app/models/decidim/user_base_entity.rb +2 -0
  27. data/app/models/decidim/user_block.rb +2 -2
  28. data/app/models/decidim/user_group.rb +1 -1
  29. data/app/packs/src/decidim/direct_uploads/upload_modal.js +0 -1
  30. data/app/packs/src/decidim/editor/clipboard_override.js +6 -2
  31. data/app/packs/src/decidim/editor.js +63 -33
  32. data/app/packs/src/decidim/form_filter.component.test.js +148 -5
  33. data/app/packs/src/decidim/form_filter.js +26 -4
  34. data/app/packs/stylesheets/decidim/email.scss +7 -0
  35. data/app/packs/stylesheets/decidim/modules/_buttons.scss +10 -6
  36. data/app/packs/stylesheets/decidim/modules/_cards.scss +1 -1
  37. data/app/packs/stylesheets/decidim/modules/_comments.scss +24 -0
  38. data/app/packs/stylesheets/decidim/modules/_input-gallery.scss +2 -1
  39. data/app/packs/stylesheets/decidim/modules/_upload_modal.scss +0 -4
  40. data/app/packs/stylesheets/decidim/vizzs/_linechart.scss +2 -2
  41. data/app/packs/stylesheets/decidim/vizzs/_rowchart.scss +2 -2
  42. data/app/presenters/decidim/admin_log/user_group_presenter.rb +1 -1
  43. data/app/presenters/decidim/admin_log/user_moderation_presenter.rb +1 -1
  44. data/app/presenters/decidim/notification_presenter.rb +1 -1
  45. data/app/presenters/decidim/notification_to_mailer_presenter.rb +1 -0
  46. data/app/presenters/decidim/push_notification_presenter.rb +1 -1
  47. data/app/presenters/decidim/user_group_presenter.rb +1 -1
  48. data/app/presenters/decidim/user_presenter.rb +1 -1
  49. data/app/scrubbers/decidim/admin_input_scrubber.rb +3 -1
  50. data/app/scrubbers/decidim/user_input_scrubber.rb +30 -1
  51. data/app/services/decidim/traceability.rb +1 -0
  52. data/app/uploaders/decidim/application_uploader.rb +1 -1
  53. data/app/uploaders/decidim/avatar_uploader.rb +2 -2
  54. data/app/validators/uploader_image_dimensions_validator.rb +22 -2
  55. data/app/views/decidim/links/_invalid_url_modal.html.erb +17 -0
  56. data/app/views/decidim/links/_modal.html.erb +1 -1
  57. data/app/views/decidim/links/invalid_url.js.erb +24 -0
  58. data/app/views/decidim/links/new.html.erb +1 -1
  59. data/app/views/decidim/messaging/conversations/_conversation.html.erb +2 -6
  60. data/app/views/decidim/newsletter_mailer/newsletter.html.erb +3 -3
  61. data/app/views/decidim/newsletters/show.html.erb +1 -1
  62. data/app/views/layouts/decidim/_mailer_logo.html.erb +2 -2
  63. data/app/views/layouts/decidim/newsletter_base.html.erb +2 -2
  64. data/config/locales/ar.yml +571 -7
  65. data/config/locales/bg.yml +6 -8
  66. data/config/locales/ca.yml +38 -30
  67. data/config/locales/cs.yml +28 -33
  68. data/config/locales/da.yml +4 -0
  69. data/config/locales/de.yml +4 -25
  70. data/config/locales/el.yml +6 -9
  71. data/config/locales/en.yml +22 -15
  72. data/config/locales/eo.yml +2 -1
  73. data/config/locales/es-MX.yml +30 -22
  74. data/config/locales/es-PY.yml +30 -22
  75. data/config/locales/es.yml +36 -28
  76. data/config/locales/et.yml +4 -0
  77. data/config/locales/eu.yml +173 -80
  78. data/config/locales/fa-IR.yml +1 -0
  79. data/config/locales/fi-plain.yml +7 -20
  80. data/config/locales/fi.yml +26 -18
  81. data/config/locales/fr-CA.yml +27 -19
  82. data/config/locales/fr.yml +25 -17
  83. data/config/locales/ga-IE.yml +1 -0
  84. data/config/locales/gl.yml +2 -25
  85. data/config/locales/gn-PY.yml +4 -0
  86. data/config/locales/hr.yml +4 -0
  87. data/config/locales/hu.yml +68 -28
  88. data/config/locales/id-ID.yml +7 -8
  89. data/config/locales/is-IS.yml +2 -2
  90. data/config/locales/it.yml +2 -10
  91. data/config/locales/ja.yml +30 -37
  92. data/config/locales/ka-GE.yml +5 -0
  93. data/config/locales/kaa.yml +1 -0
  94. data/config/locales/lb.yml +0 -8
  95. data/config/locales/lt.yml +0 -38
  96. data/config/locales/lv.yml +5 -7
  97. data/config/locales/nl.yml +1 -27
  98. data/config/locales/no.yml +3 -29
  99. data/config/locales/oc-FR.yml +3 -0
  100. data/config/locales/pl.yml +4 -39
  101. data/config/locales/pt-BR.yml +2 -10
  102. data/config/locales/pt.yml +0 -8
  103. data/config/locales/ro-RO.yml +85 -7
  104. data/config/locales/ru.yml +6 -4
  105. data/config/locales/sk.yml +8 -9
  106. data/config/locales/sl.yml +1 -0
  107. data/config/locales/sr-CS.yml +2 -0
  108. data/config/locales/sv.yml +23 -28
  109. data/config/locales/tr-TR.yml +7 -12
  110. data/config/locales/uk.yml +6 -4
  111. data/config/locales/zh-CN.yml +3 -8
  112. data/config/locales/zh-TW.yml +1872 -0
  113. data/lib/decidim/api/types/localized_string_type.rb +9 -0
  114. data/lib/decidim/api/types/translated_field_type.rb +20 -5
  115. data/lib/decidim/asset_router/pipeline.rb +95 -0
  116. data/lib/decidim/asset_router/storage.rb +82 -0
  117. data/lib/decidim/asset_router.rb +3 -75
  118. data/lib/decidim/attribute_object/form.rb +9 -0
  119. data/lib/decidim/core/test/factories.rb +13 -6
  120. data/lib/decidim/core/test/shared_examples/comments_examples.rb +36 -0
  121. data/lib/decidim/core/test/shared_examples/digest_mail_examples.rb +33 -0
  122. data/lib/decidim/core/test/shared_examples/editor_shared_examples.rb +5 -4
  123. data/lib/decidim/core/test/shared_examples/rich_text_editor_examples.rb +7 -3
  124. data/lib/decidim/core/test.rb +1 -0
  125. data/lib/decidim/core/version.rb +1 -1
  126. data/lib/decidim/dependency_resolver.rb +14 -8
  127. data/lib/decidim/form_builder.rb +5 -4
  128. data/lib/decidim/participatory_space_resourceable.rb +7 -1
  129. data/lib/decidim/publicable.rb +4 -0
  130. metadata +14 -6
@@ -8,6 +8,15 @@ module Decidim
8
8
 
9
9
  field :locale, GraphQL::Types::String, "The standard locale of this translation.", null: false
10
10
  field :text, GraphQL::Types::String, "The content of this translation.", null: true
11
+ field :machine_translated, GraphQL::Types::Boolean, "Whether this string is machine translated or not.", null: false
12
+
13
+ def machine_translated
14
+ if object.respond_to?(:machine_translated)
15
+ object.machine_translated.present?
16
+ else
17
+ false
18
+ end
19
+ end
11
20
  end
12
21
  end
13
22
  end
@@ -17,19 +17,34 @@ module Decidim
17
17
  end
18
18
 
19
19
  def locales
20
- object.keys
20
+ (defined_translations.keys + machine_translations.keys).uniq
21
21
  end
22
22
 
23
23
  def translation(locale: "")
24
- translations = object.stringify_keys
25
- translations[locale]
24
+ display_translations[locale]
26
25
  end
27
26
 
28
27
  def translations(locales: [])
29
- translations = object.stringify_keys
28
+ translations = display_translations
30
29
  translations = translations.slice(*locales) unless locales.empty?
31
30
 
32
- translations.map { |locale, text| OpenStruct.new(locale: locale, text: text) }
31
+ translations.map { |locale, text| OpenStruct.new(locale: locale, text: text, machine_translated: defined_translations[locale].blank?) }
32
+ end
33
+
34
+ private
35
+
36
+ def display_translations
37
+ @display_translations ||= locales.index_with do |locale|
38
+ defined_translations[locale].presence || machine_translations[locale]
39
+ end
40
+ end
41
+
42
+ def defined_translations
43
+ object.stringify_keys.except("machine_translations")
44
+ end
45
+
46
+ def machine_translations
47
+ object.stringify_keys["machine_translations"]&.stringify_keys || {}
33
48
  end
34
49
  end
35
50
  end
@@ -0,0 +1,95 @@
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
+ return path if path.match?(%r{\A(https?:)?//})
32
+
33
+ "#{asset_host}#{path}"
34
+ end
35
+
36
+ private
37
+
38
+ attr_reader :asset, :model
39
+
40
+ # Fetches the organization from the model or returns the model itself if
41
+ # it is an organization.
42
+ #
43
+ # @return [Decidim::Organization]
44
+ def organization
45
+ @organization ||=
46
+ if model.is_a?(Decidim::Organization)
47
+ model
48
+ else
49
+ model.try(:organization)
50
+ end
51
+ end
52
+
53
+ # Resolves the full asset host with the resolved options and also adds the
54
+ # port at the end of the URL unless it is the default port 80 or 443.
55
+ #
56
+ # @return [String] The hostname with protocol and port or an empty string
57
+ # when the host cannot be resolved.
58
+ def asset_host
59
+ return "" if default_options[:host].blank?
60
+
61
+ base_host = ActionController::Base.helpers.compute_asset_host("", **default_options)
62
+ return "" if base_host.blank?
63
+ return base_host if option_resolver.default_port?
64
+
65
+ "#{base_host}:#{option_resolver.port}"
66
+ end
67
+
68
+ # Determines the default options to be passed to the route helper.
69
+ #
70
+ # @return [Hash] The default options hash to pass to the route helper
71
+ def default_options
72
+ @default_options ||= option_resolver.options.tap do |opts|
73
+ opts[:host] = default_host if default_host
74
+ end
75
+ end
76
+
77
+ # Determines the default host for the pipeline assets. Either the
78
+ # configured assets host or the organization host when available. When the
79
+ # default host is not available, the host returned by the
80
+ # UrlOptionResolver will be used.
81
+ #
82
+ # @return [String, nil]
83
+ def default_host
84
+ @default_host ||= Rails.configuration.action_controller.asset_host || organization&.host
85
+ end
86
+
87
+ # Stores an instance of UrlOptionResolver for convenience.
88
+ #
89
+ # @return [Decidim::UrlOptionResolver]
90
+ def option_resolver
91
+ @option_resolver ||= UrlOptionResolver.new
92
+ end
93
+ end
94
+ end
95
+ 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
@@ -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|
@@ -416,6 +416,42 @@ shared_examples "comments" do
416
416
  )
417
417
  end
418
418
 
419
+ context "when user can hide replies on a thread" do
420
+ let(:thread) { comments.first }
421
+ let(:new_reply_body) { "Hey, I just jumped inside the thread!" }
422
+ let!(:new_reply) { create(:comment, commentable: thread, root_commentable: commentable, body: new_reply_body) }
423
+
424
+ it "displays the hide button" do
425
+ visit current_path
426
+ within "#comment_#{thread.id}" do
427
+ expect(page).to have_content("Hide replies")
428
+ expect(page).to have_content(new_reply_body)
429
+ end
430
+ end
431
+
432
+ it "displays the show button" do
433
+ visit current_path
434
+ within "#comment_#{thread.id}" do
435
+ click_button "Hide replies"
436
+ expect(page).to have_content("Show reply")
437
+ expect(page).not_to have_content(new_reply_body)
438
+ end
439
+ end
440
+
441
+ context "when are more replies" do
442
+ let!(:new_replies) { create_list(:comment, 2, commentable: thread, root_commentable: commentable, body: new_reply_body) }
443
+
444
+ it "displays the show button" do
445
+ visit current_path
446
+ within "#comment_#{thread.id}" do
447
+ click_button "Hide replies"
448
+ expect(page).to have_content("Show 3 replies")
449
+ expect(page).not_to have_content(new_reply_body)
450
+ end
451
+ end
452
+ end
453
+ end
454
+
419
455
  context "when inside a thread reply form" do
420
456
  let(:thread) { comments.first }
421
457
  let(:new_reply_body) { "Hey, I just jumped inside the thread!" }
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ shared_context "when sends the notification digest" do
4
+ context "when daily notification mail" do
5
+ let(:user) { create(:user, :admin, organization: organization, notifications_sending_frequency: "daily") }
6
+
7
+ it_behaves_like "notification digest mail"
8
+ end
9
+
10
+ context "when weekly notification mail" do
11
+ let(:user) { create(:user, :admin, organization: organization, notifications_sending_frequency: "weekly") }
12
+
13
+ it_behaves_like "notification digest mail"
14
+ end
15
+ end
16
+
17
+ shared_examples_for "notification digest mail" do
18
+ context "when a notificable event takes place" do
19
+ let!(:organization) { create(:organization) }
20
+ let!(:participatory_space) { create(:participatory_process, organization: organization) }
21
+
22
+ it "sends a notification to the user's email" do
23
+ perform_enqueued_jobs do
24
+ expect(command.call).to broadcast(:ok)
25
+ Decidim::Notification.last.update(created_at: 1.day.ago)
26
+ Decidim::EmailNotificationsDigestGeneratorJob.perform_now(user.id, user.notifications_sending_frequency)
27
+ end
28
+
29
+ expect(last_email_body.length).to be_positive
30
+ expect(last_email_body).not_to include("translation missing")
31
+ end
32
+ end
33
+ end
@@ -1,8 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- shared_examples_for "has embedded video in description" do |description_attribute_name|
3
+ shared_examples_for "has embedded video in description" do |description_attribute_name, count: 1|
4
4
  let(description_attribute_name) { { en: %(Description <iframe class="ql-video" allowfullscreen="true" src="#{iframe_src}" frameborder="0"></iframe>) } }
5
5
  let(:iframe_src) { "http://www.example.org" }
6
+ let!(:cookie_warning) { "You need to enable all cookies in order to see this content" }
6
7
 
7
8
  context "when cookies are rejected" do
8
9
  before do
@@ -11,7 +12,7 @@ shared_examples_for "has embedded video in description" do |description_attribut
11
12
  end
12
13
 
13
14
  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).to have_content(cookie_warning)
15
16
  expect(page).not_to have_selector("iframe")
16
17
  end
17
18
  end
@@ -23,8 +24,8 @@ shared_examples_for "has embedded video in description" do |description_attribut
23
24
  end
24
25
 
25
26
  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)
27
+ expect(page).not_to have_content(cookie_warning)
28
+ expect(page).to have_selector("iframe", count: count)
28
29
  end
29
30
  end
30
31
  end
@@ -1,13 +1,17 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- shared_examples "having a rich text editor" do |css, toolbar|
4
- it "has a form with a rich text editor" do
5
- within "form.#{css}" do
3
+ shared_examples "having a rich text editor for field" do |selector, toolbar|
4
+ it "has a rich text editor" do
5
+ within selector do
6
6
  expect(page).to have_selector("div.editor-container[data-toolbar='#{toolbar}']", visible: :all)
7
7
  end
8
8
  end
9
9
  end
10
10
 
11
+ shared_examples "having a rich text editor" do |css, toolbar|
12
+ it_behaves_like "having a rich text editor for field", "form.#{css}", toolbar
13
+ end
14
+
11
15
  shared_context "with rich text editor content" do
12
16
  let(:content) { "<p>#{safe_tags}</p>#{script}" }
13
17
  let(:safe_tags) { em + u + strong }
@@ -75,3 +75,4 @@ require "decidim/core/test/shared_examples/conversations_examples"
75
75
  require "decidim/core/test/shared_examples/resource_endorsed_event_examples"
76
76
  require "decidim/core/test/shared_examples/versions_controller_examples"
77
77
  require "decidim/core/test/shared_examples/mcell_examples"
78
+ require "decidim/core/test/shared_examples/digest_mail_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.1"
7
+ "0.27.3"
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
@@ -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)
@@ -444,7 +444,7 @@ module Decidim
444
444
  # * resouce_name: Name of the resource (e.g. user)
445
445
  # * resource_class: Attribute's resource class (e.g. Decidim::User)
446
446
  # * resouce_class: Class of the resource (e.g. user)
447
- # * optional: Whether the file can be optional or not.
447
+ # * required: Whether the file is required or not (false by default).
448
448
  # * titled: Whether the file can have title or not.
449
449
  # * show_current: Whether the current file is displayed next to the button.
450
450
  # * help: Array of help messages which are displayed inside of the upload modal.
@@ -464,7 +464,7 @@ module Decidim
464
464
  attribute: attribute,
465
465
  resource_name: @object_name,
466
466
  resource_class: options[:resource_class]&.to_s || resource_class(attribute),
467
- optional: true,
467
+ required: false,
468
468
  titled: false,
469
469
  show_current: true,
470
470
  max_file_size: max_file_size,
@@ -712,6 +712,8 @@ module Decidim
712
712
  safe_join([yield, text.html_safe])
713
713
  elsif block_given?
714
714
  safe_join([text.html_safe, yield])
715
+ else
716
+ text
715
717
  end
716
718
 
717
719
  label(attribute, text, options || {})
@@ -928,7 +930,6 @@ module Decidim
928
930
  return {} unless options[:editor_images]
929
931
 
930
932
  {
931
- editor_images: true,
932
933
  upload_images_path: Decidim::Core::Engine.routes.url_helpers.editor_images_path,
933
934
  drag_and_drop_help_text: I18n.t("drag_and_drop_help", scope: "decidim.editor_images")
934
935
  }
@@ -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)
@@ -45,5 +45,9 @@ module Decidim
45
45
  def unpublish!
46
46
  update!(published_at: nil)
47
47
  end
48
+
49
+ def previously_published?
50
+ respond_to?(:versions) && versions.where(event: "update").where("object ->> 'published_at' IS NOT NULL").any?
51
+ end
48
52
  end
49
53
  end