hunk.code_window: 			result.Err = model.NewLocAppError("SqlPostStore.GetPostContext", "store.sql_post.get_posts_around.get_parent.app_error", nil, "channelId="+channelId+err2.Error())
   		} else {
   			list := &model.PostList{Order: make([]string, 0, len(posts))}
   			// We need to flip the order if we selected backwards
   			if before {
   				for _, p := range posts {
  
hunk.labels: keep keep keep replace keep keep keep keep
hunk.after_edit: 			list := model.NewPostList()
  
hunk.file_path: store/sql_post_store.go
hunk.type: replace
file: // Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
  // See License.txt for license information.
  
  package app
  
  import (
  	"net/http"
  	"regexp"
  
  	l4g "github.com/alecthomas/log4go"
  	"github.com/dyatlov/go-opengraph/opengraph"
  	"github.com/mattermost/platform/einterfaces"
  	"github.com/mattermost/platform/model"
  	"github.com/mattermost/platform/store"
  	"github.com/mattermost/platform/utils"
  )
  
  func CreatePostAsUser(post *model.Post) (*model.Post, *model.AppError) {
  	// Check that channel has not been deleted
  	var channel *model.Channel
  	if result := <-Srv.Store.Channel().Get(post.ChannelId, true); result.Err != nil {
  		err := model.NewLocAppError("CreatePostAsUser", "api.context.invalid_param.app_error", map[string]interface{}{"Name": "post.channel_id"}, result.Err.Error())
  		err.StatusCode = http.StatusBadRequest
  		return nil, err
  	} else {
  		channel = result.Data.(*model.Channel)
  	}
  
  	if channel.DeleteAt != 0 {
  		err := model.NewLocAppError("createPost", "api.post.create_post.can_not_post_to_deleted.error", nil, "")
  		err.StatusCode = http.StatusBadRequest
  		return nil, err
  	}
  
  	if rp, err := CreatePost(post, channel.TeamId, true); err != nil {
  		if err.Id == "api.post.create_post.root_id.app_error" ||
  			err.Id == "api.post.create_post.channel_root_id.app_error" ||
  			err.Id == "api.post.create_post.parent_id.app_error" {
  			err.StatusCode = http.StatusBadRequest
  		}
  
  		return nil, err
  	} else {
  		// Update the LastViewAt only if the post does not have from_webhook prop set (eg. Zapier app)
  		if _, ok := post.Props["from_webhook"]; !ok {
  			if result := <-Srv.Store.Channel().UpdateLastViewedAt([]string{post.ChannelId}, post.UserId); result.Err != nil {
  				l4g.Error(utils.T("api.post.create_post.last_viewed.error"), post.ChannelId, post.UserId, result.Err)
  			}
  		}
  
  		return rp, nil
  	}
  
  }
  
  func CreatePost(post *model.Post, teamId string, triggerWebhooks bool) (*model.Post, *model.AppError) {
  	var pchan store.StoreChannel
  	if len(post.RootId) > 0 {
  		pchan = Srv.Store.Post().Get(post.RootId)
  	}
  
  	// Verify the parent/child relationships are correct
  	if pchan != nil {
  		if presult := <-pchan; presult.Err != nil {
  			return nil, model.NewLocAppError("createPost", "api.post.create_post.root_id.app_error", nil, "")
  		} else {
  			list := presult.Data.(*model.PostList)
  			if len(list.Posts) == 0 || !list.IsChannelId(post.ChannelId) {
  				return nil, model.NewLocAppError("createPost", "api.post.create_post.channel_root_id.app_error", nil, "")
  			}
  
  			if post.ParentId == "" {
  				post.ParentId = post.RootId
  			}
  
  			if post.RootId != post.ParentId {
  				parent := list.Posts[post.ParentId]
  				if parent == nil {
  					return nil, model.NewLocAppError("createPost", "api.post.create_post.parent_id.app_error", nil, "")
  				}
  			}
  		}
  	}
  
  	post.Hashtags, _ = model.ParseHashtags(post.Message)
  
  	var rpost *model.Post
  	if result := <-Srv.Store.Post().Save(post); result.Err != nil {
  		return nil, result.Err
  	} else {
  		rpost = result.Data.(*model.Post)
  	}
  
  	if einterfaces.GetMetricsInterface() != nil {
  		einterfaces.GetMetricsInterface().IncrementPostCreate()
  	}
  
  	if len(post.FileIds) > 0 {
  		// There's a rare bug where the client sends up duplicate FileIds so protect against that
  		post.FileIds = utils.RemoveDuplicatesFromStringArray(post.FileIds)
  
  		for _, fileId := range post.FileIds {
  			if result := <-Srv.Store.FileInfo().AttachToPost(fileId, post.Id); result.Err != nil {
  				l4g.Error(utils.T("api.post.create_post.attach_files.error"), post.Id, post.FileIds, post.UserId, result.Err)
  			}
  		}
  
  		if einterfaces.GetMetricsInterface() != nil {
  			einterfaces.GetMetricsInterface().IncrementPostFileAttachment(len(post.FileIds))
  		}
  	}
  
  	if err := handlePostEvents(rpost, teamId, triggerWebhooks); err != nil {
  		return nil, err
  	}
  
  	return rpost, nil
  }
  
  func handlePostEvents(post *model.Post, teamId string, triggerWebhooks bool) *model.AppError {
  	var tchan store.StoreChannel
  	if len(teamId) > 0 {
  		tchan = Srv.Store.Team().Get(teamId)
  	}
  	cchan := Srv.Store.Channel().Get(post.ChannelId, true)
  	uchan := Srv.Store.User().Get(post.UserId)
  
  	var team *model.Team
  	if tchan != nil {
  		if result := <-tchan; result.Err != nil {
  			return result.Err
  		} else {
  			team = result.Data.(*model.Team)
  		}
  	} else {
  		// Blank team for DMs
  		team = &model.Team{}
  	}
  
  	var channel *model.Channel
  	if result := <-cchan; result.Err != nil {
  		return result.Err
  	} else {
  		channel = result.Data.(*model.Channel)
  	}
  
  	InvalidateCacheForChannel(channel)
  	InvalidateCacheForChannelPosts(channel.Id)
  
  	var user *model.User
  	if result := <-uchan; result.Err != nil {
  		return result.Err
  	} else {
  		user = result.Data.(*model.User)
  	}
  
  	if _, err := SendNotifications(post, team, channel, user); err != nil {
  		return err
  	}
  
  	if triggerWebhooks {
  		go func() {
  			if err := handleWebhookEvents(post, team, channel, user); err != nil {
  				l4g.Error(err.Error())
  			}
  		}()
  	}
  
  	return nil
  }
  
  var linkWithTextRegex *regexp.Regexp = regexp.MustCompile(`<([^<\|]+)\|([^>]+)>`)
  
  // This method only parses and processes the attachments,
  // all else should be set in the post which is passed
  func parseSlackAttachment(post *model.Post, attachments interface{}) {
  	post.Type = model.POST_SLACK_ATTACHMENT
  
  	if list, success := attachments.([]interface{}); success {
  		for i, aInt := range list {
  			attachment := aInt.(map[string]interface{})
  			if aText, ok := attachment["text"].(string); ok {
  				aText = linkWithTextRegex.ReplaceAllString(aText, "[${2}](${1})")
  				attachment["text"] = aText
  				list[i] = attachment
  			}
  			if aText, ok := attachment["pretext"].(string); ok {
  				aText = linkWithTextRegex.ReplaceAllString(aText, "[${2}](${1})")
  				attachment["pretext"] = aText
  				list[i] = attachment
  			}
  			if fVal, ok := attachment["fields"]; ok {
  				if fields, ok := fVal.([]interface{}); ok {
  					// parse attachment field links into Markdown format
  					for j, fInt := range fields {
  						field := fInt.(map[string]interface{})
  						if fValue, ok := field["value"].(string); ok {
  							fValue = linkWithTextRegex.ReplaceAllString(fValue, "[${2}](${1})")
  							field["value"] = fValue
  							fields[j] = field
  						}
  					}
  					attachment["fields"] = fields
  					list[i] = attachment
  				}
  			}
  		}
  		post.AddProp("attachments", list)
  	}
  }
  
  func parseSlackLinksToMarkdown(text string) string {
  	return linkWithTextRegex.ReplaceAllString(text, "[${2}](${1})")
  }
  
  func SendEphemeralPost(teamId, userId string, post *model.Post) *model.Post {
  	post.Type = model.POST_EPHEMERAL
  
  	// fill in fields which haven't been specified which have sensible defaults
  	if post.Id == "" {
  		post.Id = model.NewId()
  	}
  	if post.CreateAt == 0 {
  		post.CreateAt = model.GetMillis()
  	}
  	if post.Props == nil {
  		post.Props = model.StringInterface{}
  	}
  
  	message := model.NewWebSocketEvent(model.WEBSOCKET_EVENT_EPHEMERAL_MESSAGE, "", post.ChannelId, userId, nil)
  	message.Add("post", post.ToJson())
  
  	go Publish(message)
  
  	return post
  }
  
  func UpdatePost(post *model.Post) (*model.Post, *model.AppError) {
  	if utils.IsLicensed {
  		if *utils.Cfg.ServiceSettings.AllowEditPost == model.ALLOW_EDIT_POST_NEVER {
  			err := model.NewLocAppError("updatePost", "api.post.update_post.permissions_denied.app_error", nil, "")
  			err.StatusCode = http.StatusForbidden
  			return nil, err
  		}
  	}
  
  	var oldPost *model.Post
  	if result := <-Srv.Store.Post().Get(post.Id); result.Err != nil {
  		return nil, result.Err
  	} else {
  		oldPost = result.Data.(*model.PostList).Posts[post.Id]
  
  		if oldPost == nil {
  			err := model.NewLocAppError("updatePost", "api.post.update_post.find.app_error", nil, "id="+post.Id)
  			err.StatusCode = http.StatusBadRequest
  			return nil, err
  		}
  
  		if oldPost.UserId != post.UserId {
  			err := model.NewLocAppError("updatePost", "api.post.update_post.permissions.app_error", nil, "oldUserId="+oldPost.UserId)
  			err.StatusCode = http.StatusBadRequest
  			return nil, err
  		}
  
  		if oldPost.DeleteAt != 0 {
  			err := model.NewLocAppError("updatePost", "api.post.update_post.permissions_details.app_error", map[string]interface{}{"PostId": post.Id}, "")
  			err.StatusCode = http.StatusBadRequest
  			return nil, err
  		}
  
  		if oldPost.IsSystemMessage() {
  			err := model.NewLocAppError("updatePost", "api.post.update_post.system_message.app_error", nil, "id="+post.Id)
  			err.StatusCode = http.StatusBadRequest
  			return nil, err
  		}
  
  		if utils.IsLicensed {
  			if *utils.Cfg.ServiceSettings.AllowEditPost == model.ALLOW_EDIT_POST_TIME_LIMIT && model.GetMillis() > oldPost.CreateAt+int64(*utils.Cfg.ServiceSettings.PostEditTimeLimit*1000) {
  				err := model.NewLocAppError("updatePost", "api.post.update_post.permissions_time_limit.app_error", map[string]interface{}{"timeLimit": *utils.Cfg.ServiceSettings.PostEditTimeLimit}, "")
  				err.StatusCode = http.StatusBadRequest
  				return nil, err
  			}
  		}
  	}
  
  	newPost := &model.Post{}
  	*newPost = *oldPost
  
  	newPost.Message = post.Message
  	newPost.EditAt = model.GetMillis()
  	newPost.Hashtags, _ = model.ParseHashtags(post.Message)
  
  	if result := <-Srv.Store.Post().Update(newPost, oldPost); result.Err != nil {
  		return nil, result.Err
  	} else {
  		rpost := result.Data.(*model.Post)
  
  		message := model.NewWebSocketEvent(model.WEBSOCKET_EVENT_POST_EDITED, "", rpost.ChannelId, "", nil)
  		message.Add("post", rpost.ToJson())
  
  		go Publish(message)
  
  		InvalidateCacheForChannelPosts(rpost.ChannelId)
  
  		return rpost, nil
  	}
  }
  
  func GetPostsPage(channelId string, page int, perPage int) (*model.PostList, *model.AppError) {
  	if result := <-Srv.Store.Post().GetPosts(channelId, page*perPage, perPage, true); result.Err != nil {
  		return nil, result.Err
  	} else {
  		return result.Data.(*model.PostList), nil
  	}
  }
  
  func GetPosts(channelId string, offset int, limit int) (*model.PostList, *model.AppError) {
  	if result := <-Srv.Store.Post().GetPosts(channelId, offset, limit, true); result.Err != nil {
  		return nil, result.Err
  	} else {
  		return result.Data.(*model.PostList), nil
  	}
  }
  
  func GetPostsEtag(channelId string) string {
  	return (<-Srv.Store.Post().GetEtag(channelId, true)).Data.(string)
  }
  
  func GetPostsSince(channelId string, time int64) (*model.PostList, *model.AppError) {
  	if result := <-Srv.Store.Post().GetPostsSince(channelId, time, true); result.Err != nil {
  		return nil, result.Err
  	} else {
  		return result.Data.(*model.PostList), nil
  	}
  }
  
  func GetSinglePost(postId string) (*model.Post, *model.AppError) {
  	if result := <-Srv.Store.Post().GetSingle(postId); result.Err != nil {
  		return nil, result.Err
  	} else {
  		return result.Data.(*model.Post), nil
  	}
  }
  
  func GetPostThread(postId string) (*model.PostList, *model.AppError) {
  	if result := <-Srv.Store.Post().Get(postId); result.Err != nil {
  		return nil, result.Err
  	} else {
  		return result.Data.(*model.PostList), nil
  	}
  }
  
  func GetFlaggedPosts(userId string, offset int, limit int) (*model.PostList, *model.AppError) {
  	if result := <-Srv.Store.Post().GetFlaggedPosts(userId, offset, limit); result.Err != nil {
  		return nil, result.Err
  	} else {
  		return result.Data.(*model.PostList), nil
  	}
  }
  
  func GetPermalinkPost(postId string, userId string) (*model.PostList, *model.AppError) {
  	if result := <-Srv.Store.Post().Get(postId); result.Err != nil {
  		return nil, result.Err
  	} else {
  		list := result.Data.(*model.PostList)
  
  		if len(list.Order) != 1 {
  			return nil, model.NewLocAppError("getPermalinkTmp", "api.post_get_post_by_id.get.app_error", nil, "")
  		}
  		post := list.Posts[list.Order[0]]
  
  		var channel *model.Channel
  		var err *model.AppError
  		if channel, err = GetChannel(post.ChannelId); err != nil {
  			return nil, err
  		}
  
  		if err = JoinChannel(channel, userId); err != nil {
  			return nil, err
  		}
  
  		return list, nil
  	}
  }
  
  func GetPostsAroundPost(postId, channelId string, offset, limit int, before bool) (*model.PostList, *model.AppError) {
  	var pchan store.StoreChannel
  	if before {
  		pchan = Srv.Store.Post().GetPostsBefore(channelId, postId, limit, offset)
  	} else {
  		pchan = Srv.Store.Post().GetPostsAfter(channelId, postId, limit, offset)
  	}
  
  	if result := <-pchan; result.Err != nil {
  		return nil, result.Err
  	} else {
  		return result.Data.(*model.PostList), nil
  	}
  }
  
  func DeletePost(postId string) (*model.Post, *model.AppError) {
  	if result := <-Srv.Store.Post().GetSingle(postId); result.Err != nil {
  		return nil, result.Err
  	} else {
  		post := result.Data.(*model.Post)
  
  		if result := <-Srv.Store.Post().Delete(postId, model.GetMillis()); result.Err != nil {
  			return nil, result.Err
  		}
  
  		message := model.NewWebSocketEvent(model.WEBSOCKET_EVENT_POST_DELETED, "", post.ChannelId, "", nil)
  		message.Add("post", post.ToJson())
  
  		go Publish(message)
  		go DeletePostFiles(post)
  		go DeleteFlaggedPosts(post.Id)
  
  		InvalidateCacheForChannelPosts(post.ChannelId)
  
  		return post, nil
  	}
  }
  
  func DeleteFlaggedPosts(postId string) {
  	if result := <-Srv.Store.Preference().DeleteCategoryAndName(model.PREFERENCE_CATEGORY_FLAGGED_POST, postId); result.Err != nil {
  		l4g.Warn(utils.T("api.post.delete_flagged_post.app_error.warn"), result.Err)
  		return
  	}
  }
  
  func DeletePostFiles(post *model.Post) {
  	if len(post.FileIds) != 0 {
  		return
  	}
  
  	if result := <-Srv.Store.FileInfo().DeleteForPost(post.Id); result.Err != nil {
  		l4g.Warn(utils.T("api.post.delete_post_files.app_error.warn"), post.Id, result.Err)
  	}
  }
  
  func SearchPostsInTeam(terms string, userId string, teamId string, isOrSearch bool) (*model.PostList, *model.AppError) {
  	paramsList := model.ParseSearchParams(terms)
  	channels := []store.StoreChannel{}
  
  	for _, params := range paramsList {
  		params.OrTerms = isOrSearch
  		// don't allow users to search for everything
  		if params.Terms != "*" {
  			channels = append(channels, Srv.Store.Post().Search(teamId, userId, params))
  		}
  	}
  
  	posts := &model.PostList{}
  	for _, channel := range channels {
  		if result := <-channel; result.Err != nil {
  			return nil, result.Err
  		} else {
  			data := result.Data.(*model.PostList)
  			posts.Extend(data)
  		}
  	}
  
  	return posts, nil
  }
  
  func GetFileInfosForPost(postId string) ([]*model.FileInfo, *model.AppError) {
  	pchan := Srv.Store.Post().GetSingle(postId)
  	fchan := Srv.Store.FileInfo().GetForPost(postId, true)
  
  	var infos []*model.FileInfo
  	if result := <-fchan; result.Err != nil {
  		return nil, result.Err
  	} else {
  		infos = result.Data.([]*model.FileInfo)
  	}
  
  	if len(infos) == 0 {
  		// No FileInfos were returned so check if they need to be created for this post
  		var post *model.Post
  		if result := <-pchan; result.Err != nil {
  			return nil, result.Err
  		} else {
  			post = result.Data.(*model.Post)
  		}
  
  		if len(post.Filenames) > 0 {
  			Srv.Store.FileInfo().InvalidateFileInfosForPostCache(postId)
  			// The post has Filenames that need to be replaced with FileInfos
  			infos = MigrateFilenamesToFileInfos(post)
  		}
  	}
  
  	return infos, nil
  }
  
  func GetOpenGraphMetadata(url string) *opengraph.OpenGraph {
  	og := opengraph.NewOpenGraph()
  
  	res, err := http.Get(url)
  	if err != nil {
  		return og
  	}
  	defer CloseBody(res)
  
  	if err := og.ProcessHTML(res.Body); err != nil {
  		return og
  	}
  
  	return og
  }
  