decidim-core 0.12.2 → 0.13.0.pre1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (116) hide show
  1. checksums.yaml +4 -4
  2. data/app/assets/stylesheets/decidim/modules/_order-by.scss +4 -1
  3. data/app/cells/decidim/author/comments.erb +2 -2
  4. data/app/cells/decidim/author/date.erb +1 -1
  5. data/app/cells/decidim/author_cell.rb +22 -38
  6. data/app/cells/decidim/card_m/authors.erb +9 -0
  7. data/app/cells/decidim/card_m/status.erb +1 -1
  8. data/app/cells/decidim/card_m_cell.rb +8 -0
  9. data/app/cells/decidim/collapsible_authors/show.erb +16 -0
  10. data/app/cells/decidim/collapsible_authors_cell.rb +32 -0
  11. data/app/cells/decidim/invitations_toggle/content.erb +1 -0
  12. data/app/cells/decidim/invitations_toggle/label.erb +1 -0
  13. data/app/cells/decidim/invitations_toggle_cell.rb +27 -0
  14. data/app/cells/decidim/toggle/show.erb +8 -0
  15. data/app/cells/decidim/toggle_cell.rb +42 -0
  16. data/app/cells/decidim/tos_page/refuse_btn_modal.erb +25 -14
  17. data/app/commands/decidim/create_omniauth_registration.rb +1 -1
  18. data/app/commands/decidim/create_registration.rb +1 -1
  19. data/app/commands/decidim/create_report.rb +10 -10
  20. data/app/commands/decidim/invite_friends.rb +48 -0
  21. data/app/commands/decidim/invite_user.rb +4 -2
  22. data/app/commands/decidim/unsubscribe_settings.rb +3 -3
  23. data/app/commands/decidim/update_notifications_settings.rb +1 -1
  24. data/app/controllers/concerns/decidim/needs_tos_accepted.rb +6 -1
  25. data/app/controllers/decidim/data_portability_controller.rb +55 -0
  26. data/app/controllers/decidim/devise/invitations_controller.rb +1 -0
  27. data/app/controllers/decidim/invitations_controller.rb +32 -0
  28. data/app/controllers/decidim/newsletters_opt_in_controller.rb +31 -0
  29. data/app/forms/decidim/account_form.rb +1 -1
  30. data/app/forms/decidim/invitations_form.rb +37 -0
  31. data/app/forms/decidim/notifications_settings_form.rb +5 -0
  32. data/app/forms/decidim/omniauth_registration_form.rb +1 -1
  33. data/app/forms/decidim/registration_form.rb +7 -1
  34. data/app/helpers/decidim/cells_helper.rb +44 -0
  35. data/app/jobs/decidim/data_portability_export_job.rb +21 -0
  36. data/app/jobs/decidim/newsletters_opt_in_job.rb +11 -0
  37. data/app/mailers/decidim/export_mailer.rb +13 -0
  38. data/app/mailers/decidim/newsletters_opt_in_mailer.rb +15 -0
  39. data/app/models/decidim/coauthorship.rb +23 -0
  40. data/app/models/decidim/follow.rb +10 -0
  41. data/app/models/decidim/identity.rb +10 -0
  42. data/app/models/decidim/messaging/conversation.rb +10 -0
  43. data/app/models/decidim/notification.rb +10 -0
  44. data/app/models/decidim/participatory_space_private_user.rb +10 -0
  45. data/app/models/decidim/report.rb +10 -0
  46. data/app/models/decidim/user.rb +16 -1
  47. data/app/models/decidim/user_group.rb +9 -0
  48. data/app/services/decidim/action_authorizer.rb +1 -1
  49. data/app/uploaders/decidim/avatar_uploader.rb +6 -2
  50. data/app/uploaders/decidim/data_portability_uploader.rb +19 -0
  51. data/app/views/decidim/data_portability/export.html.erb +1 -0
  52. data/app/views/decidim/data_portability/show.html.erb +5 -0
  53. data/app/views/decidim/devise/invitations/edit.html.erb +1 -1
  54. data/app/views/decidim/devise/shared/_newsletter_modal.html.erb +13 -13
  55. data/app/views/decidim/export_mailer/data_portability_export.html.erb +2 -0
  56. data/app/views/decidim/invitations/index.html.erb +48 -0
  57. data/app/views/decidim/newsletters_opt_in_mailer/notify.html.erb +23 -0
  58. data/app/views/decidim/profiles/_user.html.erb +1 -1
  59. data/app/views/devise/mailer/invite_friend.html.erb +27 -0
  60. data/app/views/devise/mailer/invite_friend.text.erb +19 -0
  61. data/app/views/layouts/decidim/_user_menu.html.erb +1 -0
  62. data/config/locales/ca.yml +68 -2
  63. data/config/locales/en.yml +73 -3
  64. data/config/locales/es-PY.yml +67 -1
  65. data/config/locales/es.yml +67 -1
  66. data/config/locales/eu.yml +68 -2
  67. data/config/locales/fi.yml +69 -3
  68. data/config/locales/fr.yml +136 -70
  69. data/config/locales/gl.yml +69 -3
  70. data/config/locales/it.yml +69 -3
  71. data/config/locales/nl.yml +69 -3
  72. data/config/locales/pl.yml +69 -3
  73. data/config/locales/pt-BR.yml +69 -3
  74. data/config/locales/pt.yml +69 -3
  75. data/config/locales/ru.yml +45 -2
  76. data/config/locales/sv.yml +69 -3
  77. data/config/locales/uk.yml +61 -2
  78. data/config/routes.rb +10 -0
  79. data/db/migrate/20171212103803_create_unique_nicknames.rb +1 -1
  80. data/db/migrate/20180221101934_fix_nickname_index.rb +3 -1
  81. data/db/migrate/20180427141253_create_coauthorships.rb +13 -0
  82. data/db/migrate/20180611121852_change_newsletter_notification_type_value.rb +21 -0
  83. data/lib/decidim/api/authorable_interface.rb +10 -1
  84. data/lib/decidim/authorable.rb +4 -1
  85. data/lib/decidim/coauthorable.rb +69 -0
  86. data/lib/decidim/component_manifest.rb +4 -0
  87. data/lib/decidim/core.rb +10 -0
  88. data/lib/decidim/core/engine.rb +5 -0
  89. data/lib/decidim/core/test.rb +1 -0
  90. data/lib/decidim/core/test/factories.rb +9 -1
  91. data/lib/decidim/core/test/shared_examples/coauthorable.rb +111 -0
  92. data/lib/decidim/core/test/shared_examples/coauthorable_interface_examples.rb +31 -0
  93. data/lib/decidim/core/test/shared_examples/simple_event.rb +7 -1
  94. data/lib/decidim/core/version.rb +1 -1
  95. data/lib/decidim/data_portability.rb +29 -0
  96. data/lib/decidim/data_portability_file_reader.rb +56 -0
  97. data/lib/decidim/data_portability_file_zipper.rb +55 -0
  98. data/lib/decidim/data_portability_serializers.rb +23 -0
  99. data/lib/decidim/data_portability_serializers/data_portability_conversation_serializer.rb +42 -0
  100. data/lib/decidim/data_portability_serializers/data_portability_follow_serializer.rb +23 -0
  101. data/lib/decidim/data_portability_serializers/data_portability_identity_serializer.rb +25 -0
  102. data/lib/decidim/data_portability_serializers/data_portability_notification_serializer.rb +26 -0
  103. data/lib/decidim/data_portability_serializers/data_portability_participatory_space_private_user_serializer.rb +25 -0
  104. data/lib/decidim/data_portability_serializers/data_portability_report_serializer.rb +36 -0
  105. data/lib/decidim/data_portability_serializers/data_portability_user_group_serializer.rb +21 -0
  106. data/lib/decidim/data_portability_serializers/data_portability_user_serializer.rb +55 -0
  107. data/lib/decidim/events.rb +1 -0
  108. data/lib/decidim/events/coauthor_event.rb +42 -0
  109. data/lib/decidim/exporters/csv.rb +0 -1
  110. data/lib/decidim/form_builder.rb +1 -2
  111. data/lib/decidim/nicknamizable.rb +15 -4
  112. data/lib/decidim/participable.rb +7 -0
  113. data/lib/decidim/participatory_space_manifest.rb +4 -0
  114. data/lib/devise/models/decidim_newsletterable.rb +60 -0
  115. data/lib/tasks/decidim_tasks.rake +29 -0
  116. metadata +73 -28
@@ -43,14 +43,16 @@ module Decidim
43
43
  @user = Decidim::User.new(
44
44
  name: form.name,
45
45
  email: form.email.downcase,
46
- nickname: User.nicknamize(form.name),
46
+ nickname: User.nicknamize(form.name, organization: form.organization),
47
47
  organization: form.organization,
48
48
  admin: form.role == "admin",
49
49
  roles: form.role == "admin" ? [] : [form.role].compact
50
50
  )
51
51
  @user.invite!(
52
52
  form.invited_by,
53
- invitation_instructions: form.invitation_instructions
53
+ {
54
+ invitation_instructions: form.invitation_instructions
55
+ }.merge(form.try(:extra_email_options) || {})
54
56
  )
55
57
  end
56
58
  end
@@ -6,13 +6,13 @@ module Decidim
6
6
  # unsubscribe user from newsletter.
7
7
  #
8
8
  # user - The user to be updated.
9
- # newsletter_notifications - to be false
9
+ # newsletter_notifications_at - to be nil
10
10
  def initialize(user)
11
11
  @user = user
12
12
  end
13
13
 
14
14
  def call
15
- return broadcast(:invalid) unless @user.newsletter_notifications
15
+ return broadcast(:invalid) unless @user.newsletter_notifications_at?
16
16
 
17
17
  update_settings
18
18
  @user.save!
@@ -23,7 +23,7 @@ module Decidim
23
23
  private
24
24
 
25
25
  def update_settings
26
- @user.newsletter_notifications = false
26
+ @user.newsletter_notifications_at = nil
27
27
  end
28
28
  end
29
29
  end
@@ -25,7 +25,7 @@ module Decidim
25
25
 
26
26
  def update_notifications_settings
27
27
  @user.email_on_notification = @form.email_on_notification
28
- @user.newsletter_notifications = @form.newsletter_notifications
28
+ @user.newsletter_notifications_at = @form.newsletter_notifications_at
29
29
  end
30
30
  end
31
31
  end
@@ -25,7 +25,12 @@ module Decidim
25
25
  end
26
26
 
27
27
  def permitted_paths?
28
- permitted_paths = [tos_path, decidim.delete_account_path, decidim.accept_tos_path]
28
+ permitted_paths = [tos_path,
29
+ decidim.delete_account_path,
30
+ decidim.accept_tos_path,
31
+ decidim.data_portability_path,
32
+ decidim.export_data_portability_path,
33
+ decidim.download_file_data_portability_path]
29
34
  permitted_paths.include?(request.path)
30
35
  end
31
36
 
@@ -0,0 +1,55 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "zip"
4
+
5
+ module Decidim
6
+ # The controller to handle the user's download_my_data page.
7
+ class DataPortabilityController < Decidim::ApplicationController
8
+ include Decidim::UserProfile
9
+
10
+ def show
11
+ enforce_permission_to :show, :user, current_user: current_user
12
+
13
+ @account = form(AccountForm).from_model(current_user)
14
+ end
15
+
16
+ def export
17
+ enforce_permission_to :export, :user, current_user: current_user
18
+
19
+ DataPortabilityExportJob.perform_later(current_user, export_format)
20
+
21
+ flash[:notice] = t("decidim.account.data_portability_export.notice")
22
+
23
+ redirect_back(fallback_location: data_portability_path)
24
+ end
25
+
26
+ def download_file
27
+ enforce_permission_to :download, :user, current_user: current_user
28
+
29
+ if params[:token].present?
30
+ file_reader = Decidim::DataPortabilityFileReader.new(current_user, params[:token])
31
+ if file_reader.valid_token?
32
+ file = File.open(file_reader.file_path)
33
+ if File.exist?(file)
34
+ send_file file, type: "application/zip", disposition: "attachment"
35
+ else
36
+ flash[:error] = t("decidim.account.data_portability_export.file_no_exists")
37
+ redirect_to data_portability_path
38
+ end
39
+ else
40
+ flash[:error] = t("decidim.account.data_portability_export.invalid_token")
41
+ redirect_to data_portability_path
42
+ end
43
+ else
44
+ flash[:error] = t("decidim.account.data_portability_export.no_token")
45
+ redirect_to data_portability_path
46
+ end
47
+ end
48
+
49
+ private
50
+
51
+ def export_format
52
+ "CSV"
53
+ end
54
+ end
55
+ end
@@ -25,6 +25,7 @@ module Decidim
25
25
  # When a managed user accepts the invitation is promoted to non-managed user.
26
26
  def accept_resource
27
27
  resource = resource_class.accept_invitation!(update_resource_params)
28
+ resource.update!(newsletter_notifications_at: Time.zone.now) if update_resource_params[:newsletter_notifications]
28
29
  resource.update!(managed: false) if resource.managed?
29
30
  resource.update!(accepted_tos_version: resource.organization.tos_version)
30
31
  resource
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Decidim
4
+ # The controller to handle friends invitations by the current user.
5
+ class InvitationsController < Decidim::ApplicationController
6
+ include FormFactory
7
+
8
+ def index
9
+ @form = form(InvitationsForm).instance
10
+ end
11
+
12
+ def create
13
+ @form = form(InvitationsForm).from_params(params)
14
+
15
+ InviteFriends.call(@form) do
16
+ on(:ok) do
17
+ flash[:notice] = t("invitations.create.success", scope: "decidim")
18
+ redirect_to account_path
19
+ end
20
+
21
+ on(:invalid) do
22
+ flash.now[:alert] = if @form.emails.empty?
23
+ t("invitations.create.error_empty_form", scope: "decidim")
24
+ else
25
+ t("invitations.create.error", scope: "decidim")
26
+ end
27
+ render action: :index
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Decidim
4
+ # The controller to control newsletter Opt-in for GDPR
5
+ class NewslettersOptInController < Decidim::ApplicationController
6
+ include FormFactory
7
+
8
+ before_action :check_current_user_with_token
9
+
10
+ def update
11
+ enforce_permission_to :update, :user, current_user: current_user
12
+
13
+ current_user.newsletter_opt_in_validate
14
+ if current_user.save
15
+ flash[:notice] = t(".success")
16
+ else
17
+ flash[:alert] = t(".error")
18
+ end
19
+ redirect_to decidim.root_path(host: current_organization)
20
+ end
21
+
22
+ private
23
+
24
+ def check_current_user_with_token
25
+ unless current_user.newsletter_token == params[:token]
26
+ flash[:alert] = t("newsletters_opt_in.unathorized", scope: "decidim")
27
+ redirect_to decidim.root_path(host: current_organization)
28
+ end
29
+ end
30
+ end
31
+ end
@@ -22,7 +22,7 @@ module Decidim
22
22
 
23
23
  validates :nickname, length: { maximum: Decidim::User.nickname_max_length, allow_blank: true }
24
24
  validates :password, confirmation: true
25
- validates :password, length: { in: Decidim::User.password_length, allow_blank: true }
25
+ validates :password, password: { name: :name, email: :email, username: :nickname }, if: -> { password.present? }
26
26
  validates :password_confirmation, presence: true, if: :password_present
27
27
  validates :avatar, file_size: { less_than_or_equal_to: ->(_record) { Decidim.maximum_avatar_size } }
28
28
 
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Decidim
4
+ # This form holds the data for the invitations system.
5
+ class InvitationsForm < Form
6
+ mimic :invitations
7
+
8
+ attribute :email_1, String
9
+ attribute :email_2, String
10
+ attribute :email_3, String
11
+ attribute :email_4, String
12
+ attribute :email_5, String
13
+ attribute :email_6, String
14
+ attribute :custom_text, String
15
+
16
+ validates :email_1,
17
+ :email_2,
18
+ :email_3,
19
+ :email_4,
20
+ :email_5,
21
+ :email_6,
22
+ "valid_email_2/email": true,
23
+ allow_blank: true
24
+ validates :emails, presence: true
25
+
26
+ def emails
27
+ [email_1, email_2, email_3, email_4, email_5, email_6].uniq.select(&:present?)
28
+ end
29
+
30
+ def clean_emails
31
+ existing_emails = Decidim::User
32
+ .where(organization: current_organization, email: emails)
33
+ .pluck(:email)
34
+ emails - existing_emails
35
+ end
36
+ end
37
+ end
@@ -11,5 +11,10 @@ module Decidim
11
11
 
12
12
  validates :email_on_notification, presence: true
13
13
  validates :newsletter_notifications, presence: true
14
+
15
+ def newsletter_notifications_at
16
+ return nil unless newsletter_notifications
17
+ Time.zone.now
18
+ end
14
19
  end
15
20
  end
@@ -24,7 +24,7 @@ module Decidim
24
24
  end
25
25
 
26
26
  def normalized_nickname
27
- User.nicknamize(nickname || name)
27
+ User.nicknamize(nickname || name, organization: current_organization)
28
28
  end
29
29
  end
30
30
  end
@@ -22,7 +22,8 @@ module Decidim
22
22
  validates :name, presence: true
23
23
  validates :nickname, presence: true, length: { maximum: Decidim::User.nickname_max_length }
24
24
  validates :email, presence: true, 'valid_email_2/email': { disposable: true }
25
- validates :password, presence: true, confirmation: true, length: { in: Decidim::User.password_length }
25
+ validates :password, confirmation: true
26
+ validates :password, password: { name: :name, email: :email, username: :nickname }
26
27
  validates :password_confirmation, presence: true
27
28
  validates :tos_agreement, allow_nil: false, acceptance: true
28
29
 
@@ -39,6 +40,11 @@ module Decidim
39
40
  sign_up_as == "user_group"
40
41
  end
41
42
 
43
+ def newsletter_at
44
+ return nil unless newsletter?
45
+ Time.zone.now
46
+ end
47
+
42
48
  private
43
49
 
44
50
  def email_unique_in_organization
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Decidim
4
+ module CellsHelper
5
+ def from_context
6
+ options[:from].presence || context[:from].presence
7
+ end
8
+
9
+ def proposals_controller?
10
+ context[:controller].class.to_s == "Decidim::Proposals::ProposalsController"
11
+ end
12
+
13
+ def posts_controller?
14
+ context[:controller].class.to_s == "Decidim::Blogs::PostsController"
15
+ end
16
+
17
+ def index_action?
18
+ context[:controller].action_name == "index"
19
+ end
20
+
21
+ def show_action?
22
+ context[:controller].action_name == "show"
23
+ end
24
+
25
+ def current_component
26
+ from_context.component
27
+ end
28
+
29
+ def withdrawable?
30
+ return unless from_context
31
+ return unless proposals_controller?
32
+ return if index_action?
33
+ from_context.withdrawable_by?(current_user)
34
+ end
35
+
36
+ def flagable?
37
+ return unless from_context
38
+ return unless proposals_controller?
39
+ return if index_action?
40
+ return if from_context.official?
41
+ true
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Decidim
4
+ class DataPortabilityExportJob < ApplicationJob
5
+ queue_as :default
6
+
7
+ def perform(user, format)
8
+ objects = Decidim::DataPortabilitySerializers.data_entities
9
+ export_data = []
10
+ export_images = []
11
+
12
+ objects.each do |object|
13
+ klass = Object.const_get(object)
14
+ export_data << [klass.model_name.name.parameterize.pluralize, Decidim::Exporters.find_exporter(format).new(klass.user_collection(user), klass.export_serializer).export]
15
+ export_images << [klass.model_name.name.parameterize.pluralize, klass.data_portability_images(user).flatten] unless klass.data_portability_images(user).nil?
16
+ end
17
+
18
+ ExportMailer.data_portability_export(user, export_data, export_images).deliver_now
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Decidim
4
+ class NewslettersOptInJob < ApplicationJob
5
+ queue_as :newsletters_opt_in
6
+
7
+ def perform(user, token)
8
+ NewslettersOptInMailer.notify(user, token).deliver_now
9
+ end
10
+ end
11
+ end
@@ -25,5 +25,18 @@ module Decidim
25
25
  mail(to: "#{user.name} <#{user.email}>", subject: I18n.t("decidim.export_mailer.subject", name: filename))
26
26
  end
27
27
  end
28
+
29
+ def data_portability_export(user, export_data, export_images)
30
+ @user = user
31
+ @organization = user.organization
32
+
33
+ file_zipper = Decidim::DataPortabilityFileZipper.new(@user, export_data, export_images)
34
+ file_zipper.make_zip
35
+
36
+ with_user(user) do
37
+ @token = file_zipper.token
38
+ mail(to: "#{user.name} <#{user.email}>", subject: I18n.t("decidim.export_mailer.subject", name: user.name))
39
+ end
40
+ end
28
41
  end
29
42
  end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Decidim
4
+ # A custom mailer for Decidim so we can notify users to verify
5
+ # his own newsletter notifications settings. GDPR releated
6
+ class NewslettersOptInMailer < ApplicationMailer
7
+ def notify(user, token)
8
+ @user = user
9
+ @organization = user.organization
10
+ @token = token
11
+
12
+ mail(to: user.email, subject: I18n.t("decidim.newsletters_opt_in_mailer.notify.subject", organization_name: @organization.name))
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Decidim
4
+ class Coauthorship < ApplicationRecord
5
+ include Decidim::Authorable
6
+
7
+ belongs_to :coauthorable, polymorphic: true, counter_cache: true
8
+
9
+ validates :coauthorable, presence: true
10
+
11
+ def identity
12
+ user_group || author
13
+ end
14
+
15
+ private
16
+
17
+ # As it is used to validate by comparing to author.organization
18
+ # @returns The Organization for the Coauthorable
19
+ def organization
20
+ coauthorable&.organization
21
+ end
22
+ end
23
+ end
@@ -2,9 +2,19 @@
2
2
 
3
3
  module Decidim
4
4
  class Follow < ApplicationRecord
5
+ include Decidim::DataPortability
6
+
5
7
  belongs_to :followable, foreign_key: "decidim_followable_id", foreign_type: "decidim_followable_type", polymorphic: true
6
8
  belongs_to :user, foreign_key: "decidim_user_id", class_name: "Decidim::User"
7
9
 
8
10
  validates :user, uniqueness: { scope: [:followable] }
11
+
12
+ def self.user_collection(user)
13
+ where(decidim_user_id: user.id)
14
+ end
15
+
16
+ def self.export_serializer
17
+ Decidim::DataPortabilitySerializers::DataPortabilityFollowSerializer
18
+ end
9
19
  end
10
20
  end