github.com/vnforks/kid/v5@v5.22.1-0.20200408055009-b89d99c65676/app/notification_email.go (about) 1 // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. 2 // See LICENSE.txt for license information. 3 4 package app 5 6 import ( 7 "fmt" 8 "html" 9 "net/url" 10 "path/filepath" 11 "strings" 12 "time" 13 14 "github.com/mattermost/go-i18n/i18n" 15 "github.com/vnforks/kid/v5/mlog" 16 "github.com/vnforks/kid/v5/model" 17 "github.com/vnforks/kid/v5/utils" 18 ) 19 20 func (a *App) sendNotificationEmail(notification *PostNotification, user *model.User, branch *model.Branch) *model.AppError { 21 class := notification.Class 22 post := notification.Post 23 24 branches, err := a.Srv().Store.Branch().GetBranchesByUserId(user.Id) 25 if err != nil { 26 return err 27 } 28 29 // if the recipient isn't in the current user's branch, just pick one 30 found := false 31 32 for i := range branches { 33 if branches[i].Id == branch.Id { 34 found = true 35 break 36 } 37 } 38 39 if !found && len(branches) > 0 { 40 branch = branches[0] 41 } else { 42 // in case the user hasn't joined any branches we send them to the select_branch page 43 branch = &model.Branch{Name: "select_branch", DisplayName: *a.Config().BranchSettings.SiteName} 44 } 45 46 if *a.Config().EmailSettings.EnableEmailBatching { 47 var sendBatched bool 48 if data, err := a.Srv().Store.Preference().Get(user.Id, model.PREFERENCE_CATEGORY_NOTIFICATIONS, model.PREFERENCE_NAME_EMAIL_INTERVAL); err != nil { 49 // if the call fails, assume that the interval has not been explicitly set and batch the notifications 50 sendBatched = true 51 } else { 52 // if the user has chosen to receive notifications immediately, don't batch them 53 sendBatched = data.Value != model.PREFERENCE_EMAIL_INTERVAL_NO_BATCHING_SECONDS 54 } 55 56 if sendBatched { 57 if err := a.AddNotificationEmailToBatch(user, post, branch); err == nil { 58 return nil 59 } 60 } 61 62 // fall back to sending a single email if we can't batch it for some reason 63 } 64 65 translateFunc := utils.GetUserTranslations(user.Locale) 66 67 var useMilitaryTime bool 68 if data, err := a.Srv().Store.Preference().Get(user.Id, model.PREFERENCE_CATEGORY_DISPLAY_SETTINGS, model.PREFERENCE_NAME_USE_MILITARY_TIME); err != nil { 69 useMilitaryTime = true 70 } else { 71 useMilitaryTime = data.Value == "true" 72 } 73 74 nameFormat := a.GetNotificationNameFormat(user) 75 76 className := notification.GetClassName(nameFormat, "") 77 senderName := notification.GetSenderName(nameFormat, *a.Config().ServiceSettings.EnablePostUsernameOverride) 78 79 emailNotificationContentsType := model.EMAIL_NOTIFICATION_CONTENTS_FULL 80 if license := a.License(); license != nil && *license.Features.EmailNotificationContents { 81 emailNotificationContentsType = *a.Config().EmailSettings.EmailNotificationContentsType 82 } 83 84 var subjectText string 85 if *a.Config().EmailSettings.UseClassInEmailNotifications { 86 subjectText = getNotificationEmailSubject(user, post, translateFunc, *a.Config().BranchSettings.SiteName, branch.DisplayName+" ("+className+")", useMilitaryTime) 87 } else { 88 subjectText = getNotificationEmailSubject(user, post, translateFunc, *a.Config().BranchSettings.SiteName, branch.DisplayName, useMilitaryTime) 89 } 90 91 landingURL := a.GetSiteURL() + "/landing#/" + branch.Name 92 var bodyText = a.getNotificationEmailBody(user, post, class, className, senderName, branch.Name, landingURL, emailNotificationContentsType, useMilitaryTime, translateFunc) 93 94 a.Srv().Go(func() { 95 if err := a.sendNotificationMail(user.Email, html.UnescapeString(subjectText), bodyText); err != nil { 96 mlog.Error("Error while sending the email", mlog.String("user_email", user.Email), mlog.Err(err)) 97 } 98 }) 99 100 if a.Metrics() != nil { 101 a.Metrics().IncrementPostSentEmail() 102 } 103 104 return nil 105 } 106 107 /** 108 * Computes the subject line for direct notification email messages 109 */ 110 func getDirectMessageNotificationEmailSubject(user *model.User, post *model.Post, translateFunc i18n.TranslateFunc, siteName string, senderName string, useMilitaryTime bool) string { 111 t := getFormattedPostTime(user, post, useMilitaryTime, translateFunc) 112 var subjectParameters = map[string]interface{}{ 113 "SiteName": siteName, 114 "SenderDisplayName": senderName, 115 "Month": t.Month, 116 "Day": t.Day, 117 "Year": t.Year, 118 } 119 return translateFunc("app.notification.subject.direct.full", subjectParameters) 120 } 121 122 /** 123 * Computes the subject line for group, public, and private email messages 124 */ 125 func getNotificationEmailSubject(user *model.User, post *model.Post, translateFunc i18n.TranslateFunc, siteName string, branchName string, useMilitaryTime bool) string { 126 t := getFormattedPostTime(user, post, useMilitaryTime, translateFunc) 127 var subjectParameters = map[string]interface{}{ 128 "SiteName": siteName, 129 "BranchName": branchName, 130 "Month": t.Month, 131 "Day": t.Day, 132 "Year": t.Year, 133 } 134 return translateFunc("app.notification.subject.notification.full", subjectParameters) 135 } 136 137 /** 138 * Computes the subject line for group email messages 139 */ 140 func getGroupMessageNotificationEmailSubject(user *model.User, post *model.Post, translateFunc i18n.TranslateFunc, siteName string, className string, emailNotificationContentsType string, useMilitaryTime bool) string { 141 t := getFormattedPostTime(user, post, useMilitaryTime, translateFunc) 142 var subjectParameters = map[string]interface{}{ 143 "SiteName": siteName, 144 "Month": t.Month, 145 "Day": t.Day, 146 "Year": t.Year, 147 } 148 if emailNotificationContentsType == model.EMAIL_NOTIFICATION_CONTENTS_FULL { 149 subjectParameters["ClassName"] = className 150 return translateFunc("app.notification.subject.group_message.full", subjectParameters) 151 } 152 return translateFunc("app.notification.subject.group_message.generic", subjectParameters) 153 } 154 155 /** 156 * Computes the email body for notification messages 157 */ 158 func (a *App) getNotificationEmailBody(recipient *model.User, post *model.Post, class *model.Class, className string, senderName string, branchName string, landingURL string, emailNotificationContentsType string, useMilitaryTime bool, translateFunc i18n.TranslateFunc) string { 159 // only include message contents in notification email if email notification contents type is set to full 160 var bodyPage *utils.HTMLTemplate 161 if emailNotificationContentsType == model.EMAIL_NOTIFICATION_CONTENTS_FULL { 162 bodyPage = a.newEmailTemplate("post_body_full", recipient.Locale) 163 postMessage := a.GetMessageForNotification(post, translateFunc) 164 postMessage = html.EscapeString(postMessage) 165 //bodyPage.Props["PostMessage"] = template.HTML(normalizedPostMessage) 166 } else { 167 bodyPage = a.newEmailTemplate("post_body_generic", recipient.Locale) 168 } 169 170 bodyPage.Props["SiteURL"] = a.GetSiteURL() 171 if branchName != "select_branch" { 172 bodyPage.Props["BranchLink"] = landingURL + "/pl/" + post.Id 173 } else { 174 bodyPage.Props["BranchLink"] = landingURL 175 } 176 177 t := getFormattedPostTime(recipient, post, useMilitaryTime, translateFunc) 178 179 info := map[string]interface{}{ 180 "Hour": t.Hour, 181 "Minute": t.Minute, 182 "TimeZone": t.TimeZone, 183 "Month": t.Month, 184 "Day": t.Day, 185 } 186 187 if emailNotificationContentsType == model.EMAIL_NOTIFICATION_CONTENTS_FULL { 188 bodyPage.Props["BodyText"] = translateFunc("app.notification.body.intro.notification.full") 189 bodyPage.Props["Info1"] = translateFunc("app.notification.body.text.notification.full", 190 map[string]interface{}{ 191 "ClassName": className, 192 }) 193 info["SenderName"] = senderName 194 bodyPage.Props["Info2"] = translateFunc("app.notification.body.text.notification.full2", info) 195 } else { 196 bodyPage.Props["BodyText"] = translateFunc("app.notification.body.intro.notification.generic", map[string]interface{}{ 197 "SenderName": senderName, 198 }) 199 bodyPage.Props["Info"] = translateFunc("app.notification.body.text.notification.generic", info) 200 } 201 202 bodyPage.Props["Button"] = translateFunc("api.templates.post_body.button") 203 204 return bodyPage.Render() 205 } 206 207 type formattedPostTime struct { 208 Time time.Time 209 Year string 210 Month string 211 Day string 212 Hour string 213 Minute string 214 TimeZone string 215 } 216 217 func getFormattedPostTime(user *model.User, post *model.Post, useMilitaryTime bool, translateFunc i18n.TranslateFunc) formattedPostTime { 218 preferredTimezone := user.GetPreferredTimezone() 219 postTime := time.Unix(post.CreateAt/1000, 0) 220 zone, _ := postTime.Zone() 221 222 localTime := postTime 223 if preferredTimezone != "" { 224 loc, _ := time.LoadLocation(preferredTimezone) 225 if loc != nil { 226 localTime = postTime.In(loc) 227 zone, _ = localTime.Zone() 228 } 229 } 230 231 hour := localTime.Format("15") 232 period := "" 233 if !useMilitaryTime { 234 hour = localTime.Format("3") 235 period = " " + localTime.Format("PM") 236 } 237 238 return formattedPostTime{ 239 Time: localTime, 240 Year: fmt.Sprintf("%d", localTime.Year()), 241 Month: translateFunc(localTime.Month().String()), 242 Day: fmt.Sprintf("%d", localTime.Day()), 243 Hour: hour, 244 Minute: fmt.Sprintf("%02d"+period, localTime.Minute()), 245 TimeZone: zone, 246 } 247 } 248 249 func (a *App) GetMessageForNotification(post *model.Post, translateFunc i18n.TranslateFunc) string { 250 if len(strings.TrimSpace(post.Message)) != 0 || len(post.FileIds) == 0 { 251 return post.Message 252 } 253 254 // extract the filenames from their paths and determine what type of files are attached 255 infos, err := a.Srv().Store.FileInfo().GetForPost(post.Id, true, false, true) 256 if err != nil { 257 mlog.Warn("Encountered error when getting files for notification message", mlog.String("post_id", post.Id), mlog.Err(err)) 258 } 259 260 filenames := make([]string, len(infos)) 261 onlyImages := true 262 for i, info := range infos { 263 if escaped, err := url.QueryUnescape(filepath.Base(info.Name)); err != nil { 264 // this should never error since filepath was escaped using url.QueryEscape 265 filenames[i] = escaped 266 } else { 267 filenames[i] = info.Name 268 } 269 270 onlyImages = onlyImages && info.IsImage() 271 } 272 273 props := map[string]interface{}{"Filenames": strings.Join(filenames, ", ")} 274 275 if onlyImages { 276 return translateFunc("api.post.get_message_for_notification.images_sent", len(filenames), props) 277 } 278 return translateFunc("api.post.get_message_for_notification.files_sent", len(filenames), props) 279 }