decidim-core 0.27.2 → 0.27.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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