decidim-core 0.12.2 → 0.13.0.pre1

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 (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