2016-07-15 19:36:39 +03:00
// Copyright 2016 The Gogs Authors. All rights reserved.
2019-09-24 08:02:49 +03:00
// Copyright 2019 The Gitea Authors. All rights reserved.
2022-11-27 21:20:29 +03:00
// SPDX-License-Identifier: MIT
2016-07-15 19:36:39 +03:00
2019-09-24 08:02:49 +03:00
package mailer
2016-07-15 19:36:39 +03:00
import (
2016-12-06 20:58:31 +03:00
"bytes"
2022-03-22 18:22:54 +03:00
"context"
2016-07-15 19:36:39 +03:00
"fmt"
"html/template"
2019-11-07 16:34:28 +03:00
"mime"
"regexp"
2021-05-22 09:47:16 +03:00
"strconv"
2019-11-07 16:34:28 +03:00
"strings"
texttmpl "text/template"
2022-02-04 02:01:16 +03:00
"time"
2016-07-15 19:36:39 +03:00
2022-08-25 05:31:57 +03:00
activities_model "code.gitea.io/gitea/models/activities"
2022-06-13 12:37:59 +03:00
issues_model "code.gitea.io/gitea/models/issues"
2024-11-24 11:18:57 +03:00
"code.gitea.io/gitea/models/renderhelper"
2021-12-10 04:27:50 +03:00
repo_model "code.gitea.io/gitea/models/repo"
2021-11-11 10:03:30 +03:00
user_model "code.gitea.io/gitea/models/user"
2016-11-10 19:24:48 +03:00
"code.gitea.io/gitea/modules/base"
2020-04-28 21:05:39 +03:00
"code.gitea.io/gitea/modules/emoji"
2016-11-10 19:24:48 +03:00
"code.gitea.io/gitea/modules/log"
2017-09-21 08:20:14 +03:00
"code.gitea.io/gitea/modules/markup/markdown"
2016-11-10 19:24:48 +03:00
"code.gitea.io/gitea/modules/setting"
2019-08-15 17:46:21 +03:00
"code.gitea.io/gitea/modules/timeutil"
2021-04-02 13:25:13 +03:00
"code.gitea.io/gitea/modules/translation"
2023-01-14 18:57:10 +03:00
incoming_payload "code.gitea.io/gitea/services/mailer/incoming/payload"
2024-11-30 04:15:41 +03:00
sender_service "code.gitea.io/gitea/services/mailer/sender"
2023-01-14 18:57:10 +03:00
"code.gitea.io/gitea/services/mailer/token"
2016-07-15 19:36:39 +03:00
)
const (
2016-11-25 11:11:12 +03:00
mailAuthActivate base . TplName = "auth/activate"
mailAuthActivateEmail base . TplName = "auth/activate_email"
mailAuthResetPassword base . TplName = "auth/reset_passwd"
mailAuthRegisterNotify base . TplName = "auth/register_notify"
2016-07-15 19:36:39 +03:00
2016-11-25 11:11:12 +03:00
mailNotifyCollaborator base . TplName = "notify/collaborator"
2019-11-07 16:34:28 +03:00
2021-03-01 03:47:30 +03:00
mailRepoTransferNotify base . TplName = "notify/repo_transfer"
2019-11-07 16:34:28 +03:00
// There's no actual limit for subject in RFC 5322
mailMaxSubjectRunes = 256
2016-07-15 19:36:39 +03:00
)
2019-11-07 16:34:28 +03:00
var (
bodyTemplates * template . Template
subjectTemplates * texttmpl . Template
subjectRemoveSpaces = regexp . MustCompile ( ` [\s]+ ` )
)
2016-07-15 19:36:39 +03:00
2016-11-25 11:11:12 +03:00
// SendTestMail sends a test mail
2016-07-15 19:36:39 +03:00
func SendTestMail ( email string ) error {
2021-08-12 10:26:33 +03:00
if setting . MailService == nil {
// No mail service configured
return nil
}
2024-11-30 04:15:41 +03:00
return sender_service . Send ( sender , sender_service . NewMessage ( email , "Gitea Test Email!" , "Gitea Test Email!" ) )
2016-07-15 19:36:39 +03:00
}
2021-04-02 13:25:13 +03:00
// sendUserMail sends a mail to the user
2021-11-24 12:49:20 +03:00
func sendUserMail ( language string , u * user_model . User , tpl base . TplName , code , subject , info string ) {
2021-04-02 13:25:13 +03:00
locale := translation . NewLocale ( language )
2023-07-04 21:36:08 +03:00
data := map [ string ] any {
2023-10-31 17:11:48 +03:00
"locale" : locale ,
2019-04-04 10:52:48 +03:00
"DisplayName" : u . DisplayName ( ) ,
2022-06-26 17:19:22 +03:00
"ActiveCodeLives" : timeutil . MinutesToFriendly ( setting . Service . ActiveCodeLives , locale ) ,
"ResetPwdCodeLives" : timeutil . MinutesToFriendly ( setting . Service . ResetPwdCodeLives , locale ) ,
2016-07-15 19:36:39 +03:00
"Code" : code ,
2021-04-02 13:25:13 +03:00
"Language" : locale . Language ( ) ,
2016-07-15 19:36:39 +03:00
}
2016-12-06 20:58:31 +03:00
var content bytes . Buffer
2019-11-07 16:34:28 +03:00
if err := bodyTemplates . ExecuteTemplate ( & content , string ( tpl ) , data ) ; err != nil {
2019-04-02 10:48:31 +03:00
log . Error ( "Template: %v" , err )
2016-07-15 19:36:39 +03:00
return
}
2024-11-30 04:15:41 +03:00
msg := sender_service . NewMessage ( u . EmailTo ( ) , subject , content . String ( ) )
2016-07-23 20:08:22 +03:00
msg . Info = fmt . Sprintf ( "UID: %d, %s" , u . ID , info )
2016-07-15 19:36:39 +03:00
2019-09-24 08:02:49 +03:00
SendAsync ( msg )
2016-07-15 19:36:39 +03:00
}
2017-05-14 05:38:30 +03:00
// SendActivateAccountMail sends an activation mail to the user (new user registration)
2021-11-24 12:49:20 +03:00
func SendActivateAccountMail ( locale translation . Locale , u * user_model . User ) {
2021-08-12 10:26:33 +03:00
if setting . MailService == nil {
// No mail service configured
return
}
2024-02-15 00:48:45 +03:00
sendUserMail ( locale . Language ( ) , u , mailAuthActivate , u . GenerateEmailActivateCode ( u . Email ) , locale . TrString ( "mail.activate_account" ) , "activate account" )
2016-07-15 19:36:39 +03:00
}
2016-11-25 11:11:12 +03:00
// SendResetPasswordMail sends a password reset mail to the user
2021-11-24 12:49:20 +03:00
func SendResetPasswordMail ( u * user_model . User ) {
2021-08-12 10:26:33 +03:00
if setting . MailService == nil {
// No mail service configured
return
}
2021-04-02 13:25:13 +03:00
locale := translation . NewLocale ( u . Language )
2024-02-15 00:48:45 +03:00
sendUserMail ( u . Language , u , mailAuthResetPassword , u . GenerateEmailActivateCode ( u . Email ) , locale . TrString ( "mail.reset_password" ) , "recover account" )
2016-07-15 19:36:39 +03:00
}
2017-05-14 05:38:30 +03:00
// SendActivateEmailMail sends confirmation email to confirm new email address
2024-02-04 16:29:09 +03:00
func SendActivateEmailMail ( u * user_model . User , email string ) {
2021-08-12 10:26:33 +03:00
if setting . MailService == nil {
// No mail service configured
return
}
2021-04-02 13:25:13 +03:00
locale := translation . NewLocale ( u . Language )
2023-07-04 21:36:08 +03:00
data := map [ string ] any {
2023-10-31 17:11:48 +03:00
"locale" : locale ,
2019-04-04 10:52:48 +03:00
"DisplayName" : u . DisplayName ( ) ,
2022-06-26 17:19:22 +03:00
"ActiveCodeLives" : timeutil . MinutesToFriendly ( setting . Service . ActiveCodeLives , locale ) ,
2024-02-04 16:29:09 +03:00
"Code" : u . GenerateEmailActivateCode ( email ) ,
"Email" : email ,
2021-04-02 13:25:13 +03:00
"Language" : locale . Language ( ) ,
2016-07-15 19:36:39 +03:00
}
2016-12-06 20:58:31 +03:00
var content bytes . Buffer
2019-11-07 16:34:28 +03:00
if err := bodyTemplates . ExecuteTemplate ( & content , string ( mailAuthActivateEmail ) , data ) ; err != nil {
2019-04-02 10:48:31 +03:00
log . Error ( "Template: %v" , err )
2016-07-15 19:36:39 +03:00
return
}
2024-11-30 04:15:41 +03:00
msg := sender_service . NewMessage ( email , locale . TrString ( "mail.activate_email" ) , content . String ( ) )
2016-07-23 20:08:22 +03:00
msg . Info = fmt . Sprintf ( "UID: %d, activate email" , u . ID )
2016-07-15 19:36:39 +03:00
2019-09-24 08:02:49 +03:00
SendAsync ( msg )
2016-07-15 19:36:39 +03:00
}
// SendRegisterNotifyMail triggers a notify e-mail by admin created a account.
2021-11-24 12:49:20 +03:00
func SendRegisterNotifyMail ( u * user_model . User ) {
2022-03-19 15:45:44 +03:00
if setting . MailService == nil || ! u . IsActive {
// No mail service configured OR user is inactive
2021-08-12 10:26:33 +03:00
return
}
2021-04-02 13:25:13 +03:00
locale := translation . NewLocale ( u . Language )
2019-09-24 08:02:49 +03:00
2023-07-04 21:36:08 +03:00
data := map [ string ] any {
2023-10-31 17:11:48 +03:00
"locale" : locale ,
2019-04-04 10:52:48 +03:00
"DisplayName" : u . DisplayName ( ) ,
"Username" : u . Name ,
2021-04-02 13:25:13 +03:00
"Language" : locale . Language ( ) ,
2016-07-15 19:36:39 +03:00
}
2016-12-06 20:58:31 +03:00
var content bytes . Buffer
2019-11-07 16:34:28 +03:00
if err := bodyTemplates . ExecuteTemplate ( & content , string ( mailAuthRegisterNotify ) , data ) ; err != nil {
2019-04-02 10:48:31 +03:00
log . Error ( "Template: %v" , err )
2016-07-15 19:36:39 +03:00
return
}
2024-11-30 04:15:41 +03:00
msg := sender_service . NewMessage ( u . EmailTo ( ) , locale . TrString ( "mail.register_notify" , setting . AppName ) , content . String ( ) )
2016-07-23 20:08:22 +03:00
msg . Info = fmt . Sprintf ( "UID: %d, registration notify" , u . ID )
2016-07-15 19:36:39 +03:00
2019-09-24 08:02:49 +03:00
SendAsync ( msg )
2016-07-15 19:36:39 +03:00
}
// SendCollaboratorMail sends mail notification to new collaborator.
2021-12-10 04:27:50 +03:00
func SendCollaboratorMail ( u , doer * user_model . User , repo * repo_model . Repository ) {
2022-03-19 15:45:44 +03:00
if setting . MailService == nil || ! u . IsActive {
// No mail service configured OR the user is inactive
2021-08-12 10:26:33 +03:00
return
}
2021-04-02 13:25:13 +03:00
locale := translation . NewLocale ( u . Language )
2020-01-12 12:36:21 +03:00
repoName := repo . FullName ( )
2016-07-15 19:36:39 +03:00
2024-02-15 00:48:45 +03:00
subject := locale . TrString ( "mail.repo.collaborator.added.subject" , doer . DisplayName ( ) , repoName )
2023-07-04 21:36:08 +03:00
data := map [ string ] any {
2023-10-31 17:11:48 +03:00
"locale" : locale ,
2016-07-15 19:36:39 +03:00
"Subject" : subject ,
"RepoName" : repoName ,
2016-08-16 20:19:09 +03:00
"Link" : repo . HTMLURL ( ) ,
2021-04-02 13:25:13 +03:00
"Language" : locale . Language ( ) ,
2016-07-15 19:36:39 +03:00
}
2016-12-06 20:58:31 +03:00
var content bytes . Buffer
2019-11-07 16:34:28 +03:00
if err := bodyTemplates . ExecuteTemplate ( & content , string ( mailNotifyCollaborator ) , data ) ; err != nil {
2019-04-02 10:48:31 +03:00
log . Error ( "Template: %v" , err )
2016-07-15 19:36:39 +03:00
return
}
2024-11-30 04:15:41 +03:00
msg := sender_service . NewMessage ( u . EmailTo ( ) , subject , content . String ( ) )
2016-07-23 20:08:22 +03:00
msg . Info = fmt . Sprintf ( "UID: %d, add collaborator" , u . ID )
2016-07-15 19:36:39 +03:00
2019-09-24 08:02:49 +03:00
SendAsync ( msg )
2016-07-15 19:36:39 +03:00
}
2024-11-30 04:15:41 +03:00
func composeIssueCommentMessages ( ctx * mailCommentContext , lang string , recipients [ ] * user_model . User , fromMention bool , info string ) ( [ ] * sender_service . Message , error ) {
2019-11-07 16:34:28 +03:00
var (
subject string
link string
prefix string
// Fall back subject for bad templates, make sure subject is never empty
2019-11-15 15:59:21 +03:00
fallback string
2022-06-13 12:37:59 +03:00
reviewComments [ ] * issues_model . Comment
2019-11-07 16:34:28 +03:00
)
2016-07-15 19:36:39 +03:00
2022-06-13 12:37:59 +03:00
commentType := issues_model . CommentTypeComment
2019-11-18 11:08:20 +03:00
if ctx . Comment != nil {
commentType = ctx . Comment . Type
link = ctx . Issue . HTMLURL ( ) + "#" + ctx . Comment . HashTag ( )
2019-07-17 22:02:42 +03:00
} else {
2019-11-18 11:08:20 +03:00
link = ctx . Issue . HTMLURL ( )
2019-06-12 22:41:28 +03:00
}
2019-11-07 16:34:28 +03:00
2022-06-13 12:37:59 +03:00
reviewType := issues_model . ReviewTypeComment
2019-11-18 11:08:20 +03:00
if ctx . Comment != nil && ctx . Comment . Review != nil {
reviewType = ctx . Comment . Review . Type
2019-11-15 15:59:21 +03:00
}
2019-11-07 16:34:28 +03:00
// This is the body of the new issue or comment, not the mail body
2024-11-24 11:18:57 +03:00
rctx := renderhelper . NewRenderContextRepoComment ( ctx . Context , ctx . Issue . Repo ) . WithUseAbsoluteLink ( true )
body , err := markdown . RenderString ( rctx ,
2024-11-22 08:48:09 +03:00
ctx . Content )
2021-04-20 01:25:08 +03:00
if err != nil {
return nil , err
}
2019-11-18 11:08:20 +03:00
actType , actName , tplName := actionToTemplate ( ctx . Issue , ctx . ActionType , commentType , reviewType )
2019-11-15 15:59:21 +03:00
2020-01-03 20:13:22 +03:00
if actName != "new" {
prefix = "Re: "
}
fallback = prefix + fallbackMailSubject ( ctx . Issue )
2019-11-18 11:08:20 +03:00
if ctx . Comment != nil && ctx . Comment . Review != nil {
2022-06-13 12:37:59 +03:00
reviewComments = make ( [ ] * issues_model . Comment , 0 , 10 )
2019-11-18 11:08:20 +03:00
for _ , lines := range ctx . Comment . Review . CodeComments {
2019-11-15 15:59:21 +03:00
for _ , comments := range lines {
reviewComments = append ( reviewComments , comments ... )
}
}
}
2021-04-02 13:25:13 +03:00
locale := translation . NewLocale ( lang )
2019-11-07 16:34:28 +03:00
2023-07-04 21:36:08 +03:00
mailMeta := map [ string ] any {
2023-10-31 17:11:48 +03:00
"locale" : locale ,
2019-11-07 16:34:28 +03:00
"FallbackSubject" : fallback ,
"Body" : body ,
"Link" : link ,
2019-11-18 11:08:20 +03:00
"Issue" : ctx . Issue ,
"Comment" : ctx . Comment ,
"IsPull" : ctx . Issue . IsPull ,
2022-11-19 11:12:33 +03:00
"User" : ctx . Issue . Repo . MustOwner ( ctx ) ,
2019-11-18 11:08:20 +03:00
"Repo" : ctx . Issue . Repo . FullName ( ) ,
"Doer" : ctx . Doer ,
2019-11-07 16:34:28 +03:00
"IsMention" : fromMention ,
"SubjectPrefix" : prefix ,
"ActionType" : actType ,
"ActionName" : actName ,
2019-11-15 15:59:21 +03:00
"ReviewComments" : reviewComments ,
2021-04-02 13:25:13 +03:00
"Language" : locale . Language ( ) ,
2023-01-16 23:58:01 +03:00
"CanReply" : setting . IncomingEmail . Enabled && commentType != issues_model . CommentTypePullRequestPush ,
2019-11-07 16:34:28 +03:00
}
var mailSubject bytes . Buffer
2022-06-20 13:02:49 +03:00
if err := subjectTemplates . ExecuteTemplate ( & mailSubject , tplName , mailMeta ) ; err == nil {
2019-11-07 16:34:28 +03:00
subject = sanitizeSubject ( mailSubject . String ( ) )
2021-04-20 01:25:08 +03:00
if subject == "" {
subject = fallback
}
2017-05-25 05:38:56 +03:00
} else {
2021-04-02 13:25:13 +03:00
log . Error ( "ExecuteTemplate [%s]: %v" , tplName + "/subject" , err )
2019-11-07 16:34:28 +03:00
}
2020-04-28 21:05:39 +03:00
subject = emoji . ReplaceAliases ( subject )
2019-11-07 16:34:28 +03:00
mailMeta [ "Subject" ] = subject
2016-12-06 20:58:31 +03:00
2017-11-03 12:23:17 +03:00
var mailBody bytes . Buffer
2016-12-06 20:58:31 +03:00
2022-06-20 13:02:49 +03:00
if err := bodyTemplates . ExecuteTemplate ( & mailBody , tplName , mailMeta ) ; err != nil {
log . Error ( "ExecuteTemplate [%s]: %v" , tplName + "/body" , err )
2016-07-15 19:36:39 +03:00
}
2016-12-06 20:58:31 +03:00
2019-11-18 11:08:20 +03:00
// Make sure to compose independent messages to avoid leaking user emails
2024-05-02 18:24:21 +03:00
msgID := generateMessageIDForIssue ( ctx . Issue , ctx . Comment , ctx . ActionType )
reference := generateMessageIDForIssue ( ctx . Issue , nil , activities_model . ActionType ( 0 ) )
2022-02-04 02:01:16 +03:00
2023-01-14 18:57:10 +03:00
var replyPayload [ ] byte
2023-11-13 06:20:34 +03:00
if ctx . Comment != nil {
if ctx . Comment . Type . HasMailReplySupport ( ) {
replyPayload , err = incoming_payload . CreateReferencePayload ( ctx . Comment )
}
2023-01-14 18:57:10 +03:00
} else {
replyPayload , err = incoming_payload . CreateReferencePayload ( ctx . Issue )
}
if err != nil {
return nil , err
}
unsubscribePayload , err := incoming_payload . CreateReferencePayload ( ctx . Issue )
if err != nil {
return nil , err
}
2024-11-30 04:15:41 +03:00
msgs := make ( [ ] * sender_service . Message , 0 , len ( recipients ) )
2021-05-22 09:47:16 +03:00
for _ , recipient := range recipients {
2024-11-30 04:15:41 +03:00
msg := sender_service . NewMessageFrom (
2024-02-03 03:41:27 +03:00
recipient . Email ,
2024-07-15 00:27:00 +03:00
fromDisplayName ( ctx . Doer ) ,
2024-02-03 03:41:27 +03:00
setting . MailService . FromEmail ,
subject ,
mailBody . String ( ) ,
)
2019-11-18 11:08:20 +03:00
msg . Info = fmt . Sprintf ( "Subject: %s, %s" , subject , info )
2023-01-14 18:57:10 +03:00
msg . SetHeader ( "Message-ID" , msgID )
msg . SetHeader ( "In-Reply-To" , reference )
references := [ ] string { reference }
listUnsubscribe := [ ] string { "<" + ctx . Issue . HTMLURL ( ) + ">" }
if setting . IncomingEmail . Enabled {
2023-11-13 06:20:34 +03:00
if replyPayload != nil {
2023-01-14 18:57:10 +03:00
token , err := token . CreateToken ( token . ReplyHandlerType , recipient , replyPayload )
if err != nil {
log . Error ( "CreateToken failed: %v" , err )
} else {
replyAddress := strings . Replace ( setting . IncomingEmail . ReplyToAddress , setting . IncomingEmail . TokenPlaceholder , token , 1 )
msg . ReplyTo = replyAddress
msg . SetHeader ( "List-Post" , fmt . Sprintf ( "<mailto:%s>" , replyAddress ) )
references = append ( references , fmt . Sprintf ( "<reply-%s@%s>" , token , setting . Domain ) )
}
}
token , err := token . CreateToken ( token . UnsubscribeHandlerType , recipient , unsubscribePayload )
if err != nil {
log . Error ( "CreateToken failed: %v" , err )
} else {
unsubAddress := strings . Replace ( setting . IncomingEmail . ReplyToAddress , setting . IncomingEmail . TokenPlaceholder , token , 1 )
listUnsubscribe = append ( listUnsubscribe , "<mailto:" + unsubAddress + ">" )
}
}
msg . SetHeader ( "References" , references ... )
msg . SetHeader ( "List-Unsubscribe" , listUnsubscribe ... )
2021-05-22 09:47:16 +03:00
for key , value := range generateAdditionalHeaders ( ctx , actType , recipient ) {
msg . SetHeader ( key , value )
}
2019-11-18 11:08:20 +03:00
msgs = append ( msgs , msg )
2019-07-17 22:02:42 +03:00
}
2021-04-20 01:25:08 +03:00
return msgs , nil
2016-07-15 19:36:39 +03:00
}
2024-05-02 18:24:21 +03:00
func generateMessageIDForIssue ( issue * issues_model . Issue , comment * issues_model . Comment , actionType activities_model . ActionType ) string {
2021-10-01 18:24:43 +03:00
var path string
if issue . IsPull {
path = "pulls"
} else {
path = "issues"
}
var extra string
if comment != nil {
extra = fmt . Sprintf ( "/comment/%d" , comment . ID )
2022-02-04 02:01:16 +03:00
} else {
switch actionType {
2022-08-25 05:31:57 +03:00
case activities_model . ActionCloseIssue , activities_model . ActionClosePullRequest :
2022-02-04 02:01:16 +03:00
extra = fmt . Sprintf ( "/close/%d" , time . Now ( ) . UnixNano ( ) / 1e6 )
2022-08-25 05:31:57 +03:00
case activities_model . ActionReopenIssue , activities_model . ActionReopenPullRequest :
2022-02-04 02:01:16 +03:00
extra = fmt . Sprintf ( "/reopen/%d" , time . Now ( ) . UnixNano ( ) / 1e6 )
2022-11-03 18:49:00 +03:00
case activities_model . ActionMergePullRequest , activities_model . ActionAutoMergePullRequest :
2022-02-04 02:01:16 +03:00
extra = fmt . Sprintf ( "/merge/%d" , time . Now ( ) . UnixNano ( ) / 1e6 )
2022-08-25 05:31:57 +03:00
case activities_model . ActionPullRequestReadyForReview :
2022-02-04 02:01:16 +03:00
extra = fmt . Sprintf ( "/ready/%d" , time . Now ( ) . UnixNano ( ) / 1e6 )
}
2021-10-01 18:24:43 +03:00
}
2023-01-14 18:57:10 +03:00
return fmt . Sprintf ( "<%s/%s/%d%s@%s>" , issue . Repo . FullName ( ) , path , issue . Index , extra , setting . Domain )
2021-10-01 18:24:43 +03:00
}
2024-05-02 18:24:21 +03:00
func generateMessageIDForRelease ( release * repo_model . Release ) string {
return fmt . Sprintf ( "<%s/releases/%d@%s>" , release . Repo . FullName ( ) , release . ID , setting . Domain )
}
2021-11-24 12:49:20 +03:00
func generateAdditionalHeaders ( ctx * mailCommentContext , reason string , recipient * user_model . User ) map [ string ] string {
2021-05-22 09:47:16 +03:00
repo := ctx . Issue . Repo
return map [ string ] string {
// https://datatracker.ietf.org/doc/html/rfc2919
"List-ID" : fmt . Sprintf ( "%s <%s.%s.%s>" , repo . FullName ( ) , repo . Name , repo . OwnerName , setting . Domain ) ,
// https://datatracker.ietf.org/doc/html/rfc2369
"List-Archive" : fmt . Sprintf ( "<%s>" , repo . HTMLURL ( ) ) ,
2022-04-30 16:40:55 +03:00
"X-Mailer" : "Gitea" ,
2021-05-22 09:47:16 +03:00
"X-Gitea-Reason" : reason ,
2024-02-03 03:41:27 +03:00
"X-Gitea-Sender" : ctx . Doer . Name ,
"X-Gitea-Recipient" : recipient . Name ,
2021-05-22 09:47:16 +03:00
"X-Gitea-Recipient-Address" : recipient . Email ,
"X-Gitea-Repository" : repo . Name ,
"X-Gitea-Repository-Path" : repo . FullName ( ) ,
"X-Gitea-Repository-Link" : repo . HTMLURL ( ) ,
"X-Gitea-Issue-ID" : strconv . FormatInt ( ctx . Issue . Index , 10 ) ,
"X-Gitea-Issue-Link" : ctx . Issue . HTMLURL ( ) ,
"X-GitHub-Reason" : reason ,
2024-02-03 03:41:27 +03:00
"X-GitHub-Sender" : ctx . Doer . Name ,
"X-GitHub-Recipient" : recipient . Name ,
2021-05-22 09:47:16 +03:00
"X-GitHub-Recipient-Address" : recipient . Email ,
"X-GitLab-NotificationReason" : reason ,
"X-GitLab-Project" : repo . Name ,
"X-GitLab-Project-Path" : repo . FullName ( ) ,
"X-GitLab-Issue-IID" : strconv . FormatInt ( ctx . Issue . Index , 10 ) ,
}
}
2019-11-07 16:34:28 +03:00
func sanitizeSubject ( subject string ) string {
runes := [ ] rune ( strings . TrimSpace ( subjectRemoveSpaces . ReplaceAllLiteralString ( subject , " " ) ) )
if len ( runes ) > mailMaxSubjectRunes {
runes = runes [ : mailMaxSubjectRunes ]
}
// Encode non-ASCII characters
return mime . QEncoding . Encode ( "utf-8" , string ( runes ) )
}
2019-11-18 11:08:20 +03:00
// SendIssueAssignedMail composes and sends issue assigned email
2022-11-19 11:12:33 +03:00
func SendIssueAssignedMail ( ctx context . Context , issue * issues_model . Issue , doer * user_model . User , content string , comment * issues_model . Comment , recipients [ ] * user_model . User ) error {
2021-08-12 10:26:33 +03:00
if setting . MailService == nil {
// No mail service configured
return nil
}
2021-09-30 02:20:18 +03:00
2022-11-19 11:12:33 +03:00
if err := issue . LoadRepo ( ctx ) ; err != nil {
2021-09-30 02:20:18 +03:00
log . Error ( "Unable to load repo [%d] for issue #%d [%d]. Error: %v" , issue . RepoID , issue . Index , issue . ID , err )
return err
}
2021-11-24 12:49:20 +03:00
langMap := make ( map [ string ] [ ] * user_model . User )
2021-04-02 13:25:13 +03:00
for _ , user := range recipients {
2022-03-19 15:45:44 +03:00
if ! user . IsActive {
// don't send emails to inactive users
continue
}
2021-05-22 09:47:16 +03:00
langMap [ user . Language ] = append ( langMap [ user . Language ] , user )
2021-04-02 13:25:13 +03:00
}
for lang , tos := range langMap {
2021-04-20 01:25:08 +03:00
msgs , err := composeIssueCommentMessages ( & mailCommentContext {
2022-11-19 11:12:33 +03:00
Context : ctx ,
2021-04-02 13:25:13 +03:00
Issue : issue ,
Doer : doer ,
2022-08-25 05:31:57 +03:00
ActionType : activities_model . ActionType ( 0 ) ,
2021-04-02 13:25:13 +03:00
Content : content ,
Comment : comment ,
2021-04-20 01:25:08 +03:00
} , lang , tos , false , "issue assigned" )
if err != nil {
return err
}
2023-10-31 17:11:48 +03:00
SendAsync ( msgs ... )
2021-04-02 13:25:13 +03:00
}
2021-04-20 01:25:08 +03:00
return nil
2019-11-07 16:34:28 +03:00
}
// actionToTemplate returns the type and name of the action facing the user
2022-08-25 05:31:57 +03:00
// (slightly different from activities_model.ActionType) and the name of the template to use (based on availability)
func actionToTemplate ( issue * issues_model . Issue , actionType activities_model . ActionType ,
2022-06-13 12:37:59 +03:00
commentType issues_model . CommentType , reviewType issues_model . ReviewType ,
2022-02-23 23:16:07 +03:00
) ( typeName , name , template string ) {
2019-11-07 16:34:28 +03:00
if issue . IsPull {
typeName = "pull"
} else {
typeName = "issue"
}
switch actionType {
2022-08-25 05:31:57 +03:00
case activities_model . ActionCreateIssue , activities_model . ActionCreatePullRequest :
2019-11-07 16:34:28 +03:00
name = "new"
2022-08-25 05:31:57 +03:00
case activities_model . ActionCommentIssue , activities_model . ActionCommentPull :
2019-11-07 16:34:28 +03:00
name = "comment"
2022-08-25 05:31:57 +03:00
case activities_model . ActionCloseIssue , activities_model . ActionClosePullRequest :
2019-11-07 16:34:28 +03:00
name = "close"
2022-08-25 05:31:57 +03:00
case activities_model . ActionReopenIssue , activities_model . ActionReopenPullRequest :
2019-11-07 16:34:28 +03:00
name = "reopen"
2022-11-03 18:49:00 +03:00
case activities_model . ActionMergePullRequest , activities_model . ActionAutoMergePullRequest :
2019-11-07 16:34:28 +03:00
name = "merge"
2022-08-25 05:31:57 +03:00
case activities_model . ActionPullReviewDismissed :
2021-02-11 20:32:25 +03:00
name = "review_dismissed"
2022-08-25 05:31:57 +03:00
case activities_model . ActionPullRequestReadyForReview :
2021-06-23 07:14:22 +03:00
name = "ready_for_review"
2019-11-07 16:34:28 +03:00
default :
switch commentType {
2022-06-13 12:37:59 +03:00
case issues_model . CommentTypeReview :
2019-11-15 15:59:21 +03:00
switch reviewType {
2022-06-13 12:37:59 +03:00
case issues_model . ReviewTypeApprove :
2019-11-15 15:59:21 +03:00
name = "approve"
2022-06-13 12:37:59 +03:00
case issues_model . ReviewTypeReject :
2019-11-15 15:59:21 +03:00
name = "reject"
default :
name = "review"
}
2022-06-13 12:37:59 +03:00
case issues_model . CommentTypeCode :
2019-11-07 16:34:28 +03:00
name = "code"
2022-06-13 12:37:59 +03:00
case issues_model . CommentTypeAssignees :
2019-11-07 16:34:28 +03:00
name = "assigned"
2022-06-13 12:37:59 +03:00
case issues_model . CommentTypePullRequestPush :
2020-05-20 15:47:24 +03:00
name = "push"
2019-11-07 16:34:28 +03:00
default :
name = "default"
}
}
template = typeName + "/" + name
ok := bodyTemplates . Lookup ( template ) != nil
if ! ok && typeName != "issue" {
template = "issue/" + name
ok = bodyTemplates . Lookup ( template ) != nil
}
if ! ok {
template = typeName + "/default"
ok = bodyTemplates . Lookup ( template ) != nil
}
if ! ok {
template = "issue/default"
}
2022-06-20 13:02:49 +03:00
return typeName , name , template
2016-07-15 19:36:39 +03:00
}
2024-07-15 00:27:00 +03:00
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 ( )
}