decidim-core 0.7.4 → 0.8.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (132) hide show
  1. checksums.yaml +5 -5
  2. data/README.md +9 -2
  3. data/app/assets/javascripts/decidim.js.es6 +1 -0
  4. data/app/assets/javascripts/decidim/editor.js.es6 +8 -2
  5. data/app/assets/javascripts/decidim/form_filter.component.js.es6 +2 -2
  6. data/app/assets/javascripts/decidim/foundation.js.es6 +18 -8
  7. data/app/assets/javascripts/decidim/select2.js.es6 +3 -0
  8. data/app/assets/stylesheets/decidim/_decidim.scss +1 -1
  9. data/app/assets/stylesheets/decidim/modules/_buttons.scss +0 -4
  10. data/app/assets/stylesheets/decidim/modules/_cards.scss +41 -1
  11. data/app/assets/stylesheets/decidim/modules/_messages.scss +35 -0
  12. data/app/assets/stylesheets/decidim/modules/_modules.scss +1 -0
  13. data/app/assets/stylesheets/decidim/modules/_navbar.scss +2 -1
  14. data/app/assets/stylesheets/decidim/utils/_settings.scss +363 -69
  15. data/app/commands/decidim/messaging/reply_to_conversation.rb +55 -0
  16. data/app/commands/decidim/messaging/start_conversation.rb +55 -0
  17. data/app/controllers/concerns/decidim/action_authorization.rb +6 -22
  18. data/app/controllers/concerns/decidim/user_profile.rb +5 -3
  19. data/app/controllers/decidim/application_controller.rb +1 -0
  20. data/app/controllers/decidim/devise/omniauth_registrations_controller.rb +2 -2
  21. data/app/controllers/decidim/devise/sessions_controller.rb +5 -3
  22. data/app/controllers/decidim/features/base_controller.rb +1 -0
  23. data/app/controllers/decidim/messaging/conversations_controller.rb +81 -0
  24. data/app/controllers/decidim/pages_controller.rb +2 -11
  25. data/app/forms/decidim/messaging/conversation_form.rb +19 -0
  26. data/app/forms/decidim/messaging/message_form.rb +14 -0
  27. data/app/forms/translatable_presence_validator.rb +7 -10
  28. data/app/helpers/decidim/authorization_form_helper.rb +1 -1
  29. data/app/helpers/decidim/cta_button_helper.rb +1 -1
  30. data/app/helpers/decidim/datetime_helper.rb +23 -0
  31. data/app/helpers/decidim/feature_path_helper.rb +4 -2
  32. data/app/helpers/decidim/layout_helper.rb +0 -2
  33. data/app/helpers/decidim/menu_helper.rb +10 -0
  34. data/app/helpers/decidim/messaging/conversation_helper.rb +36 -0
  35. data/app/helpers/decidim/sanitize_helper.rb +19 -0
  36. data/app/helpers/decidim/traceability_helper.rb +89 -0
  37. data/app/helpers/decidim/translations_helper.rb +4 -2
  38. data/app/helpers/decidim/view_hooks_helper.rb +15 -0
  39. data/app/jobs/decidim/email_notification_generator_job.rb +1 -1
  40. data/app/jobs/decidim/notification_generator_for_recipient_job.rb +1 -1
  41. data/app/jobs/decidim/notification_generator_job.rb +1 -1
  42. data/app/mailers/decidim/messaging/conversation_mailer.rb +47 -0
  43. data/app/mailers/decidim/newsletter_mailer.rb +1 -0
  44. data/app/models/decidim/abilities/base_ability.rb +16 -2
  45. data/app/models/decidim/authorization.rb +20 -2
  46. data/app/models/decidim/messaging/conversation.rb +129 -0
  47. data/app/models/decidim/messaging/message.rb +49 -0
  48. data/app/models/decidim/messaging/participation.rb +23 -0
  49. data/app/models/decidim/messaging/receipt.rb +27 -0
  50. data/app/models/decidim/moderation.rb +1 -1
  51. data/app/models/decidim/organization.rb +2 -2
  52. data/app/models/decidim/scope.rb +2 -1
  53. data/app/models/decidim/scope_type.rb +1 -1
  54. data/app/models/decidim/user.rb +11 -5
  55. data/app/models/decidim/user_group.rb +1 -1
  56. data/app/presenters/decidim/inline_menu_presenter.rb +10 -0
  57. data/app/presenters/decidim/menu_presenter.rb +1 -1
  58. data/app/queries/decidim/messaging/user_conversations.rb +29 -0
  59. data/app/scrubbers/decidim/user_input_scrubber.rb +31 -0
  60. data/app/services/decidim/action_authorizer.rb +48 -12
  61. data/app/services/decidim/traceability.rb +91 -0
  62. data/app/views/decidim/messaging/conversation_mailer/new_conversation.html.erb +9 -0
  63. data/app/views/decidim/messaging/conversation_mailer/new_message.html.erb +9 -0
  64. data/app/views/decidim/messaging/conversations/_message.html.erb +20 -0
  65. data/app/views/decidim/messaging/conversations/_reply.html.erb +11 -0
  66. data/app/views/decidim/messaging/conversations/_show.html.erb +21 -0
  67. data/app/views/decidim/messaging/conversations/_start.html.erb +12 -0
  68. data/app/views/decidim/messaging/conversations/create.js.erb +2 -0
  69. data/app/views/decidim/messaging/conversations/index.html.erb +51 -0
  70. data/app/views/decidim/messaging/conversations/new.html.erb +5 -0
  71. data/app/views/decidim/messaging/conversations/show.html.erb +9 -0
  72. data/app/views/decidim/messaging/conversations/update.js.erb +1 -0
  73. data/app/views/decidim/newsletter_mailer/newsletter.html.erb +1 -1
  74. data/app/views/decidim/notifications/_notification.html.erb +1 -1
  75. data/app/views/decidim/shared/_action_authorization_modal.html.erb +11 -1
  76. data/app/views/decidim/shared/_announcement.html.erb +1 -1
  77. data/app/views/decidim/shared/_version_author.html.erb +18 -0
  78. data/app/views/layouts/decidim/_wrapper.html.erb +3 -0
  79. data/app/views/layouts/decidim/user_profile.html.erb +1 -9
  80. data/app/views/pages/decidim_page.html.erb +1 -1
  81. data/app/views/pages/home.html.erb +0 -2
  82. data/app/views/pages/home/_footer_sub_hero.html.erb +5 -3
  83. data/app/views/pages/home/_hero.html.erb +1 -1
  84. data/app/views/pages/home/_highlighted_processes.html.erb +7 -37
  85. data/app/views/pages/home/_sub_hero.html.erb +6 -4
  86. data/config/locales/ca.yml +49 -21
  87. data/config/locales/en.yml +47 -19
  88. data/config/locales/es.yml +48 -20
  89. data/config/locales/eu.yml +51 -23
  90. data/config/locales/fi.yml +50 -22
  91. data/config/locales/fr.yml +50 -22
  92. data/config/locales/it.yml +89 -61
  93. data/config/locales/nl.yml +72 -44
  94. data/config/locales/pl.yml +49 -21
  95. data/config/locales/pt.yml +431 -0
  96. data/config/locales/ru.yml +4 -27
  97. data/config/locales/uk.yml +10 -23
  98. data/config/routes.rb +3 -5
  99. data/db/migrate/20170313095436_add_available_authorizations_to_organization.rb +2 -2
  100. data/db/migrate/20170713131308_migrate_user_roles_to_participatory_process_roles.rb +7 -3
  101. data/db/migrate/20170914092117_add_status_to_authorizations.rb +9 -0
  102. data/db/migrate/20171011194251_add_verification_metadata_to_authorizations.rb +11 -0
  103. data/db/migrate/20171013124505_add_verification_attachment_to_authorizations.rb +9 -0
  104. data/db/migrate/20171023123330_create_decidim_messaging.rb +23 -0
  105. data/db/migrate/20171107103253_create_versions.rb +18 -0
  106. data/db/migrate/20171107103254_add_object_changes_to_versions.rb +14 -0
  107. data/db/migrate/20171117100533_create_decidim_receipts.rb +13 -0
  108. data/db/seeds.rb +13 -3
  109. data/lib/decidim/core.rb +13 -6
  110. data/lib/decidim/core/engine.rb +37 -0
  111. data/lib/decidim/core/test/factories.rb +33 -21
  112. data/lib/decidim/core/test/shared_examples/follows_examples.rb +1 -1
  113. data/lib/decidim/core/test/shared_examples/manage_moderations_examples.rb +6 -11
  114. data/lib/decidim/core/test/shared_examples/process_announcements_examples.rb +2 -4
  115. data/lib/decidim/core/test/shared_examples/reportable.rb +33 -31
  116. data/lib/decidim/core/test/shared_examples/scope_helper_examples.rb +29 -31
  117. data/lib/decidim/core/version.rb +1 -1
  118. data/lib/decidim/engine_router.rb +4 -2
  119. data/lib/decidim/has_reference.rb +10 -2
  120. data/lib/decidim/messaging.rb +9 -0
  121. data/lib/decidim/participable.rb +1 -1
  122. data/lib/decidim/traceable.rb +31 -0
  123. data/lib/decidim/view_hooks.rb +108 -0
  124. data/vendor/assets/javascripts/datepicker-locales/foundation-datepicker.es.js +4 -2
  125. data/vendor/assets/javascripts/datepicker-locales/foundation-datepicker.pt.js +14 -0
  126. metadata +179 -113
  127. data/app/commands/decidim/authorize_user.rb +0 -59
  128. data/app/controllers/decidim/authorizations_controller.rb +0 -80
  129. data/app/services/decidim/authorization_handler.rb +0 -95
  130. data/app/views/decidim/authorizations/first_login.html.erb +0 -22
  131. data/app/views/decidim/authorizations/index.html.erb +0 -49
  132. data/app/views/decidim/authorizations/new.html.erb +0 -33
@@ -0,0 +1,49 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Decidim
4
+ module Messaging
5
+ #
6
+ # Holds a single message in a conversation. A message has a body, and sender
7
+ # and a set of receipts, which correspond to each user that will receive the
8
+ # message, namely, the interlocutors of the sender in the conversation.
9
+ #
10
+ class Message < ApplicationRecord
11
+ belongs_to :sender,
12
+ foreign_key: :decidim_sender_id,
13
+ class_name: "Decidim::User"
14
+
15
+ belongs_to :conversation,
16
+ foreign_key: :decidim_conversation_id,
17
+ class_name: "Decidim::Messaging::Conversation"
18
+
19
+ has_many :receipts,
20
+ dependent: :destroy,
21
+ foreign_key: :decidim_message_id,
22
+ inverse_of: :message
23
+
24
+ validates :sender, :body, presence: true
25
+ validates :body, length: { maximum: 1_000 }
26
+
27
+ validate :sender_is_participant
28
+
29
+ #
30
+ # Associates receipts for this message for each of the given users,
31
+ # including also a receipt for the remitent (sender) of the message.
32
+ # Receipts are unread by default, except for the sender's receipt.
33
+ #
34
+ # @param recipients [Array<Decidim::User>]
35
+ #
36
+ def envelope_for(recipients)
37
+ receipts.build(recipient: sender, read_at: Time.zone.now)
38
+
39
+ recipients.each { |recipient| receipts.build(recipient: recipient) }
40
+ end
41
+
42
+ private
43
+
44
+ def sender_is_participant
45
+ errors.add(:sender, :invalid) unless conversation.participants.include?(sender)
46
+ end
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Decidim
4
+ module Messaging
5
+ #
6
+ # Holds a many-to-many relationship between conversations and their participants
7
+ #
8
+ class Participation < ApplicationRecord
9
+ belongs_to :conversation,
10
+ foreign_key: :decidim_conversation_id,
11
+ class_name: "Decidim::Messaging::Conversation",
12
+ inverse_of: :participations
13
+
14
+ belongs_to :participant,
15
+ foreign_key: :decidim_participant_id,
16
+ class_name: "Decidim::User"
17
+
18
+ validates :conversation, :participant, presence: true
19
+
20
+ validates :decidim_conversation_id, uniqueness: { scope: :decidim_participant_id }
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Decidim
4
+ module Messaging
5
+ #
6
+ # Represents the reception of a message by a user. This model is supposed to
7
+ # hold any information about a message that is specific to each user, for
8
+ # example, the read/unread status, the deleted/undeleted status, and so on.
9
+ #
10
+ class Receipt < ApplicationRecord
11
+ belongs_to :recipient, foreign_key: "decidim_recipient_id", class_name: "Decidim::User"
12
+ belongs_to :message, foreign_key: "decidim_message_id", class_name: "Decidim::Messaging::Message"
13
+
14
+ validates :recipient, :message, presence: true
15
+
16
+ scope :recipient, ->(recipient) { where(recipient: recipient) }
17
+ scope :unread_by, ->(user) { recipient(user).unread }
18
+ scope :unread, -> { where(read_at: nil) }
19
+
20
+ # rubocop:disable Rails/SkipsModelValidations
21
+ def self.mark_as_read(user)
22
+ recipient(user).update_all(read_at: Time.zone.now)
23
+ end
24
+ # rubocop:enable Rails/SkipsModelValidations
25
+ end
26
+ end
27
+ end
@@ -5,7 +5,7 @@ module Decidim
5
5
  class Moderation < ApplicationRecord
6
6
  belongs_to :reportable, foreign_key: "decidim_reportable_id", foreign_type: "decidim_reportable_type", polymorphic: true
7
7
  belongs_to :participatory_space, foreign_key: "decidim_participatory_space_id", foreign_type: "decidim_participatory_space_type", polymorphic: true
8
- has_many :reports, foreign_key: "decidim_moderation_id", class_name: "Decidim::Report"
8
+ has_many :reports, foreign_key: "decidim_moderation_id", class_name: "Decidim::Report", dependent: :destroy
9
9
 
10
10
  delegate :feature, :organization, to: :reportable
11
11
  end
@@ -7,12 +7,12 @@ module Decidim
7
7
  class Organization < ApplicationRecord
8
8
  SOCIAL_HANDLERS = [:twitter, :facebook, :instagram, :youtube, :github].freeze
9
9
 
10
- has_many :static_pages, foreign_key: "decidim_organization_id", class_name: "Decidim::StaticPage", inverse_of: :organization
10
+ has_many :static_pages, foreign_key: "decidim_organization_id", class_name: "Decidim::StaticPage", inverse_of: :organization, dependent: :destroy
11
11
  has_many :scopes, -> { order(name: :asc) }, foreign_key: "decidim_organization_id", class_name: "Decidim::Scope", inverse_of: :organization
12
12
  has_many :scope_types, -> { order(name: :asc) }, foreign_key: "decidim_organization_id", class_name: "Decidim::ScopeType", inverse_of: :organization
13
13
  has_many :admins, -> { where(admin: true) }, foreign_key: "decidim_organization_id", class_name: "Decidim::User"
14
14
  has_many :users_with_any_role, -> { where.not(roles: []) }, foreign_key: "decidim_organization_id", class_name: "Decidim::User"
15
- has_many :users, foreign_key: "decidim_organization_id", class_name: "Decidim::User"
15
+ has_many :users, foreign_key: "decidim_organization_id", class_name: "Decidim::User", dependent: :destroy
16
16
 
17
17
  validates :name, :host, uniqueness: true
18
18
  validates :reference_prefix, presence: true
@@ -25,7 +25,8 @@ module Decidim
25
25
  has_many :children,
26
26
  foreign_key: "parent_id",
27
27
  class_name: "Decidim::Scope",
28
- inverse_of: :parent
28
+ inverse_of: :parent,
29
+ dependent: :destroy
29
30
 
30
31
  before_validation :update_part_of, on: :update
31
32
 
@@ -9,7 +9,7 @@ module Decidim
9
9
  class_name: "Decidim::Organization",
10
10
  inverse_of: :scope_types
11
11
 
12
- has_many :scopes, foreign_key: "decidim_scope_type_id", class_name: "Decidim::Scope", inverse_of: :scope_type
12
+ has_many :scopes, foreign_key: "scope_type_id", class_name: "Decidim::Scope", inverse_of: :scope_type, dependent: :nullify
13
13
 
14
14
  validates :name, presence: true
15
15
  end
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require_dependency "devise/models/decidim_validatable"
4
+ require "valid_email2"
4
5
 
5
6
  module Decidim
6
7
  # A User is a citizen that wants to join the platform to participate.
@@ -14,9 +15,8 @@ module Decidim
14
15
  request_keys: [:env], reset_password_keys: [:decidim_organization_id, :email]
15
16
 
16
17
  belongs_to :organization, foreign_key: "decidim_organization_id", class_name: "Decidim::Organization"
17
- has_many :authorizations, foreign_key: "decidim_user_id", class_name: "Decidim::Authorization", inverse_of: :user
18
- has_many :identities, foreign_key: "decidim_user_id", class_name: "Decidim::Identity"
19
- has_many :memberships, class_name: "Decidim::UserGroupMembership", foreign_key: :decidim_user_id
18
+ has_many :identities, foreign_key: "decidim_user_id", class_name: "Decidim::Identity", dependent: :destroy
19
+ has_many :memberships, class_name: "Decidim::UserGroupMembership", foreign_key: :decidim_user_id, dependent: :destroy
20
20
  has_many :user_groups, through: :memberships, class_name: "Decidim::UserGroup", foreign_key: :decidim_user_group_id
21
21
  has_many :follows, foreign_key: "decidim_user_id", class_name: "Decidim::Follow", dependent: :destroy
22
22
  has_many :notifications, foreign_key: "decidim_user_id", class_name: "Decidim::Notification", dependent: :destroy
@@ -26,6 +26,8 @@ module Decidim
26
26
  validates :tos_agreement, acceptance: true, allow_nil: false, on: :create
27
27
  validates :avatar, file_size: { less_than_or_equal_to: ->(_record) { Decidim.maximum_avatar_size } }
28
28
  validates :email, uniqueness: { scope: :organization }, unless: -> { deleted? || managed? }
29
+ validates :email, 'valid_email_2/email': { disposable: true }
30
+
29
31
  validate :all_roles_are_valid
30
32
 
31
33
  mount_uploader :avatar, Decidim::AvatarUploader
@@ -68,6 +70,10 @@ module Decidim
68
70
  Decidim::Follow.where(user: self, followable: followable).any?
69
71
  end
70
72
 
73
+ def unread_conversations
74
+ Decidim::Messaging::Conversation.unread_by(self)
75
+ end
76
+
71
77
  # Check if the user exists with the given email and the current organization
72
78
  #
73
79
  # warden_conditions - A hash with the authentication conditions
@@ -76,10 +82,10 @@ module Decidim
76
82
  # Returns a User.
77
83
  def self.find_for_authentication(warden_conditions)
78
84
  organization = warden_conditions.dig(:env, "decidim.current_organization")
79
- where(
85
+ find_by(
80
86
  email: warden_conditions[:email],
81
87
  decidim_organization_id: organization.id
82
- ).first
88
+ )
83
89
  end
84
90
 
85
91
  protected
@@ -5,7 +5,7 @@ module Decidim
5
5
  class UserGroup < ApplicationRecord
6
6
  belongs_to :organization, foreign_key: "decidim_organization_id", class_name: "Decidim::Organization"
7
7
 
8
- has_many :memberships, class_name: "Decidim::UserGroupMembership", foreign_key: :decidim_user_group_id
8
+ has_many :memberships, class_name: "Decidim::UserGroupMembership", foreign_key: :decidim_user_group_id, dependent: :destroy
9
9
  has_many :users, through: :memberships, class_name: "Decidim::User", foreign_key: :decidim_user_id
10
10
 
11
11
  validates :name, presence: true, uniqueness: { scope: :decidim_organization_id }
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Decidim
4
+ # A presenter to render inline menus
5
+ class InlineMenuPresenter < MenuPresenter
6
+ def render
7
+ safe_join(menu_items)
8
+ end
9
+ end
10
+ end
@@ -36,7 +36,7 @@ module Decidim
36
36
  end
37
37
  end
38
38
 
39
- private
39
+ protected
40
40
 
41
41
  def menu_items
42
42
  items.map do |menu_item|
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Decidim
4
+ module Messaging
5
+ # A class used to find the conversations a user is participating in.
6
+ class UserConversations < Rectify::Query
7
+ # Syntactic sugar to initialize the class and return the queried objects.
8
+ #
9
+ # user - a User that needs to find which processes can manage
10
+ def self.for(user)
11
+ new(user).query
12
+ end
13
+
14
+ def initialize(user)
15
+ @user = user
16
+ end
17
+
18
+ def query
19
+ Conversation
20
+ .joins(:participations)
21
+ .where(decidim_messaging_participations: { decidim_participant_id: user.id })
22
+ end
23
+
24
+ private
25
+
26
+ attr_reader :user
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Decidim
4
+ # Use this class as a scrubber to sanitize user input. The default
5
+ # scrubbed provided by Rails does not allow `iframe`s, and we're using
6
+ # them to embed videos, so we need to provide a whole new scrubber.
7
+ #
8
+ # Example:
9
+ #
10
+ # sanitize(@page.body, scrubber: Decidim::UserInputScrubber.new)
11
+ #
12
+ # Lists of default tags and attributes are extracted from
13
+ # https://stackoverflow.com/a/35073814/2110884.
14
+ class UserInputScrubber < Rails::Html::PermitScrubber
15
+ def initialize
16
+ super
17
+ self.tags = custom_allowed_tags
18
+ self.attributes = custom_allowed_attributes
19
+ end
20
+
21
+ private
22
+
23
+ def custom_allowed_attributes
24
+ Loofah::HTML5::WhiteList::ALLOWED_ATTRIBUTES + %w(frameborder allowfullscreen)
25
+ end
26
+
27
+ def custom_allowed_tags
28
+ Loofah::HTML5::WhiteList::ALLOWED_ELEMENTS_WITH_LIBXML2 + %w(iframe)
29
+ end
30
+ end
31
+ end
@@ -27,19 +27,31 @@ module Decidim
27
27
  #
28
28
  # Returns nil.
29
29
  def authorize
30
- raise AuthorizationError, "Missing data" unless feature && action
31
-
32
- return status(:ok) unless authorization_handler_name
33
-
34
- return status(:missing) unless authorization
35
- return status(:invalid, fields: unmatched_fields) if unmatched_fields.any?
36
- return status(:incomplete, fields: missing_fields) if missing_fields.any?
30
+ status_code, fields = *status_data
37
31
 
38
- status(:ok)
32
+ status(status_code, fields || {})
39
33
  end
40
34
 
41
35
  private
42
36
 
37
+ def status_data
38
+ raise AuthorizationError, "Missing data" unless feature && action
39
+
40
+ if !authorization_handler_name
41
+ :ok
42
+ elsif !authorization
43
+ :missing
44
+ elsif !authorization.granted?
45
+ :pending
46
+ elsif unmatched_fields.any?
47
+ [:invalid, fields: unmatched_fields]
48
+ elsif missing_fields.any?
49
+ [:incomplete, fields: missing_fields]
50
+ else
51
+ :ok
52
+ end
53
+ end
54
+
43
55
  def status(status_code, data = {})
44
56
  AuthorizationStatus.new(status_code, authorization_handler_name, data)
45
57
  end
@@ -52,9 +64,7 @@ module Decidim
52
64
  handler = permission["authorization_handler_name"]
53
65
  return nil unless handler
54
66
 
55
- @authorization ||= user.authorizations.find do |authorization|
56
- authorization.name == handler
57
- end
67
+ @authorization ||= Verifications::Authorizations.new(user: user, name: handler).first
58
68
  end
59
69
 
60
70
  def unmatched_fields
@@ -87,7 +97,7 @@ module Decidim
87
97
  end
88
98
 
89
99
  class AuthorizationStatus
90
- attr_reader :code, :handler_name, :data
100
+ attr_reader :code, :data
91
101
 
92
102
  def initialize(code, handler_name, data)
93
103
  @code = code.to_sym
@@ -95,9 +105,35 @@ module Decidim
95
105
  @data = data.symbolize_keys
96
106
  end
97
107
 
108
+ def auth_method
109
+ return unless @handler_name
110
+
111
+ @auth_method ||= Verifications::Adapter.from_element(@handler_name)
112
+ end
113
+
114
+ def current_path(redirect_url: nil)
115
+ return unless auth_method
116
+
117
+ if pending?
118
+ auth_method.resume_authorization_path(redirect_url: redirect_url)
119
+ else
120
+ auth_method.root_path(redirect_url: redirect_url)
121
+ end
122
+ end
123
+
124
+ def handler_name
125
+ return unless auth_method
126
+
127
+ auth_method.key
128
+ end
129
+
98
130
  def ok?
99
131
  @code == :ok
100
132
  end
133
+
134
+ def pending?
135
+ @code == :pending
136
+ end
101
137
  end
102
138
 
103
139
  class AuthorizationError < StandardError; end
@@ -0,0 +1,91 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Decidim
4
+ # This class wraps the logic to trace resource changes and their authorship.
5
+ # It is expected to be used with classes implementing the `Decidim::Traceable`
6
+ # concern. Version authors can be retrieved using the methods in
7
+ # `Decidim::TraceabilityHelper`.
8
+ #
9
+ # Examples:
10
+ #
11
+ # # consider MyResource implements Decidim::Traceable
12
+ # resource = Decidim::Traceability.new.create!(MyResource, author, params)
13
+ # resource.versions.count # => 1
14
+ # resource.versions.last.whodunnit # => author.to_gid.to_s
15
+ # resource.versions.last.event # => "create"
16
+ # resource = Decidim::Traceability.new.update!(resource, author, params)
17
+ # resource.versions.count # => 2
18
+ # resource.versions.last.event # => "update"
19
+ #
20
+ # This class uses the `paper_trail` gem internally, so refer to its documentation
21
+ # for further info on how to interact with versions.
22
+ class Traceability
23
+ # Calls the `create` method to the given class and sets the author of the version.
24
+ #
25
+ # klass - An ActiveRecord class that implements `Decidim::Traceable`
26
+ # author - An object that implements `to_gid` or a String
27
+ # params - a Hash
28
+ #
29
+ # Returns an instance of `klass`.
30
+ def create(klass, author, params)
31
+ PaperTrail.whodunnit(gid(author)) do
32
+ klass.create(params)
33
+ end
34
+ end
35
+
36
+ # Calls the `create!` method to the given class and sets the author of the version.
37
+ #
38
+ # klass - An ActiveRecord class that implements `Decidim::Traceable`
39
+ # author - An object that implements `to_gid` or a String
40
+ # params - a Hash
41
+ #
42
+ # Returns an instance of `klass`.
43
+ def create!(klass, author, params)
44
+ PaperTrail.whodunnit(gid(author)) do
45
+ klass.create!(params)
46
+ end
47
+ end
48
+
49
+ # Updates the `resource` with `update_attributes!` and sets the author of the version.
50
+ #
51
+ # resource - An ActiveRecord instance that implements `Decidim::Traceable`
52
+ # author - An object that implements `to_gid` or a String
53
+ # params - a Hash
54
+ #
55
+ # Returns the updated `resource`.
56
+ def update!(resource, author, params)
57
+ PaperTrail.whodunnit(gid(author)) do
58
+ resource.update_attributes!(params)
59
+ resource
60
+ end
61
+ end
62
+
63
+ # Finds the author of the last version of the resource.
64
+ #
65
+ # resource - an object implementing `Decidim::Traceable`
66
+ #
67
+ # Returns an object identifiable via GlobalID or a String.
68
+ def last_editor(resource)
69
+ version_editor(resource.versions.last)
70
+ end
71
+
72
+ # Finds the author of the given version.
73
+ #
74
+ # version - an object that responds to `whodunnit` and returns a String.
75
+ #
76
+ # Returns an object identifiable via GlobalID or a String.
77
+ def version_editor(version)
78
+ ::GlobalID::Locator.locate(version.whodunnit) || version.whodunnit
79
+ end
80
+
81
+ private
82
+
83
+ # Calculates the GlobalID of the version author. If the object does not respond to
84
+ # `to_gid`, then it returns the object itself.
85
+ def gid(author)
86
+ return if author.blank?
87
+ return author.to_gid if author.respond_to?(:to_gid)
88
+ author
89
+ end
90
+ end
91
+ end