decidim-core 0.28.3 → 0.28.5

Sign up to get free protection for your applications and to get access to all the features.
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