github.com/mattermosttest/mattermost-server/v5@v5.0.0-20200917143240-9dfa12e121f9/app/post.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  	"encoding/json"
     8  	"errors"
     9  	"fmt"
    10  	"net/http"
    11  	"strings"
    12  	"sync"
    13  	"time"
    14  
    15  	"github.com/mattermost/mattermost-server/v5/mlog"
    16  	"github.com/mattermost/mattermost-server/v5/model"
    17  	"github.com/mattermost/mattermost-server/v5/plugin"
    18  	"github.com/mattermost/mattermost-server/v5/services/cache"
    19  	"github.com/mattermost/mattermost-server/v5/store"
    20  	"github.com/mattermost/mattermost-server/v5/utils"
    21  )
    22  
    23  const (
    24  	PENDING_POST_IDS_CACHE_SIZE = 25000
    25  	PENDING_POST_IDS_CACHE_TTL  = 30 * time.Second
    26  	PAGE_DEFAULT                = 0
    27  )
    28  
    29  func (a *App) CreatePostAsUser(post *model.Post, currentSessionId string, setOnline bool) (*model.Post, *model.AppError) {
    30  	// Check that channel has not been deleted
    31  	channel, errCh := a.Srv().Store.Channel().Get(post.ChannelId, true)
    32  	if errCh != nil {
    33  		err := model.NewAppError("CreatePostAsUser", "api.context.invalid_param.app_error", map[string]interface{}{"Name": "post.channel_id"}, errCh.Error(), http.StatusBadRequest)
    34  		return nil, err
    35  	}
    36  
    37  	if strings.HasPrefix(post.Type, model.POST_SYSTEM_MESSAGE_PREFIX) {
    38  		err := model.NewAppError("CreatePostAsUser", "api.context.invalid_param.app_error", map[string]interface{}{"Name": "post.type"}, "", http.StatusBadRequest)
    39  		return nil, err
    40  	}
    41  
    42  	if channel.DeleteAt != 0 {
    43  		err := model.NewAppError("createPost", "api.post.create_post.can_not_post_to_deleted.error", nil, "", http.StatusBadRequest)
    44  		return nil, err
    45  	}
    46  
    47  	rp, err := a.CreatePost(post, channel, true, setOnline)
    48  	if err != nil {
    49  		if err.Id == "api.post.create_post.root_id.app_error" ||
    50  			err.Id == "api.post.create_post.channel_root_id.app_error" ||
    51  			err.Id == "api.post.create_post.parent_id.app_error" {
    52  			err.StatusCode = http.StatusBadRequest
    53  		}
    54  
    55  		if err.Id == "api.post.create_post.town_square_read_only" {
    56  			user, userErr := a.Srv().Store.User().Get(post.UserId)
    57  			if userErr != nil {
    58  				return nil, userErr
    59  			}
    60  
    61  			T := utils.GetUserTranslations(user.Locale)
    62  			a.SendEphemeralPost(
    63  				post.UserId,
    64  				&model.Post{
    65  					ChannelId: channel.Id,
    66  					ParentId:  post.ParentId,
    67  					RootId:    post.RootId,
    68  					UserId:    post.UserId,
    69  					Message:   T("api.post.create_post.town_square_read_only"),
    70  					CreateAt:  model.GetMillis() + 1,
    71  				},
    72  			)
    73  		}
    74  		return nil, err
    75  	}
    76  
    77  	// Update the LastViewAt only if the post does not have from_webhook prop set (e.g. Zapier app),
    78  	// or if it does not have from_bot set (e.g. from discovering the user is a bot within CreatePost).
    79  	_, fromWebhook := post.GetProps()["from_webhook"]
    80  	_, fromBot := post.GetProps()["from_bot"]
    81  	if !fromWebhook && !fromBot {
    82  		if _, err := a.MarkChannelsAsViewed([]string{post.ChannelId}, post.UserId, currentSessionId); err != nil {
    83  			mlog.Error(
    84  				"Encountered error updating last viewed",
    85  				mlog.String("channel_id", post.ChannelId),
    86  				mlog.String("user_id", post.UserId),
    87  				mlog.Err(err),
    88  			)
    89  		}
    90  	}
    91  
    92  	return rp, nil
    93  }
    94  
    95  func (a *App) CreatePostMissingChannel(post *model.Post, triggerWebhooks bool) (*model.Post, *model.AppError) {
    96  	channel, err := a.Srv().Store.Channel().Get(post.ChannelId, true)
    97  	if err != nil {
    98  		var nfErr *store.ErrNotFound
    99  		switch {
   100  		case errors.As(err, &nfErr):
   101  			return nil, model.NewAppError("CreatePostMissingChannel", "app.channel.get.existing.app_error", nil, nfErr.Error(), http.StatusNotFound)
   102  		default:
   103  			return nil, model.NewAppError("CreatePostMissingChannel", "app.channel.get.find.app_error", nil, err.Error(), http.StatusInternalServerError)
   104  		}
   105  	}
   106  
   107  	return a.CreatePost(post, channel, triggerWebhooks, true)
   108  }
   109  
   110  // deduplicateCreatePost attempts to make posting idempotent within a caching window.
   111  func (a *App) deduplicateCreatePost(post *model.Post) (foundPost *model.Post, err *model.AppError) {
   112  	// We rely on the client sending the pending post id across "duplicate" requests. If there
   113  	// isn't one, we can't deduplicate, so allow creation normally.
   114  	if post.PendingPostId == "" {
   115  		return nil, nil
   116  	}
   117  
   118  	const unknownPostId = ""
   119  
   120  	// Query the cache atomically for the given pending post id, saving a record if
   121  	// it hasn't previously been seen.
   122  	var postId string
   123  	nErr := a.Srv().seenPendingPostIdsCache.Get(post.PendingPostId, &postId)
   124  	if nErr == cache.ErrKeyNotFound {
   125  		a.Srv().seenPendingPostIdsCache.SetWithExpiry(post.PendingPostId, unknownPostId, PENDING_POST_IDS_CACHE_TTL)
   126  		return nil, nil
   127  	}
   128  
   129  	if nErr != nil {
   130  		return nil, model.NewAppError("errorGetPostId", "api.post.error_get_post_id.pending", nil, "", http.StatusInternalServerError)
   131  	}
   132  
   133  	// If another thread saved the cache record, but hasn't yet updated it with the actual post
   134  	// id (because it's still saving), notify the client with an error. Ideally, we'd wait
   135  	// for the other thread, but coordinating that adds complexity to the happy path.
   136  	if postId == unknownPostId {
   137  		return nil, model.NewAppError("deduplicateCreatePost", "api.post.deduplicate_create_post.pending", nil, "", http.StatusInternalServerError)
   138  	}
   139  
   140  	// If the other thread finished creating the post, return the created post back to the
   141  	// client, making the API call feel idempotent.
   142  	actualPost, err := a.GetSinglePost(postId)
   143  	if err != nil {
   144  		return nil, model.NewAppError("deduplicateCreatePost", "api.post.deduplicate_create_post.failed_to_get", nil, err.Error(), http.StatusInternalServerError)
   145  	}
   146  
   147  	mlog.Debug("Deduplicated create post", mlog.String("post_id", actualPost.Id), mlog.String("pending_post_id", post.PendingPostId))
   148  
   149  	return actualPost, nil
   150  }
   151  
   152  func (a *App) CreatePost(post *model.Post, channel *model.Channel, triggerWebhooks, setOnline bool) (savedPost *model.Post, err *model.AppError) {
   153  	foundPost, err := a.deduplicateCreatePost(post)
   154  	if err != nil {
   155  		return nil, err
   156  	}
   157  	if foundPost != nil {
   158  		return foundPost, nil
   159  	}
   160  
   161  	// If we get this far, we've recorded the client-provided pending post id to the cache.
   162  	// Remove it if we fail below, allowing a proper retry by the client.
   163  	defer func() {
   164  		if post.PendingPostId == "" {
   165  			return
   166  		}
   167  
   168  		if err != nil {
   169  			a.Srv().seenPendingPostIdsCache.Remove(post.PendingPostId)
   170  			return
   171  		}
   172  
   173  		a.Srv().seenPendingPostIdsCache.SetWithExpiry(post.PendingPostId, savedPost.Id, PENDING_POST_IDS_CACHE_TTL)
   174  	}()
   175  
   176  	post.SanitizeProps()
   177  
   178  	var pchan chan store.StoreResult
   179  	if len(post.RootId) > 0 {
   180  		pchan = make(chan store.StoreResult, 1)
   181  		go func() {
   182  			r, pErr := a.Srv().Store.Post().Get(post.RootId, false)
   183  			pchan <- store.StoreResult{Data: r, Err: pErr}
   184  			close(pchan)
   185  		}()
   186  	}
   187  
   188  	user, err := a.Srv().Store.User().Get(post.UserId)
   189  	if err != nil {
   190  		return nil, err
   191  	}
   192  
   193  	if user.IsBot {
   194  		post.AddProp("from_bot", "true")
   195  	}
   196  
   197  	if a.Srv().License() != nil && *a.Config().TeamSettings.ExperimentalTownSquareIsReadOnly &&
   198  		!post.IsSystemMessage() &&
   199  		channel.Name == model.DEFAULT_CHANNEL &&
   200  		!a.RolesGrantPermission(user.GetRoles(), model.PERMISSION_MANAGE_SYSTEM.Id) {
   201  		return nil, model.NewAppError("createPost", "api.post.create_post.town_square_read_only", nil, "", http.StatusForbidden)
   202  	}
   203  
   204  	var ephemeralPost *model.Post
   205  	if post.Type == "" && !a.HasPermissionToChannel(user.Id, channel.Id, model.PERMISSION_USE_CHANNEL_MENTIONS) {
   206  		mention := post.DisableMentionHighlights()
   207  		if mention != "" {
   208  			T := utils.GetUserTranslations(user.Locale)
   209  			ephemeralPost = &model.Post{
   210  				UserId:    user.Id,
   211  				RootId:    post.RootId,
   212  				ParentId:  post.ParentId,
   213  				ChannelId: channel.Id,
   214  				Message:   T("model.post.channel_notifications_disabled_in_channel.message", model.StringInterface{"ChannelName": channel.Name, "Mention": mention}),
   215  				Props:     model.StringInterface{model.POST_PROPS_MENTION_HIGHLIGHT_DISABLED: true},
   216  			}
   217  		}
   218  	}
   219  
   220  	// Verify the parent/child relationships are correct
   221  	var parentPostList *model.PostList
   222  	if pchan != nil {
   223  		result := <-pchan
   224  		if result.Err != nil {
   225  			return nil, model.NewAppError("createPost", "api.post.create_post.root_id.app_error", nil, "", http.StatusBadRequest)
   226  		}
   227  		parentPostList = result.Data.(*model.PostList)
   228  		if len(parentPostList.Posts) == 0 || !parentPostList.IsChannelId(post.ChannelId) {
   229  			return nil, model.NewAppError("createPost", "api.post.create_post.channel_root_id.app_error", nil, "", http.StatusInternalServerError)
   230  		}
   231  
   232  		rootPost := parentPostList.Posts[post.RootId]
   233  		if len(rootPost.RootId) > 0 {
   234  			return nil, model.NewAppError("createPost", "api.post.create_post.root_id.app_error", nil, "", http.StatusBadRequest)
   235  		}
   236  
   237  		if post.ParentId == "" {
   238  			post.ParentId = post.RootId
   239  		}
   240  
   241  		if post.RootId != post.ParentId {
   242  			parent := parentPostList.Posts[post.ParentId]
   243  			if parent == nil {
   244  				return nil, model.NewAppError("createPost", "api.post.create_post.parent_id.app_error", nil, "", http.StatusInternalServerError)
   245  			}
   246  		}
   247  	}
   248  
   249  	post.Hashtags, _ = model.ParseHashtags(post.Message)
   250  
   251  	if err = a.FillInPostProps(post, channel); err != nil {
   252  		return nil, err
   253  	}
   254  
   255  	// Temporary fix so old plugins don't clobber new fields in SlackAttachment struct, see MM-13088
   256  	if attachments, ok := post.GetProp("attachments").([]*model.SlackAttachment); ok {
   257  		jsonAttachments, err := json.Marshal(attachments)
   258  		if err == nil {
   259  			attachmentsInterface := []interface{}{}
   260  			err = json.Unmarshal(jsonAttachments, &attachmentsInterface)
   261  			post.AddProp("attachments", attachmentsInterface)
   262  		}
   263  		if err != nil {
   264  			mlog.Error("Could not convert post attachments to map interface.", mlog.Err(err))
   265  		}
   266  	}
   267  
   268  	if pluginsEnvironment := a.GetPluginsEnvironment(); pluginsEnvironment != nil {
   269  		var rejectionError *model.AppError
   270  		pluginContext := a.PluginContext()
   271  		pluginsEnvironment.RunMultiPluginHook(func(hooks plugin.Hooks) bool {
   272  			replacementPost, rejectionReason := hooks.MessageWillBePosted(pluginContext, post)
   273  			if rejectionReason != "" {
   274  				id := "Post rejected by plugin. " + rejectionReason
   275  				if rejectionReason == plugin.DismissPostError {
   276  					id = plugin.DismissPostError
   277  				}
   278  				rejectionError = model.NewAppError("createPost", id, nil, "", http.StatusBadRequest)
   279  				return false
   280  			}
   281  			if replacementPost != nil {
   282  				post = replacementPost
   283  			}
   284  
   285  			return true
   286  		}, plugin.MessageWillBePostedId)
   287  
   288  		if rejectionError != nil {
   289  			return nil, rejectionError
   290  		}
   291  	}
   292  
   293  	rpost, err := a.Srv().Store.Post().Save(post)
   294  	if err != nil {
   295  		return nil, err
   296  	}
   297  
   298  	// Update the mapping from pending post id to the actual post id, for any clients that
   299  	// might be duplicating requests.
   300  	a.Srv().seenPendingPostIdsCache.SetWithExpiry(post.PendingPostId, rpost.Id, PENDING_POST_IDS_CACHE_TTL)
   301  
   302  	if pluginsEnvironment := a.GetPluginsEnvironment(); pluginsEnvironment != nil {
   303  		a.Srv().Go(func() {
   304  			pluginContext := a.PluginContext()
   305  			pluginsEnvironment.RunMultiPluginHook(func(hooks plugin.Hooks) bool {
   306  				hooks.MessageHasBeenPosted(pluginContext, rpost)
   307  				return true
   308  			}, plugin.MessageHasBeenPostedId)
   309  		})
   310  	}
   311  
   312  	if a.Metrics() != nil {
   313  		a.Metrics().IncrementPostCreate()
   314  	}
   315  
   316  	if len(post.FileIds) > 0 {
   317  		if err = a.attachFilesToPost(post); err != nil {
   318  			mlog.Error("Encountered error attaching files to post", mlog.String("post_id", post.Id), mlog.Any("file_ids", post.FileIds), mlog.Err(err))
   319  		}
   320  
   321  		if a.Metrics() != nil {
   322  			a.Metrics().IncrementPostFileAttachment(len(post.FileIds))
   323  		}
   324  	}
   325  
   326  	// Normally, we would let the API layer call PreparePostForClient, but we do it here since it also needs
   327  	// to be done when we send the post over the websocket in handlePostEvents
   328  	rpost = a.PreparePostForClient(rpost, true, false)
   329  
   330  	if err := a.handlePostEvents(rpost, user, channel, triggerWebhooks, parentPostList, setOnline); err != nil {
   331  		mlog.Error("Failed to handle post events", mlog.Err(err))
   332  	}
   333  
   334  	// Send any ephemeral posts after the post is created to ensure it shows up after the latest post created
   335  	if ephemeralPost != nil {
   336  		a.SendEphemeralPost(post.UserId, ephemeralPost)
   337  	}
   338  
   339  	return rpost, nil
   340  }
   341  
   342  func (a *App) attachFilesToPost(post *model.Post) *model.AppError {
   343  	var attachedIds []string
   344  	for _, fileId := range post.FileIds {
   345  		err := a.Srv().Store.FileInfo().AttachToPost(fileId, post.Id, post.UserId)
   346  		if err != nil {
   347  			mlog.Warn("Failed to attach file to post", mlog.String("file_id", fileId), mlog.String("post_id", post.Id), mlog.Err(err))
   348  			continue
   349  		}
   350  
   351  		attachedIds = append(attachedIds, fileId)
   352  	}
   353  
   354  	if len(post.FileIds) != len(attachedIds) {
   355  		// We couldn't attach all files to the post, so ensure that post.FileIds reflects what was actually attached
   356  		post.FileIds = attachedIds
   357  
   358  		if _, err := a.Srv().Store.Post().Overwrite(post); err != nil {
   359  			return err
   360  		}
   361  	}
   362  
   363  	return nil
   364  }
   365  
   366  // FillInPostProps should be invoked before saving posts to fill in properties such as
   367  // channel_mentions.
   368  //
   369  // If channel is nil, FillInPostProps will look up the channel corresponding to the post.
   370  func (a *App) FillInPostProps(post *model.Post, channel *model.Channel) *model.AppError {
   371  	channelMentions := post.ChannelMentions()
   372  	channelMentionsProp := make(map[string]interface{})
   373  
   374  	if len(channelMentions) > 0 {
   375  		if channel == nil {
   376  			postChannel, err := a.Srv().Store.Channel().GetForPost(post.Id)
   377  			if err != nil {
   378  				return model.NewAppError("FillInPostProps", "api.context.invalid_param.app_error", map[string]interface{}{"Name": "post.channel_id"}, err.Error(), http.StatusBadRequest)
   379  			}
   380  			channel = postChannel
   381  		}
   382  
   383  		mentionedChannels, err := a.GetChannelsByNames(channelMentions, channel.TeamId)
   384  		if err != nil {
   385  			return err
   386  		}
   387  
   388  		for _, mentioned := range mentionedChannels {
   389  			if mentioned.Type == model.CHANNEL_OPEN {
   390  				team, err := a.Srv().Store.Team().Get(mentioned.TeamId)
   391  				if err != nil {
   392  					mlog.Error("Failed to get team of the channel mention", mlog.String("team_id", channel.TeamId), mlog.String("channel_id", channel.Id), mlog.Err(err))
   393  				}
   394  				channelMentionsProp[mentioned.Name] = map[string]interface{}{
   395  					"display_name": mentioned.DisplayName,
   396  					"team_name":    team.Name,
   397  				}
   398  			}
   399  		}
   400  	}
   401  
   402  	if len(channelMentionsProp) > 0 {
   403  		post.AddProp("channel_mentions", channelMentionsProp)
   404  	} else if post.GetProps() != nil {
   405  		post.DelProp("channel_mentions")
   406  	}
   407  
   408  	matched := model.AT_MENTION_PATTEN.MatchString(post.Message)
   409  	if a.Srv().License() != nil && *a.Srv().License().Features.LDAPGroups && matched && !a.HasPermissionToChannel(post.UserId, post.ChannelId, model.PERMISSION_USE_GROUP_MENTIONS) {
   410  		post.AddProp(model.POST_PROPS_GROUP_HIGHLIGHT_DISABLED, true)
   411  	}
   412  
   413  	return nil
   414  }
   415  
   416  func (a *App) handlePostEvents(post *model.Post, user *model.User, channel *model.Channel, triggerWebhooks bool, parentPostList *model.PostList, setOnline bool) error {
   417  	var team *model.Team
   418  	if len(channel.TeamId) > 0 {
   419  		t, err := a.Srv().Store.Team().Get(channel.TeamId)
   420  		if err != nil {
   421  			return err
   422  		}
   423  		team = t
   424  	} else {
   425  		// Blank team for DMs
   426  		team = &model.Team{}
   427  	}
   428  
   429  	a.invalidateCacheForChannel(channel)
   430  	a.invalidateCacheForChannelPosts(channel.Id)
   431  
   432  	if _, err := a.SendNotifications(post, team, channel, user, parentPostList, setOnline); err != nil {
   433  		return err
   434  	}
   435  
   436  	if post.Type != model.POST_AUTO_RESPONDER { // don't respond to an auto-responder
   437  		a.Srv().Go(func() {
   438  			_, err := a.SendAutoResponseIfNecessary(channel, user)
   439  			if err != nil {
   440  				mlog.Error("Failed to send auto response", mlog.String("user_id", user.Id), mlog.String("post_id", post.Id), mlog.Err(err))
   441  			}
   442  		})
   443  	}
   444  
   445  	if triggerWebhooks {
   446  		a.Srv().Go(func() {
   447  			if err := a.handleWebhookEvents(post, team, channel, user); err != nil {
   448  				mlog.Error(err.Error())
   449  			}
   450  		})
   451  	}
   452  
   453  	return nil
   454  }
   455  
   456  func (a *App) SendEphemeralPost(userId string, post *model.Post) *model.Post {
   457  	post.Type = model.POST_EPHEMERAL
   458  
   459  	// fill in fields which haven't been specified which have sensible defaults
   460  	if post.Id == "" {
   461  		post.Id = model.NewId()
   462  	}
   463  	if post.CreateAt == 0 {
   464  		post.CreateAt = model.GetMillis()
   465  	}
   466  	if post.GetProps() == nil {
   467  		post.SetProps(make(model.StringInterface))
   468  	}
   469  
   470  	post.GenerateActionIds()
   471  	message := model.NewWebSocketEvent(model.WEBSOCKET_EVENT_EPHEMERAL_MESSAGE, "", post.ChannelId, userId, nil)
   472  	post = a.PreparePostForClient(post, true, false)
   473  	post = model.AddPostActionCookies(post, a.PostActionCookieSecret())
   474  	message.Add("post", post.ToJson())
   475  	a.Publish(message)
   476  
   477  	return post
   478  }
   479  
   480  func (a *App) UpdateEphemeralPost(userId string, post *model.Post) *model.Post {
   481  	post.Type = model.POST_EPHEMERAL
   482  
   483  	post.UpdateAt = model.GetMillis()
   484  	if post.GetProps() == nil {
   485  		post.SetProps(make(model.StringInterface))
   486  	}
   487  
   488  	post.GenerateActionIds()
   489  	message := model.NewWebSocketEvent(model.WEBSOCKET_EVENT_POST_EDITED, "", post.ChannelId, userId, nil)
   490  	post = a.PreparePostForClient(post, true, false)
   491  	post = model.AddPostActionCookies(post, a.PostActionCookieSecret())
   492  	message.Add("post", post.ToJson())
   493  	a.Publish(message)
   494  
   495  	return post
   496  }
   497  
   498  func (a *App) DeleteEphemeralPost(userId, postId string) {
   499  	post := &model.Post{
   500  		Id:       postId,
   501  		UserId:   userId,
   502  		Type:     model.POST_EPHEMERAL,
   503  		DeleteAt: model.GetMillis(),
   504  		UpdateAt: model.GetMillis(),
   505  	}
   506  
   507  	message := model.NewWebSocketEvent(model.WEBSOCKET_EVENT_POST_DELETED, "", "", userId, nil)
   508  	message.Add("post", post.ToJson())
   509  	a.Publish(message)
   510  }
   511  
   512  func (a *App) UpdatePost(post *model.Post, safeUpdate bool) (*model.Post, *model.AppError) {
   513  	post.SanitizeProps()
   514  
   515  	postLists, err := a.Srv().Store.Post().Get(post.Id, false)
   516  	if err != nil {
   517  		return nil, err
   518  	}
   519  	oldPost := postLists.Posts[post.Id]
   520  
   521  	if oldPost == nil {
   522  		err = model.NewAppError("UpdatePost", "api.post.update_post.find.app_error", nil, "id="+post.Id, http.StatusBadRequest)
   523  		return nil, err
   524  	}
   525  
   526  	if oldPost.DeleteAt != 0 {
   527  		err = model.NewAppError("UpdatePost", "api.post.update_post.permissions_details.app_error", map[string]interface{}{"PostId": post.Id}, "", http.StatusBadRequest)
   528  		return nil, err
   529  	}
   530  
   531  	if oldPost.IsSystemMessage() {
   532  		err = model.NewAppError("UpdatePost", "api.post.update_post.system_message.app_error", nil, "id="+post.Id, http.StatusBadRequest)
   533  		return nil, err
   534  	}
   535  
   536  	if a.Srv().License() != nil {
   537  		if *a.Config().ServiceSettings.PostEditTimeLimit != -1 && model.GetMillis() > oldPost.CreateAt+int64(*a.Config().ServiceSettings.PostEditTimeLimit*1000) && post.Message != oldPost.Message {
   538  			err = model.NewAppError("UpdatePost", "api.post.update_post.permissions_time_limit.app_error", map[string]interface{}{"timeLimit": *a.Config().ServiceSettings.PostEditTimeLimit}, "", http.StatusBadRequest)
   539  			return nil, err
   540  		}
   541  	}
   542  
   543  	channel, err := a.GetChannel(oldPost.ChannelId)
   544  	if err != nil {
   545  		return nil, err
   546  	}
   547  
   548  	if channel.DeleteAt != 0 {
   549  		return nil, model.NewAppError("UpdatePost", "api.post.update_post.can_not_update_post_in_deleted.error", nil, "", http.StatusBadRequest)
   550  	}
   551  
   552  	newPost := &model.Post{}
   553  	newPost = oldPost.Clone()
   554  
   555  	if newPost.Message != post.Message {
   556  		newPost.Message = post.Message
   557  		newPost.EditAt = model.GetMillis()
   558  		newPost.Hashtags, _ = model.ParseHashtags(post.Message)
   559  	}
   560  
   561  	if !safeUpdate {
   562  		newPost.IsPinned = post.IsPinned
   563  		newPost.HasReactions = post.HasReactions
   564  		newPost.FileIds = post.FileIds
   565  		newPost.SetProps(post.GetProps())
   566  	}
   567  
   568  	// Avoid deep-equal checks if EditAt was already modified through message change
   569  	if newPost.EditAt == oldPost.EditAt && (!oldPost.FileIds.Equals(newPost.FileIds) || !oldPost.AttachmentsEqual(newPost)) {
   570  		newPost.EditAt = model.GetMillis()
   571  	}
   572  
   573  	if err = a.FillInPostProps(post, nil); err != nil {
   574  		return nil, err
   575  	}
   576  
   577  	if pluginsEnvironment := a.GetPluginsEnvironment(); pluginsEnvironment != nil {
   578  		var rejectionReason string
   579  		pluginContext := a.PluginContext()
   580  		pluginsEnvironment.RunMultiPluginHook(func(hooks plugin.Hooks) bool {
   581  			newPost, rejectionReason = hooks.MessageWillBeUpdated(pluginContext, newPost, oldPost)
   582  			return post != nil
   583  		}, plugin.MessageWillBeUpdatedId)
   584  		if newPost == nil {
   585  			return nil, model.NewAppError("UpdatePost", "Post rejected by plugin. "+rejectionReason, nil, "", http.StatusBadRequest)
   586  		}
   587  	}
   588  
   589  	rpost, err := a.Srv().Store.Post().Update(newPost, oldPost)
   590  	if err != nil {
   591  		return nil, err
   592  	}
   593  
   594  	if pluginsEnvironment := a.GetPluginsEnvironment(); pluginsEnvironment != nil {
   595  		a.Srv().Go(func() {
   596  			pluginContext := a.PluginContext()
   597  			pluginsEnvironment.RunMultiPluginHook(func(hooks plugin.Hooks) bool {
   598  				hooks.MessageHasBeenUpdated(pluginContext, newPost, oldPost)
   599  				return true
   600  			}, plugin.MessageHasBeenUpdatedId)
   601  		})
   602  	}
   603  
   604  	rpost = a.PreparePostForClient(rpost, false, true)
   605  
   606  	message := model.NewWebSocketEvent(model.WEBSOCKET_EVENT_POST_EDITED, "", rpost.ChannelId, "", nil)
   607  	message.Add("post", rpost.ToJson())
   608  	a.Publish(message)
   609  
   610  	a.invalidateCacheForChannelPosts(rpost.ChannelId)
   611  
   612  	return rpost, nil
   613  }
   614  
   615  func (a *App) PatchPost(postId string, patch *model.PostPatch) (*model.Post, *model.AppError) {
   616  	post, err := a.GetSinglePost(postId)
   617  	if err != nil {
   618  		return nil, err
   619  	}
   620  
   621  	channel, err := a.GetChannel(post.ChannelId)
   622  	if err != nil {
   623  		return nil, err
   624  	}
   625  
   626  	if channel.DeleteAt != 0 {
   627  		err = model.NewAppError("PatchPost", "api.post.patch_post.can_not_update_post_in_deleted.error", nil, "", http.StatusBadRequest)
   628  		return nil, err
   629  	}
   630  
   631  	if !a.HasPermissionToChannel(post.UserId, post.ChannelId, model.PERMISSION_USE_CHANNEL_MENTIONS) {
   632  		patch.DisableMentionHighlights()
   633  	}
   634  
   635  	post.Patch(patch)
   636  
   637  	updatedPost, err := a.UpdatePost(post, false)
   638  	if err != nil {
   639  		return nil, err
   640  	}
   641  
   642  	return updatedPost, nil
   643  }
   644  
   645  func (a *App) GetPostsPage(options model.GetPostsOptions) (*model.PostList, *model.AppError) {
   646  	return a.Srv().Store.Post().GetPosts(options, false)
   647  }
   648  
   649  func (a *App) GetPosts(channelId string, offset int, limit int) (*model.PostList, *model.AppError) {
   650  	return a.Srv().Store.Post().GetPosts(model.GetPostsOptions{ChannelId: channelId, Page: offset, PerPage: limit}, true)
   651  }
   652  
   653  func (a *App) GetPostsEtag(channelId string) string {
   654  	return a.Srv().Store.Post().GetEtag(channelId, true)
   655  }
   656  
   657  func (a *App) GetPostsSince(options model.GetPostsSinceOptions) (*model.PostList, *model.AppError) {
   658  	return a.Srv().Store.Post().GetPostsSince(options, true)
   659  }
   660  
   661  func (a *App) GetSinglePost(postId string) (*model.Post, *model.AppError) {
   662  	return a.Srv().Store.Post().GetSingle(postId)
   663  }
   664  
   665  func (a *App) GetPostThread(postId string, skipFetchThreads bool) (*model.PostList, *model.AppError) {
   666  	return a.Srv().Store.Post().Get(postId, skipFetchThreads)
   667  }
   668  
   669  func (a *App) GetFlaggedPosts(userId string, offset int, limit int) (*model.PostList, *model.AppError) {
   670  	return a.Srv().Store.Post().GetFlaggedPosts(userId, offset, limit)
   671  }
   672  
   673  func (a *App) GetFlaggedPostsForTeam(userId, teamId string, offset int, limit int) (*model.PostList, *model.AppError) {
   674  	return a.Srv().Store.Post().GetFlaggedPostsForTeam(userId, teamId, offset, limit)
   675  }
   676  
   677  func (a *App) GetFlaggedPostsForChannel(userId, channelId string, offset int, limit int) (*model.PostList, *model.AppError) {
   678  	return a.Srv().Store.Post().GetFlaggedPostsForChannel(userId, channelId, offset, limit)
   679  }
   680  
   681  func (a *App) GetPermalinkPost(postId string, userId string) (*model.PostList, *model.AppError) {
   682  	list, err := a.Srv().Store.Post().Get(postId, false)
   683  	if err != nil {
   684  		return nil, err
   685  	}
   686  
   687  	if len(list.Order) != 1 {
   688  		return nil, model.NewAppError("getPermalinkTmp", "api.post_get_post_by_id.get.app_error", nil, "", http.StatusNotFound)
   689  	}
   690  	post := list.Posts[list.Order[0]]
   691  
   692  	channel, err := a.GetChannel(post.ChannelId)
   693  	if err != nil {
   694  		return nil, err
   695  	}
   696  
   697  	if err = a.JoinChannel(channel, userId); err != nil {
   698  		return nil, err
   699  	}
   700  
   701  	return list, nil
   702  }
   703  
   704  func (a *App) GetPostsBeforePost(options model.GetPostsOptions) (*model.PostList, *model.AppError) {
   705  	return a.Srv().Store.Post().GetPostsBefore(options)
   706  }
   707  
   708  func (a *App) GetPostsAfterPost(options model.GetPostsOptions) (*model.PostList, *model.AppError) {
   709  	return a.Srv().Store.Post().GetPostsAfter(options)
   710  }
   711  
   712  func (a *App) GetPostsAroundPost(before bool, options model.GetPostsOptions) (*model.PostList, *model.AppError) {
   713  	if before {
   714  		return a.Srv().Store.Post().GetPostsBefore(options)
   715  	}
   716  	return a.Srv().Store.Post().GetPostsAfter(options)
   717  }
   718  
   719  func (a *App) GetPostAfterTime(channelId string, time int64) (*model.Post, *model.AppError) {
   720  	return a.Srv().Store.Post().GetPostAfterTime(channelId, time)
   721  }
   722  
   723  func (a *App) GetPostIdAfterTime(channelId string, time int64) (string, *model.AppError) {
   724  	return a.Srv().Store.Post().GetPostIdAfterTime(channelId, time)
   725  }
   726  
   727  func (a *App) GetPostIdBeforeTime(channelId string, time int64) (string, *model.AppError) {
   728  	return a.Srv().Store.Post().GetPostIdBeforeTime(channelId, time)
   729  }
   730  
   731  func (a *App) GetNextPostIdFromPostList(postList *model.PostList) string {
   732  	if len(postList.Order) > 0 {
   733  		firstPostId := postList.Order[0]
   734  		firstPost := postList.Posts[firstPostId]
   735  		nextPostId, err := a.GetPostIdAfterTime(firstPost.ChannelId, firstPost.CreateAt)
   736  		if err != nil {
   737  			mlog.Warn("GetNextPostIdFromPostList: failed in getting next post", mlog.Err(err))
   738  		}
   739  
   740  		return nextPostId
   741  	}
   742  
   743  	return ""
   744  }
   745  
   746  func (a *App) GetPrevPostIdFromPostList(postList *model.PostList) string {
   747  	if len(postList.Order) > 0 {
   748  		lastPostId := postList.Order[len(postList.Order)-1]
   749  		lastPost := postList.Posts[lastPostId]
   750  		previousPostId, err := a.GetPostIdBeforeTime(lastPost.ChannelId, lastPost.CreateAt)
   751  		if err != nil {
   752  			mlog.Warn("GetPrevPostIdFromPostList: failed in getting previous post", mlog.Err(err))
   753  		}
   754  
   755  		return previousPostId
   756  	}
   757  
   758  	return ""
   759  }
   760  
   761  // AddCursorIdsForPostList adds NextPostId and PrevPostId as cursor to the PostList.
   762  // The conditional blocks ensure that it sets those cursor IDs immediately as afterPost, beforePost or empty,
   763  // and only query to database whenever necessary.
   764  func (a *App) AddCursorIdsForPostList(originalList *model.PostList, afterPost, beforePost string, since int64, page, perPage int) {
   765  	prevPostIdSet := false
   766  	prevPostId := ""
   767  	nextPostIdSet := false
   768  	nextPostId := ""
   769  
   770  	if since > 0 { // "since" query to return empty NextPostId and PrevPostId
   771  		nextPostIdSet = true
   772  		prevPostIdSet = true
   773  	} else if afterPost != "" {
   774  		if page == 0 {
   775  			prevPostId = afterPost
   776  			prevPostIdSet = true
   777  		}
   778  
   779  		if len(originalList.Order) < perPage {
   780  			nextPostIdSet = true
   781  		}
   782  	} else if beforePost != "" {
   783  		if page == 0 {
   784  			nextPostId = beforePost
   785  			nextPostIdSet = true
   786  		}
   787  
   788  		if len(originalList.Order) < perPage {
   789  			prevPostIdSet = true
   790  		}
   791  	}
   792  
   793  	if !nextPostIdSet {
   794  		nextPostId = a.GetNextPostIdFromPostList(originalList)
   795  	}
   796  
   797  	if !prevPostIdSet {
   798  		prevPostId = a.GetPrevPostIdFromPostList(originalList)
   799  	}
   800  
   801  	originalList.NextPostId = nextPostId
   802  	originalList.PrevPostId = prevPostId
   803  }
   804  func (a *App) GetPostsForChannelAroundLastUnread(channelId, userId string, limitBefore, limitAfter int, skipFetchThreads bool) (*model.PostList, *model.AppError) {
   805  	var member *model.ChannelMember
   806  	var err *model.AppError
   807  	if member, err = a.GetChannelMember(channelId, userId); err != nil {
   808  		return nil, err
   809  	} else if member.LastViewedAt == 0 {
   810  		return model.NewPostList(), nil
   811  	}
   812  
   813  	lastUnreadPostId, err := a.GetPostIdAfterTime(channelId, member.LastViewedAt)
   814  	if err != nil {
   815  		return nil, err
   816  	} else if lastUnreadPostId == "" {
   817  		return model.NewPostList(), nil
   818  	}
   819  
   820  	postList, err := a.GetPostThread(lastUnreadPostId, skipFetchThreads)
   821  	if err != nil {
   822  		return nil, err
   823  	}
   824  	// Reset order to only include the last unread post: if the thread appears in the centre
   825  	// channel organically, those replies will be added below.
   826  	postList.Order = []string{lastUnreadPostId}
   827  
   828  	if postListBefore, err := a.GetPostsBeforePost(model.GetPostsOptions{ChannelId: channelId, PostId: lastUnreadPostId, Page: PAGE_DEFAULT, PerPage: limitBefore, SkipFetchThreads: skipFetchThreads}); err != nil {
   829  		return nil, err
   830  	} else if postListBefore != nil {
   831  		postList.Extend(postListBefore)
   832  	}
   833  
   834  	if postListAfter, err := a.GetPostsAfterPost(model.GetPostsOptions{ChannelId: channelId, PostId: lastUnreadPostId, Page: PAGE_DEFAULT, PerPage: limitAfter - 1, SkipFetchThreads: skipFetchThreads}); err != nil {
   835  		return nil, err
   836  	} else if postListAfter != nil {
   837  		postList.Extend(postListAfter)
   838  	}
   839  
   840  	postList.SortByCreateAt()
   841  	return postList, nil
   842  }
   843  
   844  func (a *App) DeletePost(postId, deleteByID string) (*model.Post, *model.AppError) {
   845  	post, err := a.Srv().Store.Post().GetSingle(postId)
   846  	if err != nil {
   847  		err.StatusCode = http.StatusBadRequest
   848  		return nil, err
   849  	}
   850  
   851  	channel, err := a.GetChannel(post.ChannelId)
   852  	if err != nil {
   853  		return nil, err
   854  	}
   855  
   856  	if channel.DeleteAt != 0 {
   857  		err := model.NewAppError("DeletePost", "api.post.delete_post.can_not_delete_post_in_deleted.error", nil, "", http.StatusBadRequest)
   858  		return nil, err
   859  	}
   860  
   861  	if err := a.Srv().Store.Post().Delete(postId, model.GetMillis(), deleteByID); err != nil {
   862  		return nil, err
   863  	}
   864  
   865  	message := model.NewWebSocketEvent(model.WEBSOCKET_EVENT_POST_DELETED, "", post.ChannelId, "", nil)
   866  	message.Add("post", a.PreparePostForClient(post, false, false).ToJson())
   867  	a.Publish(message)
   868  
   869  	a.Srv().Go(func() {
   870  		a.DeletePostFiles(post)
   871  	})
   872  	a.Srv().Go(func() {
   873  		a.DeleteFlaggedPosts(post.Id)
   874  	})
   875  
   876  	a.invalidateCacheForChannelPosts(post.ChannelId)
   877  
   878  	return post, nil
   879  }
   880  
   881  func (a *App) DeleteFlaggedPosts(postId string) {
   882  	if err := a.Srv().Store.Preference().DeleteCategoryAndName(model.PREFERENCE_CATEGORY_FLAGGED_POST, postId); err != nil {
   883  		mlog.Warn("Unable to delete flagged post preference when deleting post.", mlog.Err(err))
   884  		return
   885  	}
   886  }
   887  
   888  func (a *App) DeletePostFiles(post *model.Post) {
   889  	if len(post.FileIds) == 0 {
   890  		return
   891  	}
   892  
   893  	if _, err := a.Srv().Store.FileInfo().DeleteForPost(post.Id); err != nil {
   894  		mlog.Warn("Encountered error when deleting files for post", mlog.String("post_id", post.Id), mlog.Err(err))
   895  	}
   896  }
   897  
   898  func (a *App) parseAndFetchChannelIdByNameFromInFilter(channelName, userId, teamId string, includeDeleted bool) (*model.Channel, error) {
   899  	if strings.HasPrefix(channelName, "@") && strings.Contains(channelName, ",") {
   900  		var userIds []string
   901  		users, err := a.GetUsersByUsernames(strings.Split(channelName[1:], ","), false, nil)
   902  		if err != nil {
   903  			return nil, err
   904  		}
   905  		for _, user := range users {
   906  			userIds = append(userIds, user.Id)
   907  		}
   908  
   909  		channel, err := a.GetGroupChannel(userIds)
   910  		if err != nil {
   911  			return nil, err
   912  		}
   913  		return channel, nil
   914  	}
   915  
   916  	if strings.HasPrefix(channelName, "@") && !strings.Contains(channelName, ",") {
   917  		user, err := a.GetUserByUsername(channelName[1:])
   918  		if err != nil {
   919  			return nil, err
   920  		}
   921  		channel, err := a.GetOrCreateDirectChannel(userId, user.Id)
   922  		if err != nil {
   923  			return nil, err
   924  		}
   925  		return channel, nil
   926  	}
   927  
   928  	channel, err := a.GetChannelByName(channelName, teamId, includeDeleted)
   929  	if err != nil {
   930  		return nil, err
   931  	}
   932  	return channel, nil
   933  }
   934  
   935  func (a *App) searchPostsInTeam(teamId string, userId string, paramsList []*model.SearchParams, modifierFun func(*model.SearchParams)) (*model.PostList, *model.AppError) {
   936  	var wg sync.WaitGroup
   937  
   938  	pchan := make(chan store.StoreResult, len(paramsList))
   939  
   940  	for _, params := range paramsList {
   941  		// Don't allow users to search for everything.
   942  		if params.Terms == "*" {
   943  			continue
   944  		}
   945  		modifierFun(params)
   946  		wg.Add(1)
   947  
   948  		go func(params *model.SearchParams) {
   949  			defer wg.Done()
   950  			postList, err := a.Srv().Store.Post().Search(teamId, userId, params)
   951  			pchan <- store.StoreResult{Data: postList, Err: err}
   952  		}(params)
   953  	}
   954  
   955  	wg.Wait()
   956  	close(pchan)
   957  
   958  	posts := model.NewPostList()
   959  
   960  	for result := range pchan {
   961  		if result.Err != nil {
   962  			return nil, result.Err
   963  		}
   964  		data := result.Data.(*model.PostList)
   965  		posts.Extend(data)
   966  	}
   967  
   968  	posts.SortByCreateAt()
   969  	return posts, nil
   970  }
   971  
   972  func (a *App) convertChannelNamesToChannelIds(channels []string, userId string, teamId string, includeDeletedChannels bool) []string {
   973  	for idx, channelName := range channels {
   974  		channel, err := a.parseAndFetchChannelIdByNameFromInFilter(channelName, userId, teamId, includeDeletedChannels)
   975  		if err != nil {
   976  			mlog.Error("error getting channel id by name from in filter", mlog.Err(err))
   977  			continue
   978  		}
   979  		channels[idx] = channel.Id
   980  	}
   981  	return channels
   982  }
   983  
   984  func (a *App) convertUserNameToUserIds(usernames []string) []string {
   985  	for idx, username := range usernames {
   986  		if user, err := a.GetUserByUsername(username); err != nil {
   987  			mlog.Error("error getting user by username", mlog.String("user_name", username), mlog.Err(err))
   988  		} else {
   989  			usernames[idx] = user.Id
   990  		}
   991  	}
   992  	return usernames
   993  }
   994  
   995  func (a *App) SearchPostsInTeam(teamId string, paramsList []*model.SearchParams) (*model.PostList, *model.AppError) {
   996  	if !*a.Config().ServiceSettings.EnablePostSearch {
   997  		return nil, model.NewAppError("SearchPostsInTeam", "store.sql_post.search.disabled", nil, fmt.Sprintf("teamId=%v", teamId), http.StatusNotImplemented)
   998  	}
   999  	return a.searchPostsInTeam(teamId, "", paramsList, func(params *model.SearchParams) {
  1000  		params.SearchWithoutUserId = true
  1001  	})
  1002  }
  1003  
  1004  func (a *App) SearchPostsInTeamForUser(terms string, userId string, teamId string, isOrSearch bool, includeDeletedChannels bool, timeZoneOffset int, page, perPage int) (*model.PostSearchResults, *model.AppError) {
  1005  	var postSearchResults *model.PostSearchResults
  1006  	var err *model.AppError
  1007  	paramsList := model.ParseSearchParams(strings.TrimSpace(terms), timeZoneOffset)
  1008  	includeDeleted := includeDeletedChannels && *a.Config().TeamSettings.ExperimentalViewArchivedChannels
  1009  
  1010  	if !*a.Config().ServiceSettings.EnablePostSearch {
  1011  		return nil, model.NewAppError("SearchPostsInTeamForUser", "store.sql_post.search.disabled", nil, fmt.Sprintf("teamId=%v userId=%v", teamId, userId), http.StatusNotImplemented)
  1012  	}
  1013  
  1014  	finalParamsList := []*model.SearchParams{}
  1015  
  1016  	for _, params := range paramsList {
  1017  		params.OrTerms = isOrSearch
  1018  		// Don't allow users to search for "*"
  1019  		if params.Terms != "*" {
  1020  			// Convert channel names to channel IDs
  1021  			params.InChannels = a.convertChannelNamesToChannelIds(params.InChannels, userId, teamId, includeDeletedChannels)
  1022  			params.ExcludedChannels = a.convertChannelNamesToChannelIds(params.ExcludedChannels, userId, teamId, includeDeletedChannels)
  1023  
  1024  			// Convert usernames to user IDs
  1025  			params.FromUsers = a.convertUserNameToUserIds(params.FromUsers)
  1026  			params.ExcludedUsers = a.convertUserNameToUserIds(params.ExcludedUsers)
  1027  
  1028  			finalParamsList = append(finalParamsList, params)
  1029  		}
  1030  	}
  1031  
  1032  	// If the processed search params are empty, return empty search results.
  1033  	if len(finalParamsList) == 0 {
  1034  		return model.MakePostSearchResults(model.NewPostList(), nil), nil
  1035  	}
  1036  
  1037  	postSearchResults, err = a.Srv().Store.Post().SearchPostsInTeamForUser(finalParamsList, userId, teamId, isOrSearch, includeDeleted, page, perPage)
  1038  	if err != nil {
  1039  		return nil, err
  1040  	}
  1041  
  1042  	return postSearchResults, nil
  1043  }
  1044  
  1045  func (a *App) GetFileInfosForPostWithMigration(postId string) ([]*model.FileInfo, *model.AppError) {
  1046  
  1047  	pchan := make(chan store.StoreResult, 1)
  1048  	go func() {
  1049  		post, err := a.Srv().Store.Post().GetSingle(postId)
  1050  		pchan <- store.StoreResult{Data: post, Err: err}
  1051  		close(pchan)
  1052  	}()
  1053  
  1054  	infos, err := a.GetFileInfosForPost(postId, false)
  1055  	if err != nil {
  1056  		return nil, err
  1057  	}
  1058  
  1059  	if len(infos) == 0 {
  1060  		// No FileInfos were returned so check if they need to be created for this post
  1061  		result := <-pchan
  1062  		if result.Err != nil {
  1063  			return nil, result.Err
  1064  		}
  1065  		post := result.Data.(*model.Post)
  1066  
  1067  		if len(post.Filenames) > 0 {
  1068  			a.Srv().Store.FileInfo().InvalidateFileInfosForPostCache(postId, false)
  1069  			a.Srv().Store.FileInfo().InvalidateFileInfosForPostCache(postId, true)
  1070  			// The post has Filenames that need to be replaced with FileInfos
  1071  			infos = a.MigrateFilenamesToFileInfos(post)
  1072  		}
  1073  	}
  1074  
  1075  	return infos, nil
  1076  }
  1077  
  1078  func (a *App) GetFileInfosForPost(postId string, fromMaster bool) ([]*model.FileInfo, *model.AppError) {
  1079  	return a.Srv().Store.FileInfo().GetForPost(postId, fromMaster, false, true)
  1080  }
  1081  
  1082  func (a *App) PostWithProxyAddedToImageURLs(post *model.Post) *model.Post {
  1083  	if f := a.ImageProxyAdder(); f != nil {
  1084  		return post.WithRewrittenImageURLs(f)
  1085  	}
  1086  	return post
  1087  }
  1088  
  1089  func (a *App) PostWithProxyRemovedFromImageURLs(post *model.Post) *model.Post {
  1090  	if f := a.ImageProxyRemover(); f != nil {
  1091  		return post.WithRewrittenImageURLs(f)
  1092  	}
  1093  	return post
  1094  }
  1095  
  1096  func (a *App) PostPatchWithProxyRemovedFromImageURLs(patch *model.PostPatch) *model.PostPatch {
  1097  	if f := a.ImageProxyRemover(); f != nil {
  1098  		return patch.WithRewrittenImageURLs(f)
  1099  	}
  1100  	return patch
  1101  }
  1102  
  1103  func (a *App) ImageProxyAdder() func(string) string {
  1104  	if !*a.Config().ImageProxySettings.Enable {
  1105  		return nil
  1106  	}
  1107  
  1108  	return func(url string) string {
  1109  		return a.Srv().ImageProxy.GetProxiedImageURL(url)
  1110  	}
  1111  }
  1112  
  1113  func (a *App) ImageProxyRemover() (f func(string) string) {
  1114  	if !*a.Config().ImageProxySettings.Enable {
  1115  		return nil
  1116  	}
  1117  
  1118  	return func(url string) string {
  1119  		return a.Srv().ImageProxy.GetUnproxiedImageURL(url)
  1120  	}
  1121  }
  1122  
  1123  func (s *Server) MaxPostSize() int {
  1124  	maxPostSize := s.Store.Post().GetMaxPostSize()
  1125  	if maxPostSize == 0 {
  1126  		return model.POST_MESSAGE_MAX_RUNES_V1
  1127  	}
  1128  
  1129  	return maxPostSize
  1130  }
  1131  
  1132  func (a *App) MaxPostSize() int {
  1133  	return a.Srv().MaxPostSize()
  1134  }
  1135  
  1136  // countMentionsFromPost returns the number of posts in the post's channel that mention the user after and including the
  1137  // given post.
  1138  func (a *App) countMentionsFromPost(user *model.User, post *model.Post) (int, *model.AppError) {
  1139  	channel, err := a.GetChannel(post.ChannelId)
  1140  	if err != nil {
  1141  		return 0, err
  1142  	}
  1143  
  1144  	if channel.Type == model.CHANNEL_DIRECT {
  1145  		// In a DM channel, every post made by the other user is a mention
  1146  		count, countErr := a.Srv().Store.Channel().CountPostsAfter(post.ChannelId, post.CreateAt-1, channel.GetOtherUserIdForDM(user.Id))
  1147  		if countErr != nil {
  1148  			return 0, countErr
  1149  		}
  1150  
  1151  		return count, countErr
  1152  	}
  1153  
  1154  	channelMember, err := a.GetChannelMember(channel.Id, user.Id)
  1155  	if err != nil {
  1156  		return 0, err
  1157  	}
  1158  
  1159  	keywords := addMentionKeywordsForUser(
  1160  		map[string][]string{},
  1161  		user,
  1162  		channelMember.NotifyProps,
  1163  		&model.Status{Status: model.STATUS_ONLINE}, // Assume the user is online since they would've triggered this
  1164  		true, // Assume channel mentions are always allowed for simplicity
  1165  	)
  1166  	commentMentions := user.NotifyProps[model.COMMENTS_NOTIFY_PROP]
  1167  	checkForCommentMentions := commentMentions == model.COMMENTS_NOTIFY_ROOT || commentMentions == model.COMMENTS_NOTIFY_ANY
  1168  
  1169  	// A mapping of thread root IDs to whether or not a post in that thread mentions the user
  1170  	mentionedByThread := make(map[string]bool)
  1171  
  1172  	thread, err := a.GetPostThread(post.Id, false)
  1173  	if err != nil {
  1174  		return 0, err
  1175  	}
  1176  
  1177  	count := 0
  1178  
  1179  	if isPostMention(user, post, keywords, thread.Posts, mentionedByThread, checkForCommentMentions) {
  1180  		count += 1
  1181  	}
  1182  
  1183  	page := 0
  1184  	perPage := 200
  1185  	for {
  1186  		postList, err := a.GetPostsAfterPost(model.GetPostsOptions{
  1187  			ChannelId: post.ChannelId,
  1188  			PostId:    post.Id,
  1189  			Page:      page,
  1190  			PerPage:   perPage,
  1191  		})
  1192  		if err != nil {
  1193  			return 0, err
  1194  		}
  1195  
  1196  		for _, postId := range postList.Order {
  1197  			if isPostMention(user, postList.Posts[postId], keywords, postList.Posts, mentionedByThread, checkForCommentMentions) {
  1198  				count += 1
  1199  			}
  1200  		}
  1201  
  1202  		if len(postList.Order) < perPage {
  1203  			break
  1204  		}
  1205  
  1206  		page += 1
  1207  	}
  1208  
  1209  	return count, nil
  1210  }
  1211  
  1212  func isCommentMention(user *model.User, post *model.Post, otherPosts map[string]*model.Post, mentionedByThread map[string]bool) bool {
  1213  	if post.RootId == "" {
  1214  		// Not a comment
  1215  		return false
  1216  	}
  1217  
  1218  	if mentioned, ok := mentionedByThread[post.RootId]; ok {
  1219  		// We've already figured out if the user was mentioned by this thread
  1220  		return mentioned
  1221  	}
  1222  
  1223  	// Whether or not the user was mentioned because they started the thread
  1224  	mentioned := otherPosts[post.RootId].UserId == user.Id
  1225  
  1226  	// Or because they commented on it before this post
  1227  	if !mentioned && user.NotifyProps[model.COMMENTS_NOTIFY_PROP] == model.COMMENTS_NOTIFY_ANY {
  1228  		for _, otherPost := range otherPosts {
  1229  			if otherPost.Id == post.Id {
  1230  				continue
  1231  			}
  1232  
  1233  			if otherPost.RootId != post.RootId {
  1234  				continue
  1235  			}
  1236  
  1237  			if otherPost.UserId == user.Id && otherPost.CreateAt < post.CreateAt {
  1238  				// Found a comment made by the user from before this post
  1239  				mentioned = true
  1240  				break
  1241  			}
  1242  		}
  1243  	}
  1244  
  1245  	mentionedByThread[post.RootId] = mentioned
  1246  	return mentioned
  1247  }
  1248  
  1249  func isPostMention(user *model.User, post *model.Post, keywords map[string][]string, otherPosts map[string]*model.Post, mentionedByThread map[string]bool, checkForCommentMentions bool) bool {
  1250  	// Prevent the user from mentioning themselves
  1251  	if post.UserId == user.Id && post.GetProp("from_webhook") != "true" {
  1252  		return false
  1253  	}
  1254  
  1255  	// Check for keyword mentions
  1256  	mentions := getExplicitMentions(post, keywords, make(map[string]*model.Group))
  1257  	if _, ok := mentions.Mentions[user.Id]; ok {
  1258  		return true
  1259  	}
  1260  
  1261  	// Check for mentions caused by being added to the channel
  1262  	if post.Type == model.POST_ADD_TO_CHANNEL {
  1263  		if addedUserId, ok := post.GetProp(model.POST_PROPS_ADDED_USER_ID).(string); ok && addedUserId == user.Id {
  1264  			return true
  1265  		}
  1266  	}
  1267  
  1268  	// Check for comment mentions
  1269  	if checkForCommentMentions && isCommentMention(user, post, otherPosts, mentionedByThread) {
  1270  		return true
  1271  	}
  1272  
  1273  	return false
  1274  }