diff --git a/app/assets/javascripts/admin/controllers/modals/admin-incoming-email.js.es6 b/app/assets/javascripts/admin/controllers/modals/admin-incoming-email.js.es6 new file mode 100644 index 00000000000..bc9cd2edd8d --- /dev/null +++ b/app/assets/javascripts/admin/controllers/modals/admin-incoming-email.js.es6 @@ -0,0 +1,17 @@ +import ModalFunctionality from 'discourse/mixins/modal-functionality'; +import IncomingEmail from 'admin/models/incoming-email'; +import computed from 'ember-addons/ember-computed-decorators'; +import { longDate } from 'discourse/lib/formatter'; + +export default Ember.Controller.extend(ModalFunctionality, { + + @computed("model.date") + date(d) { + return longDate(d); + }, + + load(id) { + return IncomingEmail.find(id).then(result => this.set("model", result)); + } + +}); diff --git a/app/assets/javascripts/admin/models/incoming-email.js.es6 b/app/assets/javascripts/admin/models/incoming-email.js.es6 index d0e5b43c599..82534c5c43d 100644 --- a/app/assets/javascripts/admin/models/incoming-email.js.es6 +++ b/app/assets/javascripts/admin/models/incoming-email.js.es6 @@ -14,6 +14,10 @@ IncomingEmail.reopenClass({ return this._super(attrs); }, + find(id) { + return Discourse.ajax(`/admin/email/incoming/${id}.json`); + }, + findAll(filter, offset) { filter = filter || {}; offset = offset || 0; diff --git a/app/assets/javascripts/admin/routes/admin-email-rejected.js.es6 b/app/assets/javascripts/admin/routes/admin-email-rejected.js.es6 index 4d96868d299..a7819b31a60 100644 --- a/app/assets/javascripts/admin/routes/admin-email-rejected.js.es6 +++ b/app/assets/javascripts/admin/routes/admin-email-rejected.js.es6 @@ -5,9 +5,9 @@ export default AdminEmailIncomings.extend({ status: "rejected", actions: { - showRawEmail(incomingEmailId) { - showModal('raw-email'); - this.controllerFor('raw_email').loadIncomingRawEmail(incomingEmailId); + showIncomingEmail(id) { + showModal('modals/admin-incoming-email'); + this.controllerFor("modals/admin-incoming-email").load(id); } } diff --git a/app/assets/javascripts/admin/templates/email-rejected.hbs b/app/assets/javascripts/admin/templates/email-rejected.hbs index ea5c22112df..3c5f3cba8eb 100644 --- a/app/assets/javascripts/admin/templates/email-rejected.hbs +++ b/app/assets/javascripts/admin/templates/email-rejected.hbs @@ -42,7 +42,7 @@ {{email.subject}} - {{email.error}} + {{email.error}} {{else}} diff --git a/app/assets/javascripts/admin/templates/modal/admin_incoming_email.hbs b/app/assets/javascripts/admin/templates/modal/admin_incoming_email.hbs new file mode 100644 index 00000000000..d4907a7e322 --- /dev/null +++ b/app/assets/javascripts/admin/templates/modal/admin_incoming_email.hbs @@ -0,0 +1,98 @@ +
+ +
+

{{model.error}}

+ {{#if model.error_description}} +

{{model.error_description}}

+ {{/if}} +
+
+ +
+ +{{#if model.return_path}} +
+ +
+ {{model.return_path}} +
+
+{{/if}} + +
+ +
+ {{model.message_id}} +
+
+ +{{#if model.references}} +
+ +
+ +
+
+{{/if}} + +{{#if model.in_reply_to}} +
+ +
+ {{model.in_reply_to}} +
+
+{{/if}} + +
+ +
+ {{date}} +
+
+ +
+ +
+ {{model.from}} +
+
+ +
+ +
+ +
+
+ +{{#if model.cc}} +
+ +
+ {{model.cc}} +
+
+{{/if}} + +
+ +
+ {{model.subject}} +
+
+ +
+ +
+ {{textarea value=model.body}} +
+
+ diff --git a/app/assets/javascripts/admin/views/modals/admin-incoming-email.js.es6 b/app/assets/javascripts/admin/views/modals/admin-incoming-email.js.es6 new file mode 100644 index 00000000000..576feff433c --- /dev/null +++ b/app/assets/javascripts/admin/views/modals/admin-incoming-email.js.es6 @@ -0,0 +1,7 @@ +import ModalBodyView from "discourse/views/modal-body"; + +export default ModalBodyView.extend({ + templateName: 'admin/templates/modal/admin_incoming_email', + classNames: ['incoming-emails'], + title: I18n.t('admin.email.incoming_emails.modal.title') +}); diff --git a/app/assets/stylesheets/common/admin/admin_base.scss b/app/assets/stylesheets/common/admin/admin_base.scss index 5a5df3b7381..90c8bf41c7d 100644 --- a/app/assets/stylesheets/common/admin/admin_base.scss +++ b/app/assets/stylesheets/common/admin/admin_base.scss @@ -1798,6 +1798,40 @@ table#user-badges { } } +.incoming-emails { + .control-group { + margin: 8px 0; + } + .controls { + margin-left: 110px; + } + p { + margin: 5px 10px; + } + .error-description { + color: #919191; + font-size: 90%; + } + hr { + margin: 0; + } + label { + font-weight: bold; + float: left; + width: 100px; + text-align: right; + margin: 0 10px; + } + ul { + list-style: none; + margin: 0 10px; + } + textarea { + width: 95%; + height: 150px; + } +} + // Mobile specific styles // Mobile view text-inputs need some padding .mobile-view .admin-contents { diff --git a/app/controllers/admin/email_controller.rb b/app/controllers/admin/email_controller.rb index c8c603e9fdb..4b685c48e24 100644 --- a/app/controllers/admin/email_controller.rb +++ b/app/controllers/admin/email_controller.rb @@ -57,6 +57,13 @@ class Admin::EmailController < Admin::AdminController render json: { raw_email: incoming_email.raw } end + def incoming + params.require(:id) + incoming_email = IncomingEmail.find(params[:id].to_i) + serializer = IncomingEmailDetailsSerializer.new(incoming_email, root: false) + render_json_dump(serializer) + end + private def filter_email_logs(email_logs, params) diff --git a/app/serializers/incoming_email_details_serializer.rb b/app/serializers/incoming_email_details_serializer.rb new file mode 100644 index 00000000000..ac9e346caa5 --- /dev/null +++ b/app/serializers/incoming_email_details_serializer.rb @@ -0,0 +1,82 @@ +class IncomingEmailDetailsSerializer < ApplicationSerializer + + attributes :error, + :error_description, + :return_path, + :date, + :from, + :to, + :cc, + :message_id, + :references, + :in_reply_to, + :subject, + :body + + def initialize(incoming_email, opts) + super + @error_string = incoming_email.error + @mail = Mail.new(incoming_email.raw) + end + + EMAIL_RECEIVER_ERROR_PREFIX = "Email::Receiver::".freeze + + def error + @error_string + end + + def error_description + error_name = @error_string.sub(EMAIL_RECEIVER_ERROR_PREFIX, "").underscore + I18n.t("emails.incoming.errors.#{error_name}") + end + + def include_error_description? + @error_string[EMAIL_RECEIVER_ERROR_PREFIX] + end + + def return_path + @mail.return_path + end + + def date + @mail.date + end + + def from + @mail.from.first.downcase + end + + def to + @mail.to.map(&:downcase) + end + + def cc + @mail.cc.map(&:downcase) if @mail.cc.present? + end + + def message_id + @mail.message_id + end + + def references + references = Email::Receiver.extract_references(@mail.references) + references.delete(@mail.in_reply_to) if references + references + end + + def in_reply_to + @mail.in_reply_to + end + + def subject + @mail.subject.presence || "(no subject)" + end + + def body + body = @mail.text_part.decoded rescue nil + body ||= @mail.html_part.decoded rescue nil + body ||= @mail.body.decoded rescue nil + body.strip.truncate_words(100, escape: false) + end + +end diff --git a/config/locales/client.en.yml b/config/locales/client.en.yml index 96002144e70..acd880b1bc7 100644 --- a/config/locales/client.en.yml +++ b/config/locales/client.en.yml @@ -2262,6 +2262,19 @@ en: subject: "Subject" error: "Error" none: "No incoming emails found." + modal: + title: "Incoming Email Details" + error: "Error" + return_path: "Return-Path" + message_id: "Message-Id" + in_reply_to: "In-Reply-To" + references: "References" + date: "Date" + from: "From" + to: "To" + cc: "Cc" + subject: "Subject" + body: "Body" filters: from_placeholder: "from@example.com" to_placeholder: "to@example.com" diff --git a/config/locales/server.en.yml b/config/locales/server.en.yml index 71be1163da2..3cb10c0f616 100644 --- a/config/locales/server.en.yml +++ b/config/locales/server.en.yml @@ -50,6 +50,18 @@ en: emails: incoming: default_subject: "Incoming email from %{email}" + errors: + empty_email_error: "Happens when the raw mail we received was blank." + no_message_id_error: "Happens when the mail has no 'Message-Id' header." + auto_generated_email_error: "Happens when the 'precedence' header is set to: list, junk, bulk or auto_reply, or when any other header contains: auto-submitted, auto-replied or auto-generated." + no_body_detected_error: "Happens when we couldn't extract a body and there was no attachments." + inactive_user_error: "Happens when the sender is not active." + bad_destination_address: "Happens when none of the email addresses in To/Cc/Bcc fields matched a configured incoming email address." + strangers_not_allowed_error: "Happens when a user tried to create a new topic in a category they're not a member of." + insufficient_trust_level_error: "Happens when a use tried to create a new topic in a category they don't have the required trust level for." + reply_user_not_matching_error: "Happens when a reply came in from a different email address the notification was sent to." + topic_not_found_error: "Happens when a reply came in but the related topic has been deleted." + topic_closed_error: "Happens when a reply came in but the related topic has been closed." errors: &errors format: ! '%{attribute} %{message}' diff --git a/config/routes.rb b/config/routes.rb index d0457c90c48..8b77b6fc916 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -125,6 +125,7 @@ Discourse::Application.routes.draw do get "received" get "rejected" get "/incoming/:id/raw" => "email#raw_email" + get "/incoming/:id" => "email#incoming" get "preview-digest" => "email#preview_digest" post "handle_mail" end diff --git a/lib/email/receiver.rb b/lib/email/receiver.rb index 049f7fcff75..4a5b5462d61 100644 --- a/lib/email/receiver.rb +++ b/lib/email/receiver.rb @@ -215,7 +215,7 @@ module Email end def find_related_post - message_ids = [@mail.in_reply_to, extract_references] + message_ids = [@mail.in_reply_to, Email::Receiver.extract_references(@mail.references)] message_ids.flatten! message_ids.select!(&:present?) message_ids.uniq! @@ -226,11 +226,11 @@ module Email .first end - def extract_references - if Array === @mail.references - @mail.references - elsif @mail.references.present? - @mail.references.split(/[\s,]/).map { |r| r.sub(/^$/, "") } + def self.extract_references(references) + if Array === references + references + elsif references.present? + references.split(/[\s,]/).map { |r| r.sub(/^$/, "") } end end