decidim-core 0.25.2 → 0.26.0.rc1
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.
- checksums.yaml +4 -4
- data/app/cells/decidim/activity_cell.rb +2 -1
- data/app/cells/decidim/author/flag_user.erb +1 -1
- data/app/cells/decidim/author/profile_inline.erb +1 -1
- data/app/cells/decidim/author/withdraw.erb +2 -2
- data/app/cells/decidim/author_cell.rb +32 -0
- data/app/cells/decidim/card_m_cell.rb +1 -1
- data/app/cells/decidim/content_blocks/cta_cell.rb +1 -1
- data/app/cells/decidim/content_blocks/hero_cell.rb +1 -1
- data/app/cells/decidim/content_blocks/highlighted_content_banner/show.erb +1 -1
- data/app/cells/decidim/content_blocks/last_activity_cell.rb +1 -1
- data/app/cells/decidim/content_blocks/stats_cell.rb +12 -0
- data/app/cells/decidim/endorsers_list_cell.rb +3 -1
- data/app/cells/decidim/flag_modal/flag_user.erb +2 -2
- data/app/cells/decidim/flag_modal/show.erb +2 -2
- data/app/cells/decidim/flag_modal_cell.rb +10 -0
- data/app/cells/decidim/notification/show.erb +31 -0
- data/app/cells/decidim/notification_cell.rb +20 -0
- data/app/cells/decidim/notifications/show.erb +1 -24
- data/app/cells/decidim/notifications_cell.rb +0 -1
- data/app/cells/decidim/user_conversation/conversation_header.erb +1 -1
- data/app/cells/decidim/user_conversation/show.erb +4 -2
- data/app/cells/decidim/user_conversations/conversation_item.erb +1 -1
- data/app/commands/decidim/create_editor_image.rb +41 -0
- data/app/controllers/decidim/cookie_policy_controller.rb +2 -0
- data/app/controllers/decidim/editor_images_controller.rb +47 -0
- data/app/controllers/decidim/user_activities_controller.rb +2 -1
- data/app/forms/decidim/editor_image_form.rb +16 -0
- data/app/helpers/decidim/amendments_helper.rb +1 -1
- data/app/helpers/decidim/application_helper.rb +2 -2
- data/app/helpers/decidim/messaging/conversation_helper.rb +32 -3
- data/app/helpers/decidim/resource_versions_helper.rb +1 -1
- data/app/helpers/decidim/sanitize_helper.rb +65 -0
- data/app/models/decidim/editor_image.rb +14 -0
- data/app/models/decidim/messaging/conversation.rb +9 -0
- data/app/models/decidim/participatory_space_private_user.rb +16 -0
- data/app/models/decidim/user.rb +3 -3
- data/app/models/decidim/user_group.rb +40 -0
- data/app/packs/entrypoints/decidim_core.js +1 -0
- data/app/packs/src/decidim/dialog_mode.js +143 -0
- data/app/packs/src/decidim/dialog_mode.test.js +168 -0
- data/app/packs/src/decidim/editor.js +56 -14
- data/app/packs/src/decidim/form_attachments.js +5 -0
- data/app/packs/src/decidim/index.js +4 -0
- data/app/packs/src/decidim/vendor/image-resize.min.js +3 -0
- data/app/packs/src/decidim/vendor/image-upload.min.js +8 -0
- data/app/packs/stylesheets/decidim/extras/_extras.scss +0 -1
- data/app/packs/stylesheets/decidim/extras/_quill.scss +7 -0
- data/app/packs/stylesheets/decidim/modules/_buttons.scss +11 -4
- data/app/packs/stylesheets/decidim/modules/_cards.scss +4 -0
- data/app/packs/stylesheets/decidim/modules/_layout.scss +1 -1
- data/app/presenters/decidim/nil_presenter.rb +2 -2
- data/app/presenters/decidim/notification_presenter.rb +25 -0
- data/app/presenters/decidim/official_author_presenter.rb +1 -1
- data/app/presenters/decidim/validation_errors_presenter.rb +27 -0
- data/app/queries/decidim/similar_emendations.rb +1 -1
- data/app/resolvers/decidim/core/metric_resolver.rb +1 -1
- data/app/services/decidim/activity_search.rb +2 -2
- data/app/services/decidim/email_notification_generator.rb +4 -1
- data/app/services/decidim/html_truncation.rb +130 -0
- data/app/services/decidim/open_data_exporter.rb +29 -5
- data/app/services/decidim/resource_search.rb +1 -1
- data/app/uploaders/decidim/editor_image_uploader.rb +6 -0
- data/app/validators/password_validator.rb +123 -0
- data/app/views/decidim/account/_password_fields.html.erb +1 -1
- data/app/views/decidim/devise/passwords/edit.html.erb +1 -1
- data/app/views/decidim/devise/registrations/new.html.erb +1 -1
- data/app/views/decidim/devise/shared/_omniauth_buttons_mini.html.erb +6 -4
- data/app/views/decidim/messaging/conversations/_conversation.html.erb +3 -3
- data/app/views/decidim/messaging/conversations/_messages.html.erb +8 -2
- data/app/views/decidim/messaging/conversations/_show.html.erb +10 -12
- data/app/views/decidim/messaging/conversations/show.html.erb +4 -2
- data/app/views/decidim/newsletters/show.html.erb +1 -1
- data/app/views/decidim/notification_mailer/event_received.html.erb +17 -0
- data/app/views/decidim/pages/_tabbed.html.erb +1 -1
- data/app/views/decidim/searches/_filters_small_view.html.erb +3 -3
- data/app/views/decidim/shared/_login_modal.html.erb +5 -5
- data/app/views/decidim/shared/_orders.html.erb +1 -1
- data/app/views/decidim/shared/_results_per_page.html.erb +1 -1
- data/app/views/decidim/shared/participatory_space_filters/_filters_small_view.html.erb +3 -3
- data/app/views/layouts/decidim/_application.html.erb +1 -12
- data/app/views/layouts/decidim/_head.html.erb +4 -0
- data/app/views/layouts/decidim/_language_chooser.html.erb +1 -1
- data/app/views/layouts/decidim/_meta_tags_config.html.erb +11 -0
- data/app/views/layouts/decidim/_wrapper.html.erb +1 -1
- data/config/brakeman.ignore +149 -0
- data/config/initializers/devise.rb +1 -1
- data/config/initializers/rack_attack.rb +23 -21
- data/config/locales/ca.yml +2 -0
- data/config/locales/cs.yml +60 -0
- data/config/locales/en.yml +45 -0
- data/config/locales/es.yml +45 -0
- data/config/locales/eu.yml +5 -0
- data/config/locales/fi-plain.yml +6 -0
- data/config/locales/fi.yml +45 -0
- data/config/locales/fr-CA.yml +38 -0
- data/config/locales/fr.yml +44 -6
- data/config/locales/gl.yml +5 -0
- data/config/locales/it.yml +11 -0
- data/config/locales/ja.yml +72 -36
- data/config/locales/lb-LU.yml +1354 -0
- data/config/locales/lb.yml +1 -1
- data/config/locales/nl.yml +54 -0
- data/config/locales/pl.yml +5 -5
- data/config/locales/pt-BR.yml +1 -1
- data/config/locales/ro-RO.yml +8 -0
- data/config/locales/sv.yml +5 -0
- data/config/locales/val-ES.yml +1 -0
- data/config/routes.rb +2 -0
- data/db/migrate/20210730112319_create_decidim_editor_images.rb +12 -0
- data/db/migrate/20211126183540_add_timestamps_to_content_blocks.rb +14 -0
- data/db/seeds.rb +16 -14
- data/lib/decidim/api/functions/user_entity_list.rb +1 -0
- data/lib/decidim/api/input_sorts/component_input_sort.rb +1 -1
- data/lib/decidim/common_passwords.rb +56 -0
- data/lib/decidim/content_parsers/inline_images_parser.rb +68 -0
- data/lib/decidim/content_parsers.rb +1 -0
- data/lib/decidim/content_renderers/link_renderer.rb +85 -1
- data/lib/decidim/content_renderers/user_group_renderer.rb +1 -1
- data/lib/decidim/content_renderers/user_renderer.rb +1 -1
- data/lib/decidim/core/engine.rb +3 -12
- data/lib/decidim/core/test/factories.rb +7 -1
- data/lib/decidim/core/test/shared_examples/translated_event_examples.rb +131 -0
- data/lib/decidim/core/test.rb +1 -0
- data/lib/decidim/core/version.rb +1 -1
- data/lib/decidim/core.rb +22 -5
- data/lib/decidim/db/common-passwords.txt +128420 -0
- data/lib/decidim/etherpad/pad.rb +48 -0
- data/lib/decidim/etherpad.rb +7 -0
- data/lib/decidim/events/base_event.rb +18 -0
- data/lib/decidim/events/machine_translated_event.rb +36 -0
- data/lib/decidim/events/user_group_event.rb +1 -3
- data/lib/decidim/events.rb +1 -0
- data/lib/decidim/exporters/csv.rb +7 -7
- data/lib/decidim/faker/localized.rb +15 -6
- data/lib/decidim/form_builder.rb +14 -4
- data/lib/decidim/has_attachments.rb +11 -4
- data/lib/decidim/importers/import_manifest.rb +103 -3
- data/lib/decidim/paddable.rb +1 -9
- data/lib/decidim/searchable.rb +2 -2
- data/lib/decidim/settings_manifest.rb +2 -0
- data/lib/decidim/translatable_attributes.rb +6 -6
- data/lib/decidim/view_model.rb +10 -0
- data/lib/tasks/decidim_active_storage_migration_tasks.rake +68 -0
- metadata +56 -65
- data/app/packs/stylesheets/decidim/extras/_social_icons_mini.scss +0 -11
|
@@ -11,6 +11,5 @@
|
|
|
11
11
|
@import "stylesheets/decidim/extras/quill";
|
|
12
12
|
@import "stylesheets/decidim/extras/reference";
|
|
13
13
|
@import "stylesheets/decidim/extras/results-per-page";
|
|
14
|
-
@import "stylesheets/decidim/extras/social_icons_mini";
|
|
15
14
|
@import "stylesheets/decidim/extras/social_share";
|
|
16
15
|
@import "stylesheets/decidim/extras/status-labels";
|
|
@@ -441,14 +441,21 @@
|
|
|
441
441
|
text-align: center;
|
|
442
442
|
}
|
|
443
443
|
|
|
444
|
-
.button--social--mini .button--social__icon{
|
|
445
|
-
position: relative;
|
|
446
|
-
}
|
|
447
|
-
|
|
448
444
|
.button--social__text{
|
|
449
445
|
margin-left: 3rem;
|
|
450
446
|
}
|
|
451
447
|
|
|
448
|
+
.button--social--mini{
|
|
449
|
+
padding: 0;
|
|
450
|
+
min-width: 0;
|
|
451
|
+
|
|
452
|
+
.button--social__icon{
|
|
453
|
+
border: 0;
|
|
454
|
+
padding: .85em 0;
|
|
455
|
+
margin-right: 0;
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
|
|
452
459
|
.button--twitter{
|
|
453
460
|
background-color: var(--twitter);
|
|
454
461
|
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Decidim
|
|
4
|
+
#
|
|
5
|
+
# Decorator for notifications.
|
|
6
|
+
#
|
|
7
|
+
class NotificationPresenter < SimpleDelegator
|
|
8
|
+
include ActionView::Helpers::DateHelper
|
|
9
|
+
|
|
10
|
+
delegate :resource_text, to: :event_class_instance
|
|
11
|
+
|
|
12
|
+
def created_at_in_words
|
|
13
|
+
if created_at.between?(1.month.ago, Time.current)
|
|
14
|
+
time_ago_in_words(created_at)
|
|
15
|
+
else
|
|
16
|
+
format = created_at.year == Time.current.year ? :ddmm : :ddmmyyyy
|
|
17
|
+
I18n.l(created_at, format: format)
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def display_resource_text?
|
|
22
|
+
event_class.constantize.included_modules.include?(Decidim::Comments::CommentEvent)
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Decidim
|
|
4
|
+
#
|
|
5
|
+
# Decorator to format validation errors of a form in html format
|
|
6
|
+
#
|
|
7
|
+
class ValidationErrorsPresenter < Rectify::Presenter
|
|
8
|
+
include Decidim::SanitizeHelper
|
|
9
|
+
|
|
10
|
+
attr_reader :error, :form
|
|
11
|
+
|
|
12
|
+
def initialize(error, form)
|
|
13
|
+
@error = error
|
|
14
|
+
@form = form
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def message
|
|
18
|
+
"<p>#{error}</p>#{validation_errors_list}"
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def validation_errors_list
|
|
22
|
+
return "" if form.valid?
|
|
23
|
+
|
|
24
|
+
content_tag(:ul, decidim_sanitize(form.errors.full_messages.map { |err| content_tag(:li, err) }.join))
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
@@ -29,7 +29,7 @@ module Decidim
|
|
|
29
29
|
.published
|
|
30
30
|
.not_hidden
|
|
31
31
|
.where(
|
|
32
|
-
"GREATEST(#{title_similarity}, #{body_similarity}) >= ?",
|
|
32
|
+
Arel.sql("GREATEST(#{title_similarity}, #{body_similarity}) >= ?").to_s,
|
|
33
33
|
translated_attribute(emendation.title),
|
|
34
34
|
translated_attribute(emendation.body),
|
|
35
35
|
amendable_module.similarity_threshold
|
|
@@ -150,7 +150,7 @@ module Decidim
|
|
|
150
150
|
ON #{manifest.name}_private_users.privatable_to_type = '#{manifest.model_class_name}'
|
|
151
151
|
AND #{table}.id = #{manifest.name}_private_users.privatable_to_id
|
|
152
152
|
SQL
|
|
153
|
-
)
|
|
153
|
+
).to_s
|
|
154
154
|
).where(
|
|
155
155
|
Arel.sql(
|
|
156
156
|
<<~SQL.squish
|
|
@@ -158,7 +158,7 @@ module Decidim
|
|
|
158
158
|
#{table}.private_space = 'f' OR
|
|
159
159
|
#{manifest.name}_private_users.decidim_user_id = #{current_user_id}
|
|
160
160
|
SQL
|
|
161
|
-
)
|
|
161
|
+
).to_s
|
|
162
162
|
)
|
|
163
163
|
end
|
|
164
164
|
|
|
@@ -66,6 +66,9 @@ module Decidim
|
|
|
66
66
|
return unless recipient.email_on_notification?
|
|
67
67
|
return if resource.respond_to?(:can_participate?) && !resource.can_participate?(recipient)
|
|
68
68
|
|
|
69
|
+
wait_time = 0
|
|
70
|
+
wait_time = Decidim.machine_translation_delay.to_i + 1.minute if recipient.organization.enable_machine_translations
|
|
71
|
+
|
|
69
72
|
NotificationMailer
|
|
70
73
|
.event_received(
|
|
71
74
|
event,
|
|
@@ -75,7 +78,7 @@ module Decidim
|
|
|
75
78
|
user_role.to_s,
|
|
76
79
|
extra
|
|
77
80
|
)
|
|
78
|
-
.deliver_later
|
|
81
|
+
.deliver_later(wait: wait_time)
|
|
79
82
|
end
|
|
80
83
|
|
|
81
84
|
def component
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Decidim
|
|
4
|
+
class HtmlTruncation
|
|
5
|
+
include Decidim::SanitizeHelper
|
|
6
|
+
|
|
7
|
+
# Truncates html content
|
|
8
|
+
# text - Content to be truncated
|
|
9
|
+
# options - Hash with the options
|
|
10
|
+
# max_length: An Integer maximum number of characters
|
|
11
|
+
# tail: Suffix to add after truncation
|
|
12
|
+
# count_tags: A boolean which tells if html tags should be calculated to max length, otherwise just content
|
|
13
|
+
# count_tail: A boolean value that determines whether max_length contains the tail
|
|
14
|
+
# tail_before_final_tag: A boolean, add tail before final tag if true, otherwise add tail where content is cut.
|
|
15
|
+
def initialize(text, options = {})
|
|
16
|
+
@options = {
|
|
17
|
+
max_length: options[:max_length] || 30,
|
|
18
|
+
tail: options[:tail] || "...",
|
|
19
|
+
count_tags: options[:count_tags] || false,
|
|
20
|
+
count_tail: options[:count_tail] || false,
|
|
21
|
+
tail_before_final_tag: options[:tail_before_final_tag] || false
|
|
22
|
+
}
|
|
23
|
+
@text = text
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
# Perform truncation to the html content (text) added in constructor
|
|
27
|
+
# Returns truncated String
|
|
28
|
+
def perform
|
|
29
|
+
@document = Nokogiri::HTML::DocumentFragment.parse(@text)
|
|
30
|
+
@tail_added = false
|
|
31
|
+
@remaining = initial_remaining
|
|
32
|
+
@cut = false
|
|
33
|
+
cut_children(document, options[:count_tags])
|
|
34
|
+
add_tail(document) if @remaining.negative? && !@tail_added
|
|
35
|
+
escape_html_from_content(document)
|
|
36
|
+
|
|
37
|
+
# Nokogiri's to_html escapes " to &quot; and we do not want extra & so we have to unescape.
|
|
38
|
+
CGI.unescape_html(document.to_html).gsub("\n", "")
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
private
|
|
42
|
+
|
|
43
|
+
attr_reader :document, :options
|
|
44
|
+
|
|
45
|
+
def cut_children(node, count_html)
|
|
46
|
+
return @remaining -= node_length(node, count_html) if @remaining >= node_length(node, count_html)
|
|
47
|
+
return node.unlink if @remaining.negative?
|
|
48
|
+
return cut_with_tags(node, true) if count_html && @remaining < node_length(node, count_html)
|
|
49
|
+
|
|
50
|
+
if node.children.empty?
|
|
51
|
+
if node.content.present? && @remaining < node_length(node, count_html)
|
|
52
|
+
cut_content(node)
|
|
53
|
+
@remaining = -1
|
|
54
|
+
else
|
|
55
|
+
@remaining -= node_length(node, count_html)
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
return
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
node.children.each do |child|
|
|
62
|
+
cut_children(child, count_html)
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def escape_html_from_content(node)
|
|
67
|
+
node.content = decidim_html_escape(node.content) if node.is_a? Nokogiri::XML::Text
|
|
68
|
+
return if node.children.empty?
|
|
69
|
+
|
|
70
|
+
node.children.each do |child|
|
|
71
|
+
escape_html_from_content(child)
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def cut_with_tags(node, parent)
|
|
76
|
+
if parent
|
|
77
|
+
return node.children.each { |child| cut_with_tags(child, false) } if node.children.present?
|
|
78
|
+
|
|
79
|
+
return ignore_closing_tag_and_cut(node)
|
|
80
|
+
end
|
|
81
|
+
return node.unlink if @cut
|
|
82
|
+
|
|
83
|
+
if @remaining < node_length(node, true)
|
|
84
|
+
@cut = true
|
|
85
|
+
return ignore_closing_tag_and_cut(node) if @remaining > opening_tag_length(node) || node.content.present?
|
|
86
|
+
end
|
|
87
|
+
@remaining -= node_length(node, true)
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def ignore_closing_tag_and_cut(node)
|
|
91
|
+
@remaining -= opening_tag_length(node)
|
|
92
|
+
cut_content(node)
|
|
93
|
+
@remaining = -1
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
def add_tail(document)
|
|
97
|
+
return if document.children.empty? || @tail_added
|
|
98
|
+
|
|
99
|
+
target = document.children[-1]
|
|
100
|
+
if target.is_a?(Nokogiri::XML::Text) || target.content.empty?
|
|
101
|
+
document.add_child(Nokogiri::XML::Text.new(options[:tail], document))
|
|
102
|
+
else
|
|
103
|
+
target.add_child(Nokogiri::XML::Text.new(options[:tail], document))
|
|
104
|
+
end
|
|
105
|
+
@tail_added = true
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
def cut_content(node)
|
|
109
|
+
tail = options[:tail_before_final_tag] ? "" : options[:tail]
|
|
110
|
+
@tail_added = true if tail.present?
|
|
111
|
+
|
|
112
|
+
node.content = "#{node.content.truncate(@remaining, omission: "")}#{tail}"
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
def initial_remaining
|
|
116
|
+
return options[:max_length] unless options[:count_tail]
|
|
117
|
+
|
|
118
|
+
options[:max_length] - options[:tail].length
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
def opening_tag_length(node)
|
|
122
|
+
closing_tag_index = node.to_html.rindex("</") || node.to_html.length
|
|
123
|
+
node.to_html.length - (node.to_html.length - closing_tag_index) - node.content.length
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
def node_length(node, count_html)
|
|
127
|
+
count_html ? node.to_html.length : node.content.length
|
|
128
|
+
end
|
|
129
|
+
end
|
|
130
|
+
end
|
|
@@ -33,7 +33,6 @@ module Decidim
|
|
|
33
33
|
open_data_component_manifests.each do |manifest|
|
|
34
34
|
add_file_to_output(out, format(FILE_NAME_PATTERN, { host: organization.host, entity: manifest.name }), data_for_component(manifest))
|
|
35
35
|
end
|
|
36
|
-
|
|
37
36
|
open_data_participatory_space_manifests.each do |manifest|
|
|
38
37
|
add_file_to_output(out, format(FILE_NAME_PATTERN, { host: organization.host, entity: manifest.name }), data_for_participatory_space(manifest))
|
|
39
38
|
end
|
|
@@ -42,12 +41,37 @@ module Decidim
|
|
|
42
41
|
buffer.string
|
|
43
42
|
end
|
|
44
43
|
|
|
45
|
-
def data_for_component(export_manifest)
|
|
46
|
-
|
|
47
|
-
|
|
44
|
+
def data_for_component(export_manifest, col_sep = Decidim.default_csv_col_sep)
|
|
45
|
+
headers = []
|
|
46
|
+
collection = []
|
|
47
|
+
ActiveRecord::Base.uncached do
|
|
48
|
+
components.where(manifest_name: export_manifest.manifest.name).find_each do |component|
|
|
49
|
+
export_manifest.collection.call(component).find_in_batches(batch_size: 100) do |batch|
|
|
50
|
+
exporter = Decidim::Exporters::CSV.new(batch, export_manifest.serializer)
|
|
51
|
+
headers.push(*exporter.headers)
|
|
52
|
+
exported = exporter.export
|
|
53
|
+
|
|
54
|
+
tmpfile = Tempfile.new("#{export_manifest.name}-#{component.id}-")
|
|
55
|
+
tmpfile.write(exported.read)
|
|
56
|
+
# Do not delete the file when the reference is deleted
|
|
57
|
+
ObjectSpace.undefine_finalizer(tmpfile)
|
|
58
|
+
tmpfile.close
|
|
59
|
+
|
|
60
|
+
collection.push(tmpfile.path)
|
|
61
|
+
end
|
|
62
|
+
end
|
|
48
63
|
end
|
|
49
64
|
|
|
50
|
-
|
|
65
|
+
headers.uniq!
|
|
66
|
+
|
|
67
|
+
data = CSV.generate_line(headers, col_sep: col_sep)
|
|
68
|
+
collection.each do |content|
|
|
69
|
+
CSV.foreach(content, headers: true, col_sep: col_sep) do |row|
|
|
70
|
+
data << CSV.generate_line(row.values_at(*headers), col_sep: col_sep)
|
|
71
|
+
end
|
|
72
|
+
File.unlink(content)
|
|
73
|
+
end
|
|
74
|
+
Decidim::Exporters::ExportData.new(data, "csv")
|
|
51
75
|
end
|
|
52
76
|
|
|
53
77
|
def data_for_participatory_space(export_manifest)
|
|
@@ -75,7 +75,7 @@ module Decidim
|
|
|
75
75
|
conditions << "#{query.model_name.plural}.decidim_scope_id IS NULL" if clean_scope_ids.delete("global")
|
|
76
76
|
conditions.concat(["? = ANY(decidim_scopes.part_of)"] * clean_scope_ids.count) if clean_scope_ids.any?
|
|
77
77
|
|
|
78
|
-
query.includes(:scope).references(:decidim_scopes).where(conditions.join(" OR "), *clean_scope_ids.map(&:to_i))
|
|
78
|
+
query.includes(:scope).references(:decidim_scopes).where(Arel.sql(conditions.join(" OR ")).to_s, *clean_scope_ids.map(&:to_i))
|
|
79
79
|
end
|
|
80
80
|
|
|
81
81
|
# Handle the origin filter.
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Class is used to verify that the user's password is strong enough
|
|
4
|
+
class PasswordValidator < ActiveModel::EachValidator
|
|
5
|
+
MINIMUM_LENGTH = 10
|
|
6
|
+
MAX_LENGTH = 256
|
|
7
|
+
MIN_UNIQUE_CHARACTERS = 5
|
|
8
|
+
IGNORE_SIMILARITY_SHORTER_THAN = 4
|
|
9
|
+
VALIDATION_METHODS = [
|
|
10
|
+
:password_too_short?,
|
|
11
|
+
:password_too_long?,
|
|
12
|
+
:not_enough_unique_characters?,
|
|
13
|
+
:name_included_in_password?,
|
|
14
|
+
:nickname_included_in_password?,
|
|
15
|
+
:email_included_in_password?,
|
|
16
|
+
:domain_included_in_password?,
|
|
17
|
+
:password_too_common?,
|
|
18
|
+
:blacklisted?
|
|
19
|
+
].freeze
|
|
20
|
+
|
|
21
|
+
# Check if user's password is strong enough
|
|
22
|
+
#
|
|
23
|
+
# record - Instance of a form (e.g. Decidim::RegistrationForm) or model
|
|
24
|
+
# attribute - "password"
|
|
25
|
+
# value - Actual password
|
|
26
|
+
# Returns true if password is strong enough
|
|
27
|
+
def validate_each(record, attribute, value)
|
|
28
|
+
return false if value.blank?
|
|
29
|
+
|
|
30
|
+
@record = record
|
|
31
|
+
@attribute = attribute
|
|
32
|
+
@value = value
|
|
33
|
+
@weak_password_reasons = []
|
|
34
|
+
|
|
35
|
+
return true if strong?
|
|
36
|
+
|
|
37
|
+
@weak_password_reasons.each do |reason|
|
|
38
|
+
record.errors[attribute] << get_message(reason)
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
false
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
private
|
|
45
|
+
|
|
46
|
+
attr_reader :record, :attribute, :value
|
|
47
|
+
|
|
48
|
+
def get_message(reason)
|
|
49
|
+
I18n.t "password_validator.#{reason}"
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def strong?
|
|
53
|
+
VALIDATION_METHODS.each do |method|
|
|
54
|
+
@weak_password_reasons << method.to_s.sub(/\?$/, "").to_sym if send(method.to_s)
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
@weak_password_reasons.empty?
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def password_too_short?
|
|
61
|
+
value.length < MINIMUM_LENGTH
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def password_too_long?
|
|
65
|
+
value.length > MAX_LENGTH
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def not_enough_unique_characters?
|
|
69
|
+
value.chars.uniq.length < MIN_UNIQUE_CHARACTERS
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def name_included_in_password?
|
|
73
|
+
return false if !record.respond_to?(:name) || record.name.blank?
|
|
74
|
+
return true if value.include?(record.name.delete(" "))
|
|
75
|
+
|
|
76
|
+
record.name.split(" ").each do |part|
|
|
77
|
+
next if part.length < IGNORE_SIMILARITY_SHORTER_THAN
|
|
78
|
+
|
|
79
|
+
return true if value.include?(part)
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
false
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def nickname_included_in_password?
|
|
86
|
+
return false if !record.respond_to?(:nickname) || record.nickname.blank?
|
|
87
|
+
|
|
88
|
+
value.include?(record.nickname)
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def email_included_in_password?
|
|
92
|
+
return false if !record.respond_to?(:email) || record.email.blank?
|
|
93
|
+
|
|
94
|
+
name, domain, _whatever = record.email.split("@")
|
|
95
|
+
value.include?(name) || (domain && value.include?(domain.split(".").first))
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
def domain_included_in_password?
|
|
99
|
+
return false unless record&.current_organization&.host
|
|
100
|
+
return true if value.include?(record.current_organization.host)
|
|
101
|
+
|
|
102
|
+
record.current_organization.host.split(".").each do |part|
|
|
103
|
+
next if part.length < IGNORE_SIMILARITY_SHORTER_THAN
|
|
104
|
+
|
|
105
|
+
return true if value.include?(part)
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
false
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
def blacklisted?
|
|
112
|
+
Array(Decidim.password_blacklist).each do |expression|
|
|
113
|
+
return true if expression.is_a?(Regexp) && value.match?(expression)
|
|
114
|
+
return true if expression.to_s == value
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
false
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
def password_too_common?
|
|
121
|
+
Decidim::CommonPasswords.instance.passwords.include?(value)
|
|
122
|
+
end
|
|
123
|
+
end
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
<%= form.password_field :password, value: form.object.password, autocomplete: "off", help_text: t("devise.passwords.edit.password_help", minimun_characters:
|
|
1
|
+
<%= form.password_field :password, value: form.object.password, autocomplete: "off", help_text: t("devise.passwords.edit.password_help", minimun_characters: ::PasswordValidator::MINIMUM_LENGTH) %>
|
|
2
2
|
<%= form.password_field :password_confirmation, value: form.object.password_confirmation, autocomplete: "off" %>
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
<%= f.hidden_field :reset_password_token %>
|
|
17
17
|
|
|
18
18
|
<div class="field">
|
|
19
|
-
<%= f.password_field :password, autocomplete: "off", help_text: t("devise.passwords.edit.password_help", minimun_characters:
|
|
19
|
+
<%= f.password_field :password, autocomplete: "off", help_text: t("devise.passwords.edit.password_help", minimun_characters: ::PasswordValidator::MINIMUM_LENGTH) %>
|
|
20
20
|
</div>
|
|
21
21
|
|
|
22
22
|
<div class="field">
|
|
@@ -49,7 +49,7 @@
|
|
|
49
49
|
</div>
|
|
50
50
|
|
|
51
51
|
<div class="field">
|
|
52
|
-
<%= f.password_field :password, help_text: t(".password_help", minimun_characters:
|
|
52
|
+
<%= f.password_field :password, help_text: t(".password_help", minimun_characters: ::PasswordValidator::MINIMUM_LENGTH), autocomplete: "off" %>
|
|
53
53
|
</div>
|
|
54
54
|
|
|
55
55
|
<div class="field">
|
|
@@ -1,21 +1,23 @@
|
|
|
1
1
|
<% if Devise.mappings[:user].omniauthable? && current_organization.enabled_omniauth_providers.any? %>
|
|
2
2
|
<div class="row">
|
|
3
3
|
<div class="columns medium-8 medium-centered">
|
|
4
|
-
<span class="register__separator">
|
|
5
|
-
<span class="register__separator__text"><%= t("or", scope: "decidim.devise.shared.omniauth_buttons") %></span>
|
|
6
|
-
</span>
|
|
7
4
|
<div class="text-center">
|
|
8
5
|
<%- current_organization.enabled_omniauth_providers.keys.each do |provider| %>
|
|
6
|
+
<div class="social-register">
|
|
9
7
|
<%= link_to decidim.send("user_#{provider}_omniauth_authorize_path"), class: "button button--social button--#{normalize_provider_name(provider)} button--social--mini", method: :post do %>
|
|
10
8
|
<span class="button--social__icon" aria-hidden="true">
|
|
11
9
|
<%= oauth_icon provider %>
|
|
12
10
|
</span>
|
|
13
|
-
<span class="
|
|
11
|
+
<span class="button--social__text">
|
|
14
12
|
<%= t("devise.shared.links.sign_in_with_provider", provider: normalize_provider_name(provider).titleize) %>
|
|
15
13
|
</span>
|
|
16
14
|
<% end %>
|
|
15
|
+
</div>
|
|
17
16
|
<% end %>
|
|
18
17
|
</div>
|
|
18
|
+
<span class="register__separator">
|
|
19
|
+
<span class="register__separator__text"><%= t("or", scope: "decidim.devise.shared.omniauth_buttons") %></span>
|
|
20
|
+
</span>
|
|
19
21
|
</div>
|
|
20
22
|
</div>
|
|
21
23
|
<% end %>
|
|
@@ -15,9 +15,9 @@
|
|
|
15
15
|
<li class="card-data__item card--list__item card-data__item--expand absolutes">
|
|
16
16
|
<div class="mr-s">
|
|
17
17
|
<% if conversation.interlocutors(current_user).count == 1 %>
|
|
18
|
-
<%= t("from", scope: "decidim.messaging.conversations.index") %>:
|
|
18
|
+
<%= t("from", scope: "decidim.messaging.conversations.index") %>: <%= conversation_name_for(conversation.interlocutors(current_user)) %>
|
|
19
19
|
<% else %>
|
|
20
|
-
<%= t("from", scope: "decidim.messaging.conversations.index") %>: <strong><%= username_list(conversation.interlocutors(current_user), shorten: true) %></strong>
|
|
20
|
+
<%= t("from", scope: "decidim.messaging.conversations.index") %>: <strong><%= username_list(conversation.interlocutors(current_user), shorten: true).html_safe %></strong>
|
|
21
21
|
<% end %>
|
|
22
22
|
<br>
|
|
23
23
|
<span class="muted">
|
|
@@ -45,7 +45,7 @@
|
|
|
45
45
|
<% if conversation.interlocutors(current_user).count == 1 %>
|
|
46
46
|
<%= icon "chevron-right", class: "card__link icon--big", role: "img", aria_label: t(".show", sender: conversation.interlocutors(current_user).first.name) %>
|
|
47
47
|
<% else %>
|
|
48
|
-
<%= icon "chevron-right", class: "card__link icon--big", role: "img", aria_label: t(".show", sender: username_list(conversation.interlocutors(current_user))) %>
|
|
48
|
+
<%= icon "chevron-right", class: "card__link icon--big", role: "img", aria_label: t(".show", sender: strip_tags(username_list(conversation.interlocutors(current_user)))) %>
|
|
49
49
|
<% end %>
|
|
50
50
|
<% end %>
|
|
51
51
|
</div>
|
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
<div class="conversation-chat<%= " conversation-chat--offset" if sender_is_user?(sender) %>">
|
|
2
|
-
|
|
3
|
-
|
|
2
|
+
<% if sender.deleted? %>
|
|
3
|
+
<span class="label label--small label--basic">
|
|
4
|
+
<%= t("deleted", scope: "decidim.profile") %>
|
|
5
|
+
</span>
|
|
6
|
+
<% else %>
|
|
7
|
+
<%= link_to profile_path(sender.nickname) do %>
|
|
8
|
+
<%= image_tag present(sender).avatar_url, alt: t("decidim.author.avatar", name: decidim_sanitize(sender.name)) %>
|
|
9
|
+
<% end %>
|
|
4
10
|
<% end %>
|
|
5
11
|
<div>
|
|
6
12
|
<% messages.each do |message| %>
|
|
@@ -1,12 +1,6 @@
|
|
|
1
|
-
<% if participants.count == 1 %>
|
|
2
|
-
<% conversation_label = t(".chat_with") + " #{participants.first.name} (@#{participants.first.nickname})" %>
|
|
3
|
-
<% else %>
|
|
4
|
-
<% conversation_label = t(".title", usernames: username_list(participants)) %>
|
|
5
|
-
<% end %>
|
|
6
|
-
|
|
7
1
|
<div class="wrapper">
|
|
8
2
|
<div class="row">
|
|
9
|
-
<div class="columns medium-9 medium-centered tabs-panel is-active" id="conversations" role="tabpanel" aria-label="<%=
|
|
3
|
+
<div class="columns medium-9 medium-centered tabs-panel is-active" id="conversations" role="tabpanel" aria-label="<%= strip_tags(conversation_label_for(participants)) %>">
|
|
10
4
|
<div class="conversation">
|
|
11
5
|
|
|
12
6
|
<div class="conversation-header flex--cc absolutes">
|
|
@@ -15,20 +9,24 @@
|
|
|
15
9
|
<%= icon "chevron-left", role: "img", aria_label: t(".back") %>
|
|
16
10
|
<% end %>
|
|
17
11
|
</div>
|
|
12
|
+
|
|
18
13
|
<% if participants.count == 1 %>
|
|
19
|
-
|
|
20
|
-
<%= image_tag present(participants.first).avatar_url, alt: t("decidim.author.avatar", name:
|
|
14
|
+
<% if participants.first.deleted? %>
|
|
15
|
+
<%= image_tag present(participants.first).avatar_url, alt: t("decidim.author.avatar", name: t("decidim.profile.deleted")) %>
|
|
16
|
+
<% else %>
|
|
17
|
+
<%= link_to profile_path(participants.first.nickname) do %>
|
|
18
|
+
<%= image_tag present(participants.first).avatar_url, alt: t("decidim.author.avatar", name: decidim_sanitize(participants.first.name)) %>
|
|
19
|
+
<% end %>
|
|
21
20
|
<% end %>
|
|
22
21
|
<% else %>
|
|
23
22
|
<%= image_tag present(current_user).avatar.default_multiuser_url, alt: t("decidim.author.avatar_multiuser") %>
|
|
24
23
|
<% end %>
|
|
25
|
-
|
|
26
24
|
<div class="ml-s">
|
|
27
25
|
<h1>
|
|
28
26
|
<% if participants.count == 1 %>
|
|
29
|
-
<%= t(".chat_with") %>
|
|
27
|
+
<%= t(".chat_with") %> <%= conversation_name_for(participants) %>
|
|
30
28
|
<% else %>
|
|
31
|
-
<%= t(".title", usernames: username_list(participants)) %>
|
|
29
|
+
<%= t(".title", usernames: username_list(participants)).html_safe %>
|
|
32
30
|
<% end %>
|
|
33
31
|
</h1>
|
|
34
32
|
</div>
|