Extracting html templates into a library (#16946)

* Extracting html templates into a library

* Moving tests to the right place

* Fixing tests

* Addressing PR review comments

* Addressing PR review comments

* Replacing attomic with RWMutex

* Returning errors as channel for Templates watcher

* Address PR review comments

* Other small fixes

* Simplifying NewWithWatcher

* Addressing PR review comments

* Making error handling on rendering templates more robust

* Fixing tests

* Changing how we return errors

* Fixing shadow variables

* Addressing PR review comments

* Logging errors from the outside of sendNotificationEmail

* Fixing lock in shutdown

* Fixing the resource copy for commands tests temporary directories

* Removing unused import

* A couple of tiny fixes
Этот коммит содержится в:
Jesús Espino
2021-03-12 18:46:43 +01:00
коммит произвёл GitHub
родитель 58dce5930e
Коммит 95b0809850
16 изменённых файлов: 834 добавлений и 604 удалений

Просмотреть файл

@@ -6,7 +6,6 @@ package app
import (
"context"
"fmt"
"html/template"
"net/http"
"strconv"
"strings"
@@ -21,6 +20,7 @@ import (
"github.com/mattermost/mattermost-server/v5/services/timezones"
"github.com/mattermost/mattermost-server/v5/shared/i18n"
"github.com/mattermost/mattermost-server/v5/shared/mlog"
"github.com/mattermost/mattermost-server/v5/shared/templates"
"github.com/mattermost/mattermost-server/v5/utils"
)
@@ -148,12 +148,8 @@ func (a *App) TelemetryId() string {
return a.Srv().TelemetryId()
}
func (s *Server) HTMLTemplates() *template.Template {
if s.htmlTemplateWatcher != nil {
return s.htmlTemplateWatcher.Templates()
}
return nil
func (s *Server) TemplatesContainer() *templates.Container {
return s.htmlTemplateWatcher
}
func (a *App) Handle404(w http.ResponseWriter, r *http.Request) {
@@ -467,25 +463,25 @@ func (a *App) NotifyAndSetWarnMetricAck(warnMetricId string, sender *model.User,
return model.NewAppError("NotifyAndSetWarnMetricAck", "api.email.send_warn_metric_ack.missing_server.app_error", nil, i18n.T("api.context.invalid_param.app_error", map[string]interface{}{"Name": "SMTPServer"}), http.StatusInternalServerError)
}
T := i18n.GetUserTranslations(sender.Locale)
bodyPage := a.Srv().EmailService.newEmailTemplate("warn_metric_ack", sender.Locale)
bodyPage.Props["ContactNameHeader"] = T("api.templates.warn_metric_ack.body.contact_name_header")
bodyPage.Props["ContactNameValue"] = sender.GetFullName()
bodyPage.Props["ContactEmailHeader"] = T("api.templates.warn_metric_ack.body.contact_email_header")
bodyPage.Props["ContactEmailValue"] = sender.Email
data := a.Srv().EmailService.newEmailTemplateData(sender.Locale)
data.Props["ContactNameHeader"] = T("api.templates.warn_metric_ack.body.contact_name_header")
data.Props["ContactNameValue"] = sender.GetFullName()
data.Props["ContactEmailHeader"] = T("api.templates.warn_metric_ack.body.contact_email_header")
data.Props["ContactEmailValue"] = sender.Email
//same definition as the active users count metric displayed in the SystemConsole Analytics section
registeredUsersCount, cerr := a.Srv().Store.User().Count(model.UserCountOptions{})
if cerr != nil {
mlog.Warn("Error retrieving the number of registered users", mlog.Err(cerr))
} else {
bodyPage.Props["RegisteredUsersHeader"] = T("api.templates.warn_metric_ack.body.registered_users_header")
bodyPage.Props["RegisteredUsersValue"] = registeredUsersCount
data.Props["RegisteredUsersHeader"] = T("api.templates.warn_metric_ack.body.registered_users_header")
data.Props["RegisteredUsersValue"] = registeredUsersCount
}
bodyPage.Props["SiteURLHeader"] = T("api.templates.warn_metric_ack.body.site_url_header")
bodyPage.Props["SiteURL"] = a.GetSiteURL()
bodyPage.Props["TelemetryIdHeader"] = T("api.templates.warn_metric_ack.body.diagnostic_id_header")
bodyPage.Props["TelemetryIdValue"] = a.TelemetryId()
bodyPage.Props["Footer"] = T("api.templates.warn_metric_ack.footer")
data.Props["SiteURLHeader"] = T("api.templates.warn_metric_ack.body.site_url_header")
data.Props["SiteURL"] = a.GetSiteURL()
data.Props["TelemetryIdHeader"] = T("api.templates.warn_metric_ack.body.diagnostic_id_header")
data.Props["TelemetryIdValue"] = a.TelemetryId()
data.Props["Footer"] = T("api.templates.warn_metric_ack.footer")
warnMetricStatus, warnMetricDisplayTexts := a.getWarnMetricStatusAndDisplayTextsForId(warnMetricId, T, false)
if warnMetricStatus == nil {
@@ -493,10 +489,16 @@ func (a *App) NotifyAndSetWarnMetricAck(warnMetricId string, sender *model.User,
}
subject := T("api.templates.warn_metric_ack.subject")
bodyPage.Props["Title"] = warnMetricDisplayTexts.EmailBody
data.Props["Title"] = warnMetricDisplayTexts.EmailBody
mailConfig := a.Srv().MailServiceConfig()
if err := mailservice.SendMailUsingConfig(model.MM_SUPPORT_ADVISOR_ADDRESS, subject, bodyPage.Render(), mailConfig, false, sender.Email); err != nil {
body, err := a.Srv().TemplatesContainer().RenderToString("warn_metric_ack", data)
if err != nil {
return model.NewAppError("NotifyAndSetWarnMetricAck", "api.email.send_warn_metric_ack.failure.app_error", map[string]interface{}{"Error": err.Error()}, "", http.StatusInternalServerError)
}
if err := mailservice.SendMailUsingConfig(model.MM_SUPPORT_ADVISOR_ADDRESS, subject, body, mailConfig, false, sender.Email); err != nil {
return model.NewAppError("NotifyAndSetWarnMetricAck", "api.email.send_warn_metric_ack.failure.app_error", map[string]interface{}{"Error": err.Error()}, "", http.StatusInternalServerError)
}
}

Просмотреть файл

@@ -7,6 +7,7 @@ import (
"bytes"
"encoding/json"
"fmt"
"html/template"
"io"
"net/http"
"net/url"
@@ -22,7 +23,7 @@ import (
"github.com/mattermost/mattermost-server/v5/services/mailservice"
"github.com/mattermost/mattermost-server/v5/shared/i18n"
"github.com/mattermost/mattermost-server/v5/shared/mlog"
"github.com/mattermost/mattermost-server/v5/utils"
"github.com/mattermost/mattermost-server/v5/shared/templates"
)
const (
@@ -94,14 +95,19 @@ func (es *EmailService) sendChangeUsernameEmail(newUsername, email, locale, site
map[string]interface{}{"SiteName": es.srv.Config().TeamSettings.SiteName,
"TeamDisplayName": es.srv.Config().TeamSettings.SiteName})
bodyPage := es.newEmailTemplate("email_change_body", locale)
bodyPage.Props["SiteURL"] = siteURL
bodyPage.Props["Title"] = T("api.templates.username_change_body.title")
bodyPage.Props["Info"] = T("api.templates.username_change_body.info",
data := es.newEmailTemplateData(locale)
data.Props["SiteURL"] = siteURL
data.Props["Title"] = T("api.templates.username_change_body.title")
data.Props["Info"] = T("api.templates.username_change_body.info",
map[string]interface{}{"TeamDisplayName": es.srv.Config().TeamSettings.SiteName, "NewUsername": newUsername})
bodyPage.Props["Warning"] = T("api.templates.email_warning")
data.Props["Warning"] = T("api.templates.email_warning")
if err := es.sendMail(email, subject, bodyPage.Render()); err != nil {
body, err := es.srv.TemplatesContainer().RenderToString("email_change_body", data)
if err != nil {
return model.NewAppError("sendChangeUsernameEmail", "api.user.send_email_change_username_and_forget.error", nil, err.Error(), http.StatusInternalServerError)
}
if err := es.sendMail(email, subject, body); err != nil {
return model.NewAppError("sendChangeUsernameEmail", "api.user.send_email_change_username_and_forget.error", nil, err.Error(), http.StatusInternalServerError)
}
@@ -117,15 +123,20 @@ func (es *EmailService) sendEmailChangeVerifyEmail(newUserEmail, locale, siteURL
map[string]interface{}{"SiteName": es.srv.Config().TeamSettings.SiteName,
"TeamDisplayName": es.srv.Config().TeamSettings.SiteName})
bodyPage := es.newEmailTemplate("email_change_verify_body", locale)
bodyPage.Props["SiteURL"] = siteURL
bodyPage.Props["Title"] = T("api.templates.email_change_verify_body.title")
bodyPage.Props["Info"] = T("api.templates.email_change_verify_body.info",
data := es.newEmailTemplateData(locale)
data.Props["SiteURL"] = siteURL
data.Props["Title"] = T("api.templates.email_change_verify_body.title")
data.Props["Info"] = T("api.templates.email_change_verify_body.info",
map[string]interface{}{"TeamDisplayName": es.srv.Config().TeamSettings.SiteName})
bodyPage.Props["VerifyUrl"] = link
bodyPage.Props["VerifyButton"] = T("api.templates.email_change_verify_body.button")
data.Props["VerifyUrl"] = link
data.Props["VerifyButton"] = T("api.templates.email_change_verify_body.button")
if err := es.sendMail(newUserEmail, subject, bodyPage.Render()); err != nil {
body, err := es.srv.TemplatesContainer().RenderToString("email_change_verify_body", data)
if err != nil {
return model.NewAppError("sendEmailChangeVerifyEmail", "api.user.send_email_change_verify_email_and_forget.error", nil, err.Error(), http.StatusInternalServerError)
}
if err := es.sendMail(newUserEmail, subject, body); err != nil {
return model.NewAppError("sendEmailChangeVerifyEmail", "api.user.send_email_change_verify_email_and_forget.error", nil, err.Error(), http.StatusInternalServerError)
}
@@ -139,14 +150,19 @@ func (es *EmailService) sendEmailChangeEmail(oldEmail, newEmail, locale, siteURL
map[string]interface{}{"SiteName": es.srv.Config().TeamSettings.SiteName,
"TeamDisplayName": es.srv.Config().TeamSettings.SiteName})
bodyPage := es.newEmailTemplate("email_change_body", locale)
bodyPage.Props["SiteURL"] = siteURL
bodyPage.Props["Title"] = T("api.templates.email_change_body.title")
bodyPage.Props["Info"] = T("api.templates.email_change_body.info",
data := es.newEmailTemplateData(locale)
data.Props["SiteURL"] = siteURL
data.Props["Title"] = T("api.templates.email_change_body.title")
data.Props["Info"] = T("api.templates.email_change_body.info",
map[string]interface{}{"TeamDisplayName": es.srv.Config().TeamSettings.SiteName, "NewEmail": newEmail})
bodyPage.Props["Warning"] = T("api.templates.email_warning")
data.Props["Warning"] = T("api.templates.email_warning")
if err := es.sendMail(oldEmail, subject, bodyPage.Render()); err != nil {
body, err := es.srv.TemplatesContainer().RenderToString("email_change_body", data)
if err != nil {
return model.NewAppError("sendEmailChangeEmail", "api.user.send_email_change_email_and_forget.error", nil, err.Error(), http.StatusInternalServerError)
}
if err := es.sendMail(oldEmail, subject, body); err != nil {
return model.NewAppError("sendEmailChangeEmail", "api.user.send_email_change_email_and_forget.error", nil, err.Error(), http.StatusInternalServerError)
}
@@ -166,20 +182,25 @@ func (es *EmailService) sendVerifyEmail(userEmail, locale, siteURL, token, redir
subject := T("api.templates.verify_subject",
map[string]interface{}{"SiteName": es.srv.Config().TeamSettings.SiteName})
bodyPage := es.newEmailTemplate("verify_body", locale)
bodyPage.Props["SiteURL"] = siteURL
bodyPage.Props["Title"] = T("api.templates.verify_body.title")
bodyPage.Props["SubTitle1"] = T("api.templates.verify_body.subTitle1")
bodyPage.Props["ServerURL"] = T("api.templates.verify_body.serverURL", map[string]interface{}{"ServerURL": serverURL})
bodyPage.Props["SubTitle2"] = T("api.templates.verify_body.subTitle2")
bodyPage.Props["ButtonURL"] = link
bodyPage.Props["Button"] = T("api.templates.verify_body.button")
bodyPage.Props["Info"] = T("api.templates.verify_body.info")
bodyPage.Props["Info1"] = T("api.templates.verify_body.info1")
bodyPage.Props["QuestionTitle"] = T("api.templates.questions_footer.title")
bodyPage.Props["QuestionInfo"] = T("api.templates.questions_footer.info")
data := es.newEmailTemplateData(locale)
data.Props["SiteURL"] = siteURL
data.Props["Title"] = T("api.templates.verify_body.title")
data.Props["SubTitle1"] = T("api.templates.verify_body.subTitle1")
data.Props["ServerURL"] = T("api.templates.verify_body.serverURL", map[string]interface{}{"ServerURL": serverURL})
data.Props["SubTitle2"] = T("api.templates.verify_body.subTitle2")
data.Props["ButtonURL"] = link
data.Props["Button"] = T("api.templates.verify_body.button")
data.Props["Info"] = T("api.templates.verify_body.info")
data.Props["Info1"] = T("api.templates.verify_body.info1")
data.Props["QuestionTitle"] = T("api.templates.questions_footer.title")
data.Props["QuestionInfo"] = T("api.templates.questions_footer.info")
if err := es.sendMail(userEmail, subject, bodyPage.Render()); err != nil {
body, err := es.srv.TemplatesContainer().RenderToString("verify_body", data)
if err != nil {
return model.NewAppError("SendVerifyEmail", "api.user.send_verify_email_and_forget.failed.error", nil, err.Error(), http.StatusInternalServerError)
}
if err := es.sendMail(userEmail, subject, body); err != nil {
return model.NewAppError("SendVerifyEmail", "api.user.send_verify_email_and_forget.failed.error", nil, err.Error(), http.StatusInternalServerError)
}
@@ -192,14 +213,19 @@ func (es *EmailService) SendSignInChangeEmail(email, method, locale, siteURL str
subject := T("api.templates.signin_change_email.subject",
map[string]interface{}{"SiteName": es.srv.Config().TeamSettings.SiteName})
bodyPage := es.newEmailTemplate("signin_change_body", locale)
bodyPage.Props["SiteURL"] = siteURL
bodyPage.Props["Title"] = T("api.templates.signin_change_email.body.title")
bodyPage.Props["Info"] = T("api.templates.signin_change_email.body.info",
data := es.newEmailTemplateData(locale)
data.Props["SiteURL"] = siteURL
data.Props["Title"] = T("api.templates.signin_change_email.body.title")
data.Props["Info"] = T("api.templates.signin_change_email.body.info",
map[string]interface{}{"SiteName": es.srv.Config().TeamSettings.SiteName, "Method": method})
bodyPage.Props["Warning"] = T("api.templates.email_warning")
data.Props["Warning"] = T("api.templates.email_warning")
if err := es.sendMail(email, subject, bodyPage.Render()); err != nil {
body, err := es.srv.TemplatesContainer().RenderToString("signin_change_body", data)
if err != nil {
return model.NewAppError("SendSignInChangeEmail", "api.user.send_sign_in_change_email_and_forget.error", nil, err.Error(), http.StatusInternalServerError)
}
if err := es.sendMail(email, subject, body); err != nil {
return model.NewAppError("SendSignInChangeEmail", "api.user.send_sign_in_change_email_and_forget.error", nil, err.Error(), http.StatusInternalServerError)
}
@@ -222,22 +248,22 @@ func (es *EmailService) sendWelcomeEmail(userID string, email string, verified b
map[string]interface{}{"SiteName": es.srv.Config().TeamSettings.SiteName,
"ServerURL": serverURL})
bodyPage := es.newEmailTemplate("welcome_body", locale)
bodyPage.Props["SiteURL"] = siteURL
bodyPage.Props["Title"] = T("api.templates.welcome_body.title")
bodyPage.Props["SubTitle1"] = T("api.templates.welcome_body.subTitle1")
bodyPage.Props["ServerURL"] = T("api.templates.welcome_body.serverURL", map[string]interface{}{"ServerURL": serverURL})
bodyPage.Props["SubTitle2"] = T("api.templates.welcome_body.subTitle2")
bodyPage.Props["Button"] = T("api.templates.welcome_body.button")
bodyPage.Props["Info"] = T("api.templates.welcome_body.info")
bodyPage.Props["Info1"] = T("api.templates.welcome_body.info1")
bodyPage.Props["SiteURL"] = siteURL
data := es.newEmailTemplateData(locale)
data.Props["SiteURL"] = siteURL
data.Props["Title"] = T("api.templates.welcome_body.title")
data.Props["SubTitle1"] = T("api.templates.welcome_body.subTitle1")
data.Props["ServerURL"] = T("api.templates.welcome_body.serverURL", map[string]interface{}{"ServerURL": serverURL})
data.Props["SubTitle2"] = T("api.templates.welcome_body.subTitle2")
data.Props["Button"] = T("api.templates.welcome_body.button")
data.Props["Info"] = T("api.templates.welcome_body.info")
data.Props["Info1"] = T("api.templates.welcome_body.info1")
data.Props["SiteURL"] = siteURL
if *es.srv.Config().NativeAppSettings.AppDownloadLink != "" {
bodyPage.Props["AppDownloadTitle"] = T("api.templates.welcome_body.app_download_title")
bodyPage.Props["AppDownloadInfo"] = T("api.templates.welcome_body.app_download_info")
bodyPage.Props["AppDownloadButton"] = T("api.templates.welcome_body.app_download_button")
bodyPage.Props["AppDownloadLink"] = *es.srv.Config().NativeAppSettings.AppDownloadLink
data.Props["AppDownloadTitle"] = T("api.templates.welcome_body.app_download_title")
data.Props["AppDownloadInfo"] = T("api.templates.welcome_body.app_download_info")
data.Props["AppDownloadButton"] = T("api.templates.welcome_body.app_download_button")
data.Props["AppDownloadLink"] = *es.srv.Config().NativeAppSettings.AppDownloadLink
}
if !verified && *es.srv.Config().EmailSettings.RequireEmailVerification {
@@ -249,10 +275,15 @@ func (es *EmailService) sendWelcomeEmail(userID string, email string, verified b
if redirect != "" {
link += fmt.Sprintf("&redirect_to=%s", redirect)
}
bodyPage.Props["ButtonURL"] = link
data.Props["ButtonURL"] = link
}
if err := es.sendMail(email, subject, bodyPage.Render()); err != nil {
body, err := es.srv.TemplatesContainer().RenderToString("welcome_body", data)
if err != nil {
return model.NewAppError("sendWelcomeEmail", "api.user.send_welcome_email_and_forget.failed.error", nil, err.Error(), http.StatusInternalServerError)
}
if err := es.sendMail(email, subject, body); err != nil {
return model.NewAppError("sendWelcomeEmail", "api.user.send_welcome_email_and_forget.failed.error", nil, err.Error(), http.StatusInternalServerError)
}
@@ -266,30 +297,35 @@ func (es *EmailService) SendCloudWelcomeEmail(userEmail, locale, teamInviteID, w
workSpacePath := fmt.Sprintf("https://%s.cloud.mattermost.com", workSpaceName)
bodyPage := es.newEmailTemplate("cloud_welcome_email", locale)
bodyPage.Props["Title"] = T("api.templates.cloud_welcome_email.title", map[string]interface{}{"WorkSpace": workSpaceName})
bodyPage.Props["SubTitle"] = T("api.templates.cloud_welcome_email.subtitle")
bodyPage.Props["SubTitleInfo"] = T("api.templates.cloud_welcome_email.subtitle_info")
bodyPage.Props["Info"] = T("api.templates.cloud_welcome_email.info")
bodyPage.Props["Info2"] = T("api.templates.cloud_welcome_email.info2")
bodyPage.Props["WorkSpacePath"] = workSpacePath
bodyPage.Props["DNS"] = dns
bodyPage.Props["InviteInfo"] = T("api.templates.cloud_welcome_email.invite_info")
bodyPage.Props["InviteSubInfo"] = T("api.templates.cloud_welcome_email.invite_sub_info", map[string]interface{}{"WorkSpace": workSpaceName})
bodyPage.Props["InviteSubInfoLink"] = fmt.Sprintf("%s/signup_user_complete/?id=%s", workSpacePath, teamInviteID)
bodyPage.Props["AddAppsInfo"] = T("api.templates.cloud_welcome_email.add_apps_info")
bodyPage.Props["AddAppsSubInfo"] = T("api.templates.cloud_welcome_email.add_apps_sub_info")
bodyPage.Props["AppMarketPlace"] = T("api.templates.cloud_welcome_email.app_market_place")
bodyPage.Props["AppMarketPlaceLink"] = "https://integrations.mattermost.com/"
bodyPage.Props["DownloadMMInfo"] = T("api.templates.cloud_welcome_email.download_mm_info")
bodyPage.Props["SignInSubInfo"] = T("api.templates.cloud_welcome_email.signin_sub_info")
bodyPage.Props["MMApps"] = T("api.templates.cloud_welcome_email.mm_apps")
bodyPage.Props["SignInSubInfo2"] = T("api.templates.cloud_welcome_email.signin_sub_info2")
bodyPage.Props["DownloadMMAppsLink"] = "https://mattermost.com/download/"
bodyPage.Props["Button"] = T("api.templates.cloud_welcome_email.button")
bodyPage.Props["GettingStartedQuestions"] = T("api.templates.cloud_welcome_email.start_questions")
data := es.newEmailTemplateData(locale)
data.Props["Title"] = T("api.templates.cloud_welcome_email.title", map[string]interface{}{"WorkSpace": workSpaceName})
data.Props["SubTitle"] = T("api.templates.cloud_welcome_email.subtitle")
data.Props["SubTitleInfo"] = T("api.templates.cloud_welcome_email.subtitle_info")
data.Props["Info"] = T("api.templates.cloud_welcome_email.info")
data.Props["Info2"] = T("api.templates.cloud_welcome_email.info2")
data.Props["WorkSpacePath"] = workSpacePath
data.Props["DNS"] = dns
data.Props["InviteInfo"] = T("api.templates.cloud_welcome_email.invite_info")
data.Props["InviteSubInfo"] = T("api.templates.cloud_welcome_email.invite_sub_info", map[string]interface{}{"WorkSpace": workSpaceName})
data.Props["InviteSubInfoLink"] = fmt.Sprintf("%s/signup_user_complete/?id=%s", workSpacePath, teamInviteID)
data.Props["AddAppsInfo"] = T("api.templates.cloud_welcome_email.add_apps_info")
data.Props["AddAppsSubInfo"] = T("api.templates.cloud_welcome_email.add_apps_sub_info")
data.Props["AppMarketPlace"] = T("api.templates.cloud_welcome_email.app_market_place")
data.Props["AppMarketPlaceLink"] = "https://integrations.mattermost.com/"
data.Props["DownloadMMInfo"] = T("api.templates.cloud_welcome_email.download_mm_info")
data.Props["SignInSubInfo"] = T("api.templates.cloud_welcome_email.signin_sub_info")
data.Props["MMApps"] = T("api.templates.cloud_welcome_email.mm_apps")
data.Props["SignInSubInfo2"] = T("api.templates.cloud_welcome_email.signin_sub_info2")
data.Props["DownloadMMAppsLink"] = "https://mattermost.com/download/"
data.Props["Button"] = T("api.templates.cloud_welcome_email.button")
data.Props["GettingStartedQuestions"] = T("api.templates.cloud_welcome_email.start_questions")
if err := es.sendMail(userEmail, subject, bodyPage.Render()); err != nil {
body, err := es.srv.TemplatesContainer().RenderToString("cloud_welcome_email", data)
if err != nil {
return model.NewAppError("SendCloudWelcomeEmail", "api.user.send_cloud_welcome_email.error", nil, err.Error(), http.StatusInternalServerError)
}
if err := es.sendMail(userEmail, subject, body); err != nil {
return model.NewAppError("SendCloudWelcomeEmail", "api.user.send_cloud_welcome_email.error", nil, err.Error(), http.StatusInternalServerError)
}
@@ -303,14 +339,19 @@ func (es *EmailService) sendPasswordChangeEmail(email, method, locale, siteURL s
map[string]interface{}{"SiteName": es.srv.Config().TeamSettings.SiteName,
"TeamDisplayName": es.srv.Config().TeamSettings.SiteName})
bodyPage := es.newEmailTemplate("password_change_body", locale)
bodyPage.Props["SiteURL"] = siteURL
bodyPage.Props["Title"] = T("api.templates.password_change_body.title")
bodyPage.Props["Info"] = T("api.templates.password_change_body.info",
data := es.newEmailTemplateData(locale)
data.Props["SiteURL"] = siteURL
data.Props["Title"] = T("api.templates.password_change_body.title")
data.Props["Info"] = T("api.templates.password_change_body.info",
map[string]interface{}{"TeamDisplayName": es.srv.Config().TeamSettings.SiteName, "TeamURL": siteURL, "Method": method})
bodyPage.Props["Warning"] = T("api.templates.email_warning")
data.Props["Warning"] = T("api.templates.email_warning")
if err := es.sendMail(email, subject, bodyPage.Render()); err != nil {
body, err := es.srv.TemplatesContainer().RenderToString("password_change_body", data)
if err != nil {
return model.NewAppError("sendPasswordChangeEmail", "api.user.send_password_change_email_and_forget.error", nil, err.Error(), http.StatusInternalServerError)
}
if err := es.sendMail(email, subject, body); err != nil {
return model.NewAppError("sendPasswordChangeEmail", "api.user.send_password_change_email_and_forget.error", nil, err.Error(), http.StatusInternalServerError)
}
@@ -323,14 +364,19 @@ func (es *EmailService) sendUserAccessTokenAddedEmail(email, locale, siteURL str
subject := T("api.templates.user_access_token_subject",
map[string]interface{}{"SiteName": es.srv.Config().TeamSettings.SiteName})
bodyPage := es.newEmailTemplate("password_change_body", locale)
bodyPage.Props["SiteURL"] = siteURL
bodyPage.Props["Title"] = T("api.templates.user_access_token_body.title")
bodyPage.Props["Info"] = T("api.templates.user_access_token_body.info",
data := es.newEmailTemplateData(locale)
data.Props["SiteURL"] = siteURL
data.Props["Title"] = T("api.templates.user_access_token_body.title")
data.Props["Info"] = T("api.templates.user_access_token_body.info",
map[string]interface{}{"SiteName": es.srv.Config().TeamSettings.SiteName, "SiteURL": siteURL})
bodyPage.Props["Warning"] = T("api.templates.email_warning")
data.Props["Warning"] = T("api.templates.email_warning")
if err := es.sendMail(email, subject, bodyPage.Render()); err != nil {
body, err := es.srv.TemplatesContainer().RenderToString("password_change_body", data)
if err != nil {
return model.NewAppError("sendUserAccessTokenAddedEmail", "api.user.send_user_access_token.error", nil, err.Error(), http.StatusInternalServerError)
}
if err := es.sendMail(email, subject, body); err != nil {
return model.NewAppError("sendUserAccessTokenAddedEmail", "api.user.send_user_access_token.error", nil, err.Error(), http.StatusInternalServerError)
}
@@ -345,17 +391,22 @@ func (es *EmailService) SendPasswordResetEmail(email string, token *model.Token,
subject := T("api.templates.reset_subject",
map[string]interface{}{"SiteName": es.srv.Config().TeamSettings.SiteName})
bodyPage := es.newEmailTemplate("reset_body", locale)
bodyPage.Props["SiteURL"] = siteURL
bodyPage.Props["Title"] = T("api.templates.reset_body.title")
bodyPage.Props["SubTitle"] = T("api.templates.reset_body.subTitle")
bodyPage.Props["Info"] = T("api.templates.reset_body.info")
bodyPage.Props["ButtonURL"] = link
bodyPage.Props["Button"] = T("api.templates.reset_body.button")
bodyPage.Props["QuestionTitle"] = T("api.templates.questions_footer.title")
bodyPage.Props["QuestionInfo"] = T("api.templates.questions_footer.info")
data := es.newEmailTemplateData(locale)
data.Props["SiteURL"] = siteURL
data.Props["Title"] = T("api.templates.reset_body.title")
data.Props["SubTitle"] = T("api.templates.reset_body.subTitle")
data.Props["Info"] = T("api.templates.reset_body.info")
data.Props["ButtonURL"] = link
data.Props["Button"] = T("api.templates.reset_body.button")
data.Props["QuestionTitle"] = T("api.templates.questions_footer.title")
data.Props["QuestionInfo"] = T("api.templates.questions_footer.info")
if err := es.sendMail(email, subject, bodyPage.Render()); err != nil {
body, err := es.srv.TemplatesContainer().RenderToString("reset_body", data)
if err != nil {
return false, model.NewAppError("SendPasswordReset", "api.user.send_password_reset.send.app_error", nil, "err="+err.Error(), http.StatusInternalServerError)
}
if err := es.sendMail(email, subject, body); err != nil {
return false, model.NewAppError("SendPasswordReset", "api.user.send_password_reset.send.app_error", nil, "err="+err.Error(), http.StatusInternalServerError)
}
@@ -368,19 +419,24 @@ func (es *EmailService) sendMfaChangeEmail(email string, activated bool, locale,
subject := T("api.templates.mfa_change_subject",
map[string]interface{}{"SiteName": es.srv.Config().TeamSettings.SiteName})
bodyPage := es.newEmailTemplate("mfa_change_body", locale)
bodyPage.Props["SiteURL"] = siteURL
data := es.newEmailTemplateData(locale)
data.Props["SiteURL"] = siteURL
if activated {
bodyPage.Props["Info"] = T("api.templates.mfa_activated_body.info", map[string]interface{}{"SiteURL": siteURL})
bodyPage.Props["Title"] = T("api.templates.mfa_activated_body.title")
data.Props["Info"] = T("api.templates.mfa_activated_body.info", map[string]interface{}{"SiteURL": siteURL})
data.Props["Title"] = T("api.templates.mfa_activated_body.title")
} else {
bodyPage.Props["Info"] = T("api.templates.mfa_deactivated_body.info", map[string]interface{}{"SiteURL": siteURL})
bodyPage.Props["Title"] = T("api.templates.mfa_deactivated_body.title")
data.Props["Info"] = T("api.templates.mfa_deactivated_body.info", map[string]interface{}{"SiteURL": siteURL})
data.Props["Title"] = T("api.templates.mfa_deactivated_body.title")
}
bodyPage.Props["Warning"] = T("api.templates.email_warning")
data.Props["Warning"] = T("api.templates.email_warning")
if err := es.sendMail(email, subject, bodyPage.Render()); err != nil {
body, err := es.srv.TemplatesContainer().RenderToString("mfa_change_body", data)
if err != nil {
return model.NewAppError("SendMfaChangeEmail", "api.user.send_mfa_change_email.error", nil, err.Error(), http.StatusInternalServerError)
}
if err := es.sendMail(email, subject, body); err != nil {
return model.NewAppError("SendMfaChangeEmail", "api.user.send_mfa_change_email.error", nil, err.Error(), http.StatusInternalServerError)
}
@@ -411,34 +467,39 @@ func (es *EmailService) SendInviteEmails(team *model.Team, senderName string, se
"TeamDisplayName": team.DisplayName,
"SiteName": es.srv.Config().TeamSettings.SiteName})
bodyPage := es.newEmailTemplate("invite_body", "")
bodyPage.Props["SiteURL"] = siteURL
bodyPage.Props["Title"] = i18n.T("api.templates.invite_body.title", map[string]interface{}{"SenderName": senderName, "TeamDisplayName": team.DisplayName})
bodyPage.Props["SubTitle"] = i18n.T("api.templates.invite_body.subTitle")
bodyPage.Props["Button"] = i18n.T("api.templates.invite_body.button")
bodyPage.Props["SenderName"] = senderName
bodyPage.Props["InviteFooterTitle"] = i18n.T("api.templates.invite_body_footer.title")
bodyPage.Props["InviteFooterInfo"] = i18n.T("api.templates.invite_body_footer.info")
bodyPage.Props["InviteFooterLearnMore"] = i18n.T("api.templates.invite_body_footer.learn_more")
data := es.newEmailTemplateData("")
data.Props["SiteURL"] = siteURL
data.Props["Title"] = i18n.T("api.templates.invite_body.title", map[string]interface{}{"SenderName": senderName, "TeamDisplayName": team.DisplayName})
data.Props["SubTitle"] = i18n.T("api.templates.invite_body.subTitle")
data.Props["Button"] = i18n.T("api.templates.invite_body.button")
data.Props["SenderName"] = senderName
data.Props["InviteFooterTitle"] = i18n.T("api.templates.invite_body_footer.title")
data.Props["InviteFooterInfo"] = i18n.T("api.templates.invite_body_footer.info")
data.Props["InviteFooterLearnMore"] = i18n.T("api.templates.invite_body_footer.learn_more")
token := model.NewToken(
TokenTypeTeamInvitation,
model.MapToJson(map[string]string{"teamId": team.Id, "email": invite}),
)
props := make(map[string]string)
props["email"] = invite
props["display_name"] = team.DisplayName
props["name"] = team.Name
data := model.MapToJson(props)
tokenProps := make(map[string]string)
tokenProps["email"] = invite
tokenProps["display_name"] = team.DisplayName
tokenProps["name"] = team.Name
tokenData := model.MapToJson(tokenProps)
if err := es.srv.Store.Token().Save(token); err != nil {
mlog.Error("Failed to send invite email successfully ", mlog.Err(err))
continue
}
bodyPage.Props["ButtonURL"] = fmt.Sprintf("%s/signup_user_complete/?d=%s&t=%s", siteURL, url.QueryEscape(data), url.QueryEscape(token.Token))
data.Props["ButtonURL"] = fmt.Sprintf("%s/signup_user_complete/?d=%s&t=%s", siteURL, url.QueryEscape(tokenData), url.QueryEscape(token.Token))
if err := es.sendMail(invite, subject, bodyPage.Render()); err != nil {
body, err := es.srv.TemplatesContainer().RenderToString("invite_body", data)
if err != nil {
mlog.Error("Failed to send invite email successfully ", mlog.Err(err))
}
if err := es.sendMail(invite, subject, body); err != nil {
mlog.Error("Failed to send invite email successfully ", mlog.Err(err))
}
}
@@ -470,19 +531,19 @@ func (es *EmailService) sendGuestInviteEmails(team *model.Team, channels []*mode
"TeamDisplayName": team.DisplayName,
"SiteName": es.srv.Config().TeamSettings.SiteName})
bodyPage := es.newEmailTemplate("invite_body", "")
bodyPage.Props["SiteURL"] = siteURL
bodyPage.Props["Title"] = i18n.T("api.templates.invite_body.title", map[string]interface{}{"SenderName": senderName, "TeamDisplayName": team.DisplayName})
bodyPage.Props["SubTitle"] = i18n.T("api.templates.invite_body_guest.subTitle")
bodyPage.Props["Button"] = i18n.T("api.templates.invite_body.button")
bodyPage.Props["SenderName"] = senderName
bodyPage.Props["Message"] = ""
data := es.newEmailTemplateData("")
data.Props["SiteURL"] = siteURL
data.Props["Title"] = i18n.T("api.templates.invite_body.title", map[string]interface{}{"SenderName": senderName, "TeamDisplayName": team.DisplayName})
data.Props["SubTitle"] = i18n.T("api.templates.invite_body_guest.subTitle")
data.Props["Button"] = i18n.T("api.templates.invite_body.button")
data.Props["SenderName"] = senderName
data.Props["Message"] = ""
if message != "" {
bodyPage.Props["Message"] = message
data.Props["Message"] = message
}
bodyPage.Props["InviteFooterTitle"] = i18n.T("api.templates.invite_body_footer.title")
bodyPage.Props["InviteFooterInfo"] = i18n.T("api.templates.invite_body_footer.info")
bodyPage.Props["InviteFooterLearnMore"] = i18n.T("api.templates.invite_body_footer.learn_more")
data.Props["InviteFooterTitle"] = i18n.T("api.templates.invite_body_footer.title")
data.Props["InviteFooterInfo"] = i18n.T("api.templates.invite_body_footer.info")
data.Props["InviteFooterLearnMore"] = i18n.T("api.templates.invite_body_footer.learn_more")
channelIDs := []string{}
for _, channel := range channels {
@@ -499,20 +560,20 @@ func (es *EmailService) sendGuestInviteEmails(team *model.Team, channels []*mode
}),
)
props := make(map[string]string)
props["email"] = invite
props["display_name"] = team.DisplayName
props["name"] = team.Name
data := model.MapToJson(props)
tokenProps := make(map[string]string)
tokenProps["email"] = invite
tokenProps["display_name"] = team.DisplayName
tokenProps["name"] = team.Name
tokenData := model.MapToJson(tokenProps)
if err := es.srv.Store.Token().Save(token); err != nil {
mlog.Error("Failed to send invite email successfully ", mlog.Err(err))
continue
}
bodyPage.Props["ButtonURL"] = fmt.Sprintf("%s/signup_user_complete/?d=%s&t=%s", siteURL, url.QueryEscape(data), url.QueryEscape(token.Token))
data.Props["ButtonURL"] = fmt.Sprintf("%s/signup_user_complete/?d=%s&t=%s", siteURL, url.QueryEscape(tokenData), url.QueryEscape(token.Token))
if !*es.srv.Config().EmailSettings.SendEmailNotifications {
mlog.Info("sending invitation ", mlog.String("to", invite), mlog.String("link", bodyPage.Props["Link"].(string)))
mlog.Info("sending invitation ", mlog.String("to", invite), mlog.String("link", data.Props["ButtomURL"].(string)))
}
embeddedFiles := make(map[string]io.Reader)
@@ -524,7 +585,12 @@ func (es *EmailService) sendGuestInviteEmails(team *model.Team, channels []*mode
}
}
if nErr := es.sendMailWithEmbeddedFiles(invite, subject, bodyPage.Render(), embeddedFiles); nErr != nil {
body, err := es.srv.TemplatesContainer().RenderToString("invite_body", data)
if err != nil {
mlog.Error("Failed to send invite email successfully", mlog.Err(err))
}
if nErr := es.sendMailWithEmbeddedFiles(invite, subject, body, embeddedFiles); nErr != nil {
mlog.Error("Failed to send invite email successfully", mlog.Err(nErr))
}
}
@@ -532,32 +598,32 @@ func (es *EmailService) sendGuestInviteEmails(team *model.Team, channels []*mode
return nil
}
func (es *EmailService) newEmailTemplate(name, locale string) *utils.HTMLTemplate {
t := utils.NewHTMLTemplate(es.srv.HTMLTemplates(), name)
func (es *EmailService) newEmailTemplateData(locale string) templates.Data {
var localT i18n.TranslateFunc
if locale != "" {
localT = i18n.GetUserTranslations(locale)
} else {
localT = i18n.T
}
t.Props["Footer"] = localT("api.templates.email_footer")
t.Props["FooterV2"] = localT("api.templates.email_footer_v2")
organization := ""
if *es.srv.Config().EmailSettings.FeedbackOrganization != "" {
t.Props["Organization"] = localT("api.templates.email_organization") + *es.srv.Config().EmailSettings.FeedbackOrganization
} else {
t.Props["Organization"] = ""
organization = localT("api.templates.email_organization") + *es.srv.Config().EmailSettings.FeedbackOrganization
}
t.Props["EmailInfo1"] = localT("api.templates.email_info1")
t.Props["EmailInfo2"] = localT("api.templates.email_info2")
t.Props["EmailInfo3"] = localT("api.templates.email_info3",
map[string]interface{}{"SiteName": es.srv.Config().TeamSettings.SiteName})
t.Props["SupportEmail"] = *es.srv.Config().SupportSettings.SupportEmail
return t
return templates.Data{
Props: map[string]interface{}{
"EmailInfo1": localT("api.templates.email_info1"),
"EmailInfo2": localT("api.templates.email_info2"),
"EmailInfo3": localT("api.templates.email_info3",
map[string]interface{}{"SiteName": es.srv.Config().TeamSettings.SiteName}),
"SupportEmail": *es.srv.Config().SupportSettings.SupportEmail,
"Footer": localT("api.templates.email_footer"),
"FooterV2": localT("api.templates.email_footer_v2"),
"Organization": organization,
},
HTML: map[string]template.HTML{},
}
}
func (es *EmailService) SendDeactivateAccountEmail(email string, locale, siteURL string) *model.AppError {
@@ -569,14 +635,19 @@ func (es *EmailService) SendDeactivateAccountEmail(email string, locale, siteURL
map[string]interface{}{"SiteName": es.srv.Config().TeamSettings.SiteName,
"ServerURL": serverURL})
bodyPage := es.newEmailTemplate("deactivate_body", locale)
bodyPage.Props["SiteURL"] = siteURL
bodyPage.Props["Title"] = T("api.templates.deactivate_body.title", map[string]interface{}{"ServerURL": serverURL})
bodyPage.Props["Info"] = T("api.templates.deactivate_body.info",
data := es.newEmailTemplateData(locale)
data.Props["SiteURL"] = siteURL
data.Props["Title"] = T("api.templates.deactivate_body.title", map[string]interface{}{"ServerURL": serverURL})
data.Props["Info"] = T("api.templates.deactivate_body.info",
map[string]interface{}{"SiteURL": siteURL})
bodyPage.Props["Warning"] = T("api.templates.deactivate_body.warning")
data.Props["Warning"] = T("api.templates.deactivate_body.warning")
if err := es.sendMail(email, subject, bodyPage.Render()); err != nil {
body, err := es.srv.TemplatesContainer().RenderToString("deactivate_body", data)
if err != nil {
return model.NewAppError("SendDeactivateEmail", "api.user.send_deactivate_email_and_forget.failed.error", nil, err.Error(), http.StatusInternalServerError)
}
if err := es.sendMail(email, subject, body); err != nil {
return model.NewAppError("SendDeactivateEmail", "api.user.send_deactivate_email_and_forget.failed.error", nil, err.Error(), http.StatusInternalServerError)
}
@@ -595,13 +666,18 @@ func (es *EmailService) SendRemoveExpiredLicenseEmail(email string, locale, site
subject := T("api.templates.remove_expired_license.subject",
map[string]interface{}{"SiteName": es.srv.Config().TeamSettings.SiteName})
bodyPage := es.newEmailTemplate("remove_expired_license", locale)
bodyPage.Props["SiteURL"] = siteURL
bodyPage.Props["Title"] = T("api.templates.remove_expired_license.body.title")
bodyPage.Props["Link"] = renewalLink
bodyPage.Props["LinkButton"] = T("api.templates.remove_expired_license.body.renew_button")
data := es.newEmailTemplateData(locale)
data.Props["SiteURL"] = siteURL
data.Props["Title"] = T("api.templates.remove_expired_license.body.title")
data.Props["Link"] = renewalLink
data.Props["LinkButton"] = T("api.templates.remove_expired_license.body.renew_button")
if err := es.sendMail(email, subject, bodyPage.Render()); err != nil {
body, nErr := es.srv.TemplatesContainer().RenderToString("remove_expired_license", data)
if nErr != nil {
return model.NewAppError("SendRemoveExpiredLicenseEmail", "api.license.remove_expired_license.failed.error", nil, nErr.Error(), http.StatusInternalServerError)
}
if err := es.sendMail(email, subject, body); err != nil {
return model.NewAppError("SendRemoveExpiredLicenseEmail", "api.license.remove_expired_license.failed.error", nil, err.Error(), http.StatusInternalServerError)
}
@@ -667,17 +743,22 @@ func (es *EmailService) SendAtUserLimitWarningEmail(email string, locale string,
subject := T("api.templates.at_limit_subject")
bodyPage := es.newEmailTemplate("reached_user_limit_body", locale)
bodyPage.Props["SiteURL"] = siteURL
bodyPage.Props["Title"] = T("api.templates.at_limit_title")
bodyPage.Props["Info1"] = T("api.templates.at_limit_info1")
bodyPage.Props["Info2"] = T("api.templates.at_limit_info2")
bodyPage.Props["Button"] = T("api.templates.upgrade_mattermost_cloud")
bodyPage.Props["EmailUs"] = T("api.templates.email_us_anytime_at")
data := es.newEmailTemplateData(locale)
data.Props["SiteURL"] = siteURL
data.Props["Title"] = T("api.templates.at_limit_title")
data.Props["Info1"] = T("api.templates.at_limit_info1")
data.Props["Info2"] = T("api.templates.at_limit_info2")
data.Props["Button"] = T("api.templates.upgrade_mattermost_cloud")
data.Props["EmailUs"] = T("api.templates.email_us_anytime_at")
bodyPage.Props["Footer"] = T("api.templates.copyright")
data.Props["Footer"] = T("api.templates.copyright")
if err := es.sendMail(email, subject, bodyPage.Render()); err != nil {
body, err := es.srv.TemplatesContainer().RenderToString("reached_user_limit_body", data)
if err != nil {
return false, model.NewAppError("SendAtUserLimitWarningEmail", "api.user.send_password_reset.send.app_error", nil, "err="+err.Error(), http.StatusInternalServerError)
}
if err := es.sendMail(email, subject, body); err != nil {
return false, model.NewAppError("SendAtUserLimitWarningEmail", "api.user.send_password_reset.send.app_error", nil, "err="+err.Error(), http.StatusInternalServerError)
}
@@ -688,25 +769,30 @@ func (es *EmailService) SendAtUserLimitWarningEmail(email string, locale string,
func (es *EmailService) SendUpgradeEmail(user, email, locale, siteURL, action string) (bool, *model.AppError) {
T := i18n.GetUserTranslations(locale)
bodyPage := es.newEmailTemplate("cloud_upgrade_request_email", locale)
subject := T("api.templates.upgrade_request_subject")
data := es.newEmailTemplateData(locale)
data.Props["Info5"] = T("api.templates.at_limit_info5")
data.Props["BillingPath"] = "admin_console/billing/subscription"
data.Props["SiteURL"] = siteURL
data.Props["Button"] = T("api.templates.upgrade_mattermost_cloud")
data.Props["EmailUs"] = T("api.templates.email_us_anytime_at")
data.Props["Footer"] = T("api.templates.copyright")
if action == model.InviteLimitation {
bodyPage.Props["Title"] = T("api.templates.upgrade_request_title", map[string]interface{}{"UserName": user})
bodyPage.Props["Info4"] = T("api.templates.upgrade_request_info4")
data.Props["Title"] = T("api.templates.upgrade_request_title", map[string]interface{}{"UserName": user})
data.Props["Info4"] = T("api.templates.upgrade_request_info4")
} else {
bodyPage.Props["Title"] = T("api.templates.upgrade_request_title2")
bodyPage.Props["Info4"] = T("api.templates.upgrade_request_info4_2")
data.Props["Title"] = T("api.templates.upgrade_request_title2")
data.Props["Info4"] = T("api.templates.upgrade_request_info4_2")
}
subject := T("api.templates.upgrade_request_subject")
bodyPage.Props["Info5"] = T("api.templates.at_limit_info5")
bodyPage.Props["BillingPath"] = "admin_console/billing/subscription"
bodyPage.Props["SiteURL"] = siteURL
bodyPage.Props["Button"] = T("api.templates.upgrade_mattermost_cloud")
bodyPage.Props["EmailUs"] = T("api.templates.email_us_anytime_at")
bodyPage.Props["Footer"] = T("api.templates.copyright")
body, err := es.srv.TemplatesContainer().RenderToString("cloud_upgrade_request_email", data)
if err != nil {
return false, model.NewAppError("SendUpgradeEmail", "api.user.send_upgrade_request_email.error", nil, err.Error(), http.StatusInternalServerError)
}
if err := es.sendMail(email, subject, bodyPage.Render()); err != nil {
if err := es.sendMail(email, subject, body); err != nil {
return false, model.NewAppError("SendUpgradeEmail", "api.user.send_upgrade_request_email.error", nil, err.Error(), http.StatusInternalServerError)
}
@@ -718,17 +804,22 @@ func (es *EmailService) SendOverUserLimitWarningEmail(email string, locale strin
subject := T("api.templates.over_limit_subject")
bodyPage := es.newEmailTemplate("reached_user_limit_body", locale)
bodyPage.Props["SiteURL"] = siteURL
bodyPage.Props["Title"] = T("api.templates.over_limit_title")
bodyPage.Props["Info1"] = T("api.templates.over_limit_info1")
bodyPage.Props["Info2"] = T("api.templates.over_limit_info2")
bodyPage.Props["Button"] = T("api.templates.upgrade_mattermost_cloud")
bodyPage.Props["EmailUs"] = T("api.templates.email_us_anytime_at")
data := es.newEmailTemplateData(locale)
data.Props["SiteURL"] = siteURL
data.Props["Title"] = T("api.templates.over_limit_title")
data.Props["Info1"] = T("api.templates.over_limit_info1")
data.Props["Info2"] = T("api.templates.over_limit_info2")
data.Props["Button"] = T("api.templates.upgrade_mattermost_cloud")
data.Props["EmailUs"] = T("api.templates.email_us_anytime_at")
bodyPage.Props["Footer"] = T("api.templates.copyright")
data.Props["Footer"] = T("api.templates.copyright")
if err := es.sendMail(email, subject, bodyPage.Render()); err != nil {
body, err := es.srv.TemplatesContainer().RenderToString("reached_user_limit_body", data)
if err != nil {
return false, model.NewAppError("SendOverUserLimitWarningEmail", "api.user.send_password_reset.send.app_error", nil, "err="+err.Error(), http.StatusInternalServerError)
}
if err := es.sendMail(email, subject, body); err != nil {
return false, model.NewAppError("SendOverUserLimitWarningEmail", "api.user.send_password_reset.send.app_error", nil, "err="+err.Error(), http.StatusInternalServerError)
}
@@ -740,20 +831,25 @@ func (es *EmailService) SendOverUserLimitThirtyDayWarningEmail(email string, loc
subject := T("api.templates.over_limit_30_days_subject")
bodyPage := es.newEmailTemplate("over_user_limit_30_days_body", locale)
bodyPage.Props["SiteURL"] = siteURL
bodyPage.Props["Title"] = T("api.templates.over_limit_30_days_title")
bodyPage.Props["Info1"] = T("api.templates.over_limit_30_days_info1")
bodyPage.Props["Info2"] = T("api.templates.over_limit_30_days_info2")
bodyPage.Props["Info2Item1"] = T("api.templates.over_limit_30_days_info2_item1")
bodyPage.Props["Info2Item2"] = T("api.templates.over_limit_30_days_info2_item2")
bodyPage.Props["Info2Item3"] = T("api.templates.over_limit_30_days_info2_item3")
bodyPage.Props["Button"] = T("api.templates.over_limit_fix_now")
bodyPage.Props["EmailUs"] = T("api.templates.email_us_anytime_at")
data := es.newEmailTemplateData(locale)
data.Props["SiteURL"] = siteURL
data.Props["Title"] = T("api.templates.over_limit_30_days_title")
data.Props["Info1"] = T("api.templates.over_limit_30_days_info1")
data.Props["Info2"] = T("api.templates.over_limit_30_days_info2")
data.Props["Info2Item1"] = T("api.templates.over_limit_30_days_info2_item1")
data.Props["Info2Item2"] = T("api.templates.over_limit_30_days_info2_item2")
data.Props["Info2Item3"] = T("api.templates.over_limit_30_days_info2_item3")
data.Props["Button"] = T("api.templates.over_limit_fix_now")
data.Props["EmailUs"] = T("api.templates.email_us_anytime_at")
bodyPage.Props["Footer"] = T("api.templates.copyright")
data.Props["Footer"] = T("api.templates.copyright")
if err := es.sendMail(email, subject, bodyPage.Render()); err != nil {
body, err := es.srv.TemplatesContainer().RenderToString("over_user_limit_30_days_body", data)
if err != nil {
return false, model.NewAppError("SendOverUserLimitWarningEmail", "api.user.send_password_reset.send.app_error", nil, "err="+err.Error(), http.StatusInternalServerError)
}
if err := es.sendMail(email, subject, body); err != nil {
return false, model.NewAppError("SendOverUserLimitWarningEmail", "api.user.send_password_reset.send.app_error", nil, "err="+err.Error(), http.StatusInternalServerError)
}
@@ -765,19 +861,24 @@ func (es *EmailService) SendOverUserLimitNinetyDayWarningEmail(email string, loc
subject := T("api.templates.over_limit_90_days_subject")
bodyPage := es.newEmailTemplate("over_user_limit_90_days_body", locale)
bodyPage.Props["SiteURL"] = siteURL
bodyPage.Props["Title"] = T("api.templates.over_limit_90_days_title")
bodyPage.Props["Info1"] = T("api.templates.over_limit_90_days_info1", map[string]interface{}{"OverLimitDate": overLimitDate})
bodyPage.Props["Info2"] = T("api.templates.over_limit_90_days_info2")
bodyPage.Props["Info3"] = T("api.templates.over_limit_90_days_info3")
bodyPage.Props["Info4"] = T("api.templates.over_limit_90_days_info4")
bodyPage.Props["Button"] = T("api.templates.over_limit_fix_now")
bodyPage.Props["EmailUs"] = T("api.templates.email_us_anytime_at")
data := es.newEmailTemplateData(locale)
data.Props["SiteURL"] = siteURL
data.Props["Title"] = T("api.templates.over_limit_90_days_title")
data.Props["Info1"] = T("api.templates.over_limit_90_days_info1", map[string]interface{}{"OverLimitDate": overLimitDate})
data.Props["Info2"] = T("api.templates.over_limit_90_days_info2")
data.Props["Info3"] = T("api.templates.over_limit_90_days_info3")
data.Props["Info4"] = T("api.templates.over_limit_90_days_info4")
data.Props["Button"] = T("api.templates.over_limit_fix_now")
data.Props["EmailUs"] = T("api.templates.email_us_anytime_at")
bodyPage.Props["Footer"] = T("api.templates.copyright")
data.Props["Footer"] = T("api.templates.copyright")
if err := es.sendMail(email, subject, bodyPage.Render()); err != nil {
body, err := es.srv.TemplatesContainer().RenderToString("over_user_limit_90_days_body", data)
if err != nil {
return false, model.NewAppError("SendOverUserLimitWarningEmail", "api.user.send_password_reset.send.app_error", nil, "err="+err.Error(), http.StatusInternalServerError)
}
if err := es.sendMail(email, subject, body); err != nil {
return false, model.NewAppError("SendOverUserLimitWarningEmail", "api.user.send_password_reset.send.app_error", nil, "err="+err.Error(), http.StatusInternalServerError)
}
@@ -789,17 +890,22 @@ func (es *EmailService) SendOverUserLimitWorkspaceSuspendedWarningEmail(email st
subject := T("api.templates.over_limit_suspended_subject")
bodyPage := es.newEmailTemplate("over_user_limit_workspace_suspended_body", locale)
bodyPage.Props["SiteURL"] = siteURL
bodyPage.Props["Title"] = T("api.templates.over_limit_suspended_title")
bodyPage.Props["Info1"] = T("api.templates.over_limit_suspended_info1")
bodyPage.Props["Info2"] = T("api.templates.over_limit_suspended_info2")
bodyPage.Props["Button"] = T("api.templates.over_limit_suspended_contact_support")
bodyPage.Props["EmailUs"] = T("api.templates.email_us_anytime_at")
data := es.newEmailTemplateData(locale)
data.Props["SiteURL"] = siteURL
data.Props["Title"] = T("api.templates.over_limit_suspended_title")
data.Props["Info1"] = T("api.templates.over_limit_suspended_info1")
data.Props["Info2"] = T("api.templates.over_limit_suspended_info2")
data.Props["Button"] = T("api.templates.over_limit_suspended_contact_support")
data.Props["EmailUs"] = T("api.templates.email_us_anytime_at")
bodyPage.Props["Footer"] = T("api.templates.copyright")
data.Props["Footer"] = T("api.templates.copyright")
if err := es.sendMail(email, subject, bodyPage.Render()); err != nil {
body, err := es.srv.TemplatesContainer().RenderToString("over_user_limit_workspace_suspended_body", data)
if err != nil {
return false, model.NewAppError("SendOverUserLimitWarningEmail", "api.user.send_password_reset.send.app_error", nil, "err="+err.Error(), http.StatusInternalServerError)
}
if err := es.sendMail(email, subject, body); err != nil {
return false, model.NewAppError("SendOverUserLimitWarningEmail", "api.user.send_password_reset.send.app_error", nil, "err="+err.Error(), http.StatusInternalServerError)
}
@@ -811,16 +917,21 @@ func (es *EmailService) SendOverUserFourteenDayWarningEmail(email string, locale
subject := T("api.templates.over_limit_14_days_subject")
bodyPage := es.newEmailTemplate("over_user_limit_7_days_body", locale)
bodyPage.Props["SiteURL"] = siteURL
bodyPage.Props["Title"] = T("api.templates.over_limit_14_days_title")
bodyPage.Props["Info1"] = T("api.templates.over_limit_14_days_info1", map[string]interface{}{"OverLimitDate": overLimitDate})
bodyPage.Props["Button"] = T("api.templates.over_limit_fix_now")
bodyPage.Props["EmailUs"] = T("api.templates.email_us_anytime_at")
data := es.newEmailTemplateData(locale)
data.Props["SiteURL"] = siteURL
data.Props["Title"] = T("api.templates.over_limit_14_days_title")
data.Props["Info1"] = T("api.templates.over_limit_14_days_info1", map[string]interface{}{"OverLimitDate": overLimitDate})
data.Props["Button"] = T("api.templates.over_limit_fix_now")
data.Props["EmailUs"] = T("api.templates.email_us_anytime_at")
bodyPage.Props["Footer"] = T("api.templates.copyright")
data.Props["Footer"] = T("api.templates.copyright")
if err := es.sendMail(email, subject, bodyPage.Render()); err != nil {
body, err := es.srv.TemplatesContainer().RenderToString("over_user_limit_7_days_body", data)
if err != nil {
return false, model.NewAppError("SendOverUserLimitWarningEmail", "api.user.send_password_reset.send.app_error", nil, "err="+err.Error(), http.StatusInternalServerError)
}
if err := es.sendMail(email, subject, body); err != nil {
return false, model.NewAppError("SendOverUserLimitWarningEmail", "api.user.send_password_reset.send.app_error", nil, "err="+err.Error(), http.StatusInternalServerError)
}
@@ -832,16 +943,21 @@ func (es *EmailService) SendOverUserSevenDayWarningEmail(email string, locale st
subject := T("api.templates.over_limit_7_days_subject")
bodyPage := es.newEmailTemplate("over_user_limit_7_days_body", locale)
bodyPage.Props["SiteURL"] = siteURL
bodyPage.Props["Title"] = T("api.templates.over_limit_7_days_title")
bodyPage.Props["Info1"] = T("api.templates.over_limit_7_days_info1")
bodyPage.Props["Button"] = T("api.templates.over_limit_fix_now")
bodyPage.Props["EmailUs"] = T("api.templates.email_us_anytime_at")
data := es.newEmailTemplateData(locale)
data.Props["SiteURL"] = siteURL
data.Props["Title"] = T("api.templates.over_limit_7_days_title")
data.Props["Info1"] = T("api.templates.over_limit_7_days_info1")
data.Props["Button"] = T("api.templates.over_limit_fix_now")
data.Props["EmailUs"] = T("api.templates.email_us_anytime_at")
bodyPage.Props["Footer"] = T("api.templates.copyright")
data.Props["Footer"] = T("api.templates.copyright")
if err := es.sendMail(email, subject, bodyPage.Render()); err != nil {
body, err := es.srv.TemplatesContainer().RenderToString("over_user_limit_7_days_body", data)
if err != nil {
return false, model.NewAppError("SendOverUserLimitWarningEmail", "api.user.send_password_reset.send.app_error", nil, "err="+err.Error(), http.StatusInternalServerError)
}
if err := es.sendMail(email, subject, body); err != nil {
return false, model.NewAppError("SendOverUserLimitWarningEmail", "api.user.send_password_reset.send.app_error", nil, "err="+err.Error(), http.StatusInternalServerError)
}
@@ -852,15 +968,20 @@ func (es *EmailService) SendSuspensionEmailToSupport(email string, installationI
// Localization not needed
subject := fmt.Sprintf("Cloud Installation %s Scheduled Suspension", installationID)
bodyPage := es.newEmailTemplate("over_user_limit_support_body", "en")
bodyPage.Props["CustomerID"] = customerID
bodyPage.Props["SiteURL"] = siteURL
bodyPage.Props["SubscriptionID"] = subscriptionID
bodyPage.Props["InstallationID"] = installationID
bodyPage.Props["SuspensionDate"] = time.Now().AddDate(0, 0, 61).Format("2006-01-02")
bodyPage.Props["UserCount"] = userCount
data := es.newEmailTemplateData("en")
data.Props["CustomerID"] = customerID
data.Props["SiteURL"] = siteURL
data.Props["SubscriptionID"] = subscriptionID
data.Props["InstallationID"] = installationID
data.Props["SuspensionDate"] = time.Now().AddDate(0, 0, 61).Format("2006-01-02")
data.Props["UserCount"] = userCount
if err := es.sendMail(email, subject, bodyPage.Render()); err != nil {
body, err := es.srv.TemplatesContainer().RenderToString("over_user_limit_support_body", data)
if err != nil {
return false, model.NewAppError("SendOverUserLimitWarningEmail", "api.user.send_password_reset.send.app_error", nil, "err="+err.Error(), http.StatusInternalServerError)
}
if err := es.sendMail(email, subject, body); err != nil {
return false, model.NewAppError("SendOverUserLimitWarningEmail", "api.user.send_password_reset.send.app_error", nil, "err="+err.Error(), http.StatusInternalServerError)
}
@@ -872,20 +993,25 @@ func (es *EmailService) SendPaymentFailedEmail(email string, locale string, fail
subject := T("api.templates.payment_failed.subject")
bodyPage := es.newEmailTemplate("payment_failed_body", locale)
bodyPage.Props["SiteURL"] = siteURL
bodyPage.Props["Title"] = T("api.templates.payment_failed.title")
bodyPage.Props["Info1"] = T("api.templates.payment_failed.info1", map[string]interface{}{"CardBrand": failedPayment.CardBrand, "LastFour": failedPayment.LastFour})
bodyPage.Props["Info2"] = T("api.templates.payment_failed.info2")
bodyPage.Props["Info3"] = T("api.templates.payment_failed.info3")
bodyPage.Props["Button"] = T("api.templates.over_limit_fix_now")
bodyPage.Props["EmailUs"] = T("api.templates.email_us_anytime_at")
data := es.newEmailTemplateData(locale)
data.Props["SiteURL"] = siteURL
data.Props["Title"] = T("api.templates.payment_failed.title")
data.Props["Info1"] = T("api.templates.payment_failed.info1", map[string]interface{}{"CardBrand": failedPayment.CardBrand, "LastFour": failedPayment.LastFour})
data.Props["Info2"] = T("api.templates.payment_failed.info2")
data.Props["Info3"] = T("api.templates.payment_failed.info3")
data.Props["Button"] = T("api.templates.over_limit_fix_now")
data.Props["EmailUs"] = T("api.templates.email_us_anytime_at")
bodyPage.Props["Footer"] = T("api.templates.copyright")
data.Props["Footer"] = T("api.templates.copyright")
bodyPage.Props["FailedReason"] = failedPayment.FailureMessage
data.Props["FailedReason"] = failedPayment.FailureMessage
if err := es.sendMail(email, subject, bodyPage.Render()); err != nil {
body, err := es.srv.TemplatesContainer().RenderToString("payment_failed_body", data)
if err != nil {
return false, model.NewAppError("SendPaymentFailedEmail", "api.user.send_password_reset.send.app_error", nil, "err="+err.Error(), http.StatusInternalServerError)
}
if err := es.sendMail(email, subject, body); err != nil {
return false, model.NewAppError("SendPaymentFailedEmail", "api.user.send_password_reset.send.app_error", nil, "err="+err.Error(), http.StatusInternalServerError)
}
@@ -897,17 +1023,22 @@ func (es *EmailService) SendNoCardPaymentFailedEmail(email string, locale string
subject := T("api.templates.payment_failed_no_card.subject")
bodyPage := es.newEmailTemplate("payment_failed_no_card_body", locale)
bodyPage.Props["SiteURL"] = siteURL
bodyPage.Props["Title"] = T("api.templates.payment_failed_no_card.title")
bodyPage.Props["Info1"] = T("api.templates.payment_failed_no_card.info1")
bodyPage.Props["Info3"] = T("api.templates.payment_failed_no_card.info3")
bodyPage.Props["Button"] = T("api.templates.payment_failed_no_card.button")
bodyPage.Props["EmailUs"] = T("api.templates.email_us_anytime_at")
data := es.newEmailTemplateData(locale)
data.Props["SiteURL"] = siteURL
data.Props["Title"] = T("api.templates.payment_failed_no_card.title")
data.Props["Info1"] = T("api.templates.payment_failed_no_card.info1")
data.Props["Info3"] = T("api.templates.payment_failed_no_card.info3")
data.Props["Button"] = T("api.templates.payment_failed_no_card.button")
data.Props["EmailUs"] = T("api.templates.email_us_anytime_at")
bodyPage.Props["Footer"] = T("api.templates.copyright")
data.Props["Footer"] = T("api.templates.copyright")
if err := es.sendMail(email, subject, bodyPage.Render()); err != nil {
body, err := es.srv.TemplatesContainer().RenderToString("payment_failed_no_card_body", data)
if err != nil {
return model.NewAppError("SendPaymentFailedEmail", "api.user.send_password_reset.send.app_error", nil, "err="+err.Error(), http.StatusInternalServerError)
}
if err := es.sendMail(email, subject, body); err != nil {
return model.NewAppError("SendPaymentFailedEmail", "api.user.send_password_reset.send.app_error", nil, "err="+err.Error(), http.StatusInternalServerError)
}

Просмотреть файл

@@ -15,7 +15,6 @@ import (
"github.com/mattermost/mattermost-server/v5/model"
"github.com/mattermost/mattermost-server/v5/shared/i18n"
"github.com/mattermost/mattermost-server/v5/shared/mlog"
"github.com/mattermost/mattermost-server/v5/utils"
)
const (
@@ -222,7 +221,13 @@ func (es *EmailService) sendBatchedEmailNotification(userID string, notification
emailNotificationContentsType = *es.srv.Config().EmailSettings.EmailNotificationContentsType
}
contents += es.renderBatchedPost(notification, channel, sender, *es.srv.Config().ServiceSettings.SiteURL, displayNameFormat, translateFunc, user.Locale, emailNotificationContentsType)
postContent, err := es.renderBatchedPost(notification, channel, sender, *es.srv.Config().ServiceSettings.SiteURL, displayNameFormat, translateFunc, user.Locale, emailNotificationContentsType)
if err != nil {
mlog.Warn("Unable to render post for batched email notification template", mlog.Err(err))
continue
}
contents += postContent
}
tm := time.Unix(notifications[0].post.CreateAt/1000, 0)
@@ -234,34 +239,39 @@ func (es *EmailService) sendBatchedEmailNotification(userID string, notification
"Day": tm.Day(),
})
body := es.newEmailTemplate("post_batched_body", user.Locale)
body.Props["SiteURL"] = *es.srv.Config().ServiceSettings.SiteURL
body.Props["Posts"] = template.HTML(contents)
body.Props["BodyText"] = translateFunc("api.email_batching.send_batched_email_notification.body_text", len(notifications))
data := es.newEmailTemplateData(user.Locale)
data.Props["SiteURL"] = *es.srv.Config().ServiceSettings.SiteURL
data.Props["Posts"] = template.HTML(contents)
data.Props["BodyText"] = translateFunc("api.email_batching.send_batched_email_notification.body_text", len(notifications))
if nErr := es.sendNotificationMail(user.Email, subject, body.Render()); nErr != nil {
body, err2 := es.srv.TemplatesContainer().RenderToString("post_batched_body", data)
if err2 != nil {
mlog.Warn("Unable build the batched email notification template", mlog.Err(err2))
return
}
if nErr := es.sendNotificationMail(user.Email, subject, body); nErr != nil {
mlog.Warn("Unable to send batched email notification", mlog.String("email", user.Email), mlog.Err(nErr))
}
}
func (es *EmailService) renderBatchedPost(notification *batchedNotification, channel *model.Channel, sender *model.User, siteURL string, displayNameFormat string, translateFunc i18n.TranslateFunc, userLocale string, emailNotificationContentsType string) string {
func (es *EmailService) renderBatchedPost(notification *batchedNotification, channel *model.Channel, sender *model.User, siteURL string, displayNameFormat string, translateFunc i18n.TranslateFunc, userLocale string, emailNotificationContentsType string) (string, error) {
// don't include message contents if email notification contents type is set to generic
var template *utils.HTMLTemplate
var templateName = "post_batched_post_generic"
if emailNotificationContentsType == model.EMAIL_NOTIFICATION_CONTENTS_FULL {
template = es.newEmailTemplate("post_batched_post_full", userLocale)
} else {
template = es.newEmailTemplate("post_batched_post_generic", userLocale)
templateName = "post_batched_post_full"
}
template.Props["Button"] = translateFunc("api.email_batching.render_batched_post.go_to_post")
template.Props["PostMessage"] = es.srv.GetMessageForNotification(notification.post, translateFunc)
template.Props["PostLink"] = siteURL + "/" + notification.teamName + "/pl/" + notification.post.Id
template.Props["SenderName"] = sender.GetDisplayName(displayNameFormat)
data := es.newEmailTemplateData(userLocale)
data.Props["Button"] = translateFunc("api.email_batching.render_batched_post.go_to_post")
data.Props["PostMessage"] = es.srv.GetMessageForNotification(notification.post, translateFunc)
data.Props["PostLink"] = siteURL + "/" + notification.teamName + "/pl/" + notification.post.Id
data.Props["SenderName"] = sender.GetDisplayName(displayNameFormat)
tm := time.Unix(notification.post.CreateAt/1000, 0)
timezone, _ := tm.Zone()
template.Props["Date"] = translateFunc("api.email_batching.render_batched_post.date", map[string]interface{}{
data.Props["Date"] = translateFunc("api.email_batching.render_batched_post.date", map[string]interface{}{
"Year": tm.Year(),
"Month": translateFunc(tm.Month().String()),
"Day": tm.Day(),
@@ -271,17 +281,17 @@ func (es *EmailService) renderBatchedPost(notification *batchedNotification, cha
})
if channel.Type == model.CHANNEL_DIRECT {
template.Props["ChannelName"] = translateFunc("api.email_batching.render_batched_post.direct_message")
data.Props["ChannelName"] = translateFunc("api.email_batching.render_batched_post.direct_message")
} else if channel.Type == model.CHANNEL_GROUP {
template.Props["ChannelName"] = translateFunc("api.email_batching.render_batched_post.group_message")
data.Props["ChannelName"] = translateFunc("api.email_batching.render_batched_post.group_message")
} else {
// don't include channel name if email notification contents type is set to generic
if emailNotificationContentsType == model.EMAIL_NOTIFICATION_CONTENTS_FULL {
template.Props["ChannelName"] = channel.DisplayName
data.Props["ChannelName"] = channel.DisplayName
} else {
template.Props["ChannelName"] = translateFunc("api.email_batching.render_batched_post.notification")
data.Props["ChannelName"] = translateFunc("api.email_batching.render_batched_post.notification")
}
}
return template.Render()
return es.srv.TemplatesContainer().RenderToString(templateName, data)
}

Просмотреть файл

@@ -304,7 +304,8 @@ func TestRenderBatchedPostGeneric(t *testing.T) {
return translationID
}
var rendered = th.Server.EmailService.renderBatchedPost(notification, channel, sender, "http://localhost:8065", "", translateFunc, "en", model.EMAIL_NOTIFICATION_CONTENTS_GENERIC)
rendered, err := th.Server.EmailService.renderBatchedPost(notification, channel, sender, "http://localhost:8065", "", translateFunc, "en", model.EMAIL_NOTIFICATION_CONTENTS_GENERIC)
require.NoError(t, err)
require.NotContains(t, rendered, post.Message, "Rendered email should not contain post contents when email notification contents type is set to Generic.")
}
@@ -329,6 +330,7 @@ func TestRenderBatchedPostFull(t *testing.T) {
return translationID
}
var rendered = th.Server.EmailService.renderBatchedPost(notification, channel, sender, "http://localhost:8065", "", translateFunc, "en", model.EMAIL_NOTIFICATION_CONTENTS_FULL)
rendered, err := th.Server.EmailService.renderBatchedPost(notification, channel, sender, "http://localhost:8065", "", translateFunc, "en", model.EMAIL_NOTIFICATION_CONTENTS_FULL)
require.NoError(t, err)
require.Contains(t, rendered, post.Message, "Rendered email should contain post contents when email notification contents type is set to Full.")
}

Просмотреть файл

@@ -231,7 +231,10 @@ func (a *App) SendNotifications(post *model.Post, team *model.Team, channel *mod
}
if a.userAllowsEmail(profileMap[id], channelMemberNotifyPropsMap[id], post) {
a.sendNotificationEmail(notification, profileMap[id], team)
err := a.sendNotificationEmail(notification, profileMap[id], team)
if err != nil {
mlog.Warn("Unable to send notification email.", mlog.Err(err))
}
}
}
}

Просмотреть файл

@@ -7,7 +7,6 @@ import (
"fmt"
"html"
"html/template"
"net/http"
"net/url"
"path/filepath"
"strings"
@@ -16,17 +15,17 @@ import (
"github.com/mattermost/mattermost-server/v5/model"
"github.com/mattermost/mattermost-server/v5/shared/i18n"
"github.com/mattermost/mattermost-server/v5/shared/mlog"
"github.com/mattermost/mattermost-server/v5/utils"
"github.com/pkg/errors"
)
func (a *App) sendNotificationEmail(notification *PostNotification, user *model.User, team *model.Team) *model.AppError {
func (a *App) sendNotificationEmail(notification *PostNotification, user *model.User, team *model.Team) error {
channel := notification.Channel
post := notification.Post
if channel.IsGroupOrDirect() {
teams, err := a.Srv().Store.Team().GetTeamsByUserId(user.Id)
if err != nil {
return model.NewAppError("sendNotificationEmail", "app.team.get_all.app_error", nil, err.Error(), http.StatusInternalServerError)
return errors.Wrap(err, "unable to get user teams")
}
// if the recipient isn't in the current user's team, just pick one
@@ -97,7 +96,10 @@ func (a *App) sendNotificationEmail(notification *PostNotification, user *model.
}
landingURL := a.GetSiteURL() + "/landing#/" + team.Name
var bodyText = a.getNotificationEmailBody(user, post, channel, channelName, senderName, team.Name, landingURL, emailNotificationContentsType, useMilitaryTime, translateFunc)
var bodyText, err = a.getNotificationEmailBody(user, post, channel, channelName, senderName, team.Name, landingURL, emailNotificationContentsType, useMilitaryTime, translateFunc)
if err != nil {
return errors.Wrap(err, "unable to render the email notification template")
}
a.Srv().Go(func() {
if nErr := a.Srv().EmailService.sendNotificationMail(user.Email, html.UnescapeString(subjectText), bodyText); nErr != nil {
@@ -163,11 +165,12 @@ func getGroupMessageNotificationEmailSubject(user *model.User, post *model.Post,
/**
* Computes the email body for notification messages
*/
func (a *App) getNotificationEmailBody(recipient *model.User, post *model.Post, channel *model.Channel, channelName string, senderName string, teamName string, landingURL string, emailNotificationContentsType string, useMilitaryTime bool, translateFunc i18n.TranslateFunc) string {
func (a *App) getNotificationEmailBody(recipient *model.User, post *model.Post, channel *model.Channel, channelName string, senderName string, teamName string, landingURL string, emailNotificationContentsType string, useMilitaryTime bool, translateFunc i18n.TranslateFunc) (string, error) {
// only include message contents in notification email if email notification contents type is set to full
var bodyPage *utils.HTMLTemplate
var templateName = "post_body_generic"
data := a.Srv().EmailService.newEmailTemplateData(recipient.Locale)
if emailNotificationContentsType == model.EMAIL_NOTIFICATION_CONTENTS_FULL {
bodyPage = a.Srv().EmailService.newEmailTemplate("post_body_full", recipient.Locale)
templateName = "post_body_full"
postMessage := a.GetMessageForNotification(post, translateFunc)
postMessage = html.EscapeString(postMessage)
normalizedPostMessage, err := a.generateHyperlinkForChannels(postMessage, teamName, landingURL)
@@ -175,16 +178,14 @@ func (a *App) getNotificationEmailBody(recipient *model.User, post *model.Post,
mlog.Warn("Encountered error while generating hyperlink for channels", mlog.String("team_name", teamName), mlog.Err(err))
normalizedPostMessage = postMessage
}
bodyPage.Props["PostMessage"] = template.HTML(normalizedPostMessage)
} else {
bodyPage = a.Srv().EmailService.newEmailTemplate("post_body_generic", recipient.Locale)
data.Props["PostMessage"] = template.HTML(normalizedPostMessage)
}
bodyPage.Props["SiteURL"] = a.GetSiteURL()
data.Props["SiteURL"] = a.GetSiteURL()
if teamName != "select_team" {
bodyPage.Props["TeamLink"] = landingURL + "/pl/" + post.Id
data.Props["TeamLink"] = landingURL + "/pl/" + post.Id
} else {
bodyPage.Props["TeamLink"] = landingURL
data.Props["TeamLink"] = landingURL
}
t := getFormattedPostTime(recipient, post, useMilitaryTime, translateFunc)
@@ -198,51 +199,51 @@ func (a *App) getNotificationEmailBody(recipient *model.User, post *model.Post,
}
if channel.Type == model.CHANNEL_DIRECT {
if emailNotificationContentsType == model.EMAIL_NOTIFICATION_CONTENTS_FULL {
bodyPage.Props["BodyText"] = translateFunc("app.notification.body.intro.direct.full")
bodyPage.Props["Info1"] = ""
data.Props["BodyText"] = translateFunc("app.notification.body.intro.direct.full")
data.Props["Info1"] = ""
info["SenderName"] = senderName
bodyPage.Props["Info2"] = translateFunc("app.notification.body.text.direct.full", info)
data.Props["Info2"] = translateFunc("app.notification.body.text.direct.full", info)
} else {
bodyPage.Props["BodyText"] = translateFunc("app.notification.body.intro.direct.generic", map[string]interface{}{
data.Props["BodyText"] = translateFunc("app.notification.body.intro.direct.generic", map[string]interface{}{
"SenderName": senderName,
})
bodyPage.Props["Info"] = translateFunc("app.notification.body.text.direct.generic", info)
data.Props["Info"] = translateFunc("app.notification.body.text.direct.generic", info)
}
} else if channel.Type == model.CHANNEL_GROUP {
if emailNotificationContentsType == model.EMAIL_NOTIFICATION_CONTENTS_FULL {
bodyPage.Props["BodyText"] = translateFunc("app.notification.body.intro.group_message.full")
bodyPage.Props["Info1"] = translateFunc("app.notification.body.text.group_message.full",
data.Props["BodyText"] = translateFunc("app.notification.body.intro.group_message.full")
data.Props["Info1"] = translateFunc("app.notification.body.text.group_message.full",
map[string]interface{}{
"ChannelName": channelName,
})
info["SenderName"] = senderName
bodyPage.Props["Info2"] = translateFunc("app.notification.body.text.group_message.full2", info)
data.Props["Info2"] = translateFunc("app.notification.body.text.group_message.full2", info)
} else {
bodyPage.Props["BodyText"] = translateFunc("app.notification.body.intro.group_message.generic", map[string]interface{}{
data.Props["BodyText"] = translateFunc("app.notification.body.intro.group_message.generic", map[string]interface{}{
"SenderName": senderName,
})
bodyPage.Props["Info"] = translateFunc("app.notification.body.text.group_message.generic", info)
data.Props["Info"] = translateFunc("app.notification.body.text.group_message.generic", info)
}
} else {
if emailNotificationContentsType == model.EMAIL_NOTIFICATION_CONTENTS_FULL {
bodyPage.Props["BodyText"] = translateFunc("app.notification.body.intro.notification.full")
bodyPage.Props["Info1"] = translateFunc("app.notification.body.text.notification.full",
data.Props["BodyText"] = translateFunc("app.notification.body.intro.notification.full")
data.Props["Info1"] = translateFunc("app.notification.body.text.notification.full",
map[string]interface{}{
"ChannelName": channelName,
})
info["SenderName"] = senderName
bodyPage.Props["Info2"] = translateFunc("app.notification.body.text.notification.full2", info)
data.Props["Info2"] = translateFunc("app.notification.body.text.notification.full2", info)
} else {
bodyPage.Props["BodyText"] = translateFunc("app.notification.body.intro.notification.generic", map[string]interface{}{
data.Props["BodyText"] = translateFunc("app.notification.body.intro.notification.generic", map[string]interface{}{
"SenderName": senderName,
})
bodyPage.Props["Info"] = translateFunc("app.notification.body.text.notification.generic", info)
data.Props["Info"] = translateFunc("app.notification.body.text.notification.generic", info)
}
}
bodyPage.Props["Button"] = translateFunc("api.templates.post_body.button")
data.Props["Button"] = translateFunc("api.templates.post_body.button")
return bodyPage.Render()
return a.Srv().TemplatesContainer().RenderToString(templateName, data)
}
type formattedPostTime struct {

Просмотреть файл

@@ -91,7 +91,8 @@ func TestGetNotificationEmailBodyFullNotificationPublicChannel(t *testing.T) {
teamStoreMock.On("GetByName", "testteam").Return(&model.Team{Name: "testteam"}, nil)
storeMock.On("Team").Return(&teamStoreMock)
body := th.App.getNotificationEmailBody(recipient, post, channel, channelName, senderName, teamName, teamURL, emailNotificationContentsType, true, translateFunc)
body, err := th.App.getNotificationEmailBody(recipient, post, channel, channelName, senderName, teamName, teamURL, emailNotificationContentsType, true, translateFunc)
require.NoError(t, err)
require.Contains(t, body, "You have a new notification.", fmt.Sprintf("Expected email text 'You have a new notification. Got %s", body))
require.Contains(t, body, "Channel: "+channel.DisplayName, "Expected email text 'Channel: %s'. Got %s", channel.DisplayName, body)
require.Contains(t, body, senderName+" - ", fmt.Sprintf("Expected email text '%s - '. Got %s", senderName, body))
@@ -123,7 +124,8 @@ func TestGetNotificationEmailBodyFullNotificationGroupChannel(t *testing.T) {
teamStoreMock.On("GetByName", "testteam").Return(&model.Team{Name: "testteam"}, nil)
storeMock.On("Team").Return(&teamStoreMock)
body := th.App.getNotificationEmailBody(recipient, post, channel, channelName, senderName, teamName, teamURL, emailNotificationContentsType, true, translateFunc)
body, err := th.App.getNotificationEmailBody(recipient, post, channel, channelName, senderName, teamName, teamURL, emailNotificationContentsType, true, translateFunc)
require.NoError(t, err)
require.Contains(t, body, "You have a new Group Message.", fmt.Sprintf("Expected email text 'You have a new Group Message. Got "+body))
require.Contains(t, body, "Channel: ChannelName", fmt.Sprintf("Expected email text 'Channel: ChannelName'. Got %s", body))
require.Contains(t, body, senderName+" - ", fmt.Sprintf("Expected email text '%s - '. Got %s", senderName, body))
@@ -155,7 +157,8 @@ func TestGetNotificationEmailBodyFullNotificationPrivateChannel(t *testing.T) {
teamStoreMock.On("GetByName", "testteam").Return(&model.Team{Name: "testteam"}, nil)
storeMock.On("Team").Return(&teamStoreMock)
body := th.App.getNotificationEmailBody(recipient, post, channel, channelName, senderName, teamName, teamURL, emailNotificationContentsType, true, translateFunc)
body, err := th.App.getNotificationEmailBody(recipient, post, channel, channelName, senderName, teamName, teamURL, emailNotificationContentsType, true, translateFunc)
require.NoError(t, err)
require.Contains(t, body, "You have a new notification.", fmt.Sprintf("Expected email text 'You have a new notification. Got "+body))
require.Contains(t, body, "Channel: "+channel.DisplayName, fmt.Sprintf("Expected email text 'Channel: "+channel.DisplayName+"'. Got "+body))
require.Contains(t, body, senderName+" - ", fmt.Sprintf("Expected email text '%s - '. Got %s", senderName, body))
@@ -187,7 +190,8 @@ func TestGetNotificationEmailBodyFullNotificationDirectChannel(t *testing.T) {
teamStoreMock.On("GetByName", "testteam").Return(&model.Team{Name: "testteam"}, nil)
storeMock.On("Team").Return(&teamStoreMock)
body := th.App.getNotificationEmailBody(recipient, post, channel, channelName, senderName, teamName, teamURL, emailNotificationContentsType, true, translateFunc)
body, err := th.App.getNotificationEmailBody(recipient, post, channel, channelName, senderName, teamName, teamURL, emailNotificationContentsType, true, translateFunc)
require.NoError(t, err)
require.Contains(t, body, "You have a new Direct Message.", fmt.Sprintf("Expected email text 'You have a new Direct Message. Got "+body))
require.Contains(t, body, senderName+" - ", fmt.Sprintf("Expected email text '%s - '. Got %s", senderName, body))
require.Contains(t, body, post.Message, fmt.Sprintf("Expected email text '%s'. Got %s", post.Message, body))
@@ -222,7 +226,8 @@ func TestGetNotificationEmailBodyFullNotificationLocaleTimeWithTimezone(t *testi
teamStoreMock.On("GetByName", "testteam").Return(&model.Team{Name: "testteam"}, nil)
storeMock.On("Team").Return(&teamStoreMock)
body := th.App.getNotificationEmailBody(recipient, post, channel, channelName, senderName, teamName, teamURL, emailNotificationContentsType, false, translateFunc)
body, err := th.App.getNotificationEmailBody(recipient, post, channel, channelName, senderName, teamName, teamURL, emailNotificationContentsType, false, translateFunc)
require.NoError(t, err)
r, _ := regexp.Compile("E([S|D]+)T")
zone := r.FindString(body)
require.Contains(t, body, "sender - 9:43 AM "+zone+", April 25", fmt.Sprintf("Expected email text 'sender - 9:43 AM %s, April 25'. Got %s", zone, body))
@@ -274,7 +279,8 @@ func TestGetNotificationEmailBodyFullNotificationLocaleTimeNoTimezone(t *testing
err = tmp.Execute(&text, fmt.Sprintf("sender - %s:%s %s, %s %s", formattedTime.Hour, formattedTime.Minute, formattedTime.TimeZone, formattedTime.Month, formattedTime.Day))
require.NoError(t, err)
body := th.App.getNotificationEmailBody(recipient, post, channel, channelName, senderName, teamName, teamURL, emailNotificationContentsType, true, translateFunc)
body, err := th.App.getNotificationEmailBody(recipient, post, channel, channelName, senderName, teamName, teamURL, emailNotificationContentsType, true, translateFunc)
require.NoError(t, err)
postTimeLine := text.String()
require.Contains(t, body, postTimeLine, fmt.Sprintf("Expected email text '%s'. Got %s", postTimeLine, body))
}
@@ -307,7 +313,8 @@ func TestGetNotificationEmailBodyFullNotificationLocaleTime12Hour(t *testing.T)
teamStoreMock.On("GetByName", "testteam").Return(&model.Team{Name: "testteam"}, nil)
storeMock.On("Team").Return(&teamStoreMock)
body := th.App.getNotificationEmailBody(recipient, post, channel, channelName, senderName, teamName, teamURL, emailNotificationContentsType, false, translateFunc)
body, err := th.App.getNotificationEmailBody(recipient, post, channel, channelName, senderName, teamName, teamURL, emailNotificationContentsType, false, translateFunc)
require.NoError(t, err)
require.Contains(t, body, "sender - 2:30 PM", fmt.Sprintf("Expected email text 'sender - 2:30 PM'. Got %s", body))
require.Contains(t, body, "April 25", fmt.Sprintf("Expected email text 'April 25'. Got %s", body))
}
@@ -340,7 +347,8 @@ func TestGetNotificationEmailBodyFullNotificationLocaleTime24Hour(t *testing.T)
teamStoreMock.On("GetByName", "testteam").Return(&model.Team{Name: "testteam"}, nil)
storeMock.On("Team").Return(&teamStoreMock)
body := th.App.getNotificationEmailBody(recipient, post, channel, channelName, senderName, teamName, teamURL, emailNotificationContentsType, true, translateFunc)
body, err := th.App.getNotificationEmailBody(recipient, post, channel, channelName, senderName, teamName, teamURL, emailNotificationContentsType, true, translateFunc)
require.NoError(t, err)
require.Contains(t, body, "sender - 14:30", fmt.Sprintf("Expected email text 'sender - 14:30'. Got %s", body))
require.Contains(t, body, "April 25", fmt.Sprintf("Expected email text 'April 25'. Got %s", body))
}
@@ -370,7 +378,8 @@ func TestGetNotificationEmailBodyGenericNotificationPublicChannel(t *testing.T)
teamStoreMock.On("GetByName", "testteam").Return(&model.Team{Name: "testteam"}, nil)
storeMock.On("Team").Return(&teamStoreMock)
body := th.App.getNotificationEmailBody(recipient, post, channel, channelName, senderName, teamName, teamURL, emailNotificationContentsType, true, translateFunc)
body, err := th.App.getNotificationEmailBody(recipient, post, channel, channelName, senderName, teamName, teamURL, emailNotificationContentsType, true, translateFunc)
require.NoError(t, err)
require.Contains(t, body, "You have a new notification from "+senderName, fmt.Sprintf("Expected email text 'You have a new notification from %s'. Got %s", senderName, body))
require.False(t, strings.Contains(body, "Channel: "+channel.DisplayName), fmt.Sprintf("Did not expect email text 'CHANNEL: %s'. Got %s", channel.DisplayName, body))
require.False(t, strings.Contains(body, post.Message), fmt.Sprintf("Did not expect email text '%s'. Got %s", post.Message, body))
@@ -401,7 +410,8 @@ func TestGetNotificationEmailBodyGenericNotificationGroupChannel(t *testing.T) {
teamStoreMock.On("GetByName", "testteam").Return(&model.Team{Name: "testteam"}, nil)
storeMock.On("Team").Return(&teamStoreMock)
body := th.App.getNotificationEmailBody(recipient, post, channel, channelName, senderName, teamName, teamURL, emailNotificationContentsType, true, translateFunc)
body, err := th.App.getNotificationEmailBody(recipient, post, channel, channelName, senderName, teamName, teamURL, emailNotificationContentsType, true, translateFunc)
require.NoError(t, err)
require.Contains(t, body, "You have a new Group Message from "+senderName, fmt.Sprintf("Expected email text 'You have a new Group Message from %s'. Got %s", senderName, body))
require.False(t, strings.Contains(body, "CHANNEL: "+channel.DisplayName), fmt.Sprintf("Did not expect email text 'CHANNEL: %s'. Got %s", channel.DisplayName, body))
require.False(t, strings.Contains(body, post.Message), fmt.Sprintf("Did not expect email text '%s'. Got %s", post.Message, body))
@@ -432,7 +442,8 @@ func TestGetNotificationEmailBodyGenericNotificationPrivateChannel(t *testing.T)
teamStoreMock.On("GetByName", "testteam").Return(&model.Team{Name: "testteam"}, nil)
storeMock.On("Team").Return(&teamStoreMock)
body := th.App.getNotificationEmailBody(recipient, post, channel, channelName, senderName, teamName, teamURL, emailNotificationContentsType, true, translateFunc)
body, err := th.App.getNotificationEmailBody(recipient, post, channel, channelName, senderName, teamName, teamURL, emailNotificationContentsType, true, translateFunc)
require.NoError(t, err)
require.Contains(t, body, "You have a new notification from "+senderName, fmt.Sprintf("Expected email text 'You have a new notification from %s'. Got %s", senderName, body))
require.False(t, strings.Contains(body, "CHANNEL: "+channel.DisplayName), fmt.Sprintf("Did not expect email text 'CHANNEL: %s'. Got %s", channel.DisplayName, body))
require.False(t, strings.Contains(body, post.Message), fmt.Sprintf("Did not expect email text '%s'. Got %s", post.Message, body))
@@ -463,7 +474,8 @@ func TestGetNotificationEmailBodyGenericNotificationDirectChannel(t *testing.T)
teamStoreMock.On("GetByName", "testteam").Return(&model.Team{Name: "testteam"}, nil)
storeMock.On("Team").Return(&teamStoreMock)
body := th.App.getNotificationEmailBody(recipient, post, channel, channelName, senderName, teamName, teamURL, emailNotificationContentsType, true, translateFunc)
body, err := th.App.getNotificationEmailBody(recipient, post, channel, channelName, senderName, teamName, teamURL, emailNotificationContentsType, true, translateFunc)
require.NoError(t, err)
require.Contains(t, body, "You have a new Direct Message from "+senderName, fmt.Sprintf("Expected email text 'You have a new Direct Message from "+senderName+"'. Got "+body))
require.False(t, strings.Contains(body, "CHANNEL: "+channel.DisplayName), fmt.Sprintf("Did not expect email text 'CHANNEL: %s'. Got %s", channel.DisplayName, body))
require.False(t, strings.Contains(body, post.Message), fmt.Sprintf("Did not expect email text '%s'. Got %s", post.Message, body))
@@ -496,9 +508,10 @@ func TestGetNotificationEmailEscapingChars(t *testing.T) {
teamStoreMock.On("GetByName", "testteam").Return(&model.Team{Name: "testteam"}, nil)
storeMock.On("Team").Return(&teamStoreMock)
body := th.App.getNotificationEmailBody(recipient, post, ch,
body, err := th.App.getNotificationEmailBody(recipient, post, ch,
channelName, senderName, teamName, teamURL,
emailNotificationContentsType, true, translateFunc)
require.NoError(t, err)
assert.NotContains(t, body, message)
}
@@ -539,9 +552,10 @@ func TestGetNotificationEmailBodyPublicChannelMention(t *testing.T) {
channelStoreMock.On("GetByNames", "test", []string{ch.Name}, true).Return([]*model.Channel{ch}, nil)
storeMock.On("Channel").Return(&channelStoreMock)
body := th.App.getNotificationEmailBody(recipient, post, ch,
body, err := th.App.getNotificationEmailBody(recipient, post, ch,
ch.Name, senderName, teamName, teamURL,
emailNotificationContentsType, true, translateFunc)
require.NoError(t, err)
channelURL := teamURL + "/channels/" + ch.Name
mention := "~" + ch.Name
assert.Contains(t, body, "<a href='"+channelURL+"'>"+mention+"</a>")
@@ -604,9 +618,10 @@ func TestGetNotificationEmailBodyMultiPublicChannelMention(t *testing.T) {
channelStoreMock.On("GetByNames", "test", []string{ch.Name, ch2.Name, ch3.Name}, true).Return([]*model.Channel{ch, ch2, ch3}, nil)
storeMock.On("Channel").Return(&channelStoreMock)
body := th.App.getNotificationEmailBody(recipient, post, ch,
body, err := th.App.getNotificationEmailBody(recipient, post, ch,
ch.Name, senderName, teamName, teamURL,
emailNotificationContentsType, true, translateFunc)
require.NoError(t, err)
channelURL := teamURL + "/channels/" + ch.Name
channelURL2 := teamURL + "/channels/" + ch2.Name
channelURL3 := teamURL + "/channels/" + ch3.Name
@@ -652,9 +667,10 @@ func TestGetNotificationEmailBodyPrivateChannelMention(t *testing.T) {
channelStoreMock.On("GetByNames", "test", []string{ch.Name}, true).Return([]*model.Channel{ch}, nil)
storeMock.On("Channel").Return(&channelStoreMock)
body := th.App.getNotificationEmailBody(recipient, post, ch,
body, err := th.App.getNotificationEmailBody(recipient, post, ch,
ch.Name, senderName, teamName, teamURL,
emailNotificationContentsType, true, translateFunc)
require.NoError(t, err)
channelURL := teamURL + "/channels/" + ch.Name
mention := "~" + ch.Name
assert.NotContains(t, body, "<a href='"+channelURL+"'>"+mention+"</a>")
@@ -797,7 +813,8 @@ func TestLandingLink(t *testing.T) {
teamStoreMock.On("GetByName", "testteam").Return(&model.Team{Name: "testteam"}, nil)
storeMock.On("Team").Return(&teamStoreMock)
body := th.App.getNotificationEmailBody(recipient, post, channel, channelName, senderName, teamName, teamURL, emailNotificationContentsType, true, translateFunc)
body, err := th.App.getNotificationEmailBody(recipient, post, channel, channelName, senderName, teamName, teamURL, emailNotificationContentsType, true, translateFunc)
require.NoError(t, err)
require.Contains(t, body, teamURL, fmt.Sprintf("Expected email text '%s'. Got %s", teamURL, body))
}
@@ -826,6 +843,7 @@ func TestLandingLinkPermalink(t *testing.T) {
teamStoreMock.On("GetByName", "testteam").Return(&model.Team{Name: "testteam"}, nil)
storeMock.On("Team").Return(&teamStoreMock)
body := th.App.getNotificationEmailBody(recipient, post, channel, channelName, senderName, teamName, teamURL, emailNotificationContentsType, true, translateFunc)
body, err := th.App.getNotificationEmailBody(recipient, post, channel, channelName, senderName, teamName, teamURL, emailNotificationContentsType, true, translateFunc)
require.NoError(t, err)
require.Contains(t, body, teamURL+"/pl/"+post.Id, fmt.Sprintf("Expected email text '%s'. Got %s", teamURL, body))
}

Просмотреть файл

@@ -58,6 +58,7 @@ import (
"github.com/mattermost/mattermost-server/v5/shared/filestore"
"github.com/mattermost/mattermost-server/v5/shared/i18n"
"github.com/mattermost/mattermost-server/v5/shared/mlog"
"github.com/mattermost/mattermost-server/v5/shared/templates"
"github.com/mattermost/mattermost-server/v5/store"
"github.com/mattermost/mattermost-server/v5/store/localcachelayer"
"github.com/mattermost/mattermost-server/v5/store/retrylayer"
@@ -65,6 +66,7 @@ import (
"github.com/mattermost/mattermost-server/v5/store/sqlstore"
"github.com/mattermost/mattermost-server/v5/store/timerlayer"
"github.com/mattermost/mattermost-server/v5/utils"
"github.com/mattermost/mattermost-server/v5/utils/fileutils"
)
var MaxNotificationsPerChannelDefault int64 = 1000000
@@ -133,7 +135,7 @@ type Server struct {
newStore func() (store.Store, error)
htmlTemplateWatcher *utils.HTMLTemplateWatcher
htmlTemplateWatcher *templates.Container
sessionCache cache.Cache
seenPendingPostIdsCache cache.Cache
statusCache cache.Cache
@@ -391,9 +393,19 @@ func NewServer(options ...Option) (*Server, error) {
}
}
if htmlTemplateWatcher, err2 := utils.NewHTMLTemplateWatcher("templates"); err2 != nil {
mlog.Error("Failed to parse server templates", mlog.Err(err2))
templatesDir, ok := fileutils.FindDir("templates")
if !ok {
mlog.Error("Failed find server templates", mlog.String("directory", "templates"))
} else {
htmlTemplateWatcher, errorsChan, err2 := templates.NewWithWatcher(templatesDir)
if err2 != nil {
return nil, errors.Wrap(err2, "cannot initialize server templates")
}
s.Go(func() {
for err2 := range errorsChan {
mlog.Warn("Server templates error", mlog.Err(err2))
}
})
s.htmlTemplateWatcher = htmlTemplateWatcher
}
@@ -865,13 +877,10 @@ func (s *Server) Shutdown() {
// Push notification hub needs to be shutdown after HTTP server
// to prevent stray requests from generating a push notification after it's shut down.
s.StopPushNotificationsHubWorkers()
s.htmlTemplateWatcher.Close()
s.WaitForGoroutines()
if s.htmlTemplateWatcher != nil {
s.htmlTemplateWatcher.Close()
}
if s.advancedLogListenerCleanup != nil {
s.advancedLogListenerCleanup()
s.advancedLogListenerCleanup = nil

Просмотреть файл

@@ -38,7 +38,7 @@ type testHelper struct {
// Setup creates an instance of testHelper.
func Setup(t testing.TB) *testHelper {
dir, err := ioutil.TempDir("", "testHelper")
dir, err := testlib.SetupTestResources()
if err != nil {
panic("failed to create temporary directory: " + err.Error())
}
@@ -60,7 +60,7 @@ func Setup(t testing.TB) *testHelper {
// Setup creates an instance of testHelper.
func SetupWithStoreMock(t testing.TB) *testHelper {
dir, err := ioutil.TempDir("", "testHelper")
dir, err := testlib.SetupTestResources()
if err != nil {
panic("failed to create temporary directory: " + err.Error())
}

Просмотреть файл

@@ -9,9 +9,6 @@ import (
"testing"
"github.com/stretchr/testify/require"
"github.com/mattermost/mattermost-server/v5/utils"
"github.com/mattermost/mattermost-server/v5/utils/fileutils"
)
func TestConfigFlag(t *testing.T) {
@@ -19,16 +16,14 @@ func TestConfigFlag(t *testing.T) {
defer th.TearDown()
dir := th.TemporaryDirectory()
i18n, ok := fileutils.FindDir("i18n")
require.True(t, ok)
require.NoError(t, utils.CopyDir(i18n, filepath.Join(dir, "i18n")))
prevDir, err := os.Getwd()
require.NoError(t, err)
defer os.Chdir(prevDir)
os.Chdir(dir)
t.Run("version without a config file should fail", func(t *testing.T) {
err := os.RemoveAll("config")
require.NoError(t, err)
th.SetAutoConfig(false)
defer th.SetAutoConfig(true)
require.Error(t, th.RunCommand(t, "version"))

143
shared/templates/templates.go Обычный файл
Просмотреть файл

@@ -0,0 +1,143 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
package templates
import (
"bytes"
"html/template"
"io"
"path/filepath"
"sync"
"github.com/fsnotify/fsnotify"
)
// Container represents a set of templates that can be render
type Container struct {
templates *template.Template
mutex sync.RWMutex
stop chan struct{}
stopped chan struct{}
watch bool
}
// Data contains the data used to populate the template variables, it has Props
// that can be of any type and HTML that only can be `template.HTML` types.
type Data struct {
Props map[string]interface{}
HTML map[string]template.HTML
}
// NewFromTemplates creates a new templates container using a
// `template.Template` object
func NewFromTemplate(templates *template.Template) *Container {
return &Container{templates: templates}
}
// New creates a new templates container scanning a directory.
func New(directory string) (*Container, error) {
c := &Container{}
htmlTemplates, err := template.ParseGlob(filepath.Join(directory, "*.html"))
if err != nil {
return nil, err
}
c.templates = htmlTemplates
return c, nil
}
// NewWithWatcher creates a new templates container scanning a directory and
// watch the directory filesystem changes to apply them to the loaded
// templates. This function returns the container and an errors channel to pass
// all errors that can happen during the watch process, or an regular error if
// we fail to create the templates or the watcher. The caller must consume the
// returned errors channel to ensure not blocking the watch process.
func NewWithWatcher(directory string) (*Container, <-chan error, error) {
htmlTemplates, err := template.ParseGlob(filepath.Join(directory, "*.html"))
if err != nil {
return nil, nil, err
}
watcher, err := fsnotify.NewWatcher()
if err != nil {
return nil, nil, err
}
err = watcher.Add(directory)
if err != nil {
watcher.Close()
return nil, nil, err
}
c := &Container{
templates: htmlTemplates,
watch: true,
stop: make(chan struct{}),
stopped: make(chan struct{}),
}
errors := make(chan error)
go func() {
defer close(errors)
defer close(c.stopped)
defer watcher.Close()
for {
select {
case <-c.stop:
return
case event := <-watcher.Events:
if event.Op&fsnotify.Write == fsnotify.Write {
if htmlTemplates, err := template.ParseGlob(filepath.Join(directory, "*.html")); err != nil {
errors <- err
} else {
c.mutex.Lock()
c.templates = htmlTemplates
c.mutex.Unlock()
}
}
case err := <-watcher.Errors:
errors <- err
}
}
}()
return c, errors, nil
}
// Close stops the templates watcher of the container in case you have created
// it with watch parameter set to true
func (c *Container) Close() {
c.mutex.RLock()
defer c.mutex.RUnlock()
if c.watch {
close(c.stop)
<-c.stopped
}
}
// RenderToString renders the template referenced with the template name using
// the data provided and return a string with the result
func (c *Container) RenderToString(templateName string, data Data) (string, error) {
var text bytes.Buffer
if err := c.Render(&text, templateName, data); err != nil {
return "", err
}
return text.String(), nil
}
// RenderToString renders the template referenced with the template name using
// the data provided and write it to the writer provided
func (c *Container) Render(w io.Writer, templateName string, data Data) error {
c.mutex.RLock()
htmlTemplates := c.templates
c.mutex.RUnlock()
if err := htmlTemplates.ExecuteTemplate(w, templateName, data); err != nil {
return err
}
return nil
}

115
shared/templates/templates_test.go Обычный файл
Просмотреть файл

@@ -0,0 +1,115 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
package templates
import (
"bytes"
"html/template"
"io/ioutil"
"os"
"path/filepath"
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestHTMLTemplateWatcher(t *testing.T) {
dir, err := ioutil.TempDir("", "")
require.NoError(t, err)
defer os.RemoveAll(dir)
require.NoError(t, os.Mkdir(filepath.Join(dir, "templates"), 0700))
require.NoError(t, ioutil.WriteFile(filepath.Join(dir, "templates", "foo.html"), []byte(`{{ define "foo" }}foo{{ end }}`), 0600))
prevDir, err := os.Getwd()
require.NoError(t, err)
defer os.Chdir(prevDir)
os.Chdir(dir)
watcher, errChan, err := NewWithWatcher("templates")
require.NoError(t, err)
require.NotNil(t, watcher)
select {
case msg := <-errChan:
err = msg
default:
err = nil
}
require.NoError(t, err)
defer watcher.Close()
text, err := watcher.RenderToString("foo", Data{})
require.NoError(t, err)
assert.Equal(t, "foo", text)
require.NoError(t, ioutil.WriteFile(filepath.Join(dir, "templates", "foo.html"), []byte(`{{ define "foo" }}bar{{ end }}`), 0600))
require.Eventually(t, func() bool {
text, err := watcher.RenderToString("foo", Data{})
return text == "bar" && err == nil
}, time.Millisecond*1000, time.Millisecond*50)
}
func TestNewWithWatcher_BadDirectory(t *testing.T) {
watcher, errChan, err := NewWithWatcher("notarealdirectory")
require.Error(t, err)
assert.Nil(t, watcher)
assert.Nil(t, errChan)
}
func TestNew_BadDirectory(t *testing.T) {
watcher, err := New("notarealdirectory")
assert.Nil(t, watcher)
assert.Error(t, err)
}
func TestRender(t *testing.T) {
tpl := template.New("test")
_, err := tpl.Parse(`{{ define "foo" }}foo{{ .Props.Bar }}{{ end }}`)
require.NoError(t, err)
mt := NewFromTemplate(tpl)
data := Data{
Props: map[string]interface{}{
"Bar": "bar",
},
}
text, err := mt.RenderToString("foo", data)
require.NoError(t, err)
assert.Equal(t, "foobar", text)
buf := &bytes.Buffer{}
require.NoError(t, mt.Render(buf, "foo", data))
assert.Equal(t, "foobar", buf.String())
}
func TestRenderError(t *testing.T) {
tpl := template.New("test")
_, err := tpl.Parse(`{{ define "foo" }}foo{{ .Foo.Bar }}bar{{ end }}`)
require.NoError(t, err)
mt := NewFromTemplate(tpl)
text, err := mt.RenderToString("foo", Data{})
require.Error(t, err)
assert.Equal(t, "", text)
buf := &bytes.Buffer{}
assert.Error(t, mt.Render(buf, "foo", Data{}))
assert.Equal(t, "foo", buf.String())
}
func TestRenderUnknownTemplate(t *testing.T) {
tpl := template.New("")
mt := NewFromTemplate(tpl)
text, err := mt.RenderToString("foo", Data{})
require.Error(t, err)
assert.Equal(t, "", text)
buf := &bytes.Buffer{}
assert.Error(t, mt.Render(buf, "foo", Data{}))
assert.Equal(t, "", buf.String())
}

Просмотреть файл

@@ -87,6 +87,7 @@ func getTestResourcesToSetup() []testResourceDetails {
var testResourcesToSetup = []testResourceDetails{
{root, "mattermost-server", resourceTypeFolder, actionSymlink},
{"go.mod", "go.mod", resourceTypeFile, actionSymlink},
{"i18n", "i18n", resourceTypeFolder, actionSymlink},
{"templates", "templates", resourceTypeFolder, actionSymlink},
{"tests", "tests", resourceTypeFolder, actionSymlink},

Просмотреть файл

@@ -1,118 +0,0 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
package utils
import (
"bytes"
"errors"
"html/template"
"io"
"path/filepath"
"sync/atomic"
"github.com/fsnotify/fsnotify"
"github.com/mattermost/mattermost-server/v5/shared/mlog"
"github.com/mattermost/mattermost-server/v5/utils/fileutils"
)
type HTMLTemplateWatcher struct {
templates atomic.Value
stop chan struct{}
stopped chan struct{}
}
func NewHTMLTemplateWatcher(directory string) (*HTMLTemplateWatcher, error) {
templatesDir, _ := fileutils.FindDir(directory)
mlog.Debug("Parsing server templates", mlog.String("templates_directory", templatesDir))
ret := &HTMLTemplateWatcher{
stop: make(chan struct{}),
stopped: make(chan struct{}),
}
watcher, err := fsnotify.NewWatcher()
if err != nil {
return nil, err
}
if err = watcher.Add(templatesDir); err != nil {
return nil, err
}
htmlTemplates, err := template.ParseGlob(filepath.Join(templatesDir, "*.html"))
if err != nil {
return nil, err
}
ret.templates.Store(htmlTemplates)
go func() {
defer close(ret.stopped)
defer watcher.Close()
for {
select {
case <-ret.stop:
return
case event := <-watcher.Events:
if event.Op&fsnotify.Write == fsnotify.Write {
mlog.Info("Re-parsing templates because of modified file", mlog.String("file_name", event.Name))
if htmlTemplates, err := template.ParseGlob(filepath.Join(templatesDir, "*.html")); err != nil {
mlog.Error("Failed to parse templates.", mlog.Err(err))
} else {
ret.templates.Store(htmlTemplates)
}
}
case err := <-watcher.Errors:
mlog.Error("Failed in directory watcher", mlog.Err(err))
}
}
}()
return ret, nil
}
func (w *HTMLTemplateWatcher) Templates() *template.Template {
return w.templates.Load().(*template.Template)
}
func (w *HTMLTemplateWatcher) Close() {
close(w.stop)
<-w.stopped
}
type HTMLTemplate struct {
Templates *template.Template
TemplateName string
Props map[string]interface{}
HTML map[string]template.HTML
}
func NewHTMLTemplate(templates *template.Template, templateName string) *HTMLTemplate {
return &HTMLTemplate{
Templates: templates,
TemplateName: templateName,
Props: make(map[string]interface{}),
HTML: make(map[string]template.HTML),
}
}
func (t *HTMLTemplate) Render() string {
var text bytes.Buffer
t.RenderToWriter(&text)
return text.String()
}
func (t *HTMLTemplate) RenderToWriter(w io.Writer) error {
if t.Templates == nil {
return errors.New("no html templates")
}
if err := t.Templates.ExecuteTemplate(w, t.TemplateName, t); err != nil {
mlog.Warn("Error rendering template", mlog.String("template_name", t.TemplateName), mlog.Err(err))
return err
}
return nil
}

Просмотреть файл

@@ -1,86 +0,0 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
package utils
import (
"bytes"
"html/template"
"io/ioutil"
"os"
"path/filepath"
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestHTMLTemplateWatcher(t *testing.T) {
TranslationsPreInit()
dir, err := ioutil.TempDir("", "")
require.NoError(t, err)
defer os.RemoveAll(dir)
require.NoError(t, os.Mkdir(filepath.Join(dir, "templates"), 0700))
require.NoError(t, ioutil.WriteFile(filepath.Join(dir, "templates", "foo.html"), []byte(`{{ define "foo" }}foo{{ end }}`), 0600))
prevDir, err := os.Getwd()
require.NoError(t, err)
defer os.Chdir(prevDir)
os.Chdir(dir)
watcher, err := NewHTMLTemplateWatcher("templates")
require.NotNil(t, watcher)
require.NoError(t, err)
defer watcher.Close()
tpl := NewHTMLTemplate(watcher.Templates(), "foo")
assert.Equal(t, "foo", tpl.Render())
require.NoError(t, ioutil.WriteFile(filepath.Join(dir, "templates", "foo.html"), []byte(`{{ define "foo" }}bar{{ end }}`), 0600))
for i := 0; i < 30; i++ {
tpl = NewHTMLTemplate(watcher.Templates(), "foo")
if tpl.Render() == "bar" {
break
}
time.Sleep(time.Millisecond * 50)
}
assert.Equal(t, "bar", tpl.Render())
}
func TestHTMLTemplateWatcher_BadDirectory(t *testing.T) {
TranslationsPreInit()
watcher, err := NewHTMLTemplateWatcher("notarealdirectory")
assert.Nil(t, watcher)
assert.Error(t, err)
}
func TestHTMLTemplate(t *testing.T) {
tpl := template.New("test")
_, err := tpl.Parse(`{{ define "foo" }}foo{{ .Props.Bar }}{{ end }}`)
require.NoError(t, err)
htmlTemplate := NewHTMLTemplate(tpl, "foo")
htmlTemplate.Props["Bar"] = "bar"
assert.Equal(t, "foobar", htmlTemplate.Render())
buf := &bytes.Buffer{}
require.NoError(t, htmlTemplate.RenderToWriter(buf))
assert.Equal(t, "foobar", buf.String())
}
func TestHTMLTemplate_RenderError(t *testing.T) {
tpl := template.New("test")
_, err := tpl.Parse(`{{ define "foo" }}foo{{ .Foo.Bar }}bar{{ end }}`)
require.NoError(t, err)
htmlTemplate := NewHTMLTemplate(tpl, "foo")
assert.Equal(t, "foo", htmlTemplate.Render())
buf := &bytes.Buffer{}
assert.Error(t, htmlTemplate.RenderToWriter(buf))
assert.Equal(t, "foo", buf.String())
}

Просмотреть файл

@@ -9,7 +9,7 @@ import (
"github.com/avct/uasurfer"
"github.com/mattermost/mattermost-server/v5/app"
"github.com/mattermost/mattermost-server/v5/utils"
"github.com/mattermost/mattermost-server/v5/shared/templates"
)
// MattermostApp describes downloads for the Mattermost App
@@ -46,7 +46,13 @@ type SystemBrowser struct {
func renderUnsupportedBrowser(app app.AppIface, w http.ResponseWriter, r *http.Request) {
w.Header().Set("Cache-Control", "no-store")
page := utils.NewHTMLTemplate(app.Srv().HTMLTemplates(), "unsupported_browser")
data := templates.Data{
Props: map[string]interface{}{
"DownloadAppOrUpgradeBrowserString": app.T("web.error.unsupported_browser.download_app_or_upgrade_browser"),
"LearnMoreString": app.T("web.error.unsupported_browser.learn_more"),
},
}
// User Agent info
ua := uasurfer.Parse(r.UserAgent())
@@ -57,18 +63,16 @@ func renderUnsupportedBrowser(app app.AppIface, w http.ResponseWriter, r *http.R
// Basic heading translations
if isSafari {
page.Props["NoLongerSupportString"] = app.T("web.error.unsupported_browser.no_longer_support_version")
data.Props["NoLongerSupportString"] = app.T("web.error.unsupported_browser.no_longer_support_version")
} else {
page.Props["NoLongerSupportString"] = app.T("web.error.unsupported_browser.no_longer_support")
data.Props["NoLongerSupportString"] = app.T("web.error.unsupported_browser.no_longer_support")
}
page.Props["DownloadAppOrUpgradeBrowserString"] = app.T("web.error.unsupported_browser.download_app_or_upgrade_browser")
page.Props["LearnMoreString"] = app.T("web.error.unsupported_browser.learn_more")
// Mattermost app version
if isWindows {
page.Props["App"] = renderMattermostAppWindows(app)
data.Props["App"] = renderMattermostAppWindows(app)
} else if isMacOSX {
page.Props["App"] = renderMattermostAppMac(app)
data.Props["App"] = renderMattermostAppMac(app)
}
// Browsers to download
@@ -78,14 +82,14 @@ func renderUnsupportedBrowser(app app.AppIface, w http.ResponseWriter, r *http.R
if isSafari {
browsers = append(browsers, renderBrowserSafari(app))
}
page.Props["Browsers"] = browsers
data.Props["Browsers"] = browsers
// If on Windows 10, show link to Edge
if isWindows10 {
page.Props["SystemBrowser"] = renderSystemBrowserEdge(app, r)
data.Props["SystemBrowser"] = renderSystemBrowserEdge(app, r)
}
page.RenderToWriter(w)
app.Srv().TemplatesContainer().Render(w, "unsupported_browser", data)
}
func renderMattermostAppMac(app app.AppIface) MattermostApp {