From 004cc6dc0ab7cc9c324ccb4ecd420c6aeeb20500 Mon Sep 17 00:00:00 2001 From: 6543 <6543@obermui.de> Date: Sun, 14 Jul 2024 14:27:00 -0700 Subject: [PATCH] Add option to change mail from user display name (#31528) Make it posible to let mails show e.g.: `Max Musternam (via gitea.kithara.com) ` Docs: https://gitea.com/gitea/docs/pulls/23 --- *Sponsored by Kithara Software GmbH* (cherry picked from commit 0f533241829d0d48aa16a91e7dc0614fe50bc317) Conflicts: - services/mailer/mail_release.go services/mailer/mail_test.go In both cases, applied the changes manually. --- custom/conf/app.example.ini | 4 +++ modules/setting/mailer.go | 15 +++++++++++ services/mailer/mail.go | 18 ++++++++++++- services/mailer/mail_release.go | 2 +- services/mailer/mail_repo.go | 2 +- services/mailer/mail_test.go | 48 +++++++++++++++++++++++++++++++++ 6 files changed, 86 insertions(+), 3 deletions(-) diff --git a/custom/conf/app.example.ini b/custom/conf/app.example.ini index cdb7629887..bbf383b065 100644 --- a/custom/conf/app.example.ini +++ b/custom/conf/app.example.ini @@ -1727,6 +1727,10 @@ LEVEL = Info ;; Sometimes it is helpful to use a different address on the envelope. Set this to use ENVELOPE_FROM as the from on the envelope. Set to `<>` to send an empty address. ;ENVELOPE_FROM = ;; +;; If gitea sends mails on behave of users, it will just use the name also displayed in the WebUI. If you want e.g. `Mister X (by CodeIt) `, +;; set it to `{{ .DisplayName }} (by {{ .AppName }})`. Available Variables: `.DisplayName`, `.AppName` and `.Domain`. +;FROM_DISPLAY_NAME_FORMAT = {{ .DisplayName }} +;; ;; Mailer user name and password, if required by provider. ;USER = ;; diff --git a/modules/setting/mailer.go b/modules/setting/mailer.go index cfedb4613f..136d932b8d 100644 --- a/modules/setting/mailer.go +++ b/modules/setting/mailer.go @@ -8,6 +8,7 @@ import ( "net" "net/mail" "strings" + "text/template" "time" "code.gitea.io/gitea/modules/log" @@ -46,6 +47,10 @@ type Mailer struct { SendmailArgs []string `ini:"-"` SendmailTimeout time.Duration `ini:"SENDMAIL_TIMEOUT"` SendmailConvertCRLF bool `ini:"SENDMAIL_CONVERT_CRLF"` + + // Customization + FromDisplayNameFormat string `ini:"FROM_DISPLAY_NAME_FORMAT"` + FromDisplayNameFormatTemplate *template.Template `ini:"-"` } // MailService the global mailer @@ -234,6 +239,16 @@ func loadMailerFrom(rootCfg ConfigProvider) { log.Error("no mailer.FROM provided, email system may not work.") } + MailService.FromDisplayNameFormatTemplate, _ = template.New("mailFrom").Parse("{{ .DisplayName }}") + if MailService.FromDisplayNameFormat != "" { + template, err := template.New("mailFrom").Parse(MailService.FromDisplayNameFormat) + if err != nil { + log.Error("mailer.FROM_DISPLAY_NAME_FORMAT is no valid template: %v", err) + } else { + MailService.FromDisplayNameFormatTemplate = template + } + } + switch MailService.EnvelopeFrom { case "": MailService.OverrideEnvelopeFrom = false diff --git a/services/mailer/mail.go b/services/mailer/mail.go index ca8fb34ef1..38e292e6a1 100644 --- a/services/mailer/mail.go +++ b/services/mailer/mail.go @@ -313,7 +313,7 @@ func composeIssueCommentMessages(ctx *mailCommentContext, lang string, recipient for _, recipient := range recipients { msg := NewMessageFrom( recipient.Email, - ctx.Doer.GetCompleteName(), + fromDisplayName(ctx.Doer), setting.MailService.FromEmail, subject, mailBody.String(), @@ -545,3 +545,19 @@ func actionToTemplate(issue *issues_model.Issue, actionType activities_model.Act } return typeName, name, template } + +func fromDisplayName(u *user_model.User) string { + if setting.MailService.FromDisplayNameFormatTemplate != nil { + var ctx bytes.Buffer + err := setting.MailService.FromDisplayNameFormatTemplate.Execute(&ctx, map[string]any{ + "DisplayName": u.DisplayName(), + "AppName": setting.AppName, + "Domain": setting.Domain, + }) + if err == nil { + return mime.QEncoding.Encode("utf-8", ctx.String()) + } + log.Error("fromDisplayName: %w", err) + } + return u.GetCompleteName() +} diff --git a/services/mailer/mail_release.go b/services/mailer/mail_release.go index 445e58724c..0b8b97e9cd 100644 --- a/services/mailer/mail_release.go +++ b/services/mailer/mail_release.go @@ -85,7 +85,7 @@ func mailNewRelease(ctx context.Context, lang string, tos []*user_model.User, re } msgs := make([]*Message, 0, len(tos)) - publisherName := rel.Publisher.DisplayName() + publisherName := fromDisplayName(rel.Publisher) msgID := createMessageIDForRelease(rel) for _, to := range tos { msg := NewMessageFrom(to.EmailTo(), publisherName, setting.MailService.FromEmail, subject, mailBody.String()) diff --git a/services/mailer/mail_repo.go b/services/mailer/mail_repo.go index 28b9cef8a7..7003584786 100644 --- a/services/mailer/mail_repo.go +++ b/services/mailer/mail_repo.go @@ -79,7 +79,7 @@ func sendRepoTransferNotifyMailPerLang(lang string, newOwner, doer *user_model.U } for _, to := range emailTos { - msg := NewMessage(to.EmailTo(), subject, content.String()) + msg := NewMessageFrom(to.EmailTo(), fromDisplayName(doer), setting.MailService.FromEmail, subject, content.String()) msg.Info = fmt.Sprintf("UID: %d, repository pending transfer notification", newOwner.ID) SendAsync(msg) diff --git a/services/mailer/mail_test.go b/services/mailer/mail_test.go index 8fa45fd593..54b9c177e8 100644 --- a/services/mailer/mail_test.go +++ b/services/mailer/mail_test.go @@ -489,3 +489,51 @@ func Test_createReference(t *testing.T) { }) } } + +func TestFromDisplayName(t *testing.T) { + template, err := texttmpl.New("mailFrom").Parse("{{ .DisplayName }}") + assert.NoError(t, err) + setting.MailService = &setting.Mailer{FromDisplayNameFormatTemplate: template} + defer func() { setting.MailService = nil }() + + tests := []struct { + userDisplayName string + fromDisplayName string + }{{ + userDisplayName: "test", + fromDisplayName: "test", + }, { + userDisplayName: "Hi Its ", + fromDisplayName: "Hi Its ", + }, { + userDisplayName: "Æsir", + fromDisplayName: "=?utf-8?q?=C3=86sir?=", + }, { + userDisplayName: "new😀user", + fromDisplayName: "=?utf-8?q?new=F0=9F=98=80user?=", + }} + + for _, tc := range tests { + t.Run(tc.userDisplayName, func(t *testing.T) { + user := &user_model.User{FullName: tc.userDisplayName, Name: "tmp"} + got := fromDisplayName(user) + assert.EqualValues(t, tc.fromDisplayName, got) + }) + } + + t.Run("template with all available vars", func(t *testing.T) { + template, err = texttmpl.New("mailFrom").Parse("{{ .DisplayName }} (by {{ .AppName }} on [{{ .Domain }}])") + assert.NoError(t, err) + setting.MailService = &setting.Mailer{FromDisplayNameFormatTemplate: template} + oldAppName := setting.AppName + setting.AppName = "Code IT" + oldDomain := setting.Domain + setting.Domain = "code.it" + defer func() { + setting.AppName = oldAppName + setting.Domain = oldDomain + }() + + assert.EqualValues(t, "Mister X (by Code IT on [code.it])", fromDisplayName(&user_model.User{FullName: "Mister X", Name: "tmp"})) + }) +}