diff options
Diffstat (limited to 'api')
-rw-r--r-- | api/channel.go | 38 | ||||
-rw-r--r-- | api/post.go | 142 | ||||
-rw-r--r-- | api/post_test.go | 48 | ||||
-rw-r--r-- | api/preference.go | 2 | ||||
-rw-r--r-- | api/preference_test.go | 2 | ||||
-rw-r--r-- | api/templates/post_body.html | 2 | ||||
-rw-r--r-- | api/user.go | 8 | ||||
-rw-r--r-- | api/web_team_hub.go | 4 |
8 files changed, 210 insertions, 36 deletions
diff --git a/api/channel.go b/api/channel.go index 99640e71a..f17594c0a 100644 --- a/api/channel.go +++ b/api/channel.go @@ -268,19 +268,51 @@ func updateChannelHeader(c *Context, w http.ResponseWriter, r *http.Request) { if !c.HasPermissionsToTeam(channel.TeamId, "updateChannelHeader") { return } - + oldChannelHeader := channel.Header channel.Header = channelHeader if ucresult := <-Srv.Store.Channel().Update(channel); ucresult.Err != nil { c.Err = ucresult.Err return } else { + PostUpdateChannelHeaderMessageAndForget(c, channel.Id, oldChannelHeader, channelHeader) c.LogAudit("name=" + channel.Name) w.Write([]byte(channel.ToJson())) } } } +func PostUpdateChannelHeaderMessageAndForget(c *Context, channelId string, oldChannelHeader, newChannelHeader string) { + go func() { + uc := Srv.Store.User().Get(c.Session.UserId) + + if uresult := <-uc; uresult.Err != nil { + l4g.Error("Failed to retrieve user while trying to save update channel header message %v", uresult.Err) + return + } else { + user := uresult.Data.(*model.User) + + var message string + if oldChannelHeader == "" { + message = fmt.Sprintf("%s updated the channel header to: %s", user.Username, newChannelHeader) + } else if newChannelHeader == "" { + message = fmt.Sprintf("%s removed the channel header (was: %s)", user.Username, oldChannelHeader) + } else { + message = fmt.Sprintf("%s updated the channel header from: %s to: %s", user.Username, oldChannelHeader, newChannelHeader) + } + + post := &model.Post{ + ChannelId: channelId, + Message: message, + Type: model.POST_HEADER_CHANGE, + } + if _, err := CreatePost(c, post, false); err != nil { + l4g.Error("Failed to post join/leave message %v", err) + } + } + }() +} + func updateChannelPurpose(c *Context, w http.ResponseWriter, r *http.Request) { props := model.MapFromJson(r.Body) channelId := props["channel_id"] @@ -421,7 +453,7 @@ func JoinChannel(c *Context, channelId string, role string) { c.Err = err return } - PostUserAddRemoveMessageAndForget(c, channel.Id, fmt.Sprintf(`User %v has joined this channel.`, user.Username)) + PostUserAddRemoveMessageAndForget(c, channel.Id, fmt.Sprintf(`%v has joined the channel.`, user.Username)) } else { c.Err = model.NewAppError("join", "You do not have the appropriate permissions", "") c.Err.StatusCode = http.StatusForbidden @@ -708,7 +740,7 @@ func getChannelExtraInfo(c *Context, w http.ResponseWriter, r *http.Request) { } scm := Srv.Store.Channel().GetMember(id, c.Session.UserId) - ecm := Srv.Store.Channel().GetExtraMembers(id, 20) + ecm := Srv.Store.Channel().GetExtraMembers(id, 100) ccm := Srv.Store.Channel().GetMemberCount(id) if cmresult := <-scm; cmresult.Err != nil { diff --git a/api/post.go b/api/post.go index 81cc9a1c6..a102cdf4d 100644 --- a/api/post.go +++ b/api/post.go @@ -153,9 +153,6 @@ func CreateWebhookPost(c *Context, channelId, text, overrideUsername, overrideIc linkWithTextRegex := regexp.MustCompile(`<([^<\|]+)\|([^>]+)>`) text = linkWithTextRegex.ReplaceAllString(text, "[${2}](${1})") - linkRegex := regexp.MustCompile(`<\s*(\S*)\s*>`) - text = linkRegex.ReplaceAllString(text, "${1}") - post := &model.Post{UserId: c.Session.UserId, ChannelId: channelId, Message: text, Type: postType} post.AddProp("from_webhook", "true") @@ -177,7 +174,21 @@ func CreateWebhookPost(c *Context, channelId, text, overrideUsername, overrideIc if len(props) > 0 { for key, val := range props { - if key != "override_icon_url" && key != "override_username" && key != "from_webhook" { + if key == "attachments" { + if list, success := val.([]interface{}); success { + // parse attachment links into Markdown format + for i, aInt := range list { + attachment := aInt.(map[string]interface{}) + if _, ok := attachment["text"]; ok { + aText := attachment["text"].(string) + aText = linkWithTextRegex.ReplaceAllString(aText, "[${2}](${1})") + attachment["text"] = aText + list[i] = attachment + } + } + post.AddProp(key, list) + } + } else if key != "override_icon_url" && key != "override_username" && key != "from_webhook" { post.AddProp(key, val) } } @@ -225,9 +236,68 @@ func handlePostEventsAndForget(c *Context, post *model.Post, triggerWebhooks boo if triggerWebhooks { handleWebhookEventsAndForget(c, post, team, channel, user) } + + if channel.Type == model.CHANNEL_DIRECT { + go makeDirectChannelVisible(c.Session.TeamId, post.ChannelId) + } }() } +func makeDirectChannelVisible(teamId string, channelId string) { + var members []model.ChannelMember + if result := <-Srv.Store.Channel().GetMembers(channelId); result.Err != nil { + l4g.Error("Failed to get channel members channel_id=%v err=%v", channelId, result.Err.Message) + return + } else { + members = result.Data.([]model.ChannelMember) + } + + if len(members) != 2 { + l4g.Error("Failed to get 2 members for a direct channel channel_id=%v", channelId) + return + } + + // make sure the channel is visible to both members + for i, member := range members { + otherUserId := members[1-i].UserId + + if result := <-Srv.Store.Preference().Get(member.UserId, model.PREFERENCE_CATEGORY_DIRECT_CHANNEL_SHOW, otherUserId); result.Err != nil { + // create a new preference since one doesn't exist yet + preference := &model.Preference{ + UserId: member.UserId, + Category: model.PREFERENCE_CATEGORY_DIRECT_CHANNEL_SHOW, + Name: otherUserId, + Value: "true", + } + + if saveResult := <-Srv.Store.Preference().Save(&model.Preferences{*preference}); saveResult.Err != nil { + l4g.Error("Failed to save direct channel preference user_id=%v other_user_id=%v err=%v", member.UserId, otherUserId, saveResult.Err.Message) + } else { + message := model.NewMessage(teamId, channelId, member.UserId, model.ACTION_PREFERENCE_CHANGED) + message.Add("preference", preference.ToJson()) + + PublishAndForget(message) + } + } else { + preference := result.Data.(model.Preference) + + if preference.Value != "true" { + // update the existing preference to make the channel visible + preference.Value = "true" + + if updateResult := <-Srv.Store.Preference().Save(&model.Preferences{preference}); updateResult.Err != nil { + l4g.Error("Failed to update direct channel preference user_id=%v other_user_id=%v err=%v", member.UserId, otherUserId, updateResult.Err.Message) + } else { + message := model.NewMessage(teamId, channelId, member.UserId, model.ACTION_PREFERENCE_CHANGED) + message.Add("preference", preference.ToJson()) + + PublishAndForget(message) + } + } + } + } +} + func handleWebhookEventsAndForget(c *Context, post *model.Post, team *model.Team, channel *model.Channel, user *model.User) { go func() { if !utils.Cfg.ServiceSettings.EnableOutgoingWebhooks { @@ -421,35 +491,52 @@ func sendNotificationsAndForget(c *Context, post *model.Post, team *model.Team, splitF := func(c rune) bool { return model.SplitRunes[c] } - splitMessage := strings.FieldsFunc(post.Message, splitF) + splitMessage := strings.Fields(post.Message) for _, word := range splitMessage { + var userIds []string // Non-case-sensitive check for regular keys - userIds1, keyMatch := keywordMap[strings.ToLower(word)] + if ids, match := keywordMap[strings.ToLower(word)]; match { + userIds = append(userIds, ids...) + } // Case-sensitive check for first name - userIds2, firstNameMatch := keywordMap[word] + if ids, match := keywordMap[word]; match { + userIds = append(userIds, ids...) + } - userIds := append(userIds1, userIds2...) + if len(userIds) == 0 { + // No matches were found with the string split just on whitespace so try further splitting + // the message on punctuation + splitWords := strings.FieldsFunc(word, splitF) - // If one of the non-case-senstive keys or the first name matches the word - // then we add en entry to the sendEmail map - if keyMatch || firstNameMatch { - for _, userId := range userIds { - if post.UserId == userId { - continue + for _, splitWord := range splitWords { + // Non-case-sensitive check for regular keys + if ids, match := keywordMap[strings.ToLower(splitWord)]; match { + userIds = append(userIds, ids...) } - sendEmail := true - if _, ok := profileMap[userId].NotifyProps["email"]; ok && profileMap[userId].NotifyProps["email"] == "false" { - sendEmail = false - } - if sendEmail && (profileMap[userId].IsAway() || profileMap[userId].IsOffline()) { - toEmailMap[userId] = true - } else { - toEmailMap[userId] = false + + // Case-sensitive check for first name + if ids, match := keywordMap[splitWord]; match { + userIds = append(userIds, ids...) } } } + + for _, userId := range userIds { + if post.UserId == userId { + continue + } + sendEmail := true + if _, ok := profileMap[userId].NotifyProps["email"]; ok && profileMap[userId].NotifyProps["email"] == "false" { + sendEmail = false + } + if sendEmail && (profileMap[userId].IsAway() || profileMap[userId].IsOffline()) { + toEmailMap[userId] = true + } else { + toEmailMap[userId] = false + } + } } for id := range toEmailMap { @@ -466,8 +553,7 @@ func sendNotificationsAndForget(c *Context, post *model.Post, team *model.Team, teamURL := c.GetSiteURL() + "/" + team.Name // Build and send the emails - location, _ := time.LoadLocation("UTC") - tm := time.Unix(post.CreateAt/1000, 0).In(location) + tm := time.Unix(post.CreateAt/1000, 0) subjectPage := NewServerTemplatePage("post_subject") subjectPage.Props["SiteURL"] = c.GetSiteURL() @@ -499,6 +585,7 @@ func sendNotificationsAndForget(c *Context, post *model.Post, team *model.Team, bodyPage.Props["Minute"] = fmt.Sprintf("%02d", tm.Minute()) bodyPage.Props["Month"] = tm.Month().String()[:3] bodyPage.Props["Day"] = fmt.Sprintf("%d", tm.Day()) + bodyPage.Props["TimeZone"], _ = tm.Zone() bodyPage.Props["PostMessage"] = model.ClearMentionTags(post.Message) bodyPage.Props["TeamLink"] = teamURL + "/channels/" + channel.Name @@ -550,11 +637,16 @@ func sendNotificationsAndForget(c *Context, post *model.Post, team *model.Team, msg := model.PushNotification{} msg.Platform = model.PUSH_NOTIFY_APPLE - msg.Message = subjectPage.Render() msg.Badge = 1 msg.DeviceId = strings.TrimPrefix(session.DeviceId, "apple:") msg.ServerId = utils.CfgDiagnosticId + if channel.Type == model.CHANNEL_DIRECT { + msg.Message = channelName + " sent you a direct message" + } else { + msg.Message = profileMap[id].FirstName + " mentioned you in " + channelName + } + httpClient := http.Client{} request, _ := http.NewRequest("POST", *utils.Cfg.EmailSettings.PushNotificationServer+"/api/v1/send_push", strings.NewReader(msg.ToJson())) diff --git a/api/post_test.go b/api/post_test.go index 0cb437e88..8e09ca76d 100644 --- a/api/post_test.go +++ b/api/post_test.go @@ -805,3 +805,51 @@ func TestFuzzyPosts(t *testing.T) { } } } + +func TestMakeDirectChannelVisible(t *testing.T) { + Setup() + + team := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN} + team = Client.Must(Client.CreateTeam(team)).Data.(*model.Team) + + user1 := &model.User{TeamId: team.Id, Email: model.NewId() + "corey@test.com", Nickname: "Corey Hulen", Password: "pwd"} + user1 = Client.Must(Client.CreateUser(user1, "")).Data.(*model.User) + store.Must(Srv.Store.User().VerifyEmail(user1.Id)) + + user2 := &model.User{TeamId: team.Id, Email: model.NewId() + "corey@test.com", Nickname: "Corey Hulen", Password: "pwd"} + user2 = Client.Must(Client.CreateUser(user2, "")).Data.(*model.User) + store.Must(Srv.Store.User().VerifyEmail(user2.Id)) + + // user2 will be created with prefs created to show user1 in the sidebar so set that to false to get rid of it + Client.LoginByEmail(team.Name, user2.Email, "pwd") + + preferences := &model.Preferences{ + { + UserId: user2.Id, + Category: model.PREFERENCE_CATEGORY_DIRECT_CHANNEL_SHOW, + Name: user1.Id, + Value: "false", + }, + } + Client.Must(Client.SetPreferences(preferences)) + + Client.LoginByEmail(team.Name, user1.Email, "pwd") + + channel := Client.Must(Client.CreateDirectChannel(map[string]string{"user_id": user2.Id})).Data.(*model.Channel) + + makeDirectChannelVisible(team.Id, channel.Id) + + if result, err := Client.GetPreference(model.PREFERENCE_CATEGORY_DIRECT_CHANNEL_SHOW, user2.Id); err != nil { + t.Fatal("Errored trying to set direct channel to be visible for user1") + } else if pref := result.Data.(*model.Preference); pref.Value != "true" { + t.Fatal("Failed to set direct channel to be visible for user1") + } + + Client.LoginByEmail(team.Name, user2.Email, "pwd") + + if result, err := Client.GetPreference(model.PREFERENCE_CATEGORY_DIRECT_CHANNEL_SHOW, user1.Id); err != nil { + t.Fatal("Errored trying to set direct channel to be visible for user2") + } else if pref := result.Data.(*model.Preference); pref.Value != "true" { + t.Fatal("Failed to set direct channel to be visible for user2") + } +} diff --git a/api/preference.go b/api/preference.go index 6d6ac1a7f..e9c74aafe 100644 --- a/api/preference.go +++ b/api/preference.go @@ -1,4 +1,4 @@ -// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved. +// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved. // See License.txt for license information. package api diff --git a/api/preference_test.go b/api/preference_test.go index 2f6204246..310b4dcdb 100644 --- a/api/preference_test.go +++ b/api/preference_test.go @@ -1,4 +1,4 @@ -// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved. +// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved. // See License.txt for license information. package api diff --git a/api/templates/post_body.html b/api/templates/post_body.html index 182134b1a..00d4a563d 100644 --- a/api/templates/post_body.html +++ b/api/templates/post_body.html @@ -18,7 +18,7 @@ <tr> <td style="border-bottom: 1px solid #ddd; padding: 0 0 20px;"> <h2 style="font-weight: normal; margin-top: 10px;">You were mentioned</h2> - <p>CHANNEL: {{.Props.ChannelName}}<br>{{.Props.SenderName}} - {{.Props.Hour}}:{{.Props.Minute}} GMT, {{.Props.Month}} {{.Props.Day}}<br><pre style="text-align:left;font-family: 'Lato', sans-serif; white-space: pre-wrap; white-space: -moz-pre-wrap; white-space: -pre-wrap; white-space: -o-pre-wrap; word-wrap: break-word;">{{.Props.PostMessage}}</pre></p> + <p>CHANNEL: {{.Props.ChannelName}}<br>{{.Props.SenderName}} - {{.Props.Hour}}:{{.Props.Minute}} {{.Props.TimeZone}}, {{.Props.Month}} {{.Props.Day}}<br><pre style="text-align:left;font-family: 'Lato', sans-serif; white-space: pre-wrap; white-space: -moz-pre-wrap; white-space: -pre-wrap; white-space: -o-pre-wrap; word-wrap: break-word;">{{.Props.PostMessage}}</pre></p> <p style="margin: 20px 0 15px"> <a href="{{.Props.TeamLink}}" style="background: #2389D7; display: inline-block; border-radius: 3px; color: #fff; border: none; outline: none; min-width: 170px; padding: 15px 25px; font-size: 14px; font-family: inherit; cursor: pointer; -webkit-appearance: none;text-decoration: none;">Go To Channel</a> </p> diff --git a/api/user.go b/api/user.go index 62947d8fd..886e38c91 100644 --- a/api/user.go +++ b/api/user.go @@ -661,7 +661,7 @@ func getProfiles(c *Context, w http.ResponseWriter, r *http.Request) { profiles := result.Data.(map[string]*model.User) for k, p := range profiles { - options := utils.SanitizeOptions + options := utils.Cfg.GetSanitizeOptions() options["passwordupdate"] = false if c.IsSystemAdmin() { @@ -1102,7 +1102,7 @@ func updateRoles(c *Context, w http.ResponseWriter, r *http.Request) { } } - options := utils.SanitizeOptions + options := utils.Cfg.GetSanitizeOptions() options["passwordupdate"] = false ruser.Sanitize(options) w.Write([]byte(ruser.ToJson())) @@ -1222,7 +1222,7 @@ func UpdateActive(c *Context, user *model.User, active bool) *model.User { } ruser := result.Data.([2]*model.User)[0] - options := utils.SanitizeOptions + options := utils.Cfg.GetSanitizeOptions() options["passwordupdate"] = false ruser.Sanitize(options) return ruser @@ -1548,7 +1548,7 @@ func updateUserNotify(c *Context, w http.ResponseWriter, r *http.Request) { c.LogAuditWithUserId(user.Id, "") ruser := result.Data.([2]*model.User)[0] - options := utils.SanitizeOptions + options := utils.Cfg.GetSanitizeOptions() options["passwordupdate"] = false ruser.Sanitize(options) w.Write([]byte(ruser.ToJson())) diff --git a/api/web_team_hub.go b/api/web_team_hub.go index 6a25b7d3d..2c2386317 100644 --- a/api/web_team_hub.go +++ b/api/web_team_hub.go @@ -95,9 +95,11 @@ func ShouldSendEvent(webCon *WebConn, msg *model.Message) bool { return false } } else { - // Don't share a user's view events with other users + // Don't share a user's view or preference events with other users if msg.Action == model.ACTION_CHANNEL_VIEWED { return false + } else if msg.Action == model.ACTION_PREFERENCE_CHANGED { + return false } // Only report events to a user who is the subject of the event, or is in the channel of the event |