@ -0,0 +1,81 @@ | |||||
// Copyright 2016 The Gogs Authors. All rights reserved. | |||||
// Use of this source code is governed by a MIT-style | |||||
// license that can be found in the LICENSE file. | |||||
package models | |||||
import ( | |||||
"fmt" | |||||
"github.com/Unknwon/com" | |||||
"github.com/gogits/gogs/modules/log" | |||||
"github.com/gogits/gogs/modules/markdown" | |||||
"github.com/gogits/gogs/modules/setting" | |||||
) | |||||
func (issue *Issue) MailSubject() string { | |||||
return fmt.Sprintf("[%s] %s (#%d)", issue.Repo.Name, issue.Name, issue.Index) | |||||
} | |||||
// mailIssueCommentToParticipants can be used for both new issue creation and comment. | |||||
func mailIssueCommentToParticipants(issue *Issue, doer *User, mentions []string) error { | |||||
if !setting.Service.EnableNotifyMail { | |||||
return nil | |||||
} | |||||
// Mail wahtcers. | |||||
watchers, err := GetWatchers(issue.RepoID) | |||||
if err != nil { | |||||
return fmt.Errorf("GetWatchers [%d]: %v", issue.RepoID, err) | |||||
} | |||||
tos := make([]string, 0, len(watchers)) // List of email addresses. | |||||
names := make([]string, 0, len(watchers)) | |||||
for i := range watchers { | |||||
if watchers[i].UserID == doer.Id { | |||||
continue | |||||
} | |||||
to, err := GetUserByID(watchers[i].UserID) | |||||
if err != nil { | |||||
return fmt.Errorf("GetUserByID [%d]: %v", watchers[i].UserID, err) | |||||
} | |||||
if to.IsOrganization() { | |||||
continue | |||||
} | |||||
tos = append(tos, to.Email) | |||||
names = append(names, to.Name) | |||||
} | |||||
SendIssueCommentMail(issue, doer, tos) | |||||
// Mail mentioned people and exclude watchers. | |||||
names = append(names, doer.Name) | |||||
tos = make([]string, 0, len(mentions)) // list of user names. | |||||
for i := range mentions { | |||||
if com.IsSliceContainsStr(names, mentions[i]) { | |||||
continue | |||||
} | |||||
tos = append(tos, mentions[i]) | |||||
} | |||||
SendIssueMentionMail(issue, doer, GetUserEmailsByNames(tos)) | |||||
return nil | |||||
} | |||||
// MailParticipants sends new issue thread created emails to repository watchers | |||||
// and mentioned people. | |||||
func (issue *Issue) MailParticipants() (err error) { | |||||
mentions := markdown.FindAllMentions(issue.Content) | |||||
if err = UpdateIssueMentions(issue.ID, mentions); err != nil { | |||||
return fmt.Errorf("UpdateIssueMentions [%d]: %v", issue.ID, err) | |||||
} | |||||
if err = mailIssueCommentToParticipants(issue, issue.Poster, mentions); err != nil { | |||||
log.Error(4, "mailIssueCommentToParticipants: %v", err) | |||||
} | |||||
return nil | |||||
} |
@ -0,0 +1,183 @@ | |||||
// Copyright 2016 The Gogs Authors. All rights reserved. | |||||
// Use of this source code is governed by a MIT-style | |||||
// license that can be found in the LICENSE file. | |||||
package models | |||||
import ( | |||||
"fmt" | |||||
"html/template" | |||||
"path" | |||||
"gopkg.in/gomail.v2" | |||||
"gopkg.in/macaron.v1" | |||||
"github.com/gogits/gogs/modules/base" | |||||
"github.com/gogits/gogs/modules/log" | |||||
"github.com/gogits/gogs/modules/mailer" | |||||
"github.com/gogits/gogs/modules/markdown" | |||||
"github.com/gogits/gogs/modules/setting" | |||||
) | |||||
const ( | |||||
MAIL_AUTH_ACTIVATE base.TplName = "auth/activate" | |||||
MAIL_AUTH_ACTIVATE_EMAIL base.TplName = "auth/activate_email" | |||||
MAIL_AUTH_RESET_PASSWORD base.TplName = "auth/reset_passwd" | |||||
MAIL_AUTH_REGISTER_NOTIFY base.TplName = "auth/register_notify" | |||||
MAIL_ISSUE_COMMENT base.TplName = "issue/comment" | |||||
MAIL_ISSUE_MENTION base.TplName = "issue/mention" | |||||
MAIL_NOTIFY_COLLABORATOR base.TplName = "notify/collaborator" | |||||
) | |||||
type MailRender interface { | |||||
HTMLString(string, interface{}, ...macaron.HTMLOptions) (string, error) | |||||
} | |||||
var mailRender MailRender | |||||
func InitMailRender(dir, appendDir string, funcMap []template.FuncMap) { | |||||
opt := &macaron.RenderOptions{ | |||||
Directory: dir, | |||||
AppendDirectories: []string{appendDir}, | |||||
Funcs: funcMap, | |||||
Extensions: []string{".tmpl", ".html"}, | |||||
} | |||||
ts := macaron.NewTemplateSet() | |||||
ts.Set(macaron.DEFAULT_TPL_SET_NAME, opt) | |||||
mailRender = &macaron.TplRender{ | |||||
TemplateSet: ts, | |||||
Opt: opt, | |||||
} | |||||
} | |||||
func SendTestMail(email string) error { | |||||
return gomail.Send(&mailer.Sender{}, mailer.NewMessage([]string{email}, "Gogs Test Email!", "Gogs Test Email!").Message) | |||||
} | |||||
func SendUserMail(c *macaron.Context, u *User, tpl base.TplName, code, subject, info string) { | |||||
data := map[string]interface{}{ | |||||
"Username": u.DisplayName(), | |||||
"ActiveCodeLives": setting.Service.ActiveCodeLives / 60, | |||||
"ResetPwdCodeLives": setting.Service.ResetPwdCodeLives / 60, | |||||
"Code": code, | |||||
} | |||||
body, err := mailRender.HTMLString(string(tpl), data) | |||||
if err != nil { | |||||
log.Error(3, "HTMLString: %v", err) | |||||
return | |||||
} | |||||
msg := mailer.NewMessage([]string{u.Email}, subject, body) | |||||
msg.Info = fmt.Sprintf("UID: %d, %s", u.Id, info) | |||||
mailer.SendAsync(msg) | |||||
} | |||||
func SendActivateAccountMail(c *macaron.Context, u *User) { | |||||
SendUserMail(c, u, MAIL_AUTH_ACTIVATE, u.GenerateActivateCode(), c.Tr("mail.activate_account"), "activate account") | |||||
} | |||||
func SendResetPasswordMail(c *macaron.Context, u *User) { | |||||
SendUserMail(c, u, MAIL_AUTH_RESET_PASSWORD, u.GenerateActivateCode(), c.Tr("mail.reset_password"), "reset password") | |||||
} | |||||
// SendActivateAccountMail sends confirmation email. | |||||
func SendActivateEmailMail(c *macaron.Context, u *User, email *EmailAddress) { | |||||
data := map[string]interface{}{ | |||||
"Username": u.DisplayName(), | |||||
"ActiveCodeLives": setting.Service.ActiveCodeLives / 60, | |||||
"Code": u.GenerateEmailActivateCode(email.Email), | |||||
"Email": email.Email, | |||||
} | |||||
body, err := mailRender.HTMLString(string(MAIL_AUTH_ACTIVATE_EMAIL), data) | |||||
if err != nil { | |||||
log.Error(3, "HTMLString: %v", err) | |||||
return | |||||
} | |||||
msg := mailer.NewMessage([]string{email.Email}, c.Tr("mail.activate_email"), body) | |||||
msg.Info = fmt.Sprintf("UID: %d, activate email", u.Id) | |||||
mailer.SendAsync(msg) | |||||
} | |||||
// SendRegisterNotifyMail triggers a notify e-mail by admin created a account. | |||||
func SendRegisterNotifyMail(c *macaron.Context, u *User) { | |||||
data := map[string]interface{}{ | |||||
"Username": u.DisplayName(), | |||||
} | |||||
body, err := mailRender.HTMLString(string(MAIL_AUTH_REGISTER_NOTIFY), data) | |||||
if err != nil { | |||||
log.Error(3, "HTMLString: %v", err) | |||||
return | |||||
} | |||||
msg := mailer.NewMessage([]string{u.Email}, c.Tr("mail.register_notify"), body) | |||||
msg.Info = fmt.Sprintf("UID: %d, registration notify", u.Id) | |||||
mailer.SendAsync(msg) | |||||
} | |||||
// SendCollaboratorMail sends mail notification to new collaborator. | |||||
func SendCollaboratorMail(u, doer *User, repo *Repository) { | |||||
repoName := path.Join(repo.Owner.Name, repo.Name) | |||||
subject := fmt.Sprintf("%s added you to %s", doer.DisplayName(), repoName) | |||||
data := map[string]interface{}{ | |||||
"Subject": subject, | |||||
"RepoName": repoName, | |||||
"Link": repo.FullLink(), | |||||
} | |||||
body, err := mailRender.HTMLString(string(MAIL_NOTIFY_COLLABORATOR), data) | |||||
if err != nil { | |||||
log.Error(3, "HTMLString: %v", err) | |||||
return | |||||
} | |||||
msg := mailer.NewMessage([]string{u.Email}, subject, body) | |||||
msg.Info = fmt.Sprintf("UID: %d, add collaborator", u.Id) | |||||
mailer.SendAsync(msg) | |||||
} | |||||
func composeTplData(subject, body, link string) map[string]interface{} { | |||||
data := make(map[string]interface{}, 10) | |||||
data["Subject"] = subject | |||||
data["Body"] = body | |||||
data["Link"] = link | |||||
return data | |||||
} | |||||
func composeIssueMessage(issue *Issue, doer *User, tplName base.TplName, tos []string, info string) *mailer.Message { | |||||
subject := issue.MailSubject() | |||||
body := string(markdown.RenderSpecialLink([]byte(issue.Content), issue.Repo.FullLink(), issue.Repo.ComposeMetas())) | |||||
data := composeTplData(subject, body, issue.FullLink()) | |||||
data["Doer"] = doer | |||||
content, err := mailRender.HTMLString(string(tplName), data) | |||||
if err != nil { | |||||
log.Error(3, "HTMLString (%s): %v", tplName, err) | |||||
} | |||||
msg := mailer.NewMessage(tos, subject, content) | |||||
msg.Info = fmt.Sprintf("Subject: %s, %s", subject, info) | |||||
return msg | |||||
} | |||||
// SendIssueCommentMail composes and sends issue comment emails to target receivers. | |||||
func SendIssueCommentMail(issue *Issue, doer *User, tos []string) { | |||||
if len(tos) == 0 { | |||||
return | |||||
} | |||||
mailer.SendAsync(composeIssueMessage(issue, doer, MAIL_ISSUE_COMMENT, tos, "issue comment")) | |||||
} | |||||
// SendIssueMentionMail composes and sends issue mention emails to target receivers. | |||||
func SendIssueMentionMail(issue *Issue, doer *User, tos []string) { | |||||
if len(tos) == 0 { | |||||
return | |||||
} | |||||
mailer.SendAsync(composeIssueMessage(issue, doer, MAIL_ISSUE_MENTION, tos, "issue mention")) | |||||
} |
@ -1,190 +0,0 @@ | |||||
// Copyright 2014 The Gogs Authors. All rights reserved. | |||||
// Use of this source code is governed by a MIT-style | |||||
// license that can be found in the LICENSE file. | |||||
package mailer | |||||
import ( | |||||
"fmt" | |||||
"path" | |||||
"strings" | |||||
"gopkg.in/gomail.v2" | |||||
"gopkg.in/macaron.v1" | |||||
"github.com/gogits/gogs/models" | |||||
"github.com/gogits/gogs/modules/base" | |||||
"github.com/gogits/gogs/modules/log" | |||||
"github.com/gogits/gogs/modules/markdown" | |||||
"github.com/gogits/gogs/modules/setting" | |||||
) | |||||
const ( | |||||
AUTH_ACTIVATE base.TplName = "mail/auth/activate" | |||||
AUTH_ACTIVATE_EMAIL base.TplName = "mail/auth/activate_email" | |||||
AUTH_REGISTER_NOTIFY base.TplName = "mail/auth/register_notify" | |||||
AUTH_RESET_PASSWORD base.TplName = "mail/auth/reset_passwd" | |||||
NOTIFY_COLLABORATOR base.TplName = "mail/notify/collaborator" | |||||
NOTIFY_MENTION base.TplName = "mail/notify/mention" | |||||
) | |||||
func ComposeTplData(u *models.User) map[interface{}]interface{} { | |||||
data := make(map[interface{}]interface{}, 10) | |||||
data["AppName"] = setting.AppName | |||||
data["AppVer"] = setting.AppVer | |||||
data["AppUrl"] = setting.AppUrl | |||||
data["ActiveCodeLives"] = setting.Service.ActiveCodeLives / 60 | |||||
data["ResetPwdCodeLives"] = setting.Service.ResetPwdCodeLives / 60 | |||||
if u != nil { | |||||
data["User"] = u | |||||
} | |||||
return data | |||||
} | |||||
func SendUserMail(c *macaron.Context, u *models.User, tpl base.TplName, code, subject, info string) { | |||||
data := ComposeTplData(u) | |||||
data["Code"] = code | |||||
body, err := c.HTMLString(string(tpl), data) | |||||
if err != nil { | |||||
log.Error(4, "HTMLString: %v", err) | |||||
return | |||||
} | |||||
msg := NewMessage([]string{u.Email}, subject, body) | |||||
msg.Info = fmt.Sprintf("UID: %d, %s", u.Id, info) | |||||
SendAsync(msg) | |||||
} | |||||
func SendActivateAccountMail(c *macaron.Context, u *models.User) { | |||||
SendUserMail(c, u, AUTH_ACTIVATE, u.GenerateActivateCode(), c.Tr("mail.activate_account"), "activate account") | |||||
} | |||||
// SendResetPasswordMail sends reset password e-mail. | |||||
func SendResetPasswordMail(c *macaron.Context, u *models.User) { | |||||
SendUserMail(c, u, AUTH_RESET_PASSWORD, u.GenerateActivateCode(), c.Tr("mail.reset_password"), "reset password") | |||||
} | |||||
// SendRegisterNotifyMail triggers a notify e-mail by admin created a account. | |||||
func SendRegisterNotifyMail(c *macaron.Context, u *models.User) { | |||||
body, err := c.HTMLString(string(AUTH_REGISTER_NOTIFY), ComposeTplData(u)) | |||||
if err != nil { | |||||
log.Error(4, "HTMLString: %v", err) | |||||
return | |||||
} | |||||
msg := NewMessage([]string{u.Email}, c.Tr("mail.register_notify"), body) | |||||
msg.Info = fmt.Sprintf("UID: %d, registration notify", u.Id) | |||||
SendAsync(msg) | |||||
} | |||||
// SendActivateAccountMail sends confirmation e-mail. | |||||
func SendActivateEmailMail(c *macaron.Context, u *models.User, email *models.EmailAddress) { | |||||
data := ComposeTplData(u) | |||||
data["Code"] = u.GenerateEmailActivateCode(email.Email) | |||||
data["Email"] = email.Email | |||||
body, err := c.HTMLString(string(AUTH_ACTIVATE_EMAIL), data) | |||||
if err != nil { | |||||
log.Error(4, "HTMLString: %v", err) | |||||
return | |||||
} | |||||
msg := NewMessage([]string{email.Email}, c.Tr("mail.activate_email"), body) | |||||
msg.Info = fmt.Sprintf("UID: %d, activate email", u.Id) | |||||
SendAsync(msg) | |||||
} | |||||
// SendIssueNotifyMail sends mail notification of all watchers of repository. | |||||
func SendIssueNotifyMail(u, owner *models.User, repo *models.Repository, issue *models.Issue) ([]string, error) { | |||||
ws, err := models.GetWatchers(repo.ID) | |||||
if err != nil { | |||||
return nil, fmt.Errorf("GetWatchers[%d]: %v", repo.ID, err) | |||||
} | |||||
tos := make([]string, 0, len(ws)) | |||||
for i := range ws { | |||||
uid := ws[i].UserID | |||||
if u.Id == uid { | |||||
continue | |||||
} | |||||
to, err := models.GetUserByID(uid) | |||||
if err != nil { | |||||
return nil, fmt.Errorf("GetUserByID: %v", err) | |||||
} | |||||
if to.IsOrganization() { | |||||
continue | |||||
} | |||||
tos = append(tos, to.Email) | |||||
} | |||||
if len(tos) == 0 { | |||||
return tos, nil | |||||
} | |||||
subject := fmt.Sprintf("[%s] %s (#%d)", repo.Name, issue.Name, issue.Index) | |||||
content := fmt.Sprintf("%s<br>-<br> <a href=\"%s%s/%s/issues/%d\">View it on Gogs</a>.", | |||||
markdown.RenderSpecialLink([]byte(strings.Replace(issue.Content, "\n", "<br>", -1)), owner.Name+"/"+repo.Name, repo.ComposeMetas()), | |||||
setting.AppUrl, owner.Name, repo.Name, issue.Index) | |||||
msg := NewMessage(tos, subject, content) | |||||
msg.Info = fmt.Sprintf("Subject: %s, issue notify", subject) | |||||
SendAsync(msg) | |||||
return tos, nil | |||||
} | |||||
// SendIssueMentionMail sends mail notification for who are mentioned in issue. | |||||
func SendIssueMentionMail(r macaron.Render, u, owner *models.User, | |||||
repo *models.Repository, issue *models.Issue, tos []string) error { | |||||
if len(tos) == 0 { | |||||
return nil | |||||
} | |||||
subject := fmt.Sprintf("[%s] %s (#%d)", repo.Name, issue.Name, issue.Index) | |||||
data := ComposeTplData(nil) | |||||
data["IssueLink"] = fmt.Sprintf("%s/%s/issues/%d", owner.Name, repo.Name, issue.Index) | |||||
data["Subject"] = subject | |||||
data["ActUserName"] = u.DisplayName() | |||||
data["Content"] = string(markdown.RenderSpecialLink([]byte(issue.Content), owner.Name+"/"+repo.Name, repo.ComposeMetas())) | |||||
body, err := r.HTMLString(string(NOTIFY_MENTION), data) | |||||
if err != nil { | |||||
return fmt.Errorf("HTMLString: %v", err) | |||||
} | |||||
msg := NewMessage(tos, subject, body) | |||||
msg.Info = fmt.Sprintf("Subject: %s, issue mention", subject) | |||||
SendAsync(msg) | |||||
return nil | |||||
} | |||||
// SendCollaboratorMail sends mail notification to new collaborator. | |||||
func SendCollaboratorMail(r macaron.Render, u, doer *models.User, repo *models.Repository) error { | |||||
subject := fmt.Sprintf("%s added you to %s/%s", doer.Name, repo.Owner.Name, repo.Name) | |||||
data := ComposeTplData(nil) | |||||
data["RepoLink"] = path.Join(repo.Owner.Name, repo.Name) | |||||
data["Subject"] = subject | |||||
body, err := r.HTMLString(string(NOTIFY_COLLABORATOR), data) | |||||
if err != nil { | |||||
return fmt.Errorf("HTMLString: %v", err) | |||||
} | |||||
msg := NewMessage([]string{u.Email}, subject, body) | |||||
msg.Info = fmt.Sprintf("UID: %d, add collaborator", u.Id) | |||||
SendAsync(msg) | |||||
return nil | |||||
} | |||||
func SendTestMail(email string) error { | |||||
return gomail.Send(&Sender{}, NewMessage([]string{email}, "Gogs Test Email!", "Gogs Test Email!").Message) | |||||
} |
@ -0,0 +1,17 @@ | |||||
<!DOCTYPE html> | |||||
<html> | |||||
<head> | |||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> | |||||
<title>{{.Subject}}</title> | |||||
</head> | |||||
<body> | |||||
<p>@{{.Doer.Name}} mentioned you:</p> | |||||
<p>{{.Body | Str2html}}</p> | |||||
<p> | |||||
--- | |||||
<br> | |||||
<a href="{{.Link}}">View it on Gogs</a>. | |||||
</p> | |||||
</body> | |||||
</html> |