decidim-core 0.28.4 → 0.28.6

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 (141) 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/diff_cell.rb +4 -0
  7. data/app/cells/decidim/newsletter_templates/image_text_cta_cell.rb +1 -1
  8. data/app/cells/decidim/participatory_space_dropdown_metadata/show.erb +5 -3
  9. data/app/cells/decidim/resource_types_filter/show.erb +1 -1
  10. data/app/cells/decidim/resource_types_filter_cell.rb +6 -6
  11. data/app/cells/decidim/translation_bar/show.erb +3 -3
  12. data/app/cells/decidim/translation_bar_cell.rb +1 -1
  13. data/app/cells/decidim/user_activity/show.erb +1 -1
  14. data/app/commands/decidim/create_omniauth_registration.rb +14 -8
  15. data/app/commands/decidim/destroy_account.rb +3 -0
  16. data/app/commands/decidim/search.rb +14 -0
  17. data/app/controllers/decidim/doorkeeper/credentials_controller.rb +1 -1
  18. data/app/controllers/decidim/links_controller.rb +1 -1
  19. data/app/controllers/decidim/profiles_controller.rb +6 -2
  20. data/app/controllers/decidim/reports_controller.rb +1 -1
  21. data/app/controllers/decidim/user_activities_controller.rb +1 -1
  22. data/app/forms/decidim/account_form.rb +5 -2
  23. data/app/helpers/concerns/decidim/user_role_checker.rb +46 -0
  24. data/app/helpers/decidim/cta_button_helper.rb +1 -1
  25. data/app/helpers/decidim/map_helper.rb +6 -1
  26. data/app/helpers/decidim/orders_helper.rb +2 -1
  27. data/app/helpers/decidim/participatory_space_helpers.rb +1 -1
  28. data/app/helpers/decidim/sanitize_helper.rb +11 -2
  29. data/app/models/decidim/attachment.rb +1 -1
  30. data/app/models/decidim/user.rb +0 -4
  31. data/app/models/decidim/user_base_entity.rb +4 -0
  32. data/app/packs/src/decidim/append_redirect_url_to_modals.js +14 -6
  33. data/app/packs/src/decidim/direct_uploads/upload_field.js +21 -8
  34. data/app/packs/src/decidim/index.js +5 -0
  35. data/app/packs/src/decidim/map/provider/here.js +1 -1
  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/_hashtags.scss +5 -0
  40. data/app/packs/stylesheets/decidim/_header.scss +20 -2
  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/application.scss +1 -0
  44. data/app/packs/stylesheets/decidim/legacy/conference-diploma.scss +2 -1
  45. data/app/presenters/decidim/attachment_presenter.rb +1 -1
  46. data/app/presenters/decidim/log/user_presenter.rb +1 -0
  47. data/app/services/decidim/base_diff_renderer.rb +28 -2
  48. data/app/services/decidim/email_notification_generator.rb +14 -5
  49. data/app/services/decidim/static_map_generator.rb +1 -1
  50. data/app/views/decidim/last_activities/index.html.erb +1 -1
  51. data/app/views/decidim/pages/_tabbed.html.erb +2 -2
  52. data/app/views/decidim/reported_mailer/hide.html.erb +1 -1
  53. data/app/views/decidim/reported_mailer/report.html.erb +1 -1
  54. data/app/views/decidim/searches/_count.html.erb +1 -1
  55. data/app/views/decidim/searches/_filters.html.erb +40 -38
  56. data/app/views/decidim/shared/_orders.html.erb +2 -2
  57. data/app/views/layouts/decidim/header/_menu_breadcrumb_mobile_tablet.html.erb +1 -1
  58. data/config/locales/ar.yml +55 -12
  59. data/config/locales/bg.yml +17 -8
  60. data/config/locales/bn-BD.yml +1 -0
  61. data/config/locales/bs-BA.yml +100 -0
  62. data/config/locales/ca-IT.yml +2115 -0
  63. data/config/locales/ca.yml +69 -22
  64. data/config/locales/cs.yml +62 -15
  65. data/config/locales/de.yml +67 -20
  66. data/config/locales/el.yml +17 -2
  67. data/config/locales/en.yml +47 -0
  68. data/config/locales/eo.yml +2 -0
  69. data/config/locales/es-MX.yml +61 -14
  70. data/config/locales/es-PY.yml +65 -18
  71. data/config/locales/es.yml +72 -25
  72. data/config/locales/eu.yml +308 -250
  73. data/config/locales/fi-plain.yml +50 -11
  74. data/config/locales/fi.yml +87 -48
  75. data/config/locales/fr-CA.yml +57 -10
  76. data/config/locales/fr.yml +55 -8
  77. data/config/locales/ga-IE.yml +11 -0
  78. data/config/locales/gl.yml +33 -2
  79. data/config/locales/hu.yml +17 -10
  80. data/config/locales/id-ID.yml +32 -3
  81. data/config/locales/is-IS.yml +18 -2
  82. data/config/locales/it.yml +84 -14
  83. data/config/locales/ja.yml +70 -23
  84. data/config/locales/lb.yml +32 -7
  85. data/config/locales/lt.yml +9 -3
  86. data/config/locales/lv.yml +26 -2
  87. data/config/locales/nl.yml +33 -6
  88. data/config/locales/no.yml +25 -0
  89. data/config/locales/pl.yml +15 -6
  90. data/config/locales/pt-BR.yml +18 -8
  91. data/config/locales/pt.yml +31 -0
  92. data/config/locales/ro-RO.yml +475 -207
  93. data/config/locales/ru.yml +33 -1
  94. data/config/locales/sk.yml +39 -7
  95. data/config/locales/sl.yml +4 -0
  96. data/config/locales/sr-CS.yml +2 -0
  97. data/config/locales/sv.yml +35 -16
  98. data/config/locales/tr-TR.yml +32 -8
  99. data/config/locales/uk.yml +20 -2
  100. data/config/locales/zh-CN.yml +27 -2
  101. data/config/locales/zh-TW.yml +14 -0
  102. data/config/routes.rb +1 -0
  103. data/decidim-core.gemspec +4 -1
  104. data/lib/decidim/api/functions/component_list.rb +1 -1
  105. data/lib/decidim/api/functions/participatory_space_finder_base.rb +11 -1
  106. data/lib/decidim/api/interfaces/participatory_space_interface.rb +1 -1
  107. data/lib/decidim/api/types/component_type.rb +7 -0
  108. data/lib/decidim/api/types/user_group_type.rb +4 -0
  109. data/lib/decidim/api/types/user_type.rb +4 -0
  110. data/lib/decidim/attributes/rich_text.rb +38 -0
  111. data/lib/decidim/attributes/time_with_zone.rb +16 -2
  112. data/lib/decidim/attributes.rb +2 -0
  113. data/lib/decidim/content_parsers/blob_parser.rb +95 -0
  114. data/lib/decidim/content_parsers/user_parser.rb +1 -1
  115. data/lib/decidim/content_parsers.rb +1 -0
  116. data/lib/decidim/content_renderers/blob_renderer.rb +90 -0
  117. data/lib/decidim/content_renderers.rb +1 -0
  118. data/lib/decidim/core/engine.rb +29 -1
  119. data/lib/decidim/core/test/factories.rb +28 -0
  120. data/lib/decidim/core/test/shared_examples/authorable_interface_examples.rb +1 -1
  121. data/lib/decidim/core/test/shared_examples/comments_examples.rb +15 -2
  122. data/lib/decidim/core/test/shared_examples/reports_examples.rb +48 -6
  123. data/lib/decidim/core/test/shared_examples/uncommentable_component_examples.rb +26 -0
  124. data/lib/decidim/core/test/shared_examples/versions_controller_examples.rb +26 -0
  125. data/lib/decidim/core/version.rb +1 -1
  126. data/lib/decidim/diffy_extension.rb +18 -0
  127. data/lib/decidim/form_builder.rb +1 -1
  128. data/lib/decidim/map/autocomplete.rb +1 -0
  129. data/lib/decidim/map/provider/dynamic_map/here.rb +1 -40
  130. data/lib/decidim/map/provider/static_map/here.rb +34 -0
  131. data/lib/decidim/nicknamizable.rb +1 -1
  132. data/lib/decidim/participatory_space_user.rb +4 -0
  133. data/lib/decidim/query_extensions.rb +0 -26
  134. data/lib/decidim/reportable.rb +6 -2
  135. data/lib/decidim/settings_manifest.rb +2 -0
  136. data/lib/decidim/translatable_attributes.rb +10 -1
  137. data/lib/decidim/view_model.rb +1 -0
  138. data/lib/tasks/upgrade/decidim_fix_categorization.rake +34 -8
  139. data/lib/tasks/upgrade/decidim_fix_nickname_uniqueness.rake +23 -20
  140. metadata +30 -8
  141. data/app/packs/src/decidim/vendor/leaflet-tilelayer-here.js +0 -212
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 3ec3e95f4dbf1ac0e6178188a46ddf5d702bea045e85d76f64c1193af3c0e2ed
4
- data.tar.gz: 684aeb5ae98c9d8fe9fa9488721d153fbc42be5dcd1cd13d1328e34da3ebdef8
3
+ metadata.gz: 88c7b06f4bb2ad092e2e306434bd645d1f27f8e467bc34ab341093f1213ef70b
4
+ data.tar.gz: 45be0ca0bc0b4f11581b1e12c931cbc1c9a80a321ed9c7e1822bc79ff7d9d59f
5
5
  SHA512:
6
- metadata.gz: fee3f0d928f2853e9953dc65e5232aae720997c23d7933e4e08a3154246ae6612a7688e4b9dc0c7af196b9f7043aacd08b7bee72e60ebe69b6b9ce057e17149c
7
- data.tar.gz: 02fb38ba880953a3b819f4ff1b3d88d6cde4dcd66a2753ddb5178fa524d73f8e8db14b4bcf4e862651ff16c45c1f6be4044520a6daf8d8f94b1ff9dfae299ec1
6
+ metadata.gz: 79b66628f6569dd4f570d3388bf185583b5d9fe1edee9545da9948cb8371e9b7722edd651db0805a73aeeb272e8b8c2df4c55b07b9237da062761992e43e8c13
7
+ data.tar.gz: 969da677904a158b3b7744f58a834162dbc10c6b8d94a609aac305744a6c061b439117793d52d964edcbbfe656476239651ac0d6dc34d32c22fc3da79f0b32b6
@@ -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 %>
@@ -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
@@ -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
@@ -3,7 +3,9 @@
3
3
  <%= render :metadata %>
4
4
  <%= cell "decidim/content_blocks/menu_breadcrumb_last_activity", model, hide_participatory_space: true %>
5
5
  </div>
6
- <ul class="menu-bar__secondary-dropdown__menu">
7
- <%= render :links %>
8
- </ul>
6
+ <nav role="navigation" aria-label="<%= t("decidim.accessibility.secondary_menu") %>">
7
+ <ul class="menu-bar__secondary-dropdown__menu">
8
+ <%= render :links %>
9
+ </ul>
10
+ </nav>
9
11
  </div>
@@ -11,7 +11,7 @@
11
11
  <ul id="dropdown-menu-resource">
12
12
  <% resource_types.each do |resource_type| %>
13
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 %>
14
+ <%= link_to filter_url(resource_type[0]), class: "filter#{" is-active" if filter_param == resource_type[0]}" do %>
15
15
  <span class="sr-only"><%= resource_type[1] %></span>
16
16
  <%= text_with_resource_icon(*resource_type) %>
17
17
  <% end %>
@@ -29,8 +29,12 @@ module Decidim
29
29
  options[:id] || "filters"
30
30
  end
31
31
 
32
- def form_path
33
- options[:form_path]
32
+ def filter_url(resource_type)
33
+ if options[:source] == :last_activities
34
+ last_activities_path(filter: { with_resource_type: resource_type })
35
+ else
36
+ profile_activity_path(nickname: params[:nickname], filter: { resource_type: })
37
+ end
34
38
  end
35
39
 
36
40
  def filter_param_key
@@ -41,10 +45,6 @@ module Decidim
41
45
  @filter_param ||= params.dig(:filter, filter_param_key) || all_types_key
42
46
  end
43
47
 
44
- def filter
45
- options[:filter]
46
- end
47
-
48
48
  def all_resource_types_option
49
49
  [all_types_key, I18n.t("all", scope: "decidim.last_activities")]
50
50
  end
@@ -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
@@ -1,5 +1,5 @@
1
1
  <div class="profile__activity">
2
- <%= cell "decidim/resource_types_filter", resource_types, form_path: url_for, filter: %>
2
+ <%= cell "decidim/resource_types_filter", resource_types, source: :profile_activity %>
3
3
  <div class="profile__activity__container" id="activities-container">
4
4
  <% if activities.length == 0 %>
5
5
  <%= cell "decidim/announcement", t("decidim.user_activity.index.no_activities_warning") %>
@@ -45,6 +45,8 @@ module Decidim
45
45
 
46
46
  attr_reader :form, :verified_email
47
47
 
48
+ REGEXP_SANITIZER = /[<>?%&\^*#@()\[\]=+:;"{}\\|]/
49
+
48
50
  def create_or_find_user
49
51
  @user = User.find_or_initialize_by(
50
52
  email: verified_email,
@@ -63,16 +65,11 @@ module Decidim
63
65
  @user.save!
64
66
  else
65
67
  @user.email = (verified_email || form.email)
66
- @user.name = form.name
68
+ @user.name = form.name.gsub(REGEXP_SANITIZER, "")
67
69
  @user.nickname = form.normalized_nickname
68
70
  @user.newsletter_notifications_at = nil
69
71
  @user.password = SecureRandom.hex
70
- if form.avatar_url.present?
71
- url = URI.parse(form.avatar_url)
72
- filename = File.basename(url.path)
73
- file = url.open
74
- @user.avatar.attach(io: file, filename:)
75
- end
72
+ attach_avatar(form.avatar_url) if form.avatar_url.present?
76
73
  @user.skip_confirmation! if verified_email
77
74
  @user.tos_agreement = "1"
78
75
  @user.save!
@@ -81,6 +78,15 @@ module Decidim
81
78
  end
82
79
  end
83
80
 
81
+ def attach_avatar(avatar_url)
82
+ url = URI.parse(avatar_url)
83
+ filename = File.basename(url.path)
84
+ file = url.open
85
+ @user.avatar.attach(io: file, filename:)
86
+ rescue OpenURI::HTTPError, Errno::ECONNREFUSED
87
+ # Do not attach the avatar, as it fails to fetch it.
88
+ end
89
+
84
90
  def create_identity
85
91
  @user.identities.create!(
86
92
  provider: form.provider,
@@ -126,7 +132,7 @@ module Decidim
126
132
  provider: form.provider,
127
133
  uid: form.uid,
128
134
  email: form.email,
129
- name: form.name,
135
+ name: form.name.gsub(REGEXP_SANITIZER, ""),
130
136
  nickname: form.normalized_nickname,
131
137
  avatar_url: form.avatar_url,
132
138
  raw_data: form.raw_data
@@ -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
@@ -37,6 +37,12 @@ module Decidim
37
37
  klass.order_by_id_list(result_ids.take(HIGHLIGHTED_RESULTS_COUNT))
38
38
  end
39
39
 
40
+ uncommentable_resources = uncommentable_resources(results) if results.present?
41
+ if uncommentable_resources.present?
42
+ results -= uncommentable_resources
43
+ results_count -= uncommentable_resources.count
44
+ end
45
+
40
46
  results_by_type.update(class_name => {
41
47
  count: results_count,
42
48
  results:
@@ -89,5 +95,13 @@ module Decidim
89
95
  query = query.global_search(I18n.transliterate(term)) if term.present?
90
96
  query
91
97
  end
98
+
99
+ def uncommentable_resources(results)
100
+ results.where(id: results.select { |obj| related_uncommentable_resources?(obj) }.map(&:id))
101
+ end
102
+
103
+ def related_uncommentable_resources?(object)
104
+ object.respond_to?(:commentable) && !object.commentable.commentable?
105
+ end
92
106
  end
93
107
  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
@@ -24,7 +24,11 @@ module Decidim
24
24
  def show
25
25
  return redirect_to profile_members_path if profile_holder.is_a?(Decidim::UserGroup)
26
26
 
27
- redirect_to profile_activity_path(nickname: params[:nickname])
27
+ redirect_to profile_activity_path(nickname: params[:nickname].downcase)
28
+ end
29
+
30
+ def tooltip
31
+ render json: { data: cell("decidim/author", profile_holder.presenter).profile_minicard }
28
32
  end
29
33
 
30
34
  def following
@@ -112,7 +116,7 @@ module Decidim
112
116
  def profile_holder
113
117
  return if params[:nickname].blank?
114
118
 
115
- @profile_holder ||= Decidim::UserBaseEntity.find_by("LOWER(nickname) = ? AND decidim_organization_id = ?", params[:nickname].downcase, current_organization.id)
119
+ @profile_holder ||= Decidim::UserBaseEntity.find_by("nickname = ? AND decidim_organization_id = ?", params[:nickname].downcase, current_organization.id)
116
120
  end
117
121
  end
118
122
  end
@@ -16,7 +16,7 @@ module Decidim
16
16
  CreateReport.call(@form, reportable, current_user) do
17
17
  on(:ok) do
18
18
  flash[:notice] = I18n.t("decidim.reports.create.success")
19
- redirect_back fallback_location: root_path
19
+ redirect_to reportable.reload.reported_content_url
20
20
  end
21
21
 
22
22
  on(:invalid) do
@@ -22,7 +22,7 @@ module Decidim
22
22
  def user
23
23
  return unless params[:nickname]
24
24
 
25
- @user ||= current_organization.users.find_by("LOWER(nickname) = ?", params[:nickname].downcase)
25
+ @user ||= current_organization.users.find_by("nickname = ?", params[:nickname].downcase)
26
26
  end
27
27
 
28
28
  def activities
@@ -21,7 +21,10 @@ module Decidim
21
21
 
22
22
  validates :name, presence: true, format: { with: Decidim::User::REGEXP_NAME }
23
23
  validates :email, presence: true, "valid_email_2/email": { disposable: true }
24
- validates :nickname, presence: true, format: { with: Decidim::User::REGEXP_NICKNAME }
24
+ validates :nickname,
25
+ presence: true,
26
+ format: { with: Decidim::User::REGEXP_NICKNAME, message: :format },
27
+ length: { maximum: Decidim::User.nickname_max_length }
25
28
 
26
29
  validates :nickname, length: { maximum: Decidim::User.nickname_max_length, allow_blank: true }
27
30
  validates :password, password: { name: :name, email: :email, username: :nickname }, if: -> { password.present? }
@@ -66,7 +69,7 @@ module Decidim
66
69
 
67
70
  def unique_nickname
68
71
  return true if Decidim::UserBaseEntity.where(
69
- "decidim_organization_id = ? AND LOWER(nickname) = ? ",
72
+ "decidim_organization_id = ? AND nickname = ? ",
70
73
  context.current_organization.id,
71
74
  nickname.downcase
72
75
  ).where.not(id: context.current_user.id).empty?
@@ -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
@@ -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
@@ -13,7 +13,8 @@ module Decidim
13
13
  def order_selector(orders, options = {})
14
14
  render partial: "decidim/shared/orders", locals: {
15
15
  orders:,
16
- i18n_scope: options[:i18n_scope]
16
+ i18n_scope: options[:i18n_scope],
17
+ css_class: options[:css_class]
17
18
  }
18
19
  end
19
20
 
@@ -28,7 +28,7 @@ module Decidim
28
28
  end
29
29
 
30
30
  def participatory_space_floating_help
31
- return if help_section.blank?
31
+ return if help_section.blank? || strip_tags(translated_attribute(help_section).html_safe).blank?
32
32
 
33
33
  floating_help(help_id) { translated_attribute(help_section).html_safe }
34
34
  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)
@@ -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
@@ -13,8 +13,6 @@ module Decidim
13
13
  include Decidim::UserReportable
14
14
  include Decidim::Traceable
15
15
 
16
- REGEXP_NICKNAME = /\A[\w-]+\z/
17
-
18
16
  class Roles
19
17
  def self.all
20
18
  Decidim.config.user_roles
@@ -51,8 +49,6 @@ module Decidim
51
49
 
52
50
  has_one_attached :download_your_data_file
53
51
 
54
- scope :not_deleted, -> { where(deleted_at: nil) }
55
-
56
52
  scope :managed, -> { where(managed: true) }
57
53
  scope :not_managed, -> { where(managed: false) }
58
54
 
@@ -21,11 +21,13 @@ module Decidim
21
21
 
22
22
  # Regex for name & nickname format validations
23
23
  REGEXP_NAME = /\A(?!.*[<>?%&\^*#@()\[\]=+:;"{}\\|])/
24
+ REGEXP_NICKNAME = /\A[a-z0-9_-]+\z/
24
25
 
25
26
  has_one_attached :avatar
26
27
  validates_avatar :avatar, uploader: Decidim::AvatarUploader
27
28
 
28
29
  validates :name, format: { with: REGEXP_NAME }
30
+ validates :nickname, format: { with: REGEXP_NICKNAME }, unless: -> { deleted? || managed? }
29
31
 
30
32
  scope :confirmed, -> { where.not(confirmed_at: nil) }
31
33
  scope :not_confirmed, -> { where(confirmed_at: nil) }
@@ -34,6 +36,8 @@ module Decidim
34
36
  scope :not_blocked, -> { where(blocked: false) }
35
37
  scope :available, -> { where(deleted_at: nil, blocked: false, managed: false) }
36
38
 
39
+ scope :not_deleted, -> { where(deleted_at: nil) }
40
+
37
41
  # Public: Returns a collection with all the public entities this user is following.
38
42
  #
39
43
  # This cannot be done as with a `has_many :following, through: :following_follows`
@@ -53,10 +53,18 @@ $(() => {
53
53
  }
54
54
 
55
55
  $(document).on("click.zf.trigger", (event) => {
56
- const target = `#${$(event.target).data("dialogOpen")}`;
57
- const redirectUrl = $(event.target).data("redirectUrl");
56
+ // Try to get the <a> directly or find the closest parent <a>
57
+ const $target = $(event.target).closest("a");
58
58
 
59
- if (!target || !redirectUrl) {
59
+ // Check if an <a> was found
60
+ if (!$target) {
61
+ return;
62
+ }
63
+
64
+ const dialogTarget = `#${$target.data("dialog-open")}`;
65
+ const redirectUrl = $target.data("redirectUrl");
66
+
67
+ if (!dialogTarget || !redirectUrl) {
60
68
  return;
61
69
  }
62
70
 
@@ -64,10 +72,10 @@ $(() => {
64
72
  attr("id", "redirect_url").
65
73
  attr("name", "redirect_url").
66
74
  attr("value", redirectUrl).
67
- appendTo(`${target} form`);
75
+ appendTo(`${dialogTarget} form`);
68
76
 
69
- $(`${target} a`).attr("href", (index, href) => {
70
- const querystring = jQuery.param({ "redirect_url": redirectUrl });
77
+ $(`${dialogTarget} a`).attr("href", (index, href) => {
78
+ const querystring = jQuery.param({"redirect_url": redirectUrl});
71
79
  return href + (href.match(/\?/) ? "&" : "?") + querystring;
72
80
  });
73
81
  });
@@ -21,6 +21,7 @@ const updateActiveUploads = (modal) => {
21
21
  const files = document.querySelector(`[data-active-uploads=${modal.modal.id}]`)
22
22
  const previousId = Array.from(files.querySelectorAll("[type=hidden][id]"))
23
23
  const isMultiple = modal.options.multiple
24
+ const isTitled = modal.options.titled
24
25
 
25
26
  // fastest way to clean children nodes
26
27
  files.textContent = ""
@@ -34,16 +35,26 @@ const updateActiveUploads = (modal) => {
34
35
  let hidden = ""
35
36
  if (file.hiddenField) {
36
37
  // if there is hiddenField, this file is new
37
- const fileField = isMultiple
38
- ? `${modal.options.resourceName}[${modal.options.addAttribute}][${ix}][file]`
39
- : `${modal.options.resourceName}[${modal.options.addAttribute}]`
38
+ let fileField = null;
39
+ if (isMultiple) {
40
+ fileField = `${modal.options.resourceName}[${modal.options.addAttribute}][${ix}][file]`
41
+ } else if (isTitled) {
42
+ fileField = `${modal.options.resourceName}[${modal.options.addAttribute}][file]`
43
+ } else {
44
+ fileField = `${modal.options.resourceName}[${modal.options.addAttribute}]`;
45
+ }
40
46
 
41
47
  hidden = `<input type="hidden" name="${fileField}" value="${file.hiddenField}" />`
42
48
  } else {
43
49
  // otherwise, we keep the attachmentId
44
- const fileField = isMultiple
45
- ? `${modal.options.resourceName}[${modal.options.addAttribute}][${ix}][id]`
46
- : `${modal.options.resourceName}[${modal.options.addAttribute}]`
50
+ let fileField = null;
51
+ if (isMultiple) {
52
+ fileField = `${modal.options.resourceName}[${modal.options.addAttribute}][${ix}][id]`;
53
+ } else if (isTitled) {
54
+ fileField = `${modal.options.resourceName}[${modal.options.addAttribute}][id]`;
55
+ } else {
56
+ fileField = `${modal.options.resourceName}[${modal.options.addAttribute}]`;
57
+ }
47
58
 
48
59
  // convert all node attributes to string
49
60
  const attributes = Array.from(previousId.find(({ id }) => id === file.attachmentId).attributes).reduce((acc, { name, value }) => `${acc} ${name}="${value}"`, "")
@@ -51,10 +62,12 @@ const updateActiveUploads = (modal) => {
51
62
  hidden += `<input type="hidden" name="${fileField}" value="${file.attachmentId}" />`
52
63
  }
53
64
 
54
- if (modal.options.titled) {
65
+ if (isTitled) {
55
66
  const titleValue = modal.modal.querySelectorAll('input[type="text"]')[ix].value
56
67
  // NOTE - Renaming the attachment is not supported when multiple uploader is disabled
57
- const titleField = `${modal.options.resourceName}[${modal.options.addAttribute}][${ix}][title]`
68
+ const titleField = isMultiple
69
+ ? `${modal.options.resourceName}[${modal.options.addAttribute}][${ix}][title]`
70
+ : `${modal.options.resourceName}[${modal.options.addAttribute}][title]`
58
71
  hidden += `<input type="hidden" name="${titleField}" value="${escapeQuotes(titleValue)}" />`
59
72
 
60
73
  title = titleValue