decidim-core 0.32.0 → 0.32.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 (176) hide show
  1. checksums.yaml +4 -4
  2. data/app/cells/decidim/comments_button_cell.rb +9 -1
  3. data/app/cells/decidim/content_blocks/participatory_space_main_data/title.erb +1 -14
  4. data/app/cells/decidim/data_consent/category.erb +20 -33
  5. data/app/cells/decidim/following_cell.rb +1 -1
  6. data/app/cells/decidim/footer_topics/show.erb +5 -8
  7. data/app/cells/decidim/member/show.erb +1 -1
  8. data/app/cells/decidim/nav_links/show.erb +3 -1
  9. data/app/cells/decidim/share_text_widget/modal.erb +1 -0
  10. data/app/cells/decidim/share_widget/modal.erb +1 -0
  11. data/app/cells/decidim/share_widget/qr_code_modal.erb +2 -4
  12. data/app/commands/decidim/create_omniauth_registration.rb +17 -2
  13. data/app/controllers/concerns/decidim/devise_authentication_methods.rb +4 -0
  14. data/app/controllers/concerns/decidim/devise_controllers.rb +2 -0
  15. data/app/controllers/concerns/decidim/disable_redirection_to_external_host.rb +2 -0
  16. data/app/controllers/concerns/decidim/filter_resource.rb +2 -0
  17. data/app/controllers/concerns/decidim/impersonate_users.rb +2 -2
  18. data/app/controllers/concerns/decidim/locale_switcher.rb +12 -10
  19. data/app/controllers/concerns/decidim/needs_permission.rb +22 -10
  20. data/app/controllers/concerns/decidim/onboarding_action_methods.rb +2 -0
  21. data/app/controllers/concerns/decidim/paginable.rb +7 -3
  22. data/app/controllers/concerns/decidim/payload_info.rb +2 -0
  23. data/app/controllers/concerns/decidim/resource_versions_concern.rb +14 -0
  24. data/app/controllers/decidim/application_controller.rb +0 -6
  25. data/app/controllers/decidim/authorization_modals_controller.rb +2 -0
  26. data/app/controllers/decidim/components/base_controller.rb +2 -0
  27. data/app/controllers/decidim/devise/passwords_controller.rb +8 -0
  28. data/app/controllers/decidim/favicon_controller.rb +9 -0
  29. data/app/controllers/decidim/free_resource_authorization_modals_controller.rb +2 -0
  30. data/app/controllers/decidim/gamification/badges_controller.rb +2 -0
  31. data/app/controllers/decidim/homepage_controller.rb +4 -1
  32. data/app/controllers/decidim/likes_controller.rb +1 -1
  33. data/app/controllers/decidim/links_controller.rb +2 -0
  34. data/app/controllers/decidim/manifests_controller.rb +2 -0
  35. data/app/controllers/decidim/messaging/conversations_controller.rb +10 -4
  36. data/app/controllers/decidim/newsletters_controller.rb +4 -0
  37. data/app/controllers/decidim/notifications_subscriptions_controller.rb +4 -0
  38. data/app/controllers/decidim/offline_controller.rb +5 -1
  39. data/app/controllers/decidim/open_data_controller.rb +5 -1
  40. data/app/controllers/decidim/pages_controller.rb +31 -3
  41. data/app/controllers/decidim/private_downloads_controller.rb +2 -0
  42. data/app/controllers/decidim/profiles_controller.rb +19 -1
  43. data/app/controllers/decidim/qr_controller.rb +3 -1
  44. data/app/controllers/decidim/resource_autocomplete_controller.rb +2 -0
  45. data/app/controllers/decidim/searches_controller.rb +2 -0
  46. data/app/controllers/decidim/short_links_controller.rb +4 -0
  47. data/app/controllers/decidim/static_map_controller.rb +2 -0
  48. data/app/controllers/decidim/timeouts_controller.rb +4 -0
  49. data/app/controllers/decidim/tos_controller.rb +2 -0
  50. data/app/controllers/decidim/upload_validations_controller.rb +4 -2
  51. data/app/controllers/decidim/user_activities_controller.rb +3 -0
  52. data/app/forms/decidim/upload_validation_form.rb +16 -10
  53. data/app/helpers/concerns/decidim/user_role_checker.rb +35 -10
  54. data/app/helpers/decidim/decidim_form_helper.rb +1 -1
  55. data/app/helpers/decidim/menu_helper.rb +0 -2
  56. data/app/helpers/decidim/sanitize_helper.rb +11 -1
  57. data/app/jobs/decidim/application_job.rb +4 -0
  58. data/app/models/decidim/authorization.rb +24 -0
  59. data/app/models/decidim/oauth_application.rb +1 -0
  60. data/app/models/decidim/user.rb +3 -2
  61. data/app/models/decidim/user_base_entity.rb +1 -1
  62. data/app/packs/src/decidim/callout.js +10 -0
  63. data/app/packs/src/decidim/callout.test.js +32 -0
  64. data/app/packs/src/decidim/controllers/accordion/accordion.test.js +260 -42
  65. data/app/packs/src/decidim/controllers/accordion/controller.js +136 -17
  66. data/app/packs/src/decidim/controllers/autofocus/controller.js +30 -0
  67. data/app/packs/src/decidim/controllers/dropdown/controller.js +6 -0
  68. data/app/packs/src/decidim/controllers/dropdown/dropdown.test.js +28 -0
  69. data/app/packs/src/decidim/controllers/language_change/controller.js +21 -8
  70. data/app/packs/src/decidim/controllers/language_change/language_change.test.js +0 -1
  71. data/app/packs/src/decidim/controllers/main_menu/controller.js +4 -0
  72. data/app/packs/src/decidim/controllers/main_menu/main_menu.test.js +23 -0
  73. data/app/packs/src/decidim/controllers/mention/controller.js +32 -3
  74. data/app/packs/src/decidim/controllers/mention/input_mentions.test.js +8 -5
  75. data/app/packs/src/decidim/controllers/multiple_mentions/input_multiple_mentions.test.js +1 -1
  76. data/app/packs/src/decidim/controllers/password_toggler/controller.js +22 -1
  77. data/app/packs/src/decidim/controllers/password_toggler/password_toggler.test.js +11 -20
  78. data/app/packs/src/decidim/datepicker/datepicker_functions.js +41 -25
  79. data/app/packs/src/decidim/datepicker/generate_datepicker.js +4 -4
  80. data/app/packs/src/decidim/datepicker/test/datepicker_functions_adjust_picker_position.test.js +64 -0
  81. data/app/packs/src/decidim/datepicker/test/time.test.js +39 -24
  82. data/app/packs/src/decidim/direct_uploads/upload_modal.js +2 -1
  83. data/app/packs/src/decidim/editor/common/suggestion.js +32 -22
  84. data/app/packs/src/decidim/editor/extensions/mention/index.js +14 -3
  85. data/app/packs/src/decidim/editor/index.js +4 -0
  86. data/app/packs/src/decidim/editor/test/extensions/mention.test.js +32 -9
  87. data/app/packs/src/decidim/map/controller.js +10 -1
  88. data/app/packs/src/decidim/refactor/implementation/autofocus.js +22 -0
  89. data/app/packs/src/decidim/refactor/implementation/autofocus.test.js +171 -0
  90. data/app/packs/src/decidim/refactor/moved/focus_guard.js +12 -12
  91. data/app/packs/src/decidim/refactor/moved/focus_guard.test.js +136 -0
  92. data/app/packs/src/decidim/utilities/dom.js +2 -2
  93. data/app/packs/stylesheets/decidim/_cards.scss +1 -1
  94. data/app/packs/stylesheets/decidim/_content_blocks.scss +4 -10
  95. data/app/packs/stylesheets/decidim/_conversations.scss +0 -8
  96. data/app/packs/stylesheets/decidim/_cookies.scss +13 -5
  97. data/app/packs/stylesheets/decidim/_documents.scss +1 -1
  98. data/app/packs/stylesheets/decidim/_footer.scss +33 -1
  99. data/app/packs/stylesheets/decidim/_modal.scss +16 -8
  100. data/app/packs/stylesheets/decidim/_modal_share.scss +0 -22
  101. data/app/packs/stylesheets/decidim/_participatory_spaces.scss +12 -2
  102. data/app/packs/stylesheets/decidim/_rich_text.scss +12 -9
  103. data/app/packs/stylesheets/decidim/_typography.scss +5 -2
  104. data/app/packs/stylesheets/decidim/application.scss +1 -0
  105. data/app/packs/stylesheets/decidim/legacy/email.scss +0 -5
  106. data/app/permissions/decidim/permissions.rb +20 -0
  107. data/app/presenters/decidim/admin_log/component_presenter.rb +1 -1
  108. data/app/presenters/decidim/footer_menu_presenter.rb +2 -4
  109. data/app/presenters/decidim/log/base_presenter.rb +10 -1
  110. data/app/presenters/decidim/log/user_presenter.rb +2 -2
  111. data/app/presenters/decidim/menu_item_presenter.rb +1 -5
  112. data/app/presenters/decidim/resource_presenter.rb +11 -0
  113. data/app/resolvers/decidim/meta_image_url_resolver.rb +14 -1
  114. data/app/serializers/decidim/exporters/open_data_blocked_user_serializer.rb +11 -2
  115. data/app/serializers/decidim/exporters/open_data_moderation_serializer.rb +25 -1
  116. data/app/services/decidim/svg_content_validator.rb +554 -0
  117. data/app/uploaders/decidim/application_uploader.rb +2 -2
  118. data/app/validators/uploader_image_content_validator.rb +276 -0
  119. data/app/views/decidim/application/_radio_accordion.html.erb +1 -5
  120. data/app/views/decidim/messaging/conversations/_add_conversation_users.html.erb +1 -1
  121. data/app/views/decidim/messaging/conversations/create.js.erb +1 -3
  122. data/app/views/decidim/searches/_filters.html.erb +1 -1
  123. data/app/views/decidim/shared/_login_modal.html.erb +12 -14
  124. data/app/views/layouts/decidim/_mailer_logo.html.erb +1 -1
  125. data/app/views/layouts/decidim/footer/_main.html.erb +1 -1
  126. data/app/views/layouts/decidim/footer/_main_links.html.erb +56 -28
  127. data/app/views/layouts/decidim/header/_menu_breadcrumb_desktop.html.erb +3 -2
  128. data/config/initializers/rack_attack.rb +10 -0
  129. data/config/locales/bg.yml +0 -1
  130. data/config/locales/ca-IT.yml +9 -1
  131. data/config/locales/ca.yml +16 -6
  132. data/config/locales/cs.yml +31 -1
  133. data/config/locales/de.yml +99 -3
  134. data/config/locales/en.yml +11 -1
  135. data/config/locales/es-MX.yml +11 -1
  136. data/config/locales/es-PY.yml +11 -1
  137. data/config/locales/es.yml +15 -5
  138. data/config/locales/eu.yml +166 -161
  139. data/config/locales/fi-plain.yml +9 -1
  140. data/config/locales/fi.yml +9 -1
  141. data/config/locales/fr-CA.yml +12 -1
  142. data/config/locales/fr.yml +12 -1
  143. data/config/locales/gl.yml +1485 -33
  144. data/config/locales/hu.yml +0 -1
  145. data/config/locales/ja.yml +28 -1
  146. data/config/locales/pl.yml +2 -1
  147. data/config/locales/pt-BR.yml +0 -1
  148. data/config/locales/ro-RO.yml +0 -1
  149. data/config/locales/sk.yml +2 -1
  150. data/config/locales/sv.yml +68 -12
  151. data/config/locales/tr-TR.yml +0 -2
  152. data/config/routes.rb +11 -2
  153. data/db/migrate/20250217192438_convert_user_groups_into_users.rb +3 -2
  154. data/db/migrate/20260909000000_add_failed_attempts_and_locked_at_to_decidim_authorizations.rb +8 -0
  155. data/decidim-core.gemspec +4 -1
  156. data/lib/decidim/api/functions/user_entity_list.rb +1 -0
  157. data/lib/decidim/api/types/user_moderation_type.rb +2 -2
  158. data/lib/decidim/content_parsers/user_parser.rb +1 -1
  159. data/lib/decidim/content_parsers.rb +0 -1
  160. data/lib/decidim/content_renderers/mention_resource_renderer.rb +18 -4
  161. data/lib/decidim/content_renderers/user_renderer.rb +15 -4
  162. data/lib/decidim/core/test/shared_examples/access_mode_restricted_participatory_spaces.rb +8 -5
  163. data/lib/decidim/core/test/shared_examples/access_mode_transparent_participatory_spaces.rb +14 -11
  164. data/lib/decidim/core/test/shared_examples/comments_examples.rb +26 -0
  165. data/lib/decidim/core/test/shared_examples/participatory_space_members_page_examples.rb +77 -0
  166. data/lib/decidim/core/test/shared_examples/participatory_space_members_shared_examples.rb +58 -0
  167. data/lib/decidim/core/test/shared_examples/versions_controller_examples.rb +23 -1
  168. data/lib/decidim/core/version.rb +1 -1
  169. data/lib/decidim/core.rb +10 -1
  170. data/lib/decidim/form_builder.rb +9 -1
  171. data/lib/decidim/has_upload_validations.rb +1 -0
  172. data/lib/decidim/legacy_form_builder.rb +4 -0
  173. data/lib/decidim/map/dynamic_map.rb +3 -1
  174. metadata +33 -7
  175. data/lib/decidim/content_parsers/inline_images_parser.rb +0 -68
  176. data/lib/tasks/upgrade/decidim_active_storage_migration_tasks.rake +0 -79
@@ -92,14 +92,6 @@
92
92
  [data-dialog-closable] {
93
93
  @apply top-6 right-6;
94
94
  }
95
-
96
- [data-dialog-container] {
97
- @apply inline grid-cols-none;
98
- }
99
-
100
- [data-dialog-title] {
101
- @apply col-span-2 md:col-span-1 md:col-start-2 grid grid-cols-[auto_1fr] items-start md:items-center gap-2 pb-4 text-left text-xl text-gray-2 md:border-b md:border-gray-3;
102
- }
103
95
  }
104
96
 
105
97
  &-results {
@@ -65,17 +65,25 @@
65
65
  &-panel {
66
66
  @apply divide-y divide-gray-3 [&>*]:py-4 [&>p]:text-gray-2;
67
67
 
68
- &__tr {
69
- @apply flex items-center justify-between rounded first-of-type:bg-background-4;
68
+ table {
69
+ @apply w-full border-collapse;
70
70
  }
71
71
 
72
- &__th {
73
- @apply w-1/4 p-2 bg-background-4 text-gray-2 font-bold text-sm;
72
+ thead tr {
73
+ @apply bg-background-4;
74
74
  }
75
75
 
76
- &__td {
76
+ th {
77
+ @apply w-1/4 p-2 text-gray-2 font-bold text-sm text-left;
78
+ }
79
+
80
+ td {
77
81
  @apply w-1/4 p-2 text-gray-2 text-xs;
78
82
  }
83
+
84
+ tbody tr {
85
+ @apply border-b border-gray-3;
86
+ }
79
87
  }
80
88
 
81
89
  &-toggle {
@@ -3,7 +3,7 @@
3
3
  @apply divide-y divide-background [&>*]:py-4 first:[&>*]:pt-0 last:[&>*]:pb-0;
4
4
 
5
5
  > * {
6
- @apply flex items-center justify-between gap-4;
6
+ @apply grid sm:flex items-center justify-between gap-4;
7
7
  }
8
8
  }
9
9
 
@@ -3,12 +3,44 @@ footer {
3
3
  @apply bg-gray-4;
4
4
 
5
5
  &__top {
6
- @apply hidden lg:flex flex-row gap-8 container py-10;
6
+ @apply flex flex-col lg:flex-row gap-0 lg:gap-8 container py-10;
7
7
 
8
8
  .prose a,
9
9
  .prose strong {
10
10
  @apply text-white;
11
11
  }
12
+
13
+ .footer__nav {
14
+ @apply border-b border-white/20 lg:border-0;
15
+
16
+ ul {
17
+ @apply space-y-4 break-inside-avoid mb-4;
18
+
19
+ li a {
20
+ @apply font-semibold underline decoration-1 underline-offset-1;
21
+ }
22
+ }
23
+
24
+ > [data-controls] {
25
+ @apply flex items-center justify-between w-full py-4 lg:pointer-events-none lg:cursor-default text-left text-white font-semibold;
26
+
27
+ svg {
28
+ @apply w-4 h-4 fill-current transition-transform lg:hidden;
29
+ }
30
+
31
+ &[aria-expanded="true"] svg {
32
+ @apply rotate-180;
33
+ }
34
+ }
35
+
36
+ [id*="panel"][aria-hidden="true"] {
37
+ @apply hidden lg:block;
38
+ }
39
+
40
+ [id*="panel"][aria-hidden="false"] {
41
+ @apply block;
42
+ }
43
+ }
12
44
  }
13
45
 
14
46
  &__down {
@@ -22,19 +22,27 @@
22
22
  }
23
23
 
24
24
  [data-dialog-container] {
25
- @apply grid grid-cols-[auto_1fr] items-start md:items-center gap-2 text-left;
25
+ @apply block text-left;
26
26
 
27
- > svg {
28
- @apply w-6 h-6 text-gray fill-current flex-none;
29
- }
27
+ &:has(> svg) {
28
+ @apply grid grid-cols-[auto_1fr] items-start md:items-center gap-2;
29
+
30
+ > svg {
31
+ @apply w-6 h-6 text-gray fill-current flex-none mb-6;
32
+ }
30
33
 
31
- > :last-child {
32
- @apply col-span-2 md:col-span-1 md:col-start-2;
34
+ > :last-child {
35
+ @apply col-span-2 md:col-span-1 md:col-start-2;
36
+ }
33
37
  }
34
38
  }
35
39
 
36
40
  [data-dialog-title] {
37
- @apply text-2xl text-black font-semibold focus:outline-none;
41
+ @apply flex items-center gap-2 text-xl text-gray-2 font-semibold focus:outline-none pb-2 mb-4 md:border-b md:border-gray-3;
42
+
43
+ > svg {
44
+ @apply w-6 h-6 text-gray fill-current flex-none;
45
+ }
38
46
  }
39
47
 
40
48
  [data-dialog-actions] {
@@ -59,7 +67,7 @@
59
67
  }
60
68
 
61
69
  [data-dialog-container] {
62
- @apply grid-cols-none;
70
+ @apply grid grid-cols-none;
63
71
 
64
72
  &.layout-content {
65
73
  @apply bg-white;
@@ -1,21 +1,7 @@
1
1
  .qr-modal {
2
2
  @apply md:p-6 lg:p-8;
3
3
 
4
- [data-dialog-title] {
5
- @apply flex justify-start items-center text-2xl text-black font-semibold focus:outline-none;
6
-
7
- > svg {
8
- @apply w-10 h-10 text-gray fill-current flex-none;
9
- }
10
-
11
- > :last-child {
12
- @apply ml-1;
13
- }
14
- }
15
-
16
4
  [data-dialog-container] {
17
- @apply inline grid-cols-none;
18
-
19
5
  .content {
20
6
  @apply md:grid md:grid-cols-2 gap-5 md:py-4;
21
7
 
@@ -59,14 +45,6 @@
59
45
  @apply top-6 right-6;
60
46
  }
61
47
 
62
- [data-dialog-container] {
63
- @apply inline grid-cols-none;
64
- }
65
-
66
- [data-dialog-title] {
67
- @apply col-span-2 md:col-span-1 md:col-start-2 grid grid-cols-[auto_1fr] items-start md:items-center gap-2 pb-4 text-left text-xl text-gray-2 md:border-b md:border-gray-3;
68
- }
69
-
70
48
  &__list {
71
49
  @apply my-6 flex flex-wrap justify-center items-center md:grid grid-cols-4 md:grid-cols-5 gap-3 md:gap-6 max-w-96 mx-auto text-secondary;
72
50
 
@@ -49,7 +49,7 @@
49
49
  }
50
50
 
51
51
  &__collection-content {
52
- @apply w-auto ml-6 my-4 pl-4 lg:pl-8 border-l border-[#D3D5D9];
52
+ @apply w-auto sm:ml-6 sm:my-4 sm:pl-4 lg:pl-8 sm:border-l sm:border-[#D3D5D9];
53
53
 
54
54
  p {
55
55
  @apply pt-4;
@@ -111,7 +111,17 @@
111
111
  }
112
112
 
113
113
  &-item {
114
- @apply flex items-center gap-1 text-white font-semibold hover:underline [&>svg]:fill-current;
114
+ @apply flex items-center gap-1 text-white font-semibold hover:underline;
115
+ }
116
+
117
+ &-label {
118
+ @apply flex-1 min-w-0 overflow-hidden text-ellipsis;
119
+
120
+ overflow-wrap: anywhere;
121
+ }
122
+
123
+ &-item > svg {
124
+ @apply shrink-0 fill-current;
115
125
  }
116
126
  }
117
127
 
@@ -7,18 +7,21 @@
7
7
  .editor-content-videoEmbed {
8
8
  @apply my-10 mx-auto;
9
9
  }
10
+ }
10
11
 
11
- .editor-mention {
12
- @apply inline-flex items-center align-bottom font-semibold underline text-secondary;
12
+ // Resource mentions are rendered both inside rich text (`.rich-text-display`)
13
+ // and inside comments (`.editor-content`), so they cannot be scoped to one of
14
+ // those wrappers.
15
+ .editor-mention {
16
+ @apply inline-flex items-center align-bottom font-semibold underline text-secondary;
13
17
 
14
- img,
15
- svg {
16
- @apply w-5 h-5 rounded-full;
17
- }
18
+ img,
19
+ svg {
20
+ @apply w-5 h-5 m-0 rounded-full;
21
+ }
18
22
 
19
- span {
20
- @apply ml-1;
21
- }
23
+ span {
24
+ @apply ml-1;
22
25
  }
23
26
  }
24
27
 
@@ -27,7 +27,10 @@
27
27
  }
28
28
 
29
29
  .title-decorator {
30
- @apply h2 md:h1 decorator;
30
+ @apply h2 md:h1 decorator break-normal;
31
+
32
+ hyphens: auto;
33
+ overflow-wrap: anywhere;
31
34
  }
32
35
 
33
36
  .text-with-links {
@@ -38,7 +41,7 @@
38
41
 
39
42
  /* default styles for those dynamic text blocks */
40
43
  .editor-content {
41
- @apply prose max-w-full text-gray-2 text-lg prose-a:text-secondary break-words prose-ul:pl-4;
44
+ @apply prose max-w-full text-gray-2 text-lg prose-a:text-secondary break-words prose-ul:pl-8 prose-ol:pl-8;
42
45
 
43
46
  iframe {
44
47
  @apply w-full block max-w-[600px] max-h-[70vh] h-[450px] my-10 mx-auto;
@@ -83,6 +83,7 @@
83
83
  @include meta.load-css("stylesheets/decidim/actions");
84
84
  @include meta.load-css("stylesheets/decidim/emoji");
85
85
  @include meta.load-css("stylesheets/decidim/success_image");
86
+ @include meta.load-css("stylesheets/decidim/editor_suggestions");
86
87
  }
87
88
 
88
89
  // This applies the custom style imports registered at the application.
@@ -1725,7 +1725,6 @@ body.outlook p {
1725
1725
  table.body th.decidim-bar,
1726
1726
  table.body td.decidim-bar {
1727
1727
  padding: 10px 0;
1728
- background-color: #1a181d;
1729
1728
  }
1730
1729
 
1731
1730
  table.body {
@@ -1742,10 +1741,6 @@ table.body {
1742
1741
  height: 30px;
1743
1742
  }
1744
1743
 
1745
- .cityhall-bar {
1746
- background-color: #2c2930;
1747
- }
1748
-
1749
1744
  .cityhall-logo {
1750
1745
  width: 150px;
1751
1746
  margin-top: 16px;
@@ -9,6 +9,7 @@ module Decidim
9
9
  locales_action?
10
10
  component_public_action?
11
11
  search_scope_action?
12
+ public_content_action?
12
13
 
13
14
  public_report_content_action?
14
15
 
@@ -66,6 +67,23 @@ module Decidim
66
67
  toggle_allow([:search, :pick].include?(permission_action.action))
67
68
  end
68
69
 
70
+ # Public, non-resource content that any visitor is allowed to reach. The
71
+ # controllers using these subjects enforce the actual data visibility
72
+ # themselves (profile publication, private download tokens, newsletter
73
+ # tokens, etc.).
74
+ def public_content_action?
75
+ case permission_action.subject
76
+ when :open_data, :search, :profile, :private_download, :short_link,
77
+ :calendar, :badges, :static_map, :qr_code, :homepage, :manifest,
78
+ :offline, :link, :authorization, :error, :timeout, :page
79
+ allow! if permission_action.action == :read
80
+ when :newsletter
81
+ allow! if [:read, :unsubscribe].include?(permission_action.action)
82
+ when :upload_validation
83
+ allow! if permission_action.action == :create
84
+ end
85
+ end
86
+
69
87
  def manage_self_user_action?
70
88
  return unless permission_action.subject == :user
71
89
 
@@ -78,6 +96,8 @@ module Decidim
78
96
  authorization = context.fetch(:authorization, nil)
79
97
 
80
98
  case permission_action.action
99
+ when :read
100
+ allow!
81
101
  when :create
82
102
  toggle_allow(authorization.user == user && not_already_active?(authorization))
83
103
  when :update, :destroy
@@ -29,7 +29,7 @@ module Decidim
29
29
 
30
30
  def action_string
31
31
  case action
32
- when "export_component", "soft_delete", "restore"
32
+ when "export_component", "soft_delete", "restore", "menu_hidden"
33
33
  "decidim.admin_log.component.#{action}"
34
34
  when "create", "delete", "publish", "unpublish", "update_permissions", "update_filters"
35
35
  generate_action_string(action)
@@ -3,10 +3,8 @@
3
3
  module Decidim
4
4
  # A presenter to render footer menu
5
5
  class FooterMenuPresenter < MenuPresenter
6
- def render
7
- content_tag(:nav, :role => "navigation", "aria-label" => @options[:label]) do
8
- safe_join([content_tag(:h2, @options[:label], class: "h5 mb-4"), render_menu])
9
- end
6
+ def render_menu_items
7
+ render_menu
10
8
  end
11
9
  end
12
10
  end
@@ -111,9 +111,18 @@ module Decidim
111
111
  # Returns an HTML-safe String.
112
112
  def present_explanation
113
113
  h.content_tag(:div, class: "logs__log__explanation") do
114
+ escaped_params = i18n_params.transform_values do |value|
115
+ if value.is_a?(String) && !value.html_safe?
116
+ ERB::Util.html_escape(value)
117
+ else
118
+ value
119
+ end
120
+ end
121
+
114
122
  I18n.t(
115
123
  action_string,
116
- **i18n_params
124
+ escape: false,
125
+ **escaped_params
117
126
  ).html_safe
118
127
  end
119
128
  end
@@ -67,9 +67,9 @@ module Decidim
67
67
 
68
68
  # Private: Presents the nickname of the user performing the action.
69
69
  #
70
- # Returns an HTML-safe String.
70
+ # Returns a String.
71
71
  def present_user_nickname
72
- extra["nickname"].html_safe
72
+ extra["nickname"]
73
73
  end
74
74
 
75
75
  # Private: Calculates the path for the user. Returns the path of the
@@ -48,11 +48,7 @@ module Decidim
48
48
  private
49
49
 
50
50
  def link_options
51
- if active?
52
- { aria: { current: "page" } }
53
- else
54
- {}
55
- end.merge({ class: link_classes, role: menuitem_role })
51
+ { class: link_classes, role: menuitem_role }
56
52
  end
57
53
 
58
54
  def composed_label
@@ -45,5 +45,16 @@ module Decidim
45
45
  content
46
46
  end
47
47
  end
48
+
49
+ # Prepares content for plain text areas (non-editor). Renders mentions
50
+ # to plain @nickname text instead of editor-specific HTML tags.
51
+ def plain_locales(data, all_locales)
52
+ handle_locales(data, all_locales) do |content|
53
+ content = Decidim::ContentRenderers::BlobRenderer.new(content).render
54
+ content = Decidim::ContentRenderers::UserRenderer.new(content).render(plain: true)
55
+ content = Decidim::ContentRenderers::MentionResourceRenderer.new(content).render(plain: true)
56
+ content.html_safe
57
+ end
58
+ end
48
59
  end
49
60
  end
@@ -79,7 +79,10 @@ module Decidim
79
79
  image_url = image_element["src"]
80
80
  next if image_url.blank?
81
81
 
82
- blob = find_blob_by_key(image_url.split("/").second_to_last)
82
+ blob = find_blob_by_id(image_url.split("/").last)
83
+ return blob if blob.present?
84
+
85
+ blob = find_blob_from_url(image_url)
83
86
  return blob if blob.present?
84
87
  end
85
88
 
@@ -116,10 +119,20 @@ module Decidim
116
119
  nil
117
120
  end
118
121
 
122
+ def find_blob_from_url(url)
123
+ return GlobalID::Locator.locate(url, only: ActiveStorage::Blob) if url.start_with?("gid://")
124
+
125
+ find_blob_by_key(url.split("/").second_to_last)
126
+ end
127
+
119
128
  def find_blob_by_key(blob_key)
120
129
  ActiveStorage::Blob.find_signed(blob_key) if blob_key.present?
121
130
  end
122
131
 
132
+ def find_blob_by_id(blob_id)
133
+ ActiveStorage::Blob.find_by(id: blob_id) if blob_id.present?
134
+ end
135
+
123
136
  def blob_from_attached_file(file)
124
137
  return unless file.respond_to?(:attached?) && file.attached?
125
138
 
@@ -16,10 +16,19 @@ module Decidim
16
16
  about: resource.user.about,
17
17
  reasons: resource.reports.map(&:reason),
18
18
  details: resource.reports.map(&:details),
19
- block_reasons: resource.blocking.justification,
20
- blocking_user: resource.blocking.blocking_user.presenter.name
19
+ block_reasons: blocking&.justification,
20
+ blocking_user: blocking_user&.presenter&.name
21
21
  }
22
22
  end
23
+
24
+ private
25
+
26
+ # The collection filters on `decidim_users.blocked_at`, but this
27
+ # association is resolved through `decidim_users.block_id`. The two can
28
+ # disagree, in which case there is no block to read the details from.
29
+ def blocking = resource.blocking
30
+
31
+ def blocking_user = blocking&.blocking_user
23
32
  end
24
33
  end
25
34
  end
@@ -14,7 +14,7 @@ module Decidim
14
14
  id: resource.id,
15
15
  hidden_at: resource.hidden_at,
16
16
  report_count: resource.report_count,
17
- reported_url: resource.reportable.reported_content_url,
17
+ reported_url:,
18
18
  reportable_type: resource.decidim_reportable_type,
19
19
  reportable_id: resource.decidim_reportable_id,
20
20
  reported_content: resource.reported_content,
@@ -25,6 +25,30 @@ module Decidim
25
25
  }
26
26
  }
27
27
  end
28
+
29
+ private
30
+
31
+ # `Decidim::Moderation#reportable` is polymorphic, so the database cannot
32
+ # enforce the reference, and the moderation row outlives the resource it
33
+ # points at. Both of the cases below leave the moderation in the export
34
+ # collection with no URL to build.
35
+ def reported_url
36
+ reportable = resource.reportable
37
+
38
+ # The resource is gone. Reportables such as proposals and meetings are
39
+ # soft-deletable while `belongs_to :reportable` does not use
40
+ # `with_deleted`, so moving one to the trash is enough for this.
41
+ return if reportable.nil?
42
+
43
+ # The component is gone but the resource is not: trashing a component
44
+ # does not trash its contents. `ResourceLocatorPresenter#route_proxy`
45
+ # falls back to the resource itself when `component` is nil, and
46
+ # `EngineRouter.main_proxy` then calls `mounted_engine` on it, which
47
+ # only components and participatory spaces respond to.
48
+ return if reportable.is_a?(Decidim::HasComponent) && reportable.component.nil?
49
+
50
+ reportable.reported_content_url
51
+ end
28
52
  end
29
53
  end
30
54
  end