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 ( import (
"context" "context"
"fmt" "fmt"
"html/template"
"net/http" "net/http"
"strconv" "strconv"
"strings" "strings"
@@ -21,6 +20,7 @@ import (
"github.com/mattermost/mattermost-server/v5/services/timezones" "github.com/mattermost/mattermost-server/v5/services/timezones"
"github.com/mattermost/mattermost-server/v5/shared/i18n" "github.com/mattermost/mattermost-server/v5/shared/i18n"
"github.com/mattermost/mattermost-server/v5/shared/mlog" "github.com/mattermost/mattermost-server/v5/shared/mlog"
"github.com/mattermost/mattermost-server/v5/shared/templates"
"github.com/mattermost/mattermost-server/v5/utils" "github.com/mattermost/mattermost-server/v5/utils"
) )
@@ -148,12 +148,8 @@ func (a *App) TelemetryId() string {
return a.Srv().TelemetryId() return a.Srv().TelemetryId()
} }
func (s *Server) HTMLTemplates() *template.Template { func (s *Server) TemplatesContainer() *templates.Container {
if s.htmlTemplateWatcher != nil { return s.htmlTemplateWatcher
return s.htmlTemplateWatcher.Templates()
}
return nil
} }
func (a *App) Handle404(w http.ResponseWriter, r *http.Request) { 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) 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) T := i18n.GetUserTranslations(sender.Locale)
bodyPage := a.Srv().EmailService.newEmailTemplate("warn_metric_ack", sender.Locale) data := a.Srv().EmailService.newEmailTemplateData(sender.Locale)
bodyPage.Props["ContactNameHeader"] = T("api.templates.warn_metric_ack.body.contact_name_header") data.Props["ContactNameHeader"] = T("api.templates.warn_metric_ack.body.contact_name_header")
bodyPage.Props["ContactNameValue"] = sender.GetFullName() data.Props["ContactNameValue"] = sender.GetFullName()
bodyPage.Props["ContactEmailHeader"] = T("api.templates.warn_metric_ack.body.contact_email_header") data.Props["ContactEmailHeader"] = T("api.templates.warn_metric_ack.body.contact_email_header")
bodyPage.Props["ContactEmailValue"] = sender.Email data.Props["ContactEmailValue"] = sender.Email
//same definition as the active users count metric displayed in the SystemConsole Analytics section //same definition as the active users count metric displayed in the SystemConsole Analytics section
registeredUsersCount, cerr := a.Srv().Store.User().Count(model.UserCountOptions{}) registeredUsersCount, cerr := a.Srv().Store.User().Count(model.UserCountOptions{})
if cerr != nil { if cerr != nil {
mlog.Warn("Error retrieving the number of registered users", mlog.Err(cerr)) mlog.Warn("Error retrieving the number of registered users", mlog.Err(cerr))
} else { } else {
bodyPage.Props["RegisteredUsersHeader"] = T("api.templates.warn_metric_ack.body.registered_users_header") data.Props["RegisteredUsersHeader"] = T("api.templates.warn_metric_ack.body.registered_users_header")
bodyPage.Props["RegisteredUsersValue"] = registeredUsersCount data.Props["RegisteredUsersValue"] = registeredUsersCount
} }
bodyPage.Props["SiteURLHeader"] = T("api.templates.warn_metric_ack.body.site_url_header") data.Props["SiteURLHeader"] = T("api.templates.warn_metric_ack.body.site_url_header")
bodyPage.Props["SiteURL"] = a.GetSiteURL() data.Props["SiteURL"] = a.GetSiteURL()
bodyPage.Props["TelemetryIdHeader"] = T("api.templates.warn_metric_ack.body.diagnostic_id_header") data.Props["TelemetryIdHeader"] = T("api.templates.warn_metric_ack.body.diagnostic_id_header")
bodyPage.Props["TelemetryIdValue"] = a.TelemetryId() data.Props["TelemetryIdValue"] = a.TelemetryId()
bodyPage.Props["Footer"] = T("api.templates.warn_metric_ack.footer") data.Props["Footer"] = T("api.templates.warn_metric_ack.footer")
warnMetricStatus, warnMetricDisplayTexts := a.getWarnMetricStatusAndDisplayTextsForId(warnMetricId, T, false) warnMetricStatus, warnMetricDisplayTexts := a.getWarnMetricStatusAndDisplayTextsForId(warnMetricId, T, false)
if warnMetricStatus == nil { if warnMetricStatus == nil {
@@ -493,10 +489,16 @@ func (a *App) NotifyAndSetWarnMetricAck(warnMetricId string, sender *model.User,
} }
subject := T("api.templates.warn_metric_ack.subject") subject := T("api.templates.warn_metric_ack.subject")
bodyPage.Props["Title"] = warnMetricDisplayTexts.EmailBody data.Props["Title"] = warnMetricDisplayTexts.EmailBody
mailConfig := a.Srv().MailServiceConfig() 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) 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" "bytes"
"encoding/json" "encoding/json"
"fmt" "fmt"
"html/template"
"io" "io"
"net/http" "net/http"
"net/url" "net/url"
@@ -22,7 +23,7 @@ import (
"github.com/mattermost/mattermost-server/v5/services/mailservice" "github.com/mattermost/mattermost-server/v5/services/mailservice"
"github.com/mattermost/mattermost-server/v5/shared/i18n" "github.com/mattermost/mattermost-server/v5/shared/i18n"
"github.com/mattermost/mattermost-server/v5/shared/mlog" "github.com/mattermost/mattermost-server/v5/shared/mlog"
"github.com/mattermost/mattermost-server/v5/utils" "github.com/mattermost/mattermost-server/v5/shared/templates"
) )
const ( const (
@@ -94,14 +95,19 @@ func (es *EmailService) sendChangeUsernameEmail(newUsername, email, locale, site
map[string]interface{}{"SiteName": es.srv.Config().TeamSettings.SiteName, map[string]interface{}{"SiteName": es.srv.Config().TeamSettings.SiteName,
"TeamDisplayName": es.srv.Config().TeamSettings.SiteName}) "TeamDisplayName": es.srv.Config().TeamSettings.SiteName})
bodyPage := es.newEmailTemplate("email_change_body", locale) data := es.newEmailTemplateData(locale)
bodyPage.Props["SiteURL"] = siteURL data.Props["SiteURL"] = siteURL
bodyPage.Props["Title"] = T("api.templates.username_change_body.title") data.Props["Title"] = T("api.templates.username_change_body.title")
bodyPage.Props["Info"] = T("api.templates.username_change_body.info", data.Props["Info"] = T("api.templates.username_change_body.info",
map[string]interface{}{"TeamDisplayName": es.srv.Config().TeamSettings.SiteName, "NewUsername": newUsername}) 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) 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, map[string]interface{}{"SiteName": es.srv.Config().TeamSettings.SiteName,
"TeamDisplayName": es.srv.Config().TeamSettings.SiteName}) "TeamDisplayName": es.srv.Config().TeamSettings.SiteName})
bodyPage := es.newEmailTemplate("email_change_verify_body", locale) data := es.newEmailTemplateData(locale)
bodyPage.Props["SiteURL"] = siteURL data.Props["SiteURL"] = siteURL
bodyPage.Props["Title"] = T("api.templates.email_change_verify_body.title") data.Props["Title"] = T("api.templates.email_change_verify_body.title")
bodyPage.Props["Info"] = T("api.templates.email_change_verify_body.info", data.Props["Info"] = T("api.templates.email_change_verify_body.info",
map[string]interface{}{"TeamDisplayName": es.srv.Config().TeamSettings.SiteName}) map[string]interface{}{"TeamDisplayName": es.srv.Config().TeamSettings.SiteName})
bodyPage.Props["VerifyUrl"] = link data.Props["VerifyUrl"] = link
bodyPage.Props["VerifyButton"] = T("api.templates.email_change_verify_body.button") 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) 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, map[string]interface{}{"SiteName": es.srv.Config().TeamSettings.SiteName,
"TeamDisplayName": es.srv.Config().TeamSettings.SiteName}) "TeamDisplayName": es.srv.Config().TeamSettings.SiteName})
bodyPage := es.newEmailTemplate("email_change_body", locale) data := es.newEmailTemplateData(locale)
bodyPage.Props["SiteURL"] = siteURL data.Props["SiteURL"] = siteURL
bodyPage.Props["Title"] = T("api.templates.email_change_body.title") data.Props["Title"] = T("api.templates.email_change_body.title")
bodyPage.Props["Info"] = T("api.templates.email_change_body.info", data.Props["Info"] = T("api.templates.email_change_body.info",
map[string]interface{}{"TeamDisplayName": es.srv.Config().TeamSettings.SiteName, "NewEmail": newEmail}) 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) 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", subject := T("api.templates.verify_subject",
map[string]interface{}{"SiteName": es.srv.Config().TeamSettings.SiteName}) map[string]interface{}{"SiteName": es.srv.Config().TeamSettings.SiteName})
bodyPage := es.newEmailTemplate("verify_body", locale) data := es.newEmailTemplateData(locale)
bodyPage.Props["SiteURL"] = siteURL data.Props["SiteURL"] = siteURL
bodyPage.Props["Title"] = T("api.templates.verify_body.title") data.Props["Title"] = T("api.templates.verify_body.title")
bodyPage.Props["SubTitle1"] = T("api.templates.verify_body.subTitle1") data.Props["SubTitle1"] = T("api.templates.verify_body.subTitle1")
bodyPage.Props["ServerURL"] = T("api.templates.verify_body.serverURL", map[string]interface{}{"ServerURL": serverURL}) data.Props["ServerURL"] = T("api.templates.verify_body.serverURL", map[string]interface{}{"ServerURL": serverURL})
bodyPage.Props["SubTitle2"] = T("api.templates.verify_body.subTitle2") data.Props["SubTitle2"] = T("api.templates.verify_body.subTitle2")
bodyPage.Props["ButtonURL"] = link data.Props["ButtonURL"] = link
bodyPage.Props["Button"] = T("api.templates.verify_body.button") data.Props["Button"] = T("api.templates.verify_body.button")
bodyPage.Props["Info"] = T("api.templates.verify_body.info") data.Props["Info"] = T("api.templates.verify_body.info")
bodyPage.Props["Info1"] = T("api.templates.verify_body.info1") data.Props["Info1"] = T("api.templates.verify_body.info1")
bodyPage.Props["QuestionTitle"] = T("api.templates.questions_footer.title") data.Props["QuestionTitle"] = T("api.templates.questions_footer.title")
bodyPage.Props["QuestionInfo"] = T("api.templates.questions_footer.info") 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) 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", subject := T("api.templates.signin_change_email.subject",
map[string]interface{}{"SiteName": es.srv.Config().TeamSettings.SiteName}) map[string]interface{}{"SiteName": es.srv.Config().TeamSettings.SiteName})
bodyPage := es.newEmailTemplate("signin_change_body", locale) data := es.newEmailTemplateData(locale)
bodyPage.Props["SiteURL"] = siteURL data.Props["SiteURL"] = siteURL
bodyPage.Props["Title"] = T("api.templates.signin_change_email.body.title") data.Props["Title"] = T("api.templates.signin_change_email.body.title")
bodyPage.Props["Info"] = T("api.templates.signin_change_email.body.info", data.Props["Info"] = T("api.templates.signin_change_email.body.info",
map[string]interface{}{"SiteName": es.srv.Config().TeamSettings.SiteName, "Method": method}) 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) 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, map[string]interface{}{"SiteName": es.srv.Config().TeamSettings.SiteName,
"ServerURL": serverURL}) "ServerURL": serverURL})
bodyPage := es.newEmailTemplate("welcome_body", locale) data := es.newEmailTemplateData(locale)
bodyPage.Props["SiteURL"] = siteURL data.Props["SiteURL"] = siteURL
bodyPage.Props["Title"] = T("api.templates.welcome_body.title") data.Props["Title"] = T("api.templates.welcome_body.title")
bodyPage.Props["SubTitle1"] = T("api.templates.welcome_body.subTitle1") data.Props["SubTitle1"] = T("api.templates.welcome_body.subTitle1")
bodyPage.Props["ServerURL"] = T("api.templates.welcome_body.serverURL", map[string]interface{}{"ServerURL": serverURL}) data.Props["ServerURL"] = T("api.templates.welcome_body.serverURL", map[string]interface{}{"ServerURL": serverURL})
bodyPage.Props["SubTitle2"] = T("api.templates.welcome_body.subTitle2") data.Props["SubTitle2"] = T("api.templates.welcome_body.subTitle2")
bodyPage.Props["Button"] = T("api.templates.welcome_body.button") data.Props["Button"] = T("api.templates.welcome_body.button")
bodyPage.Props["Info"] = T("api.templates.welcome_body.info") data.Props["Info"] = T("api.templates.welcome_body.info")
bodyPage.Props["Info1"] = T("api.templates.welcome_body.info1") data.Props["Info1"] = T("api.templates.welcome_body.info1")
bodyPage.Props["SiteURL"] = siteURL data.Props["SiteURL"] = siteURL
if *es.srv.Config().NativeAppSettings.AppDownloadLink != "" { if *es.srv.Config().NativeAppSettings.AppDownloadLink != "" {
bodyPage.Props["AppDownloadTitle"] = T("api.templates.welcome_body.app_download_title") data.Props["AppDownloadTitle"] = T("api.templates.welcome_body.app_download_title")
bodyPage.Props["AppDownloadInfo"] = T("api.templates.welcome_body.app_download_info") data.Props["AppDownloadInfo"] = T("api.templates.welcome_body.app_download_info")
bodyPage.Props["AppDownloadButton"] = T("api.templates.welcome_body.app_download_button") data.Props["AppDownloadButton"] = T("api.templates.welcome_body.app_download_button")
bodyPage.Props["AppDownloadLink"] = *es.srv.Config().NativeAppSettings.AppDownloadLink data.Props["AppDownloadLink"] = *es.srv.Config().NativeAppSettings.AppDownloadLink
} }
if !verified && *es.srv.Config().EmailSettings.RequireEmailVerification { if !verified && *es.srv.Config().EmailSettings.RequireEmailVerification {
@@ -249,10 +275,15 @@ func (es *EmailService) sendWelcomeEmail(userID string, email string, verified b
if redirect != "" { if redirect != "" {
link += fmt.Sprintf("&redirect_to=%s", 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) 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) workSpacePath := fmt.Sprintf("https://%s.cloud.mattermost.com", workSpaceName)
bodyPage := es.newEmailTemplate("cloud_welcome_email", locale) data := es.newEmailTemplateData(locale)
bodyPage.Props["Title"] = T("api.templates.cloud_welcome_email.title", map[string]interface{}{"WorkSpace": workSpaceName}) data.Props["Title"] = T("api.templates.cloud_welcome_email.title", map[string]interface{}{"WorkSpace": workSpaceName})
bodyPage.Props["SubTitle"] = T("api.templates.cloud_welcome_email.subtitle") data.Props["SubTitle"] = T("api.templates.cloud_welcome_email.subtitle")
bodyPage.Props["SubTitleInfo"] = T("api.templates.cloud_welcome_email.subtitle_info") data.Props["SubTitleInfo"] = T("api.templates.cloud_welcome_email.subtitle_info")
bodyPage.Props["Info"] = T("api.templates.cloud_welcome_email.info") data.Props["Info"] = T("api.templates.cloud_welcome_email.info")
bodyPage.Props["Info2"] = T("api.templates.cloud_welcome_email.info2") data.Props["Info2"] = T("api.templates.cloud_welcome_email.info2")
bodyPage.Props["WorkSpacePath"] = workSpacePath data.Props["WorkSpacePath"] = workSpacePath
bodyPage.Props["DNS"] = dns data.Props["DNS"] = dns
bodyPage.Props["InviteInfo"] = T("api.templates.cloud_welcome_email.invite_info") data.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}) data.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) data.Props["InviteSubInfoLink"] = fmt.Sprintf("%s/signup_user_complete/?id=%s", workSpacePath, teamInviteID)
bodyPage.Props["AddAppsInfo"] = T("api.templates.cloud_welcome_email.add_apps_info") data.Props["AddAppsInfo"] = T("api.templates.cloud_welcome_email.add_apps_info")
bodyPage.Props["AddAppsSubInfo"] = T("api.templates.cloud_welcome_email.add_apps_sub_info") data.Props["AddAppsSubInfo"] = T("api.templates.cloud_welcome_email.add_apps_sub_info")
bodyPage.Props["AppMarketPlace"] = T("api.templates.cloud_welcome_email.app_market_place") data.Props["AppMarketPlace"] = T("api.templates.cloud_welcome_email.app_market_place")
bodyPage.Props["AppMarketPlaceLink"] = "https://integrations.mattermost.com/" data.Props["AppMarketPlaceLink"] = "https://integrations.mattermost.com/"
bodyPage.Props["DownloadMMInfo"] = T("api.templates.cloud_welcome_email.download_mm_info") data.Props["DownloadMMInfo"] = T("api.templates.cloud_welcome_email.download_mm_info")
bodyPage.Props["SignInSubInfo"] = T("api.templates.cloud_welcome_email.signin_sub_info") data.Props["SignInSubInfo"] = T("api.templates.cloud_welcome_email.signin_sub_info")
bodyPage.Props["MMApps"] = T("api.templates.cloud_welcome_email.mm_apps") data.Props["MMApps"] = T("api.templates.cloud_welcome_email.mm_apps")
bodyPage.Props["SignInSubInfo2"] = T("api.templates.cloud_welcome_email.signin_sub_info2") data.Props["SignInSubInfo2"] = T("api.templates.cloud_welcome_email.signin_sub_info2")
bodyPage.Props["DownloadMMAppsLink"] = "https://mattermost.com/download/" data.Props["DownloadMMAppsLink"] = "https://mattermost.com/download/"
bodyPage.Props["Button"] = T("api.templates.cloud_welcome_email.button") data.Props["Button"] = T("api.templates.cloud_welcome_email.button")
bodyPage.Props["GettingStartedQuestions"] = T("api.templates.cloud_welcome_email.start_questions") 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) 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, map[string]interface{}{"SiteName": es.srv.Config().TeamSettings.SiteName,
"TeamDisplayName": es.srv.Config().TeamSettings.SiteName}) "TeamDisplayName": es.srv.Config().TeamSettings.SiteName})
bodyPage := es.newEmailTemplate("password_change_body", locale) data := es.newEmailTemplateData(locale)
bodyPage.Props["SiteURL"] = siteURL data.Props["SiteURL"] = siteURL
bodyPage.Props["Title"] = T("api.templates.password_change_body.title") data.Props["Title"] = T("api.templates.password_change_body.title")
bodyPage.Props["Info"] = T("api.templates.password_change_body.info", data.Props["Info"] = T("api.templates.password_change_body.info",
map[string]interface{}{"TeamDisplayName": es.srv.Config().TeamSettings.SiteName, "TeamURL": siteURL, "Method": method}) 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) 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", subject := T("api.templates.user_access_token_subject",
map[string]interface{}{"SiteName": es.srv.Config().TeamSettings.SiteName}) map[string]interface{}{"SiteName": es.srv.Config().TeamSettings.SiteName})
bodyPage := es.newEmailTemplate("password_change_body", locale) data := es.newEmailTemplateData(locale)
bodyPage.Props["SiteURL"] = siteURL data.Props["SiteURL"] = siteURL
bodyPage.Props["Title"] = T("api.templates.user_access_token_body.title") data.Props["Title"] = T("api.templates.user_access_token_body.title")
bodyPage.Props["Info"] = T("api.templates.user_access_token_body.info", data.Props["Info"] = T("api.templates.user_access_token_body.info",
map[string]interface{}{"SiteName": es.srv.Config().TeamSettings.SiteName, "SiteURL": siteURL}) 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) 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", subject := T("api.templates.reset_subject",
map[string]interface{}{"SiteName": es.srv.Config().TeamSettings.SiteName}) map[string]interface{}{"SiteName": es.srv.Config().TeamSettings.SiteName})
bodyPage := es.newEmailTemplate("reset_body", locale) data := es.newEmailTemplateData(locale)
bodyPage.Props["SiteURL"] = siteURL data.Props["SiteURL"] = siteURL
bodyPage.Props["Title"] = T("api.templates.reset_body.title") data.Props["Title"] = T("api.templates.reset_body.title")
bodyPage.Props["SubTitle"] = T("api.templates.reset_body.subTitle") data.Props["SubTitle"] = T("api.templates.reset_body.subTitle")
bodyPage.Props["Info"] = T("api.templates.reset_body.info") data.Props["Info"] = T("api.templates.reset_body.info")
bodyPage.Props["ButtonURL"] = link data.Props["ButtonURL"] = link
bodyPage.Props["Button"] = T("api.templates.reset_body.button") data.Props["Button"] = T("api.templates.reset_body.button")
bodyPage.Props["QuestionTitle"] = T("api.templates.questions_footer.title") data.Props["QuestionTitle"] = T("api.templates.questions_footer.title")
bodyPage.Props["QuestionInfo"] = T("api.templates.questions_footer.info") 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) 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", subject := T("api.templates.mfa_change_subject",
map[string]interface{}{"SiteName": es.srv.Config().TeamSettings.SiteName}) map[string]interface{}{"SiteName": es.srv.Config().TeamSettings.SiteName})
bodyPage := es.newEmailTemplate("mfa_change_body", locale) data := es.newEmailTemplateData(locale)
bodyPage.Props["SiteURL"] = siteURL data.Props["SiteURL"] = siteURL
if activated { if activated {
bodyPage.Props["Info"] = T("api.templates.mfa_activated_body.info", map[string]interface{}{"SiteURL": siteURL}) data.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["Title"] = T("api.templates.mfa_activated_body.title")
} else { } else {
bodyPage.Props["Info"] = T("api.templates.mfa_deactivated_body.info", map[string]interface{}{"SiteURL": siteURL}) data.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["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) 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, "TeamDisplayName": team.DisplayName,
"SiteName": es.srv.Config().TeamSettings.SiteName}) "SiteName": es.srv.Config().TeamSettings.SiteName})
bodyPage := es.newEmailTemplate("invite_body", "") data := es.newEmailTemplateData("")
bodyPage.Props["SiteURL"] = siteURL data.Props["SiteURL"] = siteURL
bodyPage.Props["Title"] = i18n.T("api.templates.invite_body.title", map[string]interface{}{"SenderName": senderName, "TeamDisplayName": team.DisplayName}) data.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") data.Props["SubTitle"] = i18n.T("api.templates.invite_body.subTitle")
bodyPage.Props["Button"] = i18n.T("api.templates.invite_body.button") data.Props["Button"] = i18n.T("api.templates.invite_body.button")
bodyPage.Props["SenderName"] = senderName data.Props["SenderName"] = senderName
bodyPage.Props["InviteFooterTitle"] = i18n.T("api.templates.invite_body_footer.title") data.Props["InviteFooterTitle"] = i18n.T("api.templates.invite_body_footer.title")
bodyPage.Props["InviteFooterInfo"] = i18n.T("api.templates.invite_body_footer.info") data.Props["InviteFooterInfo"] = i18n.T("api.templates.invite_body_footer.info")
bodyPage.Props["InviteFooterLearnMore"] = i18n.T("api.templates.invite_body_footer.learn_more") data.Props["InviteFooterLearnMore"] = i18n.T("api.templates.invite_body_footer.learn_more")
token := model.NewToken( token := model.NewToken(
TokenTypeTeamInvitation, TokenTypeTeamInvitation,
model.MapToJson(map[string]string{"teamId": team.Id, "email": invite}), model.MapToJson(map[string]string{"teamId": team.Id, "email": invite}),
) )
props := make(map[string]string) tokenProps := make(map[string]string)
props["email"] = invite tokenProps["email"] = invite
props["display_name"] = team.DisplayName tokenProps["display_name"] = team.DisplayName
props["name"] = team.Name tokenProps["name"] = team.Name
data := model.MapToJson(props) tokenData := model.MapToJson(tokenProps)
if err := es.srv.Store.Token().Save(token); err != nil { if err := es.srv.Store.Token().Save(token); err != nil {
mlog.Error("Failed to send invite email successfully ", mlog.Err(err)) mlog.Error("Failed to send invite email successfully ", mlog.Err(err))
continue 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)) 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, "TeamDisplayName": team.DisplayName,
"SiteName": es.srv.Config().TeamSettings.SiteName}) "SiteName": es.srv.Config().TeamSettings.SiteName})
bodyPage := es.newEmailTemplate("invite_body", "") data := es.newEmailTemplateData("")
bodyPage.Props["SiteURL"] = siteURL data.Props["SiteURL"] = siteURL
bodyPage.Props["Title"] = i18n.T("api.templates.invite_body.title", map[string]interface{}{"SenderName": senderName, "TeamDisplayName": team.DisplayName}) data.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") data.Props["SubTitle"] = i18n.T("api.templates.invite_body_guest.subTitle")
bodyPage.Props["Button"] = i18n.T("api.templates.invite_body.button") data.Props["Button"] = i18n.T("api.templates.invite_body.button")
bodyPage.Props["SenderName"] = senderName data.Props["SenderName"] = senderName
bodyPage.Props["Message"] = "" data.Props["Message"] = ""
if message != "" { if message != "" {
bodyPage.Props["Message"] = message data.Props["Message"] = message
} }
bodyPage.Props["InviteFooterTitle"] = i18n.T("api.templates.invite_body_footer.title") data.Props["InviteFooterTitle"] = i18n.T("api.templates.invite_body_footer.title")
bodyPage.Props["InviteFooterInfo"] = i18n.T("api.templates.invite_body_footer.info") data.Props["InviteFooterInfo"] = i18n.T("api.templates.invite_body_footer.info")
bodyPage.Props["InviteFooterLearnMore"] = i18n.T("api.templates.invite_body_footer.learn_more") data.Props["InviteFooterLearnMore"] = i18n.T("api.templates.invite_body_footer.learn_more")
channelIDs := []string{} channelIDs := []string{}
for _, channel := range channels { for _, channel := range channels {
@@ -499,20 +560,20 @@ func (es *EmailService) sendGuestInviteEmails(team *model.Team, channels []*mode
}), }),
) )
props := make(map[string]string) tokenProps := make(map[string]string)
props["email"] = invite tokenProps["email"] = invite
props["display_name"] = team.DisplayName tokenProps["display_name"] = team.DisplayName
props["name"] = team.Name tokenProps["name"] = team.Name
data := model.MapToJson(props) tokenData := model.MapToJson(tokenProps)
if err := es.srv.Store.Token().Save(token); err != nil { if err := es.srv.Store.Token().Save(token); err != nil {
mlog.Error("Failed to send invite email successfully ", mlog.Err(err)) mlog.Error("Failed to send invite email successfully ", mlog.Err(err))
continue 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 { 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) 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)) 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 return nil
} }
func (es *EmailService) newEmailTemplate(name, locale string) *utils.HTMLTemplate { func (es *EmailService) newEmailTemplateData(locale string) templates.Data {
t := utils.NewHTMLTemplate(es.srv.HTMLTemplates(), name)
var localT i18n.TranslateFunc var localT i18n.TranslateFunc
if locale != "" { if locale != "" {
localT = i18n.GetUserTranslations(locale) localT = i18n.GetUserTranslations(locale)
} else { } else {
localT = i18n.T localT = i18n.T
} }
organization := ""
t.Props["Footer"] = localT("api.templates.email_footer")
t.Props["FooterV2"] = localT("api.templates.email_footer_v2")
if *es.srv.Config().EmailSettings.FeedbackOrganization != "" { if *es.srv.Config().EmailSettings.FeedbackOrganization != "" {
t.Props["Organization"] = localT("api.templates.email_organization") + *es.srv.Config().EmailSettings.FeedbackOrganization organization = localT("api.templates.email_organization") + *es.srv.Config().EmailSettings.FeedbackOrganization
} else {
t.Props["Organization"] = ""
} }
t.Props["EmailInfo1"] = localT("api.templates.email_info1") return templates.Data{
t.Props["EmailInfo2"] = localT("api.templates.email_info2") Props: map[string]interface{}{
t.Props["EmailInfo3"] = localT("api.templates.email_info3", "EmailInfo1": localT("api.templates.email_info1"),
map[string]interface{}{"SiteName": es.srv.Config().TeamSettings.SiteName}) "EmailInfo2": localT("api.templates.email_info2"),
t.Props["SupportEmail"] = *es.srv.Config().SupportSettings.SupportEmail "EmailInfo3": localT("api.templates.email_info3",
map[string]interface{}{"SiteName": es.srv.Config().TeamSettings.SiteName}),
return t "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 { 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, map[string]interface{}{"SiteName": es.srv.Config().TeamSettings.SiteName,
"ServerURL": serverURL}) "ServerURL": serverURL})
bodyPage := es.newEmailTemplate("deactivate_body", locale) data := es.newEmailTemplateData(locale)
bodyPage.Props["SiteURL"] = siteURL data.Props["SiteURL"] = siteURL
bodyPage.Props["Title"] = T("api.templates.deactivate_body.title", map[string]interface{}{"ServerURL": serverURL}) data.Props["Title"] = T("api.templates.deactivate_body.title", map[string]interface{}{"ServerURL": serverURL})
bodyPage.Props["Info"] = T("api.templates.deactivate_body.info", data.Props["Info"] = T("api.templates.deactivate_body.info",
map[string]interface{}{"SiteURL": siteURL}) 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) 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", subject := T("api.templates.remove_expired_license.subject",
map[string]interface{}{"SiteName": es.srv.Config().TeamSettings.SiteName}) map[string]interface{}{"SiteName": es.srv.Config().TeamSettings.SiteName})
bodyPage := es.newEmailTemplate("remove_expired_license", locale) data := es.newEmailTemplateData(locale)
bodyPage.Props["SiteURL"] = siteURL data.Props["SiteURL"] = siteURL
bodyPage.Props["Title"] = T("api.templates.remove_expired_license.body.title") data.Props["Title"] = T("api.templates.remove_expired_license.body.title")
bodyPage.Props["Link"] = renewalLink data.Props["Link"] = renewalLink
bodyPage.Props["LinkButton"] = T("api.templates.remove_expired_license.body.renew_button") 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) 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") subject := T("api.templates.at_limit_subject")
bodyPage := es.newEmailTemplate("reached_user_limit_body", locale) data := es.newEmailTemplateData(locale)
bodyPage.Props["SiteURL"] = siteURL data.Props["SiteURL"] = siteURL
bodyPage.Props["Title"] = T("api.templates.at_limit_title") data.Props["Title"] = T("api.templates.at_limit_title")
bodyPage.Props["Info1"] = T("api.templates.at_limit_info1") data.Props["Info1"] = T("api.templates.at_limit_info1")
bodyPage.Props["Info2"] = T("api.templates.at_limit_info2") data.Props["Info2"] = T("api.templates.at_limit_info2")
bodyPage.Props["Button"] = T("api.templates.upgrade_mattermost_cloud") data.Props["Button"] = T("api.templates.upgrade_mattermost_cloud")
bodyPage.Props["EmailUs"] = T("api.templates.email_us_anytime_at") 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) 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) { func (es *EmailService) SendUpgradeEmail(user, email, locale, siteURL, action string) (bool, *model.AppError) {
T := i18n.GetUserTranslations(locale) 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 { if action == model.InviteLimitation {
bodyPage.Props["Title"] = T("api.templates.upgrade_request_title", map[string]interface{}{"UserName": user}) data.Props["Title"] = T("api.templates.upgrade_request_title", map[string]interface{}{"UserName": user})
bodyPage.Props["Info4"] = T("api.templates.upgrade_request_info4") data.Props["Info4"] = T("api.templates.upgrade_request_info4")
} else { } else {
bodyPage.Props["Title"] = T("api.templates.upgrade_request_title2") data.Props["Title"] = T("api.templates.upgrade_request_title2")
bodyPage.Props["Info4"] = T("api.templates.upgrade_request_info4_2") data.Props["Info4"] = T("api.templates.upgrade_request_info4_2")
} }
subject := T("api.templates.upgrade_request_subject") body, err := es.srv.TemplatesContainer().RenderToString("cloud_upgrade_request_email", data)
bodyPage.Props["Info5"] = T("api.templates.at_limit_info5") if err != nil {
bodyPage.Props["BillingPath"] = "admin_console/billing/subscription" return false, model.NewAppError("SendUpgradeEmail", "api.user.send_upgrade_request_email.error", nil, err.Error(), http.StatusInternalServerError)
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")
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) 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") subject := T("api.templates.over_limit_subject")
bodyPage := es.newEmailTemplate("reached_user_limit_body", locale) data := es.newEmailTemplateData(locale)
bodyPage.Props["SiteURL"] = siteURL data.Props["SiteURL"] = siteURL
bodyPage.Props["Title"] = T("api.templates.over_limit_title") data.Props["Title"] = T("api.templates.over_limit_title")
bodyPage.Props["Info1"] = T("api.templates.over_limit_info1") data.Props["Info1"] = T("api.templates.over_limit_info1")
bodyPage.Props["Info2"] = T("api.templates.over_limit_info2") data.Props["Info2"] = T("api.templates.over_limit_info2")
bodyPage.Props["Button"] = T("api.templates.upgrade_mattermost_cloud") data.Props["Button"] = T("api.templates.upgrade_mattermost_cloud")
bodyPage.Props["EmailUs"] = T("api.templates.email_us_anytime_at") 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) 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") subject := T("api.templates.over_limit_30_days_subject")
bodyPage := es.newEmailTemplate("over_user_limit_30_days_body", locale) data := es.newEmailTemplateData(locale)
bodyPage.Props["SiteURL"] = siteURL data.Props["SiteURL"] = siteURL
bodyPage.Props["Title"] = T("api.templates.over_limit_30_days_title") data.Props["Title"] = T("api.templates.over_limit_30_days_title")
bodyPage.Props["Info1"] = T("api.templates.over_limit_30_days_info1") data.Props["Info1"] = T("api.templates.over_limit_30_days_info1")
bodyPage.Props["Info2"] = T("api.templates.over_limit_30_days_info2") data.Props["Info2"] = T("api.templates.over_limit_30_days_info2")
bodyPage.Props["Info2Item1"] = T("api.templates.over_limit_30_days_info2_item1") data.Props["Info2Item1"] = T("api.templates.over_limit_30_days_info2_item1")
bodyPage.Props["Info2Item2"] = T("api.templates.over_limit_30_days_info2_item2") data.Props["Info2Item2"] = T("api.templates.over_limit_30_days_info2_item2")
bodyPage.Props["Info2Item3"] = T("api.templates.over_limit_30_days_info2_item3") data.Props["Info2Item3"] = T("api.templates.over_limit_30_days_info2_item3")
bodyPage.Props["Button"] = T("api.templates.over_limit_fix_now") data.Props["Button"] = T("api.templates.over_limit_fix_now")
bodyPage.Props["EmailUs"] = T("api.templates.email_us_anytime_at") 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) 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") subject := T("api.templates.over_limit_90_days_subject")
bodyPage := es.newEmailTemplate("over_user_limit_90_days_body", locale) data := es.newEmailTemplateData(locale)
bodyPage.Props["SiteURL"] = siteURL data.Props["SiteURL"] = siteURL
bodyPage.Props["Title"] = T("api.templates.over_limit_90_days_title") data.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}) data.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") data.Props["Info2"] = T("api.templates.over_limit_90_days_info2")
bodyPage.Props["Info3"] = T("api.templates.over_limit_90_days_info3") data.Props["Info3"] = T("api.templates.over_limit_90_days_info3")
bodyPage.Props["Info4"] = T("api.templates.over_limit_90_days_info4") data.Props["Info4"] = T("api.templates.over_limit_90_days_info4")
bodyPage.Props["Button"] = T("api.templates.over_limit_fix_now") data.Props["Button"] = T("api.templates.over_limit_fix_now")
bodyPage.Props["EmailUs"] = T("api.templates.email_us_anytime_at") 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) 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") subject := T("api.templates.over_limit_suspended_subject")
bodyPage := es.newEmailTemplate("over_user_limit_workspace_suspended_body", locale) data := es.newEmailTemplateData(locale)
bodyPage.Props["SiteURL"] = siteURL data.Props["SiteURL"] = siteURL
bodyPage.Props["Title"] = T("api.templates.over_limit_suspended_title") data.Props["Title"] = T("api.templates.over_limit_suspended_title")
bodyPage.Props["Info1"] = T("api.templates.over_limit_suspended_info1") data.Props["Info1"] = T("api.templates.over_limit_suspended_info1")
bodyPage.Props["Info2"] = T("api.templates.over_limit_suspended_info2") data.Props["Info2"] = T("api.templates.over_limit_suspended_info2")
bodyPage.Props["Button"] = T("api.templates.over_limit_suspended_contact_support") data.Props["Button"] = T("api.templates.over_limit_suspended_contact_support")
bodyPage.Props["EmailUs"] = T("api.templates.email_us_anytime_at") 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) 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") subject := T("api.templates.over_limit_14_days_subject")
bodyPage := es.newEmailTemplate("over_user_limit_7_days_body", locale) data := es.newEmailTemplateData(locale)
bodyPage.Props["SiteURL"] = siteURL data.Props["SiteURL"] = siteURL
bodyPage.Props["Title"] = T("api.templates.over_limit_14_days_title") data.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}) data.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") data.Props["Button"] = T("api.templates.over_limit_fix_now")
bodyPage.Props["EmailUs"] = T("api.templates.email_us_anytime_at") 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) 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") subject := T("api.templates.over_limit_7_days_subject")
bodyPage := es.newEmailTemplate("over_user_limit_7_days_body", locale) data := es.newEmailTemplateData(locale)
bodyPage.Props["SiteURL"] = siteURL data.Props["SiteURL"] = siteURL
bodyPage.Props["Title"] = T("api.templates.over_limit_7_days_title") data.Props["Title"] = T("api.templates.over_limit_7_days_title")
bodyPage.Props["Info1"] = T("api.templates.over_limit_7_days_info1") data.Props["Info1"] = T("api.templates.over_limit_7_days_info1")
bodyPage.Props["Button"] = T("api.templates.over_limit_fix_now") data.Props["Button"] = T("api.templates.over_limit_fix_now")
bodyPage.Props["EmailUs"] = T("api.templates.email_us_anytime_at") 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) 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 // Localization not needed
subject := fmt.Sprintf("Cloud Installation %s Scheduled Suspension", installationID) subject := fmt.Sprintf("Cloud Installation %s Scheduled Suspension", installationID)
bodyPage := es.newEmailTemplate("over_user_limit_support_body", "en") data := es.newEmailTemplateData("en")
bodyPage.Props["CustomerID"] = customerID data.Props["CustomerID"] = customerID
bodyPage.Props["SiteURL"] = siteURL data.Props["SiteURL"] = siteURL
bodyPage.Props["SubscriptionID"] = subscriptionID data.Props["SubscriptionID"] = subscriptionID
bodyPage.Props["InstallationID"] = installationID data.Props["InstallationID"] = installationID
bodyPage.Props["SuspensionDate"] = time.Now().AddDate(0, 0, 61).Format("2006-01-02") data.Props["SuspensionDate"] = time.Now().AddDate(0, 0, 61).Format("2006-01-02")
bodyPage.Props["UserCount"] = userCount 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) 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") subject := T("api.templates.payment_failed.subject")
bodyPage := es.newEmailTemplate("payment_failed_body", locale) data := es.newEmailTemplateData(locale)
bodyPage.Props["SiteURL"] = siteURL data.Props["SiteURL"] = siteURL
bodyPage.Props["Title"] = T("api.templates.payment_failed.title") data.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}) data.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") data.Props["Info2"] = T("api.templates.payment_failed.info2")
bodyPage.Props["Info3"] = T("api.templates.payment_failed.info3") data.Props["Info3"] = T("api.templates.payment_failed.info3")
bodyPage.Props["Button"] = T("api.templates.over_limit_fix_now") data.Props["Button"] = T("api.templates.over_limit_fix_now")
bodyPage.Props["EmailUs"] = T("api.templates.email_us_anytime_at") 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) 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") subject := T("api.templates.payment_failed_no_card.subject")
bodyPage := es.newEmailTemplate("payment_failed_no_card_body", locale) data := es.newEmailTemplateData(locale)
bodyPage.Props["SiteURL"] = siteURL data.Props["SiteURL"] = siteURL
bodyPage.Props["Title"] = T("api.templates.payment_failed_no_card.title") data.Props["Title"] = T("api.templates.payment_failed_no_card.title")
bodyPage.Props["Info1"] = T("api.templates.payment_failed_no_card.info1") data.Props["Info1"] = T("api.templates.payment_failed_no_card.info1")
bodyPage.Props["Info3"] = T("api.templates.payment_failed_no_card.info3") data.Props["Info3"] = T("api.templates.payment_failed_no_card.info3")
bodyPage.Props["Button"] = T("api.templates.payment_failed_no_card.button") data.Props["Button"] = T("api.templates.payment_failed_no_card.button")
bodyPage.Props["EmailUs"] = T("api.templates.email_us_anytime_at") 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) 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/model"
"github.com/mattermost/mattermost-server/v5/shared/i18n" "github.com/mattermost/mattermost-server/v5/shared/i18n"
"github.com/mattermost/mattermost-server/v5/shared/mlog" "github.com/mattermost/mattermost-server/v5/shared/mlog"
"github.com/mattermost/mattermost-server/v5/utils"
) )
const ( const (
@@ -222,7 +221,13 @@ func (es *EmailService) sendBatchedEmailNotification(userID string, notification
emailNotificationContentsType = *es.srv.Config().EmailSettings.EmailNotificationContentsType 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) tm := time.Unix(notifications[0].post.CreateAt/1000, 0)
@@ -234,34 +239,39 @@ func (es *EmailService) sendBatchedEmailNotification(userID string, notification
"Day": tm.Day(), "Day": tm.Day(),
}) })
body := es.newEmailTemplate("post_batched_body", user.Locale) data := es.newEmailTemplateData(user.Locale)
body.Props["SiteURL"] = *es.srv.Config().ServiceSettings.SiteURL data.Props["SiteURL"] = *es.srv.Config().ServiceSettings.SiteURL
body.Props["Posts"] = template.HTML(contents) data.Props["Posts"] = template.HTML(contents)
body.Props["BodyText"] = translateFunc("api.email_batching.send_batched_email_notification.body_text", len(notifications)) 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)) 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 // 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 { if emailNotificationContentsType == model.EMAIL_NOTIFICATION_CONTENTS_FULL {
template = es.newEmailTemplate("post_batched_post_full", userLocale) templateName = "post_batched_post_full"
} else {
template = es.newEmailTemplate("post_batched_post_generic", userLocale)
} }
template.Props["Button"] = translateFunc("api.email_batching.render_batched_post.go_to_post") data := es.newEmailTemplateData(userLocale)
template.Props["PostMessage"] = es.srv.GetMessageForNotification(notification.post, translateFunc) data.Props["Button"] = translateFunc("api.email_batching.render_batched_post.go_to_post")
template.Props["PostLink"] = siteURL + "/" + notification.teamName + "/pl/" + notification.post.Id data.Props["PostMessage"] = es.srv.GetMessageForNotification(notification.post, translateFunc)
template.Props["SenderName"] = sender.GetDisplayName(displayNameFormat) data.Props["PostLink"] = siteURL + "/" + notification.teamName + "/pl/" + notification.post.Id
data.Props["SenderName"] = sender.GetDisplayName(displayNameFormat)
tm := time.Unix(notification.post.CreateAt/1000, 0) tm := time.Unix(notification.post.CreateAt/1000, 0)
timezone, _ := tm.Zone() 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(), "Year": tm.Year(),
"Month": translateFunc(tm.Month().String()), "Month": translateFunc(tm.Month().String()),
"Day": tm.Day(), "Day": tm.Day(),
@@ -271,17 +281,17 @@ func (es *EmailService) renderBatchedPost(notification *batchedNotification, cha
}) })
if channel.Type == model.CHANNEL_DIRECT { 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 { } 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 { } else {
// don't include channel name if email notification contents type is set to generic // don't include channel name if email notification contents type is set to generic
if emailNotificationContentsType == model.EMAIL_NOTIFICATION_CONTENTS_FULL { if emailNotificationContentsType == model.EMAIL_NOTIFICATION_CONTENTS_FULL {
template.Props["ChannelName"] = channel.DisplayName data.Props["ChannelName"] = channel.DisplayName
} else { } 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 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.") 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 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.") 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) { 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" "fmt"
"html" "html"
"html/template" "html/template"
"net/http"
"net/url" "net/url"
"path/filepath" "path/filepath"
"strings" "strings"
@@ -16,17 +15,17 @@ import (
"github.com/mattermost/mattermost-server/v5/model" "github.com/mattermost/mattermost-server/v5/model"
"github.com/mattermost/mattermost-server/v5/shared/i18n" "github.com/mattermost/mattermost-server/v5/shared/i18n"
"github.com/mattermost/mattermost-server/v5/shared/mlog" "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 channel := notification.Channel
post := notification.Post post := notification.Post
if channel.IsGroupOrDirect() { if channel.IsGroupOrDirect() {
teams, err := a.Srv().Store.Team().GetTeamsByUserId(user.Id) teams, err := a.Srv().Store.Team().GetTeamsByUserId(user.Id)
if err != nil { 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 // 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 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() { a.Srv().Go(func() {
if nErr := a.Srv().EmailService.sendNotificationMail(user.Email, html.UnescapeString(subjectText), bodyText); nErr != nil { 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 * 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 // 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 { 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 := a.GetMessageForNotification(post, translateFunc)
postMessage = html.EscapeString(postMessage) postMessage = html.EscapeString(postMessage)
normalizedPostMessage, err := a.generateHyperlinkForChannels(postMessage, teamName, landingURL) 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)) mlog.Warn("Encountered error while generating hyperlink for channels", mlog.String("team_name", teamName), mlog.Err(err))
normalizedPostMessage = postMessage normalizedPostMessage = postMessage
} }
bodyPage.Props["PostMessage"] = template.HTML(normalizedPostMessage) data.Props["PostMessage"] = template.HTML(normalizedPostMessage)
} else {
bodyPage = a.Srv().EmailService.newEmailTemplate("post_body_generic", recipient.Locale)
} }
bodyPage.Props["SiteURL"] = a.GetSiteURL() data.Props["SiteURL"] = a.GetSiteURL()
if teamName != "select_team" { if teamName != "select_team" {
bodyPage.Props["TeamLink"] = landingURL + "/pl/" + post.Id data.Props["TeamLink"] = landingURL + "/pl/" + post.Id
} else { } else {
bodyPage.Props["TeamLink"] = landingURL data.Props["TeamLink"] = landingURL
} }
t := getFormattedPostTime(recipient, post, useMilitaryTime, translateFunc) 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 channel.Type == model.CHANNEL_DIRECT {
if emailNotificationContentsType == model.EMAIL_NOTIFICATION_CONTENTS_FULL { if emailNotificationContentsType == model.EMAIL_NOTIFICATION_CONTENTS_FULL {
bodyPage.Props["BodyText"] = translateFunc("app.notification.body.intro.direct.full") data.Props["BodyText"] = translateFunc("app.notification.body.intro.direct.full")
bodyPage.Props["Info1"] = "" data.Props["Info1"] = ""
info["SenderName"] = senderName 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 { } 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, "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 { } else if channel.Type == model.CHANNEL_GROUP {
if emailNotificationContentsType == model.EMAIL_NOTIFICATION_CONTENTS_FULL { if emailNotificationContentsType == model.EMAIL_NOTIFICATION_CONTENTS_FULL {
bodyPage.Props["BodyText"] = translateFunc("app.notification.body.intro.group_message.full") data.Props["BodyText"] = translateFunc("app.notification.body.intro.group_message.full")
bodyPage.Props["Info1"] = translateFunc("app.notification.body.text.group_message.full", data.Props["Info1"] = translateFunc("app.notification.body.text.group_message.full",
map[string]interface{}{ map[string]interface{}{
"ChannelName": channelName, "ChannelName": channelName,
}) })
info["SenderName"] = senderName 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 { } 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, "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 { } else {
if emailNotificationContentsType == model.EMAIL_NOTIFICATION_CONTENTS_FULL { if emailNotificationContentsType == model.EMAIL_NOTIFICATION_CONTENTS_FULL {
bodyPage.Props["BodyText"] = translateFunc("app.notification.body.intro.notification.full") data.Props["BodyText"] = translateFunc("app.notification.body.intro.notification.full")
bodyPage.Props["Info1"] = translateFunc("app.notification.body.text.notification.full", data.Props["Info1"] = translateFunc("app.notification.body.text.notification.full",
map[string]interface{}{ map[string]interface{}{
"ChannelName": channelName, "ChannelName": channelName,
}) })
info["SenderName"] = senderName 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 { } 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, "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 { type formattedPostTime struct {

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

@@ -91,7 +91,8 @@ func TestGetNotificationEmailBodyFullNotificationPublicChannel(t *testing.T) {
teamStoreMock.On("GetByName", "testteam").Return(&model.Team{Name: "testteam"}, nil) teamStoreMock.On("GetByName", "testteam").Return(&model.Team{Name: "testteam"}, nil)
storeMock.On("Team").Return(&teamStoreMock) 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, "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, "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)) 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) teamStoreMock.On("GetByName", "testteam").Return(&model.Team{Name: "testteam"}, nil)
storeMock.On("Team").Return(&teamStoreMock) 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, "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, "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)) 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) teamStoreMock.On("GetByName", "testteam").Return(&model.Team{Name: "testteam"}, nil)
storeMock.On("Team").Return(&teamStoreMock) 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, "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, "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)) 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) teamStoreMock.On("GetByName", "testteam").Return(&model.Team{Name: "testteam"}, nil)
storeMock.On("Team").Return(&teamStoreMock) 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, "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, 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)) 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) teamStoreMock.On("GetByName", "testteam").Return(&model.Team{Name: "testteam"}, nil)
storeMock.On("Team").Return(&teamStoreMock) 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") r, _ := regexp.Compile("E([S|D]+)T")
zone := r.FindString(body) 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)) 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)) 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) 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() postTimeLine := text.String()
require.Contains(t, body, postTimeLine, fmt.Sprintf("Expected email text '%s'. Got %s", postTimeLine, body)) 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) teamStoreMock.On("GetByName", "testteam").Return(&model.Team{Name: "testteam"}, nil)
storeMock.On("Team").Return(&teamStoreMock) 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, "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)) 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) teamStoreMock.On("GetByName", "testteam").Return(&model.Team{Name: "testteam"}, nil)
storeMock.On("Team").Return(&teamStoreMock) 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, "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)) 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) teamStoreMock.On("GetByName", "testteam").Return(&model.Team{Name: "testteam"}, nil)
storeMock.On("Team").Return(&teamStoreMock) 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.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, "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)) 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) teamStoreMock.On("GetByName", "testteam").Return(&model.Team{Name: "testteam"}, nil)
storeMock.On("Team").Return(&teamStoreMock) 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.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, "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)) 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) teamStoreMock.On("GetByName", "testteam").Return(&model.Team{Name: "testteam"}, nil)
storeMock.On("Team").Return(&teamStoreMock) 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.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, "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)) 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) teamStoreMock.On("GetByName", "testteam").Return(&model.Team{Name: "testteam"}, nil)
storeMock.On("Team").Return(&teamStoreMock) 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.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, "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)) 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) teamStoreMock.On("GetByName", "testteam").Return(&model.Team{Name: "testteam"}, nil)
storeMock.On("Team").Return(&teamStoreMock) storeMock.On("Team").Return(&teamStoreMock)
body := th.App.getNotificationEmailBody(recipient, post, ch, body, err := th.App.getNotificationEmailBody(recipient, post, ch,
channelName, senderName, teamName, teamURL, channelName, senderName, teamName, teamURL,
emailNotificationContentsType, true, translateFunc) emailNotificationContentsType, true, translateFunc)
require.NoError(t, err)
assert.NotContains(t, body, message) 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) channelStoreMock.On("GetByNames", "test", []string{ch.Name}, true).Return([]*model.Channel{ch}, nil)
storeMock.On("Channel").Return(&channelStoreMock) 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, ch.Name, senderName, teamName, teamURL,
emailNotificationContentsType, true, translateFunc) emailNotificationContentsType, true, translateFunc)
require.NoError(t, err)
channelURL := teamURL + "/channels/" + ch.Name channelURL := teamURL + "/channels/" + ch.Name
mention := "~" + ch.Name mention := "~" + ch.Name
assert.Contains(t, body, "<a href='"+channelURL+"'>"+mention+"</a>") 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) channelStoreMock.On("GetByNames", "test", []string{ch.Name, ch2.Name, ch3.Name}, true).Return([]*model.Channel{ch, ch2, ch3}, nil)
storeMock.On("Channel").Return(&channelStoreMock) 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, ch.Name, senderName, teamName, teamURL,
emailNotificationContentsType, true, translateFunc) emailNotificationContentsType, true, translateFunc)
require.NoError(t, err)
channelURL := teamURL + "/channels/" + ch.Name channelURL := teamURL + "/channels/" + ch.Name
channelURL2 := teamURL + "/channels/" + ch2.Name channelURL2 := teamURL + "/channels/" + ch2.Name
channelURL3 := teamURL + "/channels/" + ch3.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) channelStoreMock.On("GetByNames", "test", []string{ch.Name}, true).Return([]*model.Channel{ch}, nil)
storeMock.On("Channel").Return(&channelStoreMock) 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, ch.Name, senderName, teamName, teamURL,
emailNotificationContentsType, true, translateFunc) emailNotificationContentsType, true, translateFunc)
require.NoError(t, err)
channelURL := teamURL + "/channels/" + ch.Name channelURL := teamURL + "/channels/" + ch.Name
mention := "~" + ch.Name mention := "~" + ch.Name
assert.NotContains(t, body, "<a href='"+channelURL+"'>"+mention+"</a>") 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) teamStoreMock.On("GetByName", "testteam").Return(&model.Team{Name: "testteam"}, nil)
storeMock.On("Team").Return(&teamStoreMock) 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)) 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) teamStoreMock.On("GetByName", "testteam").Return(&model.Team{Name: "testteam"}, nil)
storeMock.On("Team").Return(&teamStoreMock) 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)) 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/filestore"
"github.com/mattermost/mattermost-server/v5/shared/i18n" "github.com/mattermost/mattermost-server/v5/shared/i18n"
"github.com/mattermost/mattermost-server/v5/shared/mlog" "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"
"github.com/mattermost/mattermost-server/v5/store/localcachelayer" "github.com/mattermost/mattermost-server/v5/store/localcachelayer"
"github.com/mattermost/mattermost-server/v5/store/retrylayer" "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/sqlstore"
"github.com/mattermost/mattermost-server/v5/store/timerlayer" "github.com/mattermost/mattermost-server/v5/store/timerlayer"
"github.com/mattermost/mattermost-server/v5/utils" "github.com/mattermost/mattermost-server/v5/utils"
"github.com/mattermost/mattermost-server/v5/utils/fileutils"
) )
var MaxNotificationsPerChannelDefault int64 = 1000000 var MaxNotificationsPerChannelDefault int64 = 1000000
@@ -133,7 +135,7 @@ type Server struct {
newStore func() (store.Store, error) newStore func() (store.Store, error)
htmlTemplateWatcher *utils.HTMLTemplateWatcher htmlTemplateWatcher *templates.Container
sessionCache cache.Cache sessionCache cache.Cache
seenPendingPostIdsCache cache.Cache seenPendingPostIdsCache cache.Cache
statusCache cache.Cache statusCache cache.Cache
@@ -391,9 +393,19 @@ func NewServer(options ...Option) (*Server, error) {
} }
} }
if htmlTemplateWatcher, err2 := utils.NewHTMLTemplateWatcher("templates"); err2 != nil { templatesDir, ok := fileutils.FindDir("templates")
mlog.Error("Failed to parse server templates", mlog.Err(err2)) if !ok {
mlog.Error("Failed find server templates", mlog.String("directory", "templates"))
} else { } 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 s.htmlTemplateWatcher = htmlTemplateWatcher
} }
@@ -865,13 +877,10 @@ func (s *Server) Shutdown() {
// Push notification hub needs to be shutdown after HTTP server // Push notification hub needs to be shutdown after HTTP server
// to prevent stray requests from generating a push notification after it's shut down. // to prevent stray requests from generating a push notification after it's shut down.
s.StopPushNotificationsHubWorkers() s.StopPushNotificationsHubWorkers()
s.htmlTemplateWatcher.Close()
s.WaitForGoroutines() s.WaitForGoroutines()
if s.htmlTemplateWatcher != nil {
s.htmlTemplateWatcher.Close()
}
if s.advancedLogListenerCleanup != nil { if s.advancedLogListenerCleanup != nil {
s.advancedLogListenerCleanup() s.advancedLogListenerCleanup()
s.advancedLogListenerCleanup = nil s.advancedLogListenerCleanup = nil

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

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

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

@@ -9,9 +9,6 @@ import (
"testing" "testing"
"github.com/stretchr/testify/require" "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) { func TestConfigFlag(t *testing.T) {
@@ -19,16 +16,14 @@ func TestConfigFlag(t *testing.T) {
defer th.TearDown() defer th.TearDown()
dir := th.TemporaryDirectory() 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() prevDir, err := os.Getwd()
require.NoError(t, err) require.NoError(t, err)
defer os.Chdir(prevDir) defer os.Chdir(prevDir)
os.Chdir(dir) os.Chdir(dir)
t.Run("version without a config file should fail", func(t *testing.T) { t.Run("version without a config file should fail", func(t *testing.T) {
err := os.RemoveAll("config")
require.NoError(t, err)
th.SetAutoConfig(false) th.SetAutoConfig(false)
defer th.SetAutoConfig(true) defer th.SetAutoConfig(true)
require.Error(t, th.RunCommand(t, "version")) 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{ var testResourcesToSetup = []testResourceDetails{
{root, "mattermost-server", resourceTypeFolder, actionSymlink}, {root, "mattermost-server", resourceTypeFolder, actionSymlink},
{"go.mod", "go.mod", resourceTypeFile, actionSymlink},
{"i18n", "i18n", resourceTypeFolder, actionSymlink}, {"i18n", "i18n", resourceTypeFolder, actionSymlink},
{"templates", "templates", resourceTypeFolder, actionSymlink}, {"templates", "templates", resourceTypeFolder, actionSymlink},
{"tests", "tests", 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/avct/uasurfer"
"github.com/mattermost/mattermost-server/v5/app" "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 // 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) { func renderUnsupportedBrowser(app app.AppIface, w http.ResponseWriter, r *http.Request) {
w.Header().Set("Cache-Control", "no-store") 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 // User Agent info
ua := uasurfer.Parse(r.UserAgent()) ua := uasurfer.Parse(r.UserAgent())
@@ -57,18 +63,16 @@ func renderUnsupportedBrowser(app app.AppIface, w http.ResponseWriter, r *http.R
// Basic heading translations // Basic heading translations
if isSafari { 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 { } 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 // Mattermost app version
if isWindows { if isWindows {
page.Props["App"] = renderMattermostAppWindows(app) data.Props["App"] = renderMattermostAppWindows(app)
} else if isMacOSX { } else if isMacOSX {
page.Props["App"] = renderMattermostAppMac(app) data.Props["App"] = renderMattermostAppMac(app)
} }
// Browsers to download // Browsers to download
@@ -78,14 +82,14 @@ func renderUnsupportedBrowser(app app.AppIface, w http.ResponseWriter, r *http.R
if isSafari { if isSafari {
browsers = append(browsers, renderBrowserSafari(app)) browsers = append(browsers, renderBrowserSafari(app))
} }
page.Props["Browsers"] = browsers data.Props["Browsers"] = browsers
// If on Windows 10, show link to Edge // If on Windows 10, show link to Edge
if isWindows10 { 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 { func renderMattermostAppMac(app app.AppIface) MattermostApp {