decidim-core 0.31.0 → 0.31.2

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 (107) hide show
  1. checksums.yaml +4 -4
  2. data/app/cells/decidim/announcement_cell.rb +10 -2
  3. data/app/cells/decidim/attachments_file_tab/show.erb +1 -1
  4. data/app/cells/decidim/card_g/show.erb +1 -1
  5. data/app/cells/decidim/content_blocks/participatory_space_metadata/content.erb +2 -2
  6. data/app/cells/decidim/nav_links/show.erb +3 -3
  7. data/app/cells/decidim/notification/deleted.erb +12 -0
  8. data/app/cells/decidim/notification/not_available.erb +12 -0
  9. data/app/cells/decidim/notification_cell.rb +5 -1
  10. data/app/cells/decidim/share_widget/modal.erb +1 -1
  11. data/app/cells/decidim/statistic/show.erb +6 -6
  12. data/app/cells/decidim/upload_modal/files.erb +12 -8
  13. data/app/cells/decidim/upload_modal_cell.rb +11 -4
  14. data/app/commands/decidim/destroy_account.rb +49 -4
  15. data/app/controllers/concerns/decidim/direct_upload.rb +2 -12
  16. data/app/controllers/decidim/devise/sessions_controller.rb +7 -0
  17. data/app/events/decidim/welcome_notification_event.rb +1 -1
  18. data/app/helpers/concerns/decidim/flash_helper_extensions.rb +2 -2
  19. data/app/jobs/decidim/export_participatory_space_job.rb +1 -1
  20. data/app/jobs/decidim/remove_search_indexes_job.rb +18 -0
  21. data/app/mailers/decidim/notifications_digest_mailer.rb +18 -1
  22. data/app/models/decidim/attachment.rb +22 -1
  23. data/app/models/decidim/component.rb +3 -0
  24. data/app/models/decidim/notification.rb +12 -0
  25. data/app/models/decidim/user.rb +4 -0
  26. data/app/models/decidim/user_base_entity.rb +1 -1
  27. data/app/packs/src/decidim/controllers/account_form/controller.js +8 -0
  28. data/app/packs/src/decidim/controllers/language_change/controller.js +38 -0
  29. data/app/packs/src/decidim/controllers/language_change/language_change.test.js +105 -0
  30. data/app/packs/src/decidim/controllers/multiple_mentions/controller.js +25 -2
  31. data/app/packs/src/decidim/direct_uploads/upload_field.js +1 -1
  32. data/app/packs/src/decidim/editor/extensions/link/index.js +0 -1
  33. data/app/packs/src/decidim/editor/index.js +5 -1
  34. data/app/packs/src/decidim/editor/test/extensions/link.test.js +2 -2
  35. data/app/packs/src/decidim/editor/test/toolbar/shared/behaves_like_basic_link.js +1 -1
  36. data/app/packs/stylesheets/decidim/_conversations.scss +1 -1
  37. data/app/packs/stylesheets/decidim/_flash.scss +1 -1
  38. data/app/packs/stylesheets/decidim/_modal.scss +5 -1
  39. data/app/packs/stylesheets/decidim/_modal_fingerprint.scss +1 -1
  40. data/app/packs/stylesheets/decidim/_modal_update.scss +1 -1
  41. data/app/packs/stylesheets/decidim/_participatory_spaces.scss +1 -1
  42. data/app/presenters/decidim/log/user_presenter.rb +2 -1
  43. data/app/presenters/decidim/organization_presenter.rb +4 -0
  44. data/app/presenters/decidim/user_presenter.rb +6 -1
  45. data/app/services/decidim/open_data_exporter.rb +1 -1
  46. data/app/views/decidim/manifests/show.json.erb +1 -0
  47. data/app/views/decidim/pages/_tabbed.html.erb +1 -1
  48. data/app/views/decidim/shared/_orders.html.erb +1 -1
  49. data/app/views/layouts/decidim/header/_main_links_mobile_account.html.erb +16 -2
  50. data/config/locales/ar.yml +0 -4
  51. data/config/locales/bg.yml +0 -8
  52. data/config/locales/ca-IT.yml +18 -11
  53. data/config/locales/ca.yml +18 -11
  54. data/config/locales/cs.yml +53 -16
  55. data/config/locales/de.yml +9 -20
  56. data/config/locales/el.yml +1 -3
  57. data/config/locales/en.yml +24 -17
  58. data/config/locales/es-MX.yml +17 -10
  59. data/config/locales/es-PY.yml +17 -10
  60. data/config/locales/es.yml +16 -9
  61. data/config/locales/eu.yml +49 -44
  62. data/config/locales/fa-IR.yml +3 -0
  63. data/config/locales/fi-plain.yml +11 -8
  64. data/config/locales/fi.yml +11 -8
  65. data/config/locales/fr-CA.yml +24 -11
  66. data/config/locales/fr.yml +25 -11
  67. data/config/locales/ga-IE.yml +0 -4
  68. data/config/locales/gl.yml +0 -3
  69. data/config/locales/hu.yml +0 -6
  70. data/config/locales/id-ID.yml +0 -3
  71. data/config/locales/it.yml +0 -3
  72. data/config/locales/ja.yml +28 -22
  73. data/config/locales/ko.yml +3 -0
  74. data/config/locales/lb.yml +0 -3
  75. data/config/locales/lt.yml +0 -5
  76. data/config/locales/lv.yml +0 -3
  77. data/config/locales/mt.yml +3 -0
  78. data/config/locales/nl.yml +0 -3
  79. data/config/locales/no.yml +0 -3
  80. data/config/locales/pl.yml +11 -7
  81. data/config/locales/pt-BR.yml +626 -5
  82. data/config/locales/pt.yml +0 -3
  83. data/config/locales/ro-RO.yml +483 -121
  84. data/config/locales/ru.yml +0 -3
  85. data/config/locales/sk.yml +0 -3
  86. data/config/locales/sv.yml +303 -47
  87. data/config/locales/tr-TR.yml +0 -5
  88. data/config/locales/uk.yml +0 -3
  89. data/config/locales/vi.yml +3 -0
  90. data/config/locales/zh-CN.yml +0 -3
  91. data/config/locales/zh-TW.yml +0 -5
  92. data/db/data/20251125144141_add_short_name_to_organizations.rb +35 -0
  93. data/db/migrate/20251031150928_add_short_name_to_organization.rb +7 -0
  94. data/decidim-core.gemspec +1 -0
  95. data/lib/decidim/core/seeds.rb +2 -2
  96. data/lib/decidim/core/test/factories.rb +25 -0
  97. data/lib/decidim/core/test/shared_examples/comments_examples.rb +27 -1
  98. data/lib/decidim/core/test/shared_examples/fingerprint_examples.rb +13 -0
  99. data/lib/decidim/core/version.rb +1 -1
  100. data/lib/decidim/events/base_event.rb +4 -0
  101. data/lib/decidim/form_builder.rb +41 -7
  102. data/lib/decidim/has_private_users.rb +1 -0
  103. data/lib/decidim/seeds.rb +1 -1
  104. data/lib/tasks/decidim_procfile.rake +1 -1
  105. data/lib/tasks/upgrade/decidim_remove_deleted_users_left_data_tasks.rake +30 -0
  106. data/lib/tasks/upgrade/fix_deleted_private_follows.rake +26 -0
  107. metadata +29 -6
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 644720c93a8b08cdb4aa839557a74298774aef2719321671ca3bee677d8e93a6
4
- data.tar.gz: e603634ef33432a213f5122b5e00c11614085d95fcc35e4a3c1b180d582427d8
3
+ metadata.gz: d24b967768820d8eb01abd435e318b7a869af854b0f374ca54c54a6dddbdddb6
4
+ data.tar.gz: f4374ee02ae0c5c9a580c8ad1c371c6552719be39b40e22cdf3d37018464950c
5
5
  SHA512:
6
- metadata.gz: a98933696db00e724d2b7ae56c14e3cc718be1026234a9a36679ef3678395a3fe4308be1ff8d9fd59aa233f7c056459e2466089a2f9e21368c9df6b37c722505
7
- data.tar.gz: 2dc98284fe86eb99c409a4f418b00ec6baba661476bfa2114b518a63dfba5313b33e268099a1e90703d98ec4ec438c8e1e4a30e51717ca63c2fb2f57b20e4664
6
+ metadata.gz: a4baebaa527bd42897989aee6feeb29b4bf2773872b3bb7f8379a230111a94f0ee8565e15b02599fb6a1c20bb83a24feed96707448eecc70b866d74a8dd34bec
7
+ data.tar.gz: 47436bb0abfd556afcebe781b153aaaa9e960653be07fdf19486f11d82bfc40b61472dc24cfbc397838b19638a395bbad3dc3af8e1d791b2277412b3b56e719d
@@ -68,15 +68,23 @@ module Decidim
68
68
  def clean_body
69
69
  return unless body
70
70
 
71
- Array(body).map { |paragraph| tag.p(clean(paragraph)) }.join
71
+ Array(body).map { |paragraph| clean(paragraph) }.join
72
72
  end
73
73
 
74
74
  def clean_announcement
75
+ return if announcement.is_a?(Hash) && announcement.values.all?(&:blank?)
76
+
75
77
  clean(announcement)
76
78
  end
77
79
 
78
80
  def clean(value)
79
- decidim_sanitize_admin(translated_attribute(value))
81
+ return if value.blank? || value.nil?
82
+
83
+ if value.include?("rich-text-display")
84
+ decidim_sanitize_admin(translated_attribute(value))
85
+ else
86
+ tag.p(decidim_sanitize_admin(translated_attribute(value)))
87
+ end
80
88
  end
81
89
  end
82
90
  end
@@ -1,3 +1,3 @@
1
1
  <div class="row column">
2
- <%= form.upload :file, button_class: "button button__sm button__transparent-secondary" %>
2
+ <%= form.upload :file, attachments: form.object.file.present? ? [form.object.file] : [], button_class: "button button__sm button__transparent-secondary" %>
3
3
  </div>
@@ -1,7 +1,7 @@
1
1
  <%= link_to resource_path, class: classes[:default], id: resource_id do %>
2
2
  <div class="<%= classes[:img] %>">
3
3
  <% if has_image? %>
4
- <%= image_tag resource_image_url, alt: alt_title %>
4
+ <%= image_tag resource_image_url, alt: "" %>
5
5
  <% else %>
6
6
  <%= external_icon "media/images/placeholder-card-g.svg", class: "card__placeholder-g" %>
7
7
  <% end %>
@@ -2,9 +2,9 @@
2
2
  <% metadata_valued_items.each do |item| %>
3
3
  <div class="participatory-space__metadata-item">
4
4
  <div class="participatory-space__metadata-item-title">
5
- <span><%= item[:title] %></span>
5
+ <p><%= item[:title] %></p>
6
6
  </div>
7
- <span><%= item[:value] %></span>
7
+ <p><%= item[:value] %></p>
8
8
  </div>
9
9
  <% end %>
10
10
  </div>
@@ -1,14 +1,14 @@
1
1
  <div class="participatory-space__nav-container">
2
- <button id="dropdown-trigger-participatory-space" data-controller="dropdown" data-target="dropdown-menu-participatory-space" data-auto-close="true" data-scroll-to-menu="true" data-open-md="true">
2
+ <button id="dropdown-trigger-participatory-space" data-controller="dropdown" data-target="dropdown-menu-participatory-space" data-auto-close="true" data-scroll-to-menu="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">
7
+ <ul id="dropdown-menu-participatory-space" class="participatory-space__nav" aria-hidden="true">
8
8
  <% model.each do |item| %>
9
9
  <li role="menuitem">
10
10
  <%= link_to item[:url], class: "participatory-space__nav-item" do %>
11
- <%= item[:name] %>
11
+ <%= decidim_escape_translated(item[:name]) %>
12
12
  <%= icon "arrow-right-line" %>
13
13
  <% end %>
14
14
  </li>
@@ -0,0 +1,12 @@
1
+ <div class="notification" data-notification>
2
+ <div class="notification__wrapper">
3
+ <div class="notification__time" title="<%= l(notification.created_at) %>"> <%= notification.created_at_in_words %></div>
4
+ <div class="notification__snippet">
5
+ <span class="notification__snippet-title text-gray"><%= t("decidim.notifications.show.deleted") %></span>
6
+ </div>
7
+ </div>
8
+ <%= link_to model, remote: true, method: :delete, class: "notification__button", data: { "notification-read": "" } do %>
9
+ <span class="sr-only md:not-sr-only"><%= t("mark_as_read", scope: "layouts.decidim.notifications_dashboard") %></span>
10
+ <%= icon "check-line", class: "fill-current" %>
11
+ <% end %>
12
+ </div>
@@ -0,0 +1,12 @@
1
+ <div class="notification" data-notification>
2
+ <div class="notification__wrapper">
3
+ <div class="notification__time" title="<%= l(notification.created_at) %>"> <%= notification.created_at_in_words %></div>
4
+ <div class="notification__snippet">
5
+ <span class="notification__snippet-title text-gray"><%= t("decidim.notifications.show.not_available") %></span>
6
+ </div>
7
+ </div>
8
+ <%= link_to model, remote: true, method: :delete, class: "notification__button", data: { "notification-read": "" } do %>
9
+ <span class="sr-only md:not-sr-only"><%= t("mark_as_read", scope: "layouts.decidim.notifications_dashboard") %></span>
10
+ <%= icon "check-line", class: "fill-current" %>
11
+ <% end %>
12
+ </div>
@@ -7,7 +7,11 @@ module Decidim
7
7
  include Decidim::Core::Engine.routes.url_helpers
8
8
 
9
9
  def show
10
- if notification.event_class_instance.try(:hidden_resource?)
10
+ if !notification.can_participate?(current_user)
11
+ render :not_available
12
+ elsif notification.deleted_resource?
13
+ render :deleted
14
+ elsif notification.hidden_resource?
11
15
  render :moderated
12
16
  else
13
17
  render :show
@@ -1,6 +1,6 @@
1
1
  <%= decidim_modal id: "socialShare", class: "share-modal" do %>
2
2
  <div data-dialog-container>
3
- <h2 tabindex="-1" data-dialog-title><%= t("share", scope: "decidim.shared.share_modal") %></h2>
3
+ <h2 id="dialog-title-social-share-modal" tabindex="-1" data-dialog-title><%= t("share", scope: "decidim.shared.share_modal") %></h2>
4
4
 
5
5
  <div>
6
6
 
@@ -1,9 +1,9 @@
1
1
  <div class="statistic <%= stat_dom_class %>" data-statistic>
2
2
  <div class="statistic__header">
3
3
  <div class="statistic__metric">
4
- <div class="statistic__title" title="<%= stat_title %>">
4
+ <p class="statistic__title" title="<%= stat_title %>">
5
5
  <%= stat_title %>
6
- </div>
6
+ </p>
7
7
  <div class="statistic__tooltip">
8
8
  <%= information_tooltip %>
9
9
  </div>
@@ -13,14 +13,14 @@
13
13
  </div>
14
14
  </div>
15
15
  <% if second_stat_number %>
16
- <div class="statistic__second-number">
16
+ <p class="statistic__second-number">
17
17
  <%= stat_sub_title %>
18
18
  <span class="font-semibold"><%= second_stat_number %></span>
19
- </div>
19
+ </p>
20
20
  <% else %>
21
21
  <p class="h-[21px]"></p>
22
22
  <% end %>
23
- <div class="statistic__number">
23
+ <p class="statistic__number">
24
24
  <%= stat_number %>
25
- </div>
25
+ </p>
26
26
  </div>
@@ -1,9 +1,9 @@
1
1
  <div class="upload-modal__files-container upload-container-for-<%= attribute %> <%= with_title %>">
2
2
  <div>
3
- <%= label %>
3
+ <%= options[:paragraph] == true ? paragraph : label %>
4
4
 
5
5
  <% if options[:help_text].present? %>
6
- <span class="help-text"><%= options[:help_text] %></span>
6
+ <p class="help-text"><%= options[:help_text] %></p>
7
7
  <% end %>
8
8
 
9
9
  <%# NOTE: this block is about wrapping a default image for the avatar with the new styles,
@@ -22,24 +22,28 @@
22
22
  <div class="upload-modal__files" data-active-uploads="<%= modal_id %>">
23
23
  <% attachments.each do |attachment| %>
24
24
  <% next if [Array, Hash].any? { |klass| attachment.is_a? klass } %>
25
+ <% is_persisted_attachment = attachment.is_a?(Decidim::Attachment) && attachment.persisted? %>
26
+ <% attachment_blob = blob(attachment) %>
25
27
 
26
- <div class="attachment-details" data-attachment-id="<%= attachment.id %>" data-title="<%= title_for(attachment) %>" data-filename="<%= file_name_for(attachment) %>" data-state="uploaded">
27
- <% if file_attachment_path(attachment) && blob(attachment).image? %>
28
+ <div class="attachment-details"<% if is_persisted_attachment %> data-attachment-id="<%= attachment.id %>"<% end %> data-title="<%= title_for(attachment) %>" data-filename="<%= file_name_for(attachment) %>" data-state="uploaded" data-hidden-field="<%= attachment_blob&.signed_id %>">
29
+ <% if file_attachment_path(attachment) && attachment_blob&.image? %>
28
30
  <div><%= image_tag(file_attachment_path(attachment), alt: "") %></div>
29
31
  <% elsif uploader_default_image_path(attribute).present? %>
30
32
  <div><%= image_tag uploader_default_image_path(attribute) %></div>
31
33
  <% end %>
32
34
 
33
35
  <% if has_title? %>
34
- <span><%= title_for(attachment) %></span>
35
- <%= form.hidden_field attribute, multiple: true, value: attachment.id, id: attachment.id %>
36
+ <p><%= title_for(attachment) %></p>
36
37
  <% else %>
37
- <% if blob(attachment).image? %>
38
- <span><%= title_for(attachment) %></span>
38
+ <% if attachment_blob&.image? %>
39
+ <p><%= title_for(attachment) %></p>
39
40
  <% else %>
40
41
  <%= link_to title_for(attachment), file_attachment_path(attachment), class: "w-full break-all mb-2" %>
41
42
  <% end %>
42
43
  <% end %>
44
+ <% if attachment_blob.present? %>
45
+ <%= form.hidden_field attribute, value: attachment_blob.signed_id, id: "hidden_#{attribute}_#{attachment_blob.id}" %>
46
+ <% end %>
43
47
  </div>
44
48
  <% end %>
45
49
  </div>
@@ -30,6 +30,10 @@ module Decidim
30
30
  form.send(:custom_label, attribute, options[:label], { required: required?, for: nil })
31
31
  end
32
32
 
33
+ def paragraph
34
+ form.send(:custom_paragraph, attribute, options[:label], { required: required? })
35
+ end
36
+
33
37
  def button_label
34
38
  return button_edit_label if attachments.count.positive?
35
39
 
@@ -71,13 +75,16 @@ module Decidim
71
75
  end
72
76
 
73
77
  # By default FoundationRailsHelper adds form errors next to input, but since input is in the modal
74
- # and modal is hidden by default, we need to add an additional validation field to the form.
78
+ # and modal is hidden by default, we add a hidden checkbox field to handle HTML5 validation.
75
79
  # This should only be necessary when file is required by the form.
80
+ # Note: Validation errors are now displayed in the main form area, not inside the modal.
76
81
  def input_validation_field
77
82
  object_name = form.object.present? ? "#{form.object.model_name.param_key}[#{add_attribute}_validation]" : "#{add_attribute}_validation"
78
- input = check_box_tag object_name, 1, attachments.present?, class: "reset-defaults", hidden: true, label: false, required: required?
79
- message = form.send(:abide_error_element, add_attribute) + form.send(:error_and_help_text, add_attribute)
80
- input + message
83
+ check_box_tag object_name, 1, attachments.present?, class: "reset-defaults", hidden: true, label: false, required: required?, id: validation_field_id
84
+ end
85
+
86
+ def validation_field_id
87
+ "#{attribute}_validation"
81
88
  end
82
89
 
83
90
  def explanation
@@ -19,6 +19,15 @@ module Decidim
19
19
  destroy_user_account!
20
20
  destroy_user_identities
21
21
  destroy_follows
22
+ destroy_user_versions
23
+ destroy_user_private_exports
24
+ destroy_user_access_grants
25
+ destroy_user_access_tokens
26
+ destroy_user_reminders
27
+ destroy_user_notifications
28
+ destroy_user_badges
29
+ destroy_user_likes
30
+ destroy_user_reports
22
31
  destroy_participatory_space_private_user
23
32
  delegate_destroy_to_participatory_spaces
24
33
  end
@@ -47,17 +56,53 @@ module Decidim
47
56
  current_user.save!
48
57
  end
49
58
 
59
+ def destroy_user_badges
60
+ Decidim::Gamification::BadgeScore.where(user: current_user).find_each(&:destroy)
61
+ end
62
+
63
+ def destroy_user_reports
64
+ Decidim::UserModeration.where(user: current_user).find_each(&:destroy)
65
+ end
66
+
67
+ def destroy_user_likes
68
+ Decidim::Like.where(author: current_user).find_each(&:destroy)
69
+ end
70
+
50
71
  def destroy_user_identities
51
- current_user.identities.destroy_all
72
+ current_user.identities.find_each(&:destroy)
73
+ end
74
+
75
+ def destroy_user_versions
76
+ current_user.versions.find_each(&:destroy)
77
+ end
78
+
79
+ def destroy_user_private_exports
80
+ current_user.private_exports.find_each(&:destroy)
81
+ end
82
+
83
+ def destroy_user_access_grants
84
+ current_user.access_grants.find_each(&:destroy)
85
+ end
86
+
87
+ def destroy_user_access_tokens
88
+ current_user.access_tokens.find_each(&:destroy)
89
+ end
90
+
91
+ def destroy_user_reminders
92
+ current_user.reminders.find_each(&:destroy)
93
+ end
94
+
95
+ def destroy_user_notifications
96
+ current_user.notifications.find_each(&:destroy)
52
97
  end
53
98
 
54
99
  def destroy_follows
55
- Decidim::Follow.where(followable: current_user).destroy_all
56
- Decidim::Follow.where(user: current_user).destroy_all
100
+ Decidim::Follow.where(followable: current_user).find_each(&:destroy)
101
+ Decidim::Follow.where(user: current_user).find_each(&:destroy)
57
102
  end
58
103
 
59
104
  def destroy_participatory_space_private_user
60
- Decidim::ParticipatorySpacePrivateUser.where(user: current_user).destroy_all
105
+ Decidim::ParticipatorySpacePrivateUser.where(user: current_user).find_each(&:destroy)
61
106
  end
62
107
 
63
108
  def delegate_destroy_to_participatory_spaces
@@ -9,8 +9,6 @@ module Decidim
9
9
  skip_before_action :verify_organization
10
10
 
11
11
  before_action :check_organization!,
12
- :check_authenticated!,
13
- :check_user_belongs_to_organization,
14
12
  :validate_direct_upload
15
13
  end
16
14
 
@@ -42,16 +40,6 @@ module Decidim
42
40
  head :unauthorized if current_organization.blank? && current_admin.blank?
43
41
  end
44
42
 
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
43
  def allowed_extensions
56
44
  if user_has_elevated_role?
57
45
  current_organization.settings.upload_allowed_file_extensions_admin
@@ -71,6 +59,8 @@ module Decidim
71
59
  private
72
60
 
73
61
  def user_has_elevated_role?
62
+ return false if current_user.blank? || current_organization.blank? || current_user.organization != current_organization
63
+
74
64
  [
75
65
  current_user&.admin?,
76
66
  defined?(Decidim::Assemblies::AssembliesWithUserRole) && Decidim::Assemblies::AssembliesWithUserRole.for(current_user).any?,
@@ -9,6 +9,8 @@ module Decidim
9
9
 
10
10
  before_action :check_sign_in_enabled, only: :create
11
11
 
12
+ rescue_from ActionController::InvalidAuthenticityToken, with: :redirect_to_referer_or_path
13
+
12
14
  def create
13
15
  super do |user|
14
16
  if user.admin?
@@ -44,6 +46,11 @@ module Decidim
44
46
 
45
47
  private
46
48
 
49
+ def redirect_to_referer_or_path
50
+ set_flash_message(:alert, "csrf_token", scope: "devise.failure")
51
+ redirect_back(fallback_location: root_path) && return
52
+ end
53
+
47
54
  def check_sign_in_enabled
48
55
  redirect_to new_user_session_path unless current_organization.sign_in_enabled?
49
56
  end
@@ -43,7 +43,7 @@ module Decidim
43
43
 
44
44
  def interpolate(template)
45
45
  template
46
- .gsub("{{name}}", user.name)
46
+ .gsub("{{name}}", user.presenter.name)
47
47
  .gsub("{{organization}}", organization_name(organization))
48
48
  .gsub("{{help_url}}", url_helpers.pages_url(host: organization.host))
49
49
  .gsub("{{badges_url}}", url_helpers.gamification_badges_url(host: organization.host))
@@ -117,9 +117,9 @@ module Decidim
117
117
  end
118
118
 
119
119
  def message(value)
120
- return content_tag(:div, value, class: "flash__message") unless value.is_a?(Hash)
120
+ return content_tag(:p, value, class: "flash__message") unless value.is_a?(Hash)
121
121
 
122
- content_tag(:div, class: "flash__message") do
122
+ content_tag(:p, class: "flash__message") do
123
123
  concat value[:title]
124
124
  concat content_tag(:span, value[:body], class: "flash__message-body")
125
125
  end
@@ -11,7 +11,7 @@ module Decidim
11
11
  manifest.name == name.to_sym
12
12
  end
13
13
 
14
- collection = export_manifest.collection.call(participatory_space)
14
+ collection = export_manifest.collection.call(participatory_space, user)
15
15
  serializer = export_manifest.serializer
16
16
 
17
17
  export_data = Decidim::Exporters.find_exporter(format).new(collection, serializer).export
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Decidim
4
+ class RemoveSearchIndexesJob < ApplicationJob
5
+ queue_as :default
6
+
7
+ def perform(elements)
8
+ elements.each do |element|
9
+ element.remove_from_index(element)
10
+ next unless element.respond_to?(:comments)
11
+
12
+ element.comments.each do |comment|
13
+ Decidim::RemoveSearchIndexesJob.perform_later([comment])
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
@@ -17,13 +17,30 @@ module Decidim
17
17
  # Note that this could be improved by adding a "type" column to the notifications table
18
18
  # This fix can generate lists of notifications that are below the SIZE_LIMIT
19
19
  @notifications = notifications[0...SIZE_LIMIT].filter_map do |notification|
20
+ # Check if is a notification that can be sent on email
20
21
  next unless notification.event_class_instance.respond_to?(:email_intro)
22
+ # checks if the resource exists, as we have implemented the possibility of soft deleting resources
23
+ next unless resource_is_present?(notification)
24
+ # checks if the resource is visible
25
+ next unless notification.can_participate?(@user)
26
+ # It usually checks if the resource is reportable and is not hidden, however, there are some exceptions
27
+ # like in the comments, where we check if the resource and intended comment is visible.
28
+ next if notification.hidden_resource?
29
+ # It usually checks if the resource is deletable and is not deleted, however, there are some exceptions
30
+ # like in the comments, where we check if the resource and intended comment is visible.
31
+ next if notification.deleted_resource?
21
32
 
22
33
  Decidim::NotificationToMailerPresenter.new(notification)
23
34
  end
24
35
 
25
- mail(to: user.email, subject: @notifications_digest.subject)
36
+ mail(to: user.email, subject: @notifications_digest.subject) if @notifications.any?
26
37
  end
27
38
  end
39
+
40
+ private
41
+
42
+ def resource_is_present?(notification)
43
+ notification.resource
44
+ end
28
45
  end
29
46
  end
@@ -9,7 +9,7 @@ module Decidim
9
9
  include Traceable
10
10
 
11
11
  before_save :set_content_type_and_size, if: :attached?
12
- before_validation :set_link_content_type_and_size, if: :link?
12
+ before_validation :set_link_content_type_and_size, if: :editable_link?
13
13
 
14
14
  translatable_fields :title, :description
15
15
  belongs_to :attachment_collection, class_name: "Decidim::AttachmentCollection", optional: true
@@ -69,6 +69,20 @@ module Decidim
69
69
  link.present?
70
70
  end
71
71
 
72
+ # Whether this attachment is a link that can be edited or not.
73
+ #
74
+ # Returns Boolean.
75
+ def editable_link?
76
+ !destroyed? && !frozen? && link?
77
+ end
78
+
79
+ # Whether this attachment has a file or not.
80
+ #
81
+ # Returns Boolean.
82
+ def file?
83
+ file.attached?
84
+ end
85
+
72
86
  # Which kind of file this is.
73
87
  #
74
88
  # Returns String.
@@ -123,5 +137,12 @@ module Decidim
123
137
  def self.log_presenter_class_for(_log)
124
138
  Decidim::AdminLog::AttachmentPresenter
125
139
  end
140
+
141
+ def can_participate?(user)
142
+ return true unless attached_to
143
+ return true unless attached_to.respond_to?(:can_participate?)
144
+
145
+ attached_to.can_participate?(user)
146
+ end
126
147
  end
127
148
  end
@@ -100,11 +100,14 @@ module Decidim
100
100
  def resource_description; end
101
101
 
102
102
  def can_participate_in_space?(user)
103
+ return false unless published?
104
+ return false unless participatory_space.published?
103
105
  return true unless participatory_space.try(:private_space?)
104
106
  return false unless user
105
107
 
106
108
  participatory_space.can_participate?(user)
107
109
  end
110
+ alias can_participate? can_participate_in_space?
108
111
 
109
112
  def private_non_transparent_space?
110
113
  return false unless participatory_space.respond_to?(:private_space?)
@@ -37,8 +37,20 @@ module Decidim
37
37
  where(decidim_user_id: user.id)
38
38
  end
39
39
 
40
+ def can_participate?(user)
41
+ resource.can_participate?(user)
42
+ end
43
+
40
44
  def self.export_serializer
41
45
  Decidim::DownloadYourDataSerializers::DownloadYourDataNotificationSerializer
42
46
  end
47
+
48
+ def hidden_resource?
49
+ event_class_instance.respond_to?(:hidden_resource?) && event_class_instance.hidden_resource?
50
+ end
51
+
52
+ def deleted_resource?
53
+ event_class_instance.respond_to?(:deleted_resource?) && event_class_instance.deleted_resource?
54
+ end
43
55
  end
44
56
  end
@@ -153,6 +153,10 @@ module Decidim
153
153
  deleted_at.present?
154
154
  end
155
155
 
156
+ def can_participate?(_user)
157
+ true
158
+ end
159
+
156
160
  # Public: whether the user has been officialized or not
157
161
  def officialized?
158
162
  !officialized_at.nil?
@@ -20,7 +20,7 @@ module Decidim
20
20
  has_one :blocking, class_name: "Decidim::UserBlock", foreign_key: :id, primary_key: :block_id, dependent: :destroy
21
21
 
22
22
  # Regex for name & nickname format validations
23
- REGEXP_NAME = /\A(?!.*[<>?%&\^*#@()\[\]=+:;"{}\\|])/
23
+ REGEXP_NAME = /\A(?!.*[<>?%&\^*#@()\[\]=+:;"{}\\|\n\r])/m
24
24
  REGEXP_NICKNAME = /\A[a-z0-9_-]+\z/
25
25
 
26
26
  has_one_attached :avatar
@@ -42,6 +42,10 @@ export default class extends Controller {
42
42
  }
43
43
 
44
44
  setupMutationObserver() {
45
+ if (!this.newPasswordPanel) {
46
+ return;
47
+ }
48
+
45
49
  this.observer = new MutationObserver(() => {
46
50
  let ariaHiddenValue = this.newPasswordPanel.getAttribute("aria-hidden");
47
51
  this.newPwVisible = ariaHiddenValue === "false";
@@ -54,6 +58,10 @@ export default class extends Controller {
54
58
  }
55
59
 
56
60
  setupEmailChangeListener() {
61
+ if (!this.emailField) {
62
+ return;
63
+ }
64
+
57
65
  this.emailField.addEventListener("change", () => {
58
66
  this.emailChanged = this.emailField.value !== this.originalEmail;
59
67
  this.toggleOldPassword();
@@ -0,0 +1,38 @@
1
+ import { Controller } from "@hotwired/stimulus"
2
+
3
+ /**
4
+ * This controller is used to change the active tab when the language is changed in the admin or system panel.
5
+ * It uses a select element to list the languages available in the platform and adds an observer that would set
6
+ * the tab the active tab to what is selected in the select element by toggling the aria-hidden attribute on the
7
+ * tab container.
8
+ */
9
+ export default class extends Controller {
10
+ connect() {
11
+ this.handleChange = this.handleChange.bind(this);
12
+ this.element.addEventListener("change", this.handleChange);
13
+ }
14
+
15
+ disconnect() {
16
+ this.element.removeEventListener("change", this.handleChange)
17
+ }
18
+
19
+ handleChange(event) {
20
+ let targetTabPaneSelector = event.target.value;
21
+ let tabsContent = event.target.parentElement.parentElement.nextElementSibling;
22
+
23
+ if (!tabsContent) {
24
+ return;
25
+ }
26
+
27
+ let activeTabContent = tabsContent.querySelector(".is-active");
28
+ if (activeTabContent) {
29
+ activeTabContent.ariaHidden = "true";
30
+ activeTabContent.classList.remove("is-active");
31
+ }
32
+ let activePane = tabsContent.querySelector(targetTabPaneSelector);
33
+ if (activePane) {
34
+ activePane.ariaHidden = "false";
35
+ activePane.classList.add("is-active");
36
+ }
37
+ }
38
+ }