decidim-core 0.27.2 → 0.27.4

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 (140) hide show
  1. checksums.yaml +4 -4
  2. data/app/cells/decidim/activities_cell.rb +1 -7
  3. data/app/cells/decidim/collapsible_list/show.erb +1 -1
  4. data/app/cells/decidim/content_blocks/last_activity_cell.rb +1 -4
  5. data/app/cells/decidim/tags_cell.rb +13 -2
  6. data/app/cells/decidim/upload_modal/files.erb +1 -0
  7. data/app/cells/decidim/upload_modal_cell.rb +14 -4
  8. data/app/commands/decidim/attachment_methods.rb +20 -2
  9. data/app/commands/decidim/create_omniauth_registration.rb +2 -2
  10. data/app/commands/decidim/create_registration.rb +1 -0
  11. data/app/commands/decidim/gallery_methods.rb +1 -1
  12. data/app/commands/decidim/update_account.rb +1 -0
  13. data/app/commands/decidim/update_password.rb +2 -0
  14. data/app/controllers/decidim/authorization_modals_controller.rb +1 -1
  15. data/app/controllers/decidim/devise/sessions_controller.rb +18 -2
  16. data/app/controllers/decidim/last_activities_controller.rb +1 -7
  17. data/app/controllers/decidim/links_controller.rb +8 -11
  18. data/app/controllers/decidim/short_links_controller.rb +1 -1
  19. data/app/forms/decidim/notifications_settings_form.rb +1 -1
  20. data/app/forms/url_validator.rb +1 -1
  21. data/app/helpers/decidim/cells_helper.rb +1 -0
  22. data/app/helpers/decidim/external_domain_helper.rb +14 -3
  23. data/app/helpers/decidim/layout_helper.rb +4 -1
  24. data/app/helpers/decidim/layout_helper.rb.orig +225 -0
  25. data/app/helpers/decidim/sanitize_helper.rb +3 -2
  26. data/app/models/decidim/organization.rb +6 -0
  27. data/app/models/decidim/scope_type.rb +24 -0
  28. data/app/models/decidim/user.rb +4 -2
  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/input_character_counter.js +1 -1
  33. data/app/packs/src/decidim/map/controller/drag_marker.js +0 -2
  34. data/app/packs/src/decidim/map/controller/markers.js +0 -1
  35. data/app/packs/src/decidim/map/controller/static.js +0 -1
  36. data/app/packs/src/decidim/map/controller.js +0 -2
  37. data/app/packs/src/decidim/map/factory.js +4 -1
  38. data/app/packs/src/decidim/map/icon.js +0 -1
  39. data/app/packs/src/decidim/map/legacy.js +0 -1
  40. data/app/packs/src/decidim/map/provider/default.js +2 -0
  41. data/app/packs/src/decidim/map/provider/here.js +2 -1
  42. data/app/packs/stylesheets/decidim/modules/_buttons.scss +10 -6
  43. data/app/packs/stylesheets/decidim/modules/_cards.scss +1 -1
  44. data/app/packs/stylesheets/decidim/modules/_comments.scss +24 -0
  45. data/app/packs/stylesheets/decidim/modules/_dropdown_menu.scss +9 -0
  46. data/app/packs/stylesheets/decidim/modules/_input-gallery.scss +2 -1
  47. data/app/packs/stylesheets/decidim/modules/_upload_modal.scss +0 -4
  48. data/app/packs/stylesheets/decidim/vizzs/_linechart.scss +2 -2
  49. data/app/packs/stylesheets/decidim/vizzs/_rowchart.scss +2 -2
  50. data/app/presenters/decidim/notification_presenter.rb +1 -1
  51. data/app/presenters/decidim/notification_to_mailer_presenter.rb +1 -0
  52. data/app/presenters/decidim/user_group_presenter.rb +1 -1
  53. data/app/presenters/decidim/user_presenter.rb +1 -1
  54. data/app/queries/decidim/last_activity.rb +96 -0
  55. data/app/queries/decidim/metrics/users_metric_manage.rb +6 -6
  56. data/app/queries/decidim/public_activities.rb +5 -57
  57. data/app/scrubbers/decidim/admin_input_scrubber.rb +3 -1
  58. data/app/scrubbers/decidim/user_input_scrubber.rb +30 -1
  59. data/app/services/decidim/email_notification_generator.rb +7 -1
  60. data/app/services/decidim/send_push_notification.rb +1 -1
  61. data/app/services/decidim/traceability.rb +1 -0
  62. data/app/uploaders/decidim/application_uploader.rb +2 -0
  63. data/app/validators/uploader_image_dimensions_validator.rb +22 -2
  64. data/app/views/decidim/devise/registrations/new.html.erb.orig +231 -0
  65. data/app/views/decidim/links/_invalid_url_modal.html.erb +17 -0
  66. data/app/views/decidim/links/_modal.html.erb +1 -1
  67. data/app/views/decidim/links/invalid_url.js.erb +24 -0
  68. data/app/views/decidim/links/new.html.erb +1 -1
  69. data/app/views/decidim/messaging/conversations/_conversation.html.erb +1 -5
  70. data/config/environment.rb +0 -0
  71. data/config/locales/ar.yml +567 -4
  72. data/config/locales/bg.yml +5 -4
  73. data/config/locales/ca.yml +27 -22
  74. data/config/locales/cs.yml +41 -33
  75. data/config/locales/da.yml +4 -0
  76. data/config/locales/de.yml +66 -21
  77. data/config/locales/el.yml +147 -2
  78. data/config/locales/en.yml +17 -13
  79. data/config/locales/eo.yml +5 -1
  80. data/config/locales/es-MX.yml +22 -17
  81. data/config/locales/es-PY.yml +24 -19
  82. data/config/locales/es.yml +26 -21
  83. data/config/locales/et.yml +4 -0
  84. data/config/locales/eu.yml +154 -63
  85. data/config/locales/fa-IR.yml +1 -0
  86. data/config/locales/fi-plain.yml +17 -12
  87. data/config/locales/fi.yml +20 -15
  88. data/config/locales/fr-CA.yml +29 -21
  89. data/config/locales/fr.yml +28 -20
  90. data/config/locales/ga-IE.yml +5 -0
  91. data/config/locales/gl.yml +8 -22
  92. data/config/locales/gn-PY.yml +4 -0
  93. data/config/locales/hr.yml +4 -0
  94. data/config/locales/hu.yml +82 -20
  95. data/config/locales/id-ID.yml +10 -4
  96. data/config/locales/is-IS.yml +5 -2
  97. data/config/locales/it.yml +9 -9
  98. data/config/locales/ja.yml +18 -14
  99. data/config/locales/ka-GE.yml +4 -0
  100. data/config/locales/kaa.yml +11 -0
  101. data/config/locales/lb.yml +12 -12
  102. data/config/locales/lt.yml +1 -35
  103. data/config/locales/lv.yml +4 -3
  104. data/config/locales/nl.yml +5 -27
  105. data/config/locales/no.yml +9 -27
  106. data/config/locales/oc-FR.yml +3 -0
  107. data/config/locales/pl.yml +4 -34
  108. data/config/locales/pt-BR.yml +7 -7
  109. data/config/locales/pt.yml +5 -5
  110. data/config/locales/ro-RO.yml +40 -4
  111. data/config/locales/ru.yml +3 -3
  112. data/config/locales/sk.yml +22 -10
  113. data/config/locales/sl.yml +1 -0
  114. data/config/locales/sr-CS.yml +10 -0
  115. data/config/locales/sv.yml +11 -33
  116. data/config/locales/tr-TR.yml +7 -7
  117. data/config/locales/uk.yml +3 -3
  118. data/config/locales/zh-CN.yml +4 -4
  119. data/config/locales/zh-TW.yml +1886 -0
  120. data/db/migrate/20181030090144_destroy_deleted_users_follows.rb +1 -1
  121. data/db/migrate/20181204110723_remove_following_users_count_from_users.rb +11 -2
  122. data/db/migrate/20181214101250_add_notification_types_to_users.rb +6 -1
  123. data/db/migrate/20190412131728_fix_user_names.rb +9 -2
  124. data/db/migrate/20200211173227_add_direct_message_types_to_users.rb +6 -1
  125. data/db/migrate/20210302150803_invalidate_all_sessions_for_deleted_users.rb +10 -3
  126. data/db/migrate/20210310120640_add_followable_counter_cache_to_users.rb +13 -3
  127. data/db/seeds.rb +4 -3
  128. data/lib/decidim/asset_router/pipeline.rb +2 -0
  129. data/lib/decidim/core/test/shared_examples/comments_examples.rb +36 -0
  130. data/lib/decidim/core/test/shared_examples/digest_mail_examples.rb +33 -0
  131. data/lib/decidim/core/test/shared_examples/editor_shared_examples.rb +5 -4
  132. data/lib/decidim/core/test/shared_examples/map_examples.rb +4 -1
  133. data/lib/decidim/core/test/shared_examples/rich_text_editor_examples.rb +7 -3
  134. data/lib/decidim/core/test.rb +1 -0
  135. data/lib/decidim/core/version.rb +1 -1
  136. data/lib/decidim/core.rb +17 -0
  137. data/lib/decidim/form_builder.rb +10 -16
  138. data/lib/decidim/publicable.rb +4 -0
  139. data/lib/tasks/upgrade/decidim_user_moderation.rake +14 -0
  140. metadata +23 -12
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 391ba34f55c860208f7644dd10b1b3e2b6465ed45b9e9f9fd194ce1036516995
4
- data.tar.gz: 93636f8d556d73547fcdff45986fcc85ba4a22e9c399fdeca0c550ee6c241363
3
+ metadata.gz: d976829c2218ab34a1bf7c2ad99443edd61c4f37bb56963c8bb21a3e74afddef
4
+ data.tar.gz: acbaa2ce577af7e32648ce22941616e258f48bdbfb304e754344770fafaf9ef4
5
5
  SHA512:
6
- metadata.gz: 157c005fbea98fe374586f91f15f17bc54450d6e4b6f7136d51582d5cae7af9442d250edbbf4fe2a4308aac902ba0eb28c49681153dcda6b6948f8589414e930
7
- data.tar.gz: 8ebc9089ad86980956ded3024d490f419bf8015ae0dc4b3fe6e003d415010de8ed28751bd0c168cf526c8a17f5db50467c9223242c2150cf600304990e1eda04
6
+ metadata.gz: 868d324e0f83442b37750933c39985dffdba9bfd06a2f47ad00836b7c9b89164bafff5d2083d5e24d7f01eb9c47dac46f564a375bacaa17726fe5d472bf6c86b
7
+ data.tar.gz: 1af449d8a281aa2ae5db6c0f03a7be077f7a7eeaa2a300e8bc2a2d36622cd4be5aafb4245878a24e4bd2496542d0166a84287974e72cdeb294acdcd021144b00
@@ -27,13 +27,7 @@ module Decidim
27
27
  end
28
28
 
29
29
  def activities
30
- @activities ||= last_activities.select do |activity|
31
- activity.visible_for?(current_user)
32
- end
33
- end
34
-
35
- def last_activities
36
- @last_activities ||= model.map do |activity|
30
+ @activities ||= model.map do |activity|
37
31
  activity.organization_lazy
38
32
  activity.resource_lazy
39
33
  activity.participatory_space_lazy
@@ -16,7 +16,7 @@
16
16
  </span>
17
17
  </div>
18
18
  <% else %>
19
- <div class="collapsible-list <%= list_class %>">
19
+ <div class="<%= list_class %>">
20
20
  <% list.each do |element| %>
21
21
  <% if cell_name %>
22
22
  <%= cell cell_name, element, cell_options %>
@@ -56,10 +56,7 @@ module Decidim
56
56
  end
57
57
 
58
58
  def activities
59
- @activities ||= ActionLog.where(
60
- organization: current_organization,
61
- visibility: %w(public-only all)
62
- ).with_new_resource_type("all").order(created_at: :desc).limit(activities_to_show * 6)
59
+ @activities ||= Decidim::LastActivity.new(current_organization, current_user: current_user).query.limit(activities_to_show * 6)
63
60
  end
64
61
 
65
62
  def activities_to_show
@@ -55,7 +55,7 @@ module Decidim
55
55
  end
56
56
 
57
57
  def category_path
58
- resource_locator(model).index(filter: { category_id: [model.category.id.to_s] })
58
+ resource_locator(model).index(filter: { filter_param(:category) => [model.category.id.to_s] })
59
59
  end
60
60
 
61
61
  def scope?
@@ -86,7 +86,18 @@ module Decidim
86
86
  end
87
87
 
88
88
  def scope_path
89
- resource_locator(model).index(filter: { scope_id: [model.scope.id] })
89
+ resource_locator(model).index(filter: { filter_param(:scope) => [model.scope.id] })
90
+ end
91
+
92
+ def filter_param(name)
93
+ candidates = ["with_any_#{name}".to_sym, "with_#{name}".to_sym]
94
+ return candidates.first unless controller.respond_to?(:default_filter_params, true)
95
+
96
+ available_params = controller.send(:default_filter_params)
97
+ candidates.each do |candidate|
98
+ return candidate if available_params.has_key?(candidate)
99
+ end
100
+ candidates.first
90
101
  end
91
102
  end
92
103
  end
@@ -30,6 +30,7 @@
30
30
  add_attribute: add_attribute,
31
31
  resource_name: resource_name,
32
32
  resource_class: resource_class,
33
+ required: required?,
33
34
  optional: optional,
34
35
  max_file_size: max_file_size,
35
36
  multiple: multiple,
@@ -5,6 +5,7 @@ module Decidim
5
5
  class UploadModalCell < Decidim::ViewModel
6
6
  include Cell::ViewModel::Partial
7
7
  include ERB::Util
8
+ include Decidim::SanitizeHelper
8
9
 
9
10
  alias form model
10
11
 
@@ -29,7 +30,7 @@ module Decidim
29
30
  end
30
31
 
31
32
  def label
32
- options[:label]
33
+ form.send(:custom_label, attribute, options[:label], { required: required?, for: nil })
33
34
  end
34
35
 
35
36
  def button_label
@@ -72,8 +73,17 @@ module Decidim
72
73
  options[:multiple] || false
73
74
  end
74
75
 
76
+ # @deprecated Please use `required?` instead.
77
+ #
78
+ # NOTE: When this is removed, also the `optional` option should be removed.
75
79
  def optional
76
- options[:optional]
80
+ !required?
81
+ end
82
+
83
+ def required?
84
+ return !options[:optional] if options.has_key?(:optional)
85
+
86
+ options[:required] == true
77
87
  end
78
88
 
79
89
  # By default Foundation adds form errors next to input, but since input is in the modal
@@ -81,7 +91,7 @@ module Decidim
81
91
  # This should only be necessary when file is required by the form.
82
92
  def input_validation_field
83
93
  object_name = form.object.present? ? "#{form.object.model_name.param_key}[#{add_attribute}_validation]" : "#{add_attribute}_validation"
84
- input = check_box_tag object_name, 1, attachments.present?, class: "hide", label: false, required: !optional
94
+ input = check_box_tag object_name, 1, attachments.present?, class: "hide", label: false, required: required?
85
95
  message = form.send(:abide_error_element, add_attribute) + form.send(:error_and_help_text, add_attribute)
86
96
  input + message
87
97
  end
@@ -142,7 +152,7 @@ module Decidim
142
152
  def title_for(attachment)
143
153
  return unless has_title?
144
154
 
145
- translated_attribute(attachment.title)
155
+ decidim_html_escape(decidim_sanitize(translated_attribute(attachment.title)))
146
156
  end
147
157
 
148
158
  def truncated_file_name_for(attachment, max_length = 31)
@@ -12,8 +12,8 @@ module Decidim
12
12
  @attachment = Attachment.new(
13
13
  title: { I18n.locale => @form.attachment.title },
14
14
  attached_to: attached_to,
15
- file: @form.attachment.file, # Define attached_to before this
16
- content_type: @form.attachment.file.content_type
15
+ file: signed_id_for(@form.attachment.file),
16
+ content_type: content_type_for(@form.attachment.file)
17
17
  )
18
18
  end
19
19
 
@@ -53,5 +53,23 @@ module Decidim
53
53
  def delete_attachment?
54
54
  @form.attachment&.delete_file.present?
55
55
  end
56
+
57
+ protected
58
+
59
+ def signed_id_for(attachment)
60
+ return attachment[:file] if attachment.is_a?(Hash)
61
+
62
+ attachment
63
+ end
64
+
65
+ def content_type_for(attachment)
66
+ return attachment.content_type if attachment.instance_of?(ActionDispatch::Http::UploadedFile)
67
+
68
+ blob(signed_id_for(attachment)).content_type
69
+ end
70
+
71
+ def blob(signed_id)
72
+ ActiveStorage::Blob.find_signed(signed_id)
73
+ end
56
74
  end
57
75
  end
@@ -46,8 +46,6 @@ module Decidim
46
46
  attr_reader :form, :verified_email
47
47
 
48
48
  def create_or_find_user
49
- generated_password = SecureRandom.hex
50
-
51
49
  @user = User.find_or_initialize_by(
52
50
  email: verified_email,
53
51
  organization: organization
@@ -59,6 +57,8 @@ module Decidim
59
57
  # to be marked confirmed.
60
58
  @user.skip_confirmation! if !@user.confirmed? && @user.email == verified_email
61
59
  else
60
+ generated_password = SecureRandom.hex
61
+
62
62
  @user.email = (verified_email || form.email)
63
63
  @user.name = form.name
64
64
  @user.nickname = form.normalized_nickname
@@ -41,6 +41,7 @@ module Decidim
41
41
  nickname: form.nickname,
42
42
  password: form.password,
43
43
  password_confirmation: form.password_confirmation,
44
+ password_updated_at: Time.current,
44
45
  organization: form.current_organization,
45
46
  tos_agreement: form.tos_agreement,
46
47
  newsletter_notifications_at: form.newsletter_at,
@@ -24,7 +24,7 @@ module Decidim
24
24
  end
25
25
 
26
26
  def update_attachment_title_for(photo)
27
- Decidim::Attachment.find(photo[:id]).update(title: title_for(photo))
27
+ Decidim::Attachment.find(photo[:id]).update(title: photos_title(photo))
28
28
  end
29
29
 
30
30
  def image?(signed_id)
@@ -55,6 +55,7 @@ module Decidim
55
55
 
56
56
  @user.password = @form.password
57
57
  @user.password_confirmation = @form.password_confirmation
58
+ @user.password_updated_at = Time.current
58
59
  end
59
60
 
60
61
  def notify_followers
@@ -16,6 +16,8 @@ module Decidim
16
16
  return broadcast(:invalid) if form.invalid?
17
17
 
18
18
  user.password = form.password
19
+ user.password_confirmation = form.password
20
+ user.password_updated_at = Time.current
19
21
 
20
22
  if user.save
21
23
  broadcast(:ok)
@@ -17,7 +17,7 @@ module Decidim
17
17
  end
18
18
 
19
19
  def current_component
20
- @current_component ||= Decidim::Component.find(params[:component_id])
20
+ @current_component ||= Decidim::Component.where(participatory_space: current_organization.participatory_spaces).find(params[:component_id])
21
21
  end
22
22
 
23
23
  def authorization_action
@@ -6,9 +6,25 @@ module Decidim
6
6
  class SessionsController < ::Devise::SessionsController
7
7
  include Decidim::DeviseControllers
8
8
 
9
- # rubocop: disable Rails/LexicallyScopedActionFilter
10
9
  before_action :check_sign_in_enabled, only: :create
11
- # rubocop: enable Rails/LexicallyScopedActionFilter
10
+
11
+ def create
12
+ super do |user|
13
+ if user.admin?
14
+ # Check that the admin password passes the validation and clear the
15
+ # `password_updated_at` field when the password is weak to force a
16
+ # password update on the user.
17
+ #
18
+ # Handles a case when the user registers through the registration
19
+ # form and they are promoted to an admin after that. In this case,
20
+ # the newly promoted admin user would otherwise have to change their
21
+ # password straight away even if they originally registered with a
22
+ # strong password.
23
+ validator = PasswordValidator.new({ attributes: :password })
24
+ user.update!(password_updated_at: nil) unless validator.validate_each(user, :password, sign_in_params[:password])
25
+ end
26
+ end
27
+ end
12
28
 
13
29
  def destroy
14
30
  current_user.invalidate_all_sessions!
@@ -32,13 +32,7 @@ module Decidim
32
32
  end
33
33
 
34
34
  def search_collection
35
- ActionLog
36
- .where(
37
- organization: current_organization,
38
- visibility: %w(public-only all)
39
- )
40
- .with_new_resource_type("all")
41
- .order(created_at: :desc)
35
+ LastActivity.new(current_organization, current_user: current_user).query
42
36
  end
43
37
 
44
38
  def default_filter_params
@@ -21,24 +21,21 @@ module Decidim
21
21
 
22
22
  def invalid_url
23
23
  flash[:alert] = I18n.t("decidim.links.invalid_url")
24
- redirect_to decidim.root_path
24
+ if request.xhr?
25
+ render "invalid_url"
26
+ else
27
+ redirect_to decidim.root_path
28
+ end
25
29
  end
26
30
 
27
31
  def parse_url
32
+ raise Decidim::InvalidUrlError if params[:external_url].blank?
28
33
  raise Decidim::InvalidUrlError unless external_url
29
-
30
- parts = external_url.match %r{\A(([a-z]+):)?//([^/]+)(/.*)?\z}
31
- raise Decidim::InvalidUrlError unless parts
32
-
33
- @url_parts = {
34
- protocol: parts[1],
35
- domain: parts[3],
36
- path: parts[4]
37
- }
34
+ raise Decidim::InvalidUrlError unless %w(http https).include?(external_url.scheme)
38
35
  end
39
36
 
40
37
  def external_url
41
- @external_url ||= URI.parse(params[:external_url]).to_s
38
+ @external_url ||= URI.parse(params[:external_url])
42
39
  end
43
40
  end
44
41
  end
@@ -29,7 +29,7 @@ module Decidim
29
29
  #
30
30
  # @return [Decidim::ShortLink] The short link matching the identifier
31
31
  def link
32
- @link ||= Decidim::ShortLink.find_by(identifier: params[:id])
32
+ @link ||= Decidim::ShortLink.find_by(identifier: params[:id], organization: current_organization)
33
33
  end
34
34
  end
35
35
  end
@@ -53,7 +53,7 @@ module Decidim
53
53
  end
54
54
 
55
55
  def meet_push_notifications_requirements?
56
- Rails.application.secrets.vapid[:enabled]
56
+ Rails.application.secrets.dig(:vapid, :enabled) || false
57
57
  end
58
58
  end
59
59
  end
@@ -6,7 +6,7 @@
6
6
  #
7
7
  class UrlValidator < ActiveModel::EachValidator
8
8
  def validate_each(record, attribute, value)
9
- record.errors.add attribute, (options[:message] || "must be a valid URL") unless url_valid?(value)
9
+ record.errors.add attribute, :url_format, **options unless url_valid?(value)
10
10
  end
11
11
 
12
12
  # a URL may be technically well-formed but may
@@ -35,6 +35,7 @@ module Decidim
35
35
  end
36
36
 
37
37
  def user_flaggable?
38
+ return if (try(:profile_holder) || try(:profile_user) || try(:model)).try(:blocked)
38
39
  return unless context[:controller].try(:flaggable_controller?)
39
40
 
40
41
  true
@@ -3,10 +3,21 @@
3
3
  module Decidim
4
4
  module ExternalDomainHelper
5
5
  def highlight_domain
6
+ highlighted_domain = [
7
+ external_url.host,
8
+ (external_url.port && [80, 443].include?(external_url.port) ? "" : ":#{external_url.port}")
9
+ ].join
10
+
11
+ path = [
12
+ external_url.path,
13
+ (external_url.query ? "?#{external_url.query}" : ""),
14
+ (external_url.fragment ? "##{external_url.fragment}" : "")
15
+ ].join
16
+
6
17
  tag.div do
7
- content_tag(:span, "#{@url_parts[:protocol]}//") +
8
- content_tag(:span, @url_parts[:domain], class: "alert") +
9
- content_tag(:span, @url_parts[:path])
18
+ content_tag(:span, "#{external_url.scheme}://") +
19
+ content_tag(:span, highlighted_domain, class: "text-alert") +
20
+ content_tag(:span, path)
10
21
  end
11
22
  end
12
23
  end
@@ -105,7 +105,10 @@ module Decidim
105
105
  # non-nil because otherwise it will be set to the asset host at
106
106
  # ActionView::Helpers::AssetUrlHelper#compute_asset_host.
107
107
  img_path = asset_pack_path(path, host: "", protocol: :relative)
108
- Rails.public_path.join(img_path.sub(%r{^/}, ""))
108
+ path = Rails.public_path.join(img_path.sub(%r{^/}, ""))
109
+ return unless File.exist?(path)
110
+
111
+ path
109
112
  rescue ::Webpacker::Manifest::MissingEntryError
110
113
  nil
111
114
  end
@@ -0,0 +1,225 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Decidim
4
+ # View helpers related to the layout.
5
+ module LayoutHelper
6
+ include Decidim::ModalHelper
7
+ include Decidim::TooltipHelper
8
+
9
+ # Public: Generates a set of meta tags that generate the different favicon
10
+ # versions for an organization.
11
+ #
12
+ # Returns a safe String with the versions.
13
+ def favicon
14
+ return if current_organization.favicon.blank?
15
+
16
+ safe_join(Decidim::OrganizationFaviconUploader::SIZES.map do |version, size|
17
+ favicon_link_tag(current_organization.attached_uploader(:favicon).variant_url(version, host: current_organization.host), sizes: "#{size}x#{size}")
18
+ end)
19
+ end
20
+
21
+ def apple_favicon
22
+ icon_image = current_organization.attached_uploader(:favicon).variant_url(:medium, host: current_organization.host)
23
+ return unless icon_image
24
+
25
+ favicon_link_tag(icon_image, rel: "apple-touch-icon", type: "image/png")
26
+ end
27
+
28
+ def legacy_favicon
29
+ variant = :favicon if current_organization.favicon.content_type != "image/vnd.microsoft.icon"
30
+ icon_image = current_organization.attached_uploader(:favicon).variant_url(variant, host: current_organization.host)
31
+ return unless icon_image
32
+
33
+ favicon_link_tag(icon_image, rel: "icon", sizes: "any", type: nil)
34
+ end
35
+
36
+ # Outputs an SVG-based icon.
37
+ #
38
+ # name - The String with the icon name.
39
+ # options - The Hash options used to customize the icon (default {}):
40
+ # :width - The Number of width in pixels (optional).
41
+ # :height - The Number of height in pixels (optional).
42
+ # :title - The title for the SVG element (optional, similar to alt for img)
43
+ # :aria_label - The String to set as aria label (optional).
44
+ # :aria_hidden - The Truthy value to enable aria_hidden (optional).
45
+ # :role - The String to set as the role (optional).
46
+ # :class - The String to add as a CSS class (optional).
47
+ #
48
+ # Returns a String.
49
+ def redesigned_icon(name, options = {})
50
+ default_html_properties = {
51
+ "width" => "1em",
52
+ "height" => "1em",
53
+ "role" => "img",
54
+ "aria-hidden" => "true"
55
+ }
56
+
57
+ html_properties = options.with_indifferent_access.transform_keys(&:dasherize).slice("width", "height", "aria-label", "role", "aria-hidden", "class", "style")
58
+ html_properties = default_html_properties.merge(html_properties)
59
+
60
+ href = Decidim.cors_enabled ? "" : asset_pack_path("media/images/remixicon.symbol.svg")
61
+
62
+ content_tag :svg, html_properties do
63
+ content_tag :use, nil, "href" => "#{href}#ri-#{name}", tabindex: -1
64
+ end
65
+ end
66
+
67
+ def legacy_icon(name, options = {})
68
+ options = options.with_indifferent_access
69
+ html_properties = {}
70
+
71
+ html_properties["width"] = options[:width]
72
+ html_properties["height"] = options[:height]
73
+ html_properties["aria-label"] = options[:aria_label] || options[:"aria-label"]
74
+ html_properties["role"] = options[:role] || "img"
75
+ html_properties["aria-hidden"] = options[:aria_hidden] || options[:"aria-hidden"]
76
+
77
+ html_properties["class"] = (["icon--#{name}"] + _icon_classes(options)).join(" ")
78
+
79
+ title = options["title"] || html_properties["aria-label"]
80
+ if title.blank? && html_properties["role"] == "img"
81
+ # This will make the accessibility audit tools happy as with the "img"
82
+ # role, the alternative text (aria-label) and title are required for the
83
+ # element. This will also force the SVG to be hidden because otherwise
84
+ # the screen reader would announce the icon name which can be in
85
+ # different language (English) than the page language which is not
86
+ # allowed.
87
+ title = name
88
+ html_properties["aria-label"] = title
89
+ html_properties["aria-hidden"] = true
90
+ end
91
+
92
+ href = Decidim.cors_enabled ? "" : asset_pack_path("media/images/icons.svg")
93
+
94
+ content_tag :svg, html_properties do
95
+ inner = content_tag :title, title
96
+ inner += content_tag :use, nil, "href" => "#{href}#icon-#{name}"
97
+
98
+ inner
99
+ end
100
+ end
101
+
102
+ def icon(*args)
103
+ redesign_enabled? ? redesigned_icon(*args) : legacy_icon(*args)
104
+ end
105
+
106
+ # Outputs a SVG icon from an external file. It apparently renders an image
107
+ # tag, but then a JS script kicks in and replaces it with an inlined SVG
108
+ # version.
109
+ #
110
+ # path - The asset's path
111
+ #
112
+ # Returns an <img /> tag with the SVG icon.
113
+ def external_icon(path, options = {})
114
+ classes = _icon_classes(options) + ["external-icon"]
115
+
116
+ if path.split(".").last == "svg"
117
+ icon_path = application_path(path)
118
+ return unless icon_path
119
+
120
+ attributes = { class: classes.join(" ") }.merge(options)
121
+ asset = File.read(icon_path)
122
+ asset.gsub("<svg ", "<svg#{tag_builder.tag_options(attributes)} ").html_safe
123
+ else
124
+ image_pack_tag(path, class: classes.join(" "), style: "display: none")
125
+ end
126
+ end
127
+
128
+ def application_path(path)
129
+ # Force the path to be returned without the protocol and host even when a
130
+ # custom asset host has been defined. The host parameter needs to be a
131
+ # non-nil because otherwise it will be set to the asset host at
132
+ # ActionView::Helpers::AssetUrlHelper#compute_asset_host.
133
+ img_path = asset_pack_path(path, host: "", protocol: :relative)
134
+ path = Rails.public_path.join(img_path.sub(%r{^/}, ""))
135
+ return unless File.exist?(path)
136
+
137
+ path
138
+ rescue ::Webpacker::Manifest::MissingEntryError
139
+ nil
140
+ end
141
+
142
+ # Allows to create role attribute according to accessibility rules
143
+ #
144
+ # Returns role attribute string if role option is specified
145
+ def role(options = {})
146
+ "role=\"#{options[:role]}\" " if options[:role]
147
+ end
148
+
149
+ def _icon_classes(options = {})
150
+ classes = options[:remove_icon_class] ? [] : ["icon"]
151
+ classes += [options[:class]]
152
+ classes.compact
153
+ end
154
+
155
+ def extended_navigation_bar(items, max_items: 5)
156
+ return unless items.any?
157
+
158
+ extra_items = items.slice((max_items + 1)..-1) || []
159
+ active_item = items.find { |item| item[:active] }
160
+
161
+ controller.view_context.render partial: "decidim/shared/extended_navigation_bar", locals: {
162
+ items:,
163
+ extra_items:,
164
+ active_item:,
165
+ max_items:
166
+ }
167
+ end
168
+
169
+ # Renders a view with the customizable CSS variables in two flavours:
170
+ # 1. as a hexadecimal valid CSS color (ie: #ff0000)
171
+ # 2. as a disassembled RGB components (ie: 255 0 0)
172
+ #
173
+ # Example:
174
+ #
175
+ # --primary: #ff0000;
176
+ # --primary-rgb: 255,0,0
177
+ #
178
+ # Hexadecimal variables can be used as a normal CSS color:
179
+ #
180
+ # color: var(--primary)
181
+ #
182
+ # While the disassembled variant can be used where you need to manipulate
183
+ # the color somehow (ie: adding a background transparency):
184
+ #
185
+ # background-color: rgba(var(--primary-rgb), 0.5)
186
+ def organization_colors
187
+ css = current_organization.colors.each.map { |k, v| "--#{k}: #{v};--#{k}-rgb: #{v[1..2].hex} #{v[3..4].hex} #{v[5..6].hex};" }.join
188
+ render partial: "layouts/decidim/organization_colors", locals: { css: }
189
+ end
190
+
191
+ <<<<<<< HEAD
192
+ def current_user_unread_data
193
+ return {} if current_user.blank?
194
+
195
+ {}.tap do |d|
196
+ d.merge!(unread_notifications: true) if current_user.notifications.any?
197
+ d.merge!(unread_conversations: true) if current_user.unread_conversations.any?
198
+ d.merge!(unread_items: d.present?)
199
+ end
200
+ end
201
+
202
+ ||||||| parent of 53b6893e5c (Use local emojibase data instead of CDN)
203
+ =======
204
+ # Public: Gets the name of the webpacker entrypoint that will be used
205
+ # for the locale of the Emojibase NPM package, used with @picmo/popup-picker
206
+ #
207
+ # Returns a string with the entrypoint name
208
+ def emojibase_entrypoint_locale
209
+ entrypoint = Decidim::Webpacker.configuration.entrypoints.keys.select do |entry|
210
+ entry == "decidim_emojibase_#{I18n.locale}"
211
+ end
212
+
213
+ return "decidim_emojibase_en" if entrypoint.empty?
214
+
215
+ entrypoint.first
216
+ end
217
+
218
+ >>>>>>> 53b6893e5c (Use local emojibase data instead of CDN)
219
+ private
220
+
221
+ def tag_builder
222
+ @tag_builder ||= ActionView::Helpers::TagHelper::TagBuilder.new(self)
223
+ end
224
+ end
225
+ end
@@ -112,9 +112,10 @@ module Decidim
112
112
  #
113
113
  # @return ActiveSupport::SafeBuffer
114
114
  def render_sanitized_content(resource, method)
115
- content = present(resource).send(method, links: true, strip_tags: !safe_content?)
115
+ content = present(resource).send(method, links: true, strip_tags: !try(:safe_content?))
116
116
 
117
- return decidim_sanitize(content, {}) unless safe_content?
117
+ return decidim_sanitize(content, {}) unless try(:safe_content?)
118
+ return decidim_sanitize_editor_admin(content, {}) if try(:safe_content_admin?)
118
119
 
119
120
  decidim_sanitize_editor(content)
120
121
  end
@@ -87,6 +87,12 @@ module Decidim
87
87
  @top_scopes ||= scopes.top_level
88
88
  end
89
89
 
90
+ def participatory_spaces
91
+ @participatory_spaces ||= Decidim.participatory_space_manifests.flat_map do |manifest|
92
+ manifest.participatory_spaces.call(self)
93
+ end
94
+ end
95
+
90
96
  def public_participatory_spaces
91
97
  @public_participatory_spaces ||= Decidim.participatory_space_manifests.flat_map do |manifest|
92
98
  manifest.participatory_spaces.call(self).public_spaces