decidim-core 0.28.3 → 0.28.5

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 (132) hide show
  1. checksums.yaml +4 -4
  2. data/app/cells/decidim/activity_cell.rb +1 -4
  3. data/app/cells/decidim/author/show.erb +5 -4
  4. data/app/cells/decidim/author_cell.rb +26 -0
  5. data/app/cells/decidim/card_s/show.erb +5 -3
  6. data/app/cells/decidim/content_blocks/stats_cell.rb +1 -1
  7. data/app/cells/decidim/diff_cell.rb +4 -0
  8. data/app/cells/decidim/endorsement_buttons_cell.rb +1 -1
  9. data/app/cells/decidim/nav_links/show.erb +3 -3
  10. data/app/cells/decidim/newsletter_templates/image_text_cta_cell.rb +1 -1
  11. data/app/cells/decidim/resource_types_filter/show.erb +11 -12
  12. data/app/cells/decidim/translation_bar/show.erb +3 -3
  13. data/app/cells/decidim/translation_bar_cell.rb +1 -1
  14. data/app/commands/decidim/amendable/create_draft.rb +1 -0
  15. data/app/commands/decidim/destroy_account.rb +3 -0
  16. data/app/controllers/concerns/decidim/devise_authentication_methods.rb +1 -1
  17. data/app/controllers/concerns/decidim/direct_upload.rb +82 -0
  18. data/app/controllers/decidim/doorkeeper/credentials_controller.rb +1 -1
  19. data/app/controllers/decidim/links_controller.rb +1 -1
  20. data/app/controllers/decidim/profiles_controller.rb +4 -0
  21. data/app/forms/decidim/upload_validation_form.rb +1 -1
  22. data/app/helpers/concerns/decidim/user_role_checker.rb +46 -0
  23. data/app/helpers/decidim/cta_button_helper.rb +1 -1
  24. data/app/helpers/decidim/layout_helper.rb +28 -0
  25. data/app/helpers/decidim/map_helper.rb +6 -1
  26. data/app/helpers/decidim/menu_helper.rb +1 -1
  27. data/app/helpers/decidim/sanitize_helper.rb +11 -2
  28. data/app/helpers/decidim/scopes_helper.rb +5 -2
  29. data/app/models/decidim/action_log.rb +11 -1
  30. data/app/models/decidim/attachment.rb +1 -1
  31. data/app/packs/src/decidim/a11y.js +11 -15
  32. data/app/packs/src/decidim/append_redirect_url_to_modals.js +24 -14
  33. data/app/packs/src/decidim/direct_uploads/upload_field.js +21 -8
  34. data/app/packs/src/decidim/direct_uploads/upload_modal.js +3 -0
  35. data/app/packs/src/decidim/index.js +3 -0
  36. data/app/packs/src/decidim/remote_tooltips.js +38 -0
  37. data/app/packs/src/decidim/toggle.js +1 -1
  38. data/app/packs/src/decidim/tooltips.js +42 -22
  39. data/app/packs/stylesheets/decidim/_buttons.scss +1 -1
  40. data/app/packs/stylesheets/decidim/_modal_update.scss +4 -0
  41. data/app/packs/stylesheets/decidim/_profile.scss +1 -1
  42. data/app/packs/stylesheets/decidim/_progress-bar.scss +1 -1
  43. data/app/packs/stylesheets/decidim/legacy/conference-diploma.scss +2 -1
  44. data/app/presenters/decidim/attachment_presenter.rb +1 -1
  45. data/app/presenters/decidim/menu_item_presenter.rb +1 -1
  46. data/app/queries/decidim/last_activity.rb +16 -5
  47. data/app/services/decidim/base_diff_renderer.rb +26 -2
  48. data/app/services/decidim/email_notification_generator.rb +14 -5
  49. data/app/views/decidim/devise/omniauth_registrations/new.html.erb +1 -1
  50. data/app/views/decidim/offline/show.html.erb +1 -1
  51. data/app/views/decidim/pages/_tabbed.html.erb +5 -5
  52. data/app/views/decidim/shared/_filters.html.erb +5 -5
  53. data/app/views/decidim/shared/_orders.html.erb +3 -2
  54. data/app/views/decidim/shared/filters/_check_boxes_tree.html.erb +1 -1
  55. data/app/views/decidim/shared/filters/_collection.html.erb +1 -1
  56. data/app/views/layouts/decidim/_head.html.erb +1 -1
  57. data/app/views/layouts/decidim/header/_menu_breadcrumb_mobile_tablet.html.erb +1 -1
  58. data/app/views/layouts/decidim/shared/_layout_user_profile.html.erb +2 -2
  59. data/config/locales/ar.yml +12 -1
  60. data/config/locales/bg.yml +0 -1
  61. data/config/locales/bn-BD.yml +1 -0
  62. data/config/locales/bs-BA.yml +98 -0
  63. data/config/locales/ca.yml +14 -10
  64. data/config/locales/cs.yml +6 -1
  65. data/config/locales/de.yml +18 -14
  66. data/config/locales/el.yml +3 -1
  67. data/config/locales/en.yml +5 -1
  68. data/config/locales/es-MX.yml +6 -2
  69. data/config/locales/es-PY.yml +6 -2
  70. data/config/locales/es.yml +12 -8
  71. data/config/locales/eu.yml +202 -185
  72. data/config/locales/fi-plain.yml +5 -1
  73. data/config/locales/fi.yml +40 -36
  74. data/config/locales/fr-CA.yml +6 -2
  75. data/config/locales/fr.yml +5 -1
  76. data/config/locales/ga-IE.yml +5 -0
  77. data/config/locales/gl.yml +4 -1
  78. data/config/locales/hu.yml +1 -2
  79. data/config/locales/id-ID.yml +4 -0
  80. data/config/locales/is-IS.yml +4 -1
  81. data/config/locales/it.yml +40 -0
  82. data/config/locales/ja.yml +18 -16
  83. data/config/locales/lb.yml +5 -0
  84. data/config/locales/lt.yml +1 -2
  85. data/config/locales/lv.yml +4 -0
  86. data/config/locales/nl.yml +6 -1
  87. data/config/locales/no.yml +5 -0
  88. data/config/locales/pl.yml +1 -2
  89. data/config/locales/pt-BR.yml +199 -1
  90. data/config/locales/pt.yml +11 -0
  91. data/config/locales/ro-RO.yml +302 -180
  92. data/config/locales/ru.yml +4 -0
  93. data/config/locales/sk.yml +5 -1
  94. data/config/locales/sv.yml +452 -81
  95. data/config/locales/tr-TR.yml +6 -1
  96. data/config/locales/uk.yml +4 -1
  97. data/config/locales/zh-CN.yml +5 -0
  98. data/config/locales/zh-TW.yml +4 -1
  99. data/config/routes.rb +1 -0
  100. data/decidim-core.gemspec +4 -1
  101. data/lib/decidim/api/functions/component_list.rb +1 -1
  102. data/lib/decidim/api/functions/participatory_space_finder_base.rb +11 -1
  103. data/lib/decidim/api/interfaces/participatory_space_interface.rb +1 -1
  104. data/lib/decidim/api/types/component_type.rb +7 -0
  105. data/lib/decidim/api/types/user_group_type.rb +4 -0
  106. data/lib/decidim/api/types/user_type.rb +4 -0
  107. data/lib/decidim/attributes/rich_text.rb +38 -0
  108. data/lib/decidim/attributes/time_with_zone.rb +11 -1
  109. data/lib/decidim/attributes.rb +2 -0
  110. data/lib/decidim/content_parsers/blob_parser.rb +93 -0
  111. data/lib/decidim/content_parsers.rb +1 -0
  112. data/lib/decidim/content_renderers/blob_renderer.rb +90 -0
  113. data/lib/decidim/content_renderers.rb +1 -0
  114. data/lib/decidim/core/engine.rb +35 -1
  115. data/lib/decidim/core/test/factories.rb +28 -0
  116. data/lib/decidim/core/test/shared_examples/authorable_interface_examples.rb +1 -1
  117. data/lib/decidim/core/test/shared_examples/comments_examples.rb +25 -2
  118. data/lib/decidim/core/test/shared_examples/system_endorse_resource_examples.rb +112 -14
  119. data/lib/decidim/core/version.rb +1 -1
  120. data/lib/decidim/core.rb +11 -0
  121. data/lib/decidim/diffy_extension.rb +18 -0
  122. data/lib/decidim/form_builder.rb +1 -1
  123. data/lib/decidim/map/autocomplete.rb +1 -0
  124. data/lib/decidim/organization_settings.rb +4 -1
  125. data/lib/decidim/participatory_space_user.rb +4 -0
  126. data/lib/decidim/query_extensions.rb +0 -26
  127. data/lib/decidim/settings_manifest.rb +2 -0
  128. data/lib/decidim/translatable_attributes.rb +6 -1
  129. data/lib/decidim/view_model.rb +1 -1
  130. data/lib/tasks/upgrade/decidim_attachments.rake +14 -0
  131. data/lib/tasks/upgrade/decidim_fix_categorization.rake +34 -8
  132. metadata +30 -7
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 7ed7b2e2e440e7ab68c508dc8242f6adbee52c292cfa8fd3fd43e42fc75e8f8f
4
- data.tar.gz: ee75d95c1002bc872483de673a2e21e23c100d387d2bd50d86bb05ed5a91d1fa
3
+ metadata.gz: 30504edbc5451f226b04213804ae8b02392132d8f66f9d5ebe9ea58ad264031e
4
+ data.tar.gz: 0ff217364122ef3812f7aa950e4908364126ceca46f135d0d5dc049b1ee19537
5
5
  SHA512:
6
- metadata.gz: d82a46910165f8510bec81d79677b26057d49e88ddc9ddd3fda2443b0d8529804faebb2eb8949313b4cf4c25fc8fd406da049dc2e33d172430d7614dc043b9cd
7
- data.tar.gz: e86a808c22abc5db470d2bcf7d9427a2d8ea13588a73fcbde936fbd22c381d606f30b45353e3638f39150e16966cf75edcf6d81698164b3bdef9055b1881a02f
6
+ metadata.gz: 897aec8c06d1dd2110a5bd880e07061cab5e12495b707ed175afa81468c5ac162cf3d7a6643879eab83a82a9ca76232ff82dcb0d9b29ccf277d328fec6539a35
7
+ data.tar.gz: 88e9607861160a270e3604733a323fc2b1d359779c335ff8ccce97c9e587062c9c916d07682ad0ddda2b50abe2f718d3ce4baeb5bf77cbf3aab77ea231160014
@@ -106,10 +106,7 @@ module Decidim
106
106
  hash << id_prefix
107
107
  hash << I18n.locale.to_s
108
108
  hash << model.class.name.underscore
109
- hash << model.cache_key_with_version
110
- if (author_cell = author)
111
- hash.push(Digest::MD5.hexdigest(author_cell.send(:cache_hash)))
112
- end
109
+ hash << model.cache_key_with_version if model.respond_to?(:cache_key_with_version)
113
110
 
114
111
  hash.join(Decidim.cache_key_separator)
115
112
  end
@@ -1,6 +1,7 @@
1
- <% data = has_tooltip? ? { tooltip: render(:profile_minicard).html_safe } : nil %>
2
- <span class="author" data-author>
3
- <%= content_tag :span, class: "author__container#{" is-compact" if layout == :compact}", data: do %>
1
+ <%# If the options hash has the demo key it means we are in the decidim-design engine, so it is not a real-world scenario with actual users %>
2
+ <% tooltip = options.has_key?(:demo) ? { tooltip: render(:profile_minicard).html_safe } : nil %>
3
+ <%= content_tag(:span, class: :author, data: ) do %>
4
+ <%= content_tag :span, class: "author__container#{" is-compact" if layout == :compact}", data: tooltip do %>
4
5
  <% if layout == :compact %>
5
6
  <%= render :avatar %>
6
7
 
@@ -24,4 +25,4 @@
24
25
  <%= render action %>
25
26
  <% end %>
26
27
  <% end %>
27
- </span>
28
+ <% end %>
@@ -49,8 +49,26 @@ module Decidim
49
49
  @context_actions_options ||= options[:context_actions].map(&:to_sym)
50
50
  end
51
51
 
52
+ def profile_minicard
53
+ render
54
+ end
55
+
52
56
  private
53
57
 
58
+ # If the options hash has the demo key it means we are in the decidim-design engine,
59
+ # so it is not a real-world scenario with actual users
60
+ def data
61
+ @data ||= begin
62
+ internal_data = { author: true }
63
+ if has_tooltip? && !options.has_key?(:demo)
64
+ internal_data["remote_tooltip"] = true
65
+ internal_data["tooltip-url"] = decidim.profile_tooltip_path(raw_model.nickname)
66
+ end
67
+
68
+ internal_data
69
+ end
70
+ end
71
+
54
72
  def layout
55
73
  @layout ||= LAYOUTS.include?(options[:layout]) ? options[:layout] : :default
56
74
  end
@@ -162,5 +180,13 @@ module Decidim
162
180
  def resource_name
163
181
  @resource_name ||= from_context.class.name.demodulize.underscore
164
182
  end
183
+
184
+ def has_tooltip?
185
+ return false if model.deleted?
186
+ return false if model.respond_to?(:blocked?) && model.blocked?
187
+ return true if options.has_key?(:tooltip)
188
+
189
+ model.has_tooltip?
190
+ end
165
191
  end
166
192
  end
@@ -1,10 +1,12 @@
1
- <%= link_to resource_path, class: "card__search", id: dom_id(resource) do %>
1
+ <%= content_tag :div, id: dom_id(resource), class: "card__search" do %>
2
2
  <%= content_tag title_tag, class: "h4 card__search-title" do %>
3
- <%= title %>
3
+ <%= link_to resource_path, class: "card__search" do %>
4
+ <%= title %>
5
+ <% end %>
4
6
  <% end %>
5
7
  <% if metadata_cell.present? %>
6
8
  <div class="card__search-metadata">
7
- <%= cell metadata_cell, resource, links: false %>
9
+ <%= cell metadata_cell, resource, links: false %>
8
10
  </div>
9
11
  <% end %>
10
12
  <% end %>
@@ -17,7 +17,7 @@ module Decidim
17
17
  end
18
18
 
19
19
  def cache_expiry_time
20
- 10.minutes
20
+ Decidim.stats_cache_expiry_time
21
21
  end
22
22
  end
23
23
  end
@@ -71,6 +71,10 @@ module Decidim
71
71
 
72
72
  # DiffRenderer class for the current_version's item; falls back to `BaseDiffRenderer`.
73
73
  def diff_renderer_class
74
+ renderer_class = "#{current_version.item_type}DiffRenderer".safe_constantize
75
+
76
+ return renderer_class if renderer_class
77
+
74
78
  if current_version.item_type.deconstantize == "Decidim"
75
79
  "#{current_version.item_type.pluralize}::DiffRenderer".constantize
76
80
  else
@@ -77,7 +77,7 @@ module Decidim
77
77
  end
78
78
 
79
79
  def user_has_verified_groups?
80
- current_user && Decidim::UserGroups::ManageableUserGroups.for(current_user).verified.any?
80
+ current_user && current_organization.user_groups_enabled? && Decidim::UserGroups::ManageableUserGroups.for(current_user).verified.any?
81
81
  end
82
82
 
83
83
  def endorse_translated
@@ -1,12 +1,12 @@
1
1
  <div class="participatory-space__nav-container">
2
- <button id="dropdown-trigger-participatory-space" data-component="dropdown" data-target="dropdown-menu-participatory-space" data-auto-close="true" data-scroll-to-menu="true">
2
+ <button id="dropdown-trigger-participatory-space" data-component="dropdown" data-target="dropdown-menu-participatory-space" data-auto-close="true" data-scroll-to-menu="true" data-open-md="true">
3
3
  <span><%= t("decidim.searches.filters.jump_to") %></span>
4
4
  <%= icon "arrow-down-s-line" %>
5
5
  <%= icon "arrow-up-s-line" %>
6
6
  </button>
7
- <ul id="dropdown-menu-participatory-space" class="participatory-space__nav" aria-hidden="true">
7
+ <ul id="dropdown-menu-participatory-space" class="participatory-space__nav">
8
8
  <% model.each do |item| %>
9
- <li>
9
+ <li role="menuitem">
10
10
  <%= link_to item[:url], class: "participatory-space__nav-item" do %>
11
11
  <%= item[:name] %>
12
12
  <%= icon "arrow-right-line" %>
@@ -50,7 +50,7 @@ module Decidim
50
50
  end
51
51
 
52
52
  def main_image_url
53
- newsletter.template.images_container.attached_uploader(:main_image).url(host: organization.host)
53
+ newsletter.template.images_container.attached_uploader(:main_image).url
54
54
  end
55
55
 
56
56
  def organization_primary_color
@@ -1,5 +1,5 @@
1
1
  <div id="<%= id %>" class="filter-container">
2
- <button id="dropdown-trigger-resource" data-component="dropdown" data-target="dropdown-menu-resource" data-auto-close="true">
2
+ <button id="dropdown-trigger-resource" data-component="dropdown" data-target="dropdown-menu-resource" data-open-md="true">
3
3
  <% resource_types.each do |resource_type| %>
4
4
  <span data-value="<%= resource_type[0] %>" class="<%= "is-active" if filter_param == resource_type[0] %>">
5
5
  <%= text_with_resource_icon(*resource_type) %>
@@ -8,15 +8,14 @@
8
8
  <%= icon "arrow-down-s-line" %>
9
9
  <%= icon "arrow-up-s-line" %>
10
10
  </button>
11
- <%= filter_form_for filter, form_path, :class => "new_filter", :id => "dropdown-menu-resource", "aria-hidden" => true do |form| %>
12
- <%= form.collection_radio_buttons(
13
- filter_param_key,
14
- resource_types,
15
- :first,
16
- :last,
17
- { checked: filter_param }
18
- ) do |builder|
19
- builder.label { builder.radio_button(class: "reset-defaults", hidden: true) + content_tag(:span, text_with_resource_icon(builder.value, builder.text), class: "filter") }
20
- end %>
21
- <% end %>
11
+ <ul id="dropdown-menu-resource">
12
+ <% resource_types.each do |resource_type| %>
13
+ <li role="menuitem">
14
+ <%= link_to decidim.last_activities_path(filter: { with_resource_type: resource_type[0] } ), class: "filter#{" is-active" if filter_param == resource_type[0]}" do %>
15
+ <span class="sr-only"><%= resource_type[1] %></span>
16
+ <%= text_with_resource_icon(*resource_type) %>
17
+ <% end %>
18
+ </li>
19
+ <% end %>
20
+ </ul>
22
21
  </div>
@@ -1,6 +1,6 @@
1
- <div class="wrapper-mini translation-bar">
2
- <div class="row column">
1
+ <div>
2
+ <div class="pt-4 pb-4 text-center bg-background">
3
3
  <%= link %>
4
- <span class="translation-button-help"><%= help_text %></span>
4
+ <span class="translation-button-help ml-4"><%= help_text %></span>
5
5
  </div>
6
6
  </div>
@@ -34,7 +34,7 @@ module Decidim
34
34
  parsed_url.query = new_query
35
35
  url = parsed_url.to_s
36
36
 
37
- link_to button_text, url, class: "button small hollow"
37
+ link_to button_text, url, class: "button button__sm button__transparent-secondary"
38
38
  end
39
39
 
40
40
  def button_text
@@ -53,6 +53,7 @@ module Decidim
53
53
  emendation.body = { I18n.locale => form.emendation_params.with_indifferent_access[:body] }
54
54
  emendation.component = amendable.component
55
55
  emendation.add_author(current_user, user_group)
56
+ emendation.category = amendable.category if amendable.respond_to?(:category)
56
57
  emendation.save!
57
58
  emendation
58
59
  end
@@ -35,6 +35,9 @@ module Decidim
35
35
  @user.name = ""
36
36
  @user.nickname = ""
37
37
  @user.email = ""
38
+ @user.personal_url = ""
39
+ @user.about = ""
40
+ @user.notifications_sending_frequency = "none"
38
41
  @user.delete_reason = @form.delete_reason
39
42
  @user.admin = false if @user.admin?
40
43
  @user.deleted_at = Time.current
@@ -12,7 +12,7 @@ module Decidim
12
12
  if user.present? && user.blocked?
13
13
  check_user_block_status(user)
14
14
  elsif user.needs_password_update?
15
- change_password_path
15
+ decidim.change_password_path
16
16
  elsif first_login_and_not_authorized?(user) && !user.admin? && !pending_redirect?(user)
17
17
  decidim_verifications.first_login_authorizations_path
18
18
  else
@@ -0,0 +1,82 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Decidim
4
+ module DirectUpload
5
+ extend ActiveSupport::Concern
6
+
7
+ included do
8
+ include Decidim::NeedsOrganization
9
+ skip_before_action :verify_organization
10
+
11
+ before_action :check_organization!,
12
+ :check_authenticated!,
13
+ :check_user_belongs_to_organization,
14
+ :validate_direct_upload
15
+ end
16
+
17
+ protected
18
+
19
+ def validate_direct_upload
20
+ # We skip the validation if we are in system panel. `current_admin` refers to the main system admin user.
21
+ return if current_admin.present?
22
+
23
+ head :unprocessable_entity unless [
24
+ maximum_allowed_size.try(:to_i) >= blob_args[:byte_size].try(:to_i),
25
+ content_types.any? { |pattern| pattern.match?(blob_args[:content_type]) },
26
+ content_types.any? { |pattern| pattern.match?(MiniMime.lookup_by_extension(extension)&.content_type) },
27
+ allowed_extensions.any? { |pattern| pattern.match?(extension) }
28
+ ].all?
29
+ rescue NoMethodError
30
+ head :unprocessable_entity
31
+ end
32
+
33
+ def extension
34
+ File.extname(blob_args[:filename]).delete(".")
35
+ end
36
+
37
+ def maximum_allowed_size
38
+ current_organization.settings.upload_maximum_file_size
39
+ end
40
+
41
+ def check_organization!
42
+ head :unauthorized if current_organization.blank? && current_admin.blank?
43
+ end
44
+
45
+ def check_authenticated!
46
+ head :unauthorized if current_user.blank? && current_admin.blank?
47
+ end
48
+
49
+ def check_user_belongs_to_organization
50
+ return if current_admin.present?
51
+
52
+ head :unauthorized unless current_organization == current_user.organization
53
+ end
54
+
55
+ def allowed_extensions
56
+ if user_has_elevated_role?
57
+ current_organization.settings.upload_allowed_file_extensions_admin
58
+ else
59
+ current_organization.settings.upload_allowed_file_extensions
60
+ end
61
+ end
62
+
63
+ def content_types
64
+ if user_has_elevated_role?
65
+ current_organization.settings.upload_allowed_content_types_admin
66
+ else
67
+ current_organization.settings.upload_allowed_content_types
68
+ end
69
+ end
70
+
71
+ private
72
+
73
+ def user_has_elevated_role?
74
+ [
75
+ current_user&.admin?,
76
+ defined?(Decidim::Assemblies::AssembliesWithUserRole) && Decidim::Assemblies::AssembliesWithUserRole.for(current_user).any?,
77
+ defined?(Decidim::Conferences::ConferencesWithUserRole) && Decidim::Conferences::ConferencesWithUserRole.for(current_user).any?,
78
+ defined?(Decidim::ParticipatoryProcessesWithUserRole) && Decidim::ParticipatoryProcessesWithUserRole.for(current_user).any?
79
+ ].any?
80
+ end
81
+ end
82
+ end
@@ -28,7 +28,7 @@ module Decidim
28
28
  end
29
29
 
30
30
  def avatar_url
31
- avatar_url = current_resource_owner.attached_uploader(:avatar).url(host: current_resource_owner.organization.host)
31
+ avatar_url = current_resource_owner.attached_uploader(:avatar).url
32
32
  return unless avatar_url
33
33
 
34
34
  unless %r{^https?://}.match? avatar_url
@@ -41,7 +41,7 @@ module Decidim
41
41
  end
42
42
 
43
43
  def escape_url(external_url)
44
- before_fragment, fragment = external_url.split("#", 2)
44
+ before_fragment, fragment = URI.decode_www_form_component(external_url).split("#", 2)
45
45
  escaped_before_fragment = URI::Parser.new.escape(before_fragment)
46
46
 
47
47
  if fragment
@@ -27,6 +27,10 @@ module Decidim
27
27
  redirect_to profile_activity_path(nickname: params[:nickname])
28
28
  end
29
29
 
30
+ def tooltip
31
+ render json: { data: cell("decidim/author", profile_holder.presenter).profile_minicard }
32
+ end
33
+
30
34
  def following
31
35
  @content_cell = "decidim/following"
32
36
  @title_key = "following"
@@ -10,7 +10,7 @@ module Decidim
10
10
  # Property is named as attribute in upload modal and passthru validator, but
11
11
  # it cannot be named as attribute here.
12
12
  attribute :property, String
13
- attribute :blob, String
13
+ attribute :blob, Decidim::Attributes::Blob
14
14
  attribute :form_class, String
15
15
 
16
16
  validates :resource_class, presence: true
@@ -0,0 +1,46 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Decidim
4
+ module UserRoleChecker
5
+ # Shared behaviour for signed_in admins
6
+ extend ActiveSupport::Concern
7
+
8
+ private
9
+
10
+ def user_has_any_role?(user, participatory_space = nil, broad_check: false)
11
+ return false unless user
12
+
13
+ [
14
+ user.admin,
15
+ user.roles.any?,
16
+ participatory_process_user_role?(user, participatory_space, broad_check:),
17
+ assembly_user_role?(user, participatory_space, broad_check:),
18
+ conference_user_role?(user, participatory_space, broad_check:)
19
+ ].any?
20
+ end
21
+
22
+ def participatory_process_user_role?(user, participatory_process = nil, broad_check: false)
23
+ return false unless Decidim.module_installed?(:participatory_processes)
24
+ return Decidim::ParticipatoryProcessUserRole.exists?(user:) if broad_check
25
+ return false unless participatory_process.is_a?(Decidim::ParticipatoryProcess)
26
+
27
+ Decidim::ParticipatoryProcessUserRole.exists?(user:, participatory_process:)
28
+ end
29
+
30
+ def assembly_user_role?(user, assembly = nil, broad_check: false)
31
+ return false unless Decidim.module_installed?(:assemblies)
32
+ return Decidim::AssemblyUserRole.exists?(user:) if broad_check
33
+ return false unless assembly.is_a?(Decidim::Assembly)
34
+
35
+ Decidim::AssemblyUserRole.exists?(user:, assembly:)
36
+ end
37
+
38
+ def conference_user_role?(user, conference = nil, broad_check: false)
39
+ return false unless Decidim.module_installed?(:conferences)
40
+ return Decidim::ConferenceUserRole.exists?(user:) if broad_check
41
+ return false unless conference.is_a?(Decidim::Conference)
42
+
43
+ Decidim::ConferenceUserRole.exists?(user:, conference:)
44
+ end
45
+ end
46
+ end
@@ -16,7 +16,7 @@ module Decidim
16
16
  # Finds the CTA button path to reuse it in other places.
17
17
  def cta_button_path
18
18
  if current_organization.cta_button_path.present?
19
- current_organization.cta_button_path
19
+ "/#{current_organization.cta_button_path}"
20
20
  elsif Decidim::ParticipatoryProcess.where(organization: current_organization).published.any?
21
21
  decidim_participatory_processes.participatory_processes_path
22
22
  elsif current_user
@@ -169,6 +169,20 @@ module Decidim
169
169
  end
170
170
  end
171
171
 
172
+ def current_url(params = request.parameters)
173
+ return url_for(params) if respond_to?(:current_participatory_space) || respond_to?(:current_component)
174
+
175
+ each_decidim_engine do |helpers|
176
+ return helpers.url_for(params)
177
+ rescue ActionController::UrlGenerationError
178
+ # Continue to next engine in case the URL is not available.
179
+ end
180
+
181
+ main_app.url_for(params)
182
+ rescue ActionController::UrlGenerationError
183
+ "#{request.base_url}#{"?#{params.to_query}" unless params.empty?}"
184
+ end
185
+
172
186
  private
173
187
 
174
188
  def empty_organization_description?
@@ -177,6 +191,20 @@ module Decidim
177
191
  organization_description.blank? || organization_description == "<p></p>"
178
192
  end
179
193
 
194
+ def each_decidim_engine
195
+ Rails.application.railties.each do |engine|
196
+ next unless engine.is_a?(Rails::Engine)
197
+ next unless engine.isolated?
198
+ next unless engine.engine_name.start_with?("decidim_")
199
+ next unless respond_to?(engine.engine_name)
200
+
201
+ yield public_send(engine.engine_name)
202
+ end
203
+ return unless respond_to?(:decidim)
204
+
205
+ yield decidim
206
+ end
207
+
180
208
  def tag_builder
181
209
  @tag_builder ||= ActionView::Helpers::TagHelper::TagBuilder.new(self)
182
210
  end
@@ -35,7 +35,12 @@ module Decidim
35
35
  data: { "external-link": "text-only" }
36
36
  }.merge(map_html_options)
37
37
  return link_to(map_url, html_options) do
38
- image_tag decidim.static_map_path(sgid: resource.to_sgid.to_s), alt: "#{map_service_brand} - #{address_text}"
38
+ # We also add the latitude and the longitude to prevent the Workbox cache to be overly aggressive when updating a map
39
+ image_tag decidim.static_map_path(
40
+ sgid: resource.to_sgid.to_s,
41
+ latitude: resource.latitude,
42
+ longitude: resource.longitude
43
+ ), alt: "#{map_service_brand} - #{address_text}"
39
44
  end
40
45
  end
41
46
  end
@@ -49,7 +49,7 @@ module Decidim
49
49
  self,
50
50
  element_class: "font-semibold underline",
51
51
  active_class: "is-active",
52
- container_options: { class: "space-y-4 break-inside-avoid" },
52
+ container_options: { class: "space-y-4 break-inside-avoid", role: :menu },
53
53
  label: t("layouts.decidim.footer.decidim_title")
54
54
  )
55
55
  end
@@ -37,13 +37,22 @@ module Decidim
37
37
  end
38
38
  end
39
39
 
40
+ # Converts the blob and blob variant references to blob URLs.
41
+ def decidim_rich_text(html, options = {})
42
+ renderer = Decidim::ContentProcessor.renderer_klass(:blob).constantize.new(html)
43
+ renderer.render(options)
44
+ end
45
+
40
46
  def decidim_sanitize_editor(html, options = {})
41
47
  content_tag(:div, decidim_sanitize(html, options), class: %w(rich-text-display))
42
48
  end
43
49
 
44
50
  def decidim_sanitize_editor_admin(html, options = {})
45
51
  html = Decidim::IframeDisabler.new(html, options).perform
46
- decidim_sanitize_editor(html, { scrubber: Decidim::AdminInputScrubber.new }.merge(options))
52
+ decidim_sanitize_editor(
53
+ decidim_rich_text(html),
54
+ { scrubber: Decidim::AdminInputScrubber.new }.merge(options)
55
+ )
47
56
  end
48
57
 
49
58
  def decidim_html_escape(text)
@@ -51,7 +60,7 @@ module Decidim
51
60
  end
52
61
 
53
62
  def decidim_url_escape(text)
54
- decidim_html_escape(text).sub(/^javascript:/, "")
63
+ decidim_html_escape(text).sub(/^\s*javascript:/, "")
55
64
  end
56
65
 
57
66
  def decidim_sanitize_translated(text)
@@ -53,11 +53,14 @@ module Decidim
53
53
  # options - An optional Hash with options:
54
54
  #
55
55
  # Returns nothing.
56
- def scopes_select_field(form, name, root: false, options: {})
56
+ def scopes_select_field(form, name, root: false, options: {}, html_options: {})
57
+ options = options.merge(include_blank: I18n.t("decidim.scopes.prompt")) unless options.has_key?(:include_blank)
58
+
57
59
  form.select(
58
60
  name,
59
61
  ordered_scopes_descendants_for_select(root),
60
- options.merge(include_blank: I18n.t("decidim.scopes.prompt"))
62
+ options,
63
+ html_options
61
64
  )
62
65
  end
63
66
 
@@ -138,6 +138,7 @@ module Decidim
138
138
  Decidim::Proposals::Proposal
139
139
  Decidim::Surveys::Survey
140
140
  Decidim::Assembly
141
+ Decidim::Conference
141
142
  Decidim::Initiative
142
143
  Decidim::ParticipatoryProcess
143
144
  ).select do |klass|
@@ -154,7 +155,16 @@ module Decidim
154
155
  end
155
156
 
156
157
  def self.publicable_public_resource_types
157
- @publicable_public_resource_types ||= public_resource_types.select { |klass| klass.constantize.column_names.include?("published_at") }
158
+ @publicable_public_resource_types ||= public_resource_types
159
+ .select { |klass| klass.constantize.column_names.include?("published_at") } - publicable_exceptions
160
+ end
161
+
162
+ def self.publicable_exceptions
163
+ @publicable_exceptions = %w(
164
+ Decidim::Blogs::Post
165
+ ).select do |klass|
166
+ klass.safe_constantize.present?
167
+ end
158
168
  end
159
169
 
160
170
  def self.ransackable_scopes(auth_object = nil)
@@ -65,7 +65,7 @@ module Decidim
65
65
  #
66
66
  # Returns String.
67
67
  def file_type
68
- url&.split(".")&.last&.downcase
68
+ url&.split(".")&.last&.split("&")&.first&.downcase
69
69
  end
70
70
 
71
71
  def url
@@ -59,7 +59,6 @@ const createDropdown = (component) => {
59
59
  const dropdownOptions = {};
60
60
  dropdownOptions.dropdown = component.dataset.target;
61
61
  dropdownOptions.hover = component.dataset.hover === "true";
62
- dropdownOptions.isOpen = component.dataset.open === "true";
63
62
  dropdownOptions.autoClose = component.dataset.autoClose === "true";
64
63
 
65
64
  // This snippet allows to disable the dropdown based on the current viewport
@@ -78,6 +77,17 @@ const createDropdown = (component) => {
78
77
  return
79
78
  }
80
79
 
80
+ dropdownOptions.isOpen = component.dataset.open === "true";
81
+
82
+ const isOpen = Object.keys(screens).some((key) => {
83
+ if (!isScreenSize(key)) {
84
+ return false;
85
+ }
86
+ return Boolean(component.dataset[`open-${key}`.replace(/-([a-z])/g, (str) => str[1].toUpperCase())]);
87
+ });
88
+
89
+ dropdownOptions.isOpen = dropdownOptions.isOpen || isOpen;
90
+
81
91
  if (!component.id) {
82
92
  // when component has no id, we enforce to have it one
83
93
  component.id = `dropdown-${Math.random().toString(36).substring(7)}`
@@ -104,20 +114,6 @@ const createDropdown = (component) => {
104
114
  });
105
115
  }
106
116
 
107
- // Disable focus on children elements so we can pass the AXE accessibility tests
108
- const dropdownMenu = document.getElementById(dropdownOptions.dropdown);
109
- if (dropdownMenu.getAttribute("aria-hidden") === "true") {
110
- dropdownMenu.
111
- querySelectorAll("a, input, button").
112
- forEach((element) => { element.tabIndex = -1 })
113
- }
114
-
115
- component.addEventListener("click", () => {
116
- dropdownMenu.
117
- querySelectorAll("a, input, button").
118
- forEach((element) => { element.tabIndex = 0 })
119
- })
120
-
121
117
  Dropdowns.render(component.id, dropdownOptions);
122
118
  }
123
119