diff --git a/.rubocop_rails.yml b/.rubocop_rails.yml index e3067271ee..b0e45aa030 100644 --- a/.rubocop_rails.yml +++ b/.rubocop_rails.yml @@ -109,3 +109,7 @@ RSpec/MultipleMemoizedHelpers: RSpec/AnyInstance: Enabled: false + +RSpec/BeEq: + Exclude: + - spec/events/decidim/proposals/author_confirmation_proposal_event_spec.rb \ No newline at end of file diff --git a/app/commands/decidim/proposals/publish_proposal.rb b/app/commands/decidim/proposals/publish_proposal.rb new file mode 100644 index 0000000000..a637eaed4f --- /dev/null +++ b/app/commands/decidim/proposals/publish_proposal.rb @@ -0,0 +1,126 @@ +# frozen_string_literal: true + +module Decidim + module Proposals + # A command with all the business logic when a user publishes a draft proposal. + class PublishProposal < Decidim::Command + include Decidim::AnonymousProposals::AnonymousBehaviorCommandsConcern + + # Public: Initializes the command. + # + # proposal - The proposal to publish. + # current_user - The current user. + # override: decidim-module-anonymous_proposals/app/commands/decidim/anonymous_proposals/publish_proposal_command_overrides.rb + def initialize(proposal, current_user) + @proposal = proposal + @is_anonymous = allow_anonymous_proposals? && (current_user.blank? || proposal.authored_by?(anonymous_group)) + set_current_user(current_user) + end + + # Executes the command. Broadcasts these events: + # + # - :ok when everything is valid and the proposal is published. + # - :invalid if the proposal's author is not the current user. + # + # Returns nothing. + def call + return broadcast(:invalid) unless @proposal.authored_by?(@current_user) + + transaction do + publish_proposal + increment_scores + send_notification + send_notification_to_participatory_space + send_publication_notification + end + + broadcast(:ok, @proposal) + end + + private + + # This will be the PaperTrail version that is + # shown in the version control feature (1 of 1) + # + # For an attribute to appear in the new version it has to be reset + # and reassigned, as PaperTrail only keeps track of object CHANGES. + def publish_proposal + title = reset(:title) + body = reset(:body) + + Decidim.traceability.perform_action!( + "publish", + @proposal, + @current_user, + visibility: "public-only" + ) do + @proposal.update title: title, body: body, published_at: Time.current + end + end + + # Reset the attribute to an empty string and return the old value + def reset(attribute) + attribute_value = @proposal[attribute] + PaperTrail.request(enabled: false) do + # rubocop:disable Rails/SkipsModelValidations + @proposal.update_attribute attribute, "" + # rubocop:enable Rails/SkipsModelValidations + end + attribute_value + end + + def send_notification + return if @proposal.coauthorships.empty? + + Decidim::EventsManager.publish( + event: "decidim.events.proposals.proposal_published", + event_class: Decidim::Proposals::PublishProposalEvent, + resource: @proposal, + followers: coauthors_followers + ) + end + + def send_publication_notification + Decidim::EventsManager.publish( + event: "decidim.events.proposals.author_confirmation_proposal_event", + event_class: Decidim::Proposals::AuthorConfirmationProposalEvent, + resource: @proposal, + affected_users: [@proposal.creator_identity], + extra: { force_email: true }, + force_send: true + ) + end + + def send_notification_to_participatory_space + Decidim::EventsManager.publish( + event: "decidim.events.proposals.proposal_published", + event_class: Decidim::Proposals::PublishProposalEvent, + resource: @proposal, + followers: @proposal.participatory_space.followers - coauthors_followers, + extra: { + participatory_space: true + } + ) + end + + def coauthors_followers + @coauthors_followers ||= @proposal.authors.flat_map(&:followers) + end + + def increment_scores + @proposal.coauthorships.find_each do |coauthorship| + if coauthorship.user_group + Decidim::Gamification.increment_score(coauthorship.user_group, :proposals) + else + Decidim::Gamification.increment_score(coauthorship.author, :proposals) + end + end + end + + # override: decidim-module-anonymous_proposals/app/commands/decidim/anonymous_proposals/publish_proposal_command_overrides.rb + def component + @component ||= @proposal.component + end + end + end +end diff --git a/app/events/decidim/proposals/author_confirmation_proposal_event.rb b/app/events/decidim/proposals/author_confirmation_proposal_event.rb new file mode 100644 index 0000000000..22745650a4 --- /dev/null +++ b/app/events/decidim/proposals/author_confirmation_proposal_event.rb @@ -0,0 +1,15 @@ +# frozen_string_literal: true + +module Decidim + module Proposals + class AuthorConfirmationProposalEvent < Decidim::Events::SimpleEvent + def self.model_name + ActiveModel::Name.new(self, nil, I18n.t("decidim.events.proposals.author_confirmation_proposal_event.email_subject")) + end + + def resource_title + translated_attribute(resource.title) + end + end + end +end diff --git a/config/i18n-tasks.yml b/config/i18n-tasks.yml index be5b5b9147..fef7c18f22 100644 --- a/config/i18n-tasks.yml +++ b/config/i18n-tasks.yml @@ -164,5 +164,8 @@ ignore_unused: - decidim.newsletters.unsubscribe.error - decidim.newsletters.unsubscribe.token_error - decidim.half_signup.quick_auth.sms_verification.text_message + - decidim.events.proposals.author_confirmation_proposal_event.email_intro + - decidim.events.proposals.author_confirmation_proposal_event.email_outro + - decidim.events.proposals.author_confirmation_proposal_event.notification_title diff --git a/config/locales/en.yml b/config/locales/en.yml index 477839a9f7..56be2f097d 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -77,6 +77,12 @@ en: email_outro: You have received this notification because you are following the initiative "%{resource_title}". email_subject: Initiative "%{resource_title}" has been answered notification_title: The initiative %{resource_title} has been answered. + proposals: + author_confirmation_proposal_event: + email_intro: 'Your proposal " %{resource_title} " was successfully received and is now public. Thank you for participating ! You can view it here:' + email_outro: You received this notification because you are the author of the proposal. You can unfollow it by visiting the proposal page (" %{resource_title} ") and clicking on " Unfollow ". + email_subject: Your proposal has been published! + notification_title: Your proposal %{resource_title} is now live. users: user_officialized: email_intro: Participant %{name} (%{nickname}) has been officialized. diff --git a/config/locales/fr.yml b/config/locales/fr.yml index a82163e17b..6e725c5912 100644 --- a/config/locales/fr.yml +++ b/config/locales/fr.yml @@ -79,6 +79,12 @@ fr: email_outro: Vous avez reçu cette notification parce que vous suivez la pétition "%{resource_title}". email_subject: La pétition "%{resource_title}" a reçu une réponse. notification_title: La pétition %{resource_title} a reçu une réponse. + proposals: + author_confirmation_proposal_event: + email_intro: 'Votre proposition « %{resource_title} » a été reçue avec succès et est maintenant publique. Merci pour votre participation ! Vous pouvez la consulter ici :' + email_outro: Vous recevez cette notification car vous êtes l’auteur de la proposition. Vous pouvez vous désabonner en visitant la page de la proposition (« %{resource_title} ») et en cliquant sur « Ne plus suivre ». + email_subject: Votre proposition a été publiée ! + notification_title: Votre proposition %{resource_title} est maintenant en ligne. users: user_officialized: email_intro: Le participant %{name} (%{nickname}) a été officialisé.