2019-09-24 08:02:49 +03:00
// Copyright 2019 The Gitea Authors. All rights reserved.
2022-11-27 21:20:29 +03:00
// SPDX-License-Identifier: MIT
2016-07-15 19:36:39 +03:00
2019-09-24 08:02:49 +03:00
package mailer
2016-07-15 19:36:39 +03:00
import (
2022-01-20 02:26:57 +03:00
"context"
2016-07-15 19:36:39 +03:00
"fmt"
2022-08-25 05:31:57 +03:00
activities_model "code.gitea.io/gitea/models/activities"
2022-06-13 12:37:59 +03:00
issues_model "code.gitea.io/gitea/models/issues"
2022-08-25 05:31:57 +03:00
access_model "code.gitea.io/gitea/models/perm/access"
2021-12-12 18:48:20 +03:00
repo_model "code.gitea.io/gitea/models/repo"
2021-11-09 22:57:58 +03:00
"code.gitea.io/gitea/models/unit"
2021-11-24 12:49:20 +03:00
user_model "code.gitea.io/gitea/models/user"
2022-10-12 08:18:26 +03:00
"code.gitea.io/gitea/modules/container"
2016-11-10 19:24:48 +03:00
"code.gitea.io/gitea/modules/log"
2021-08-12 10:26:33 +03:00
"code.gitea.io/gitea/modules/setting"
2016-07-15 19:36:39 +03:00
)
2022-06-13 12:37:59 +03:00
func fallbackMailSubject ( issue * issues_model . Issue ) string {
2019-05-28 12:41:48 +03:00
return fmt . Sprintf ( "[%s] %s (#%d)" , issue . Repo . FullName ( ) , issue . Title , issue . Index )
2016-07-15 19:36:39 +03:00
}
2019-11-18 11:08:20 +03:00
type mailCommentContext struct {
2022-01-20 02:26:57 +03:00
context . Context
2022-11-03 18:49:00 +03:00
Issue * issues_model . Issue
Doer * user_model . User
ActionType activities_model . ActionType
Content string
Comment * issues_model . Comment
ForceDoerNotification bool
2019-11-18 11:08:20 +03:00
}
2021-04-02 13:25:13 +03:00
const (
// MailBatchSize set the batch size used in mailIssueCommentBatch
MailBatchSize = 100
)
2016-07-15 19:36:39 +03:00
// mailIssueCommentToParticipants can be used for both new issue creation and comment.
2017-03-16 04:34:24 +03:00
// This function sends two list of emails:
2021-06-23 07:14:22 +03:00
// 1. Repository watchers (except for WIP pull requests) and users who are participated in comments.
2017-03-16 04:34:24 +03:00
// 2. Users who are not in 1. but get mentioned in current issue/comment.
2021-11-24 12:49:20 +03:00
func mailIssueCommentToParticipants ( ctx * mailCommentContext , mentions [ ] * user_model . User ) error {
2019-11-18 11:08:20 +03:00
// Required by the mail composer; make sure to load these before calling the async function
2022-04-08 12:11:15 +03:00
if err := ctx . Issue . LoadRepo ( ctx ) ; err != nil {
2022-11-19 11:12:33 +03:00
return fmt . Errorf ( "LoadRepo: %w" , err )
2017-03-16 04:34:24 +03:00
}
2022-11-19 11:12:33 +03:00
if err := ctx . Issue . LoadPoster ( ctx ) ; err != nil {
return fmt . Errorf ( "LoadPoster: %w" , err )
2019-11-18 11:08:20 +03:00
}
2022-11-19 11:12:33 +03:00
if err := ctx . Issue . LoadPullRequest ( ctx ) ; err != nil {
return fmt . Errorf ( "LoadPullRequest: %w" , err )
2017-03-16 04:34:24 +03:00
}
2019-11-18 11:08:20 +03:00
// Enough room to avoid reallocations
unfiltered := make ( [ ] int64 , 1 , 64 )
// =========== Original poster ===========
unfiltered [ 0 ] = ctx . Issue . PosterID
// =========== Assignees ===========
2022-11-19 11:12:33 +03:00
ids , err := issues_model . GetAssigneeIDsByIssue ( ctx , ctx . Issue . ID )
2017-09-16 03:18:25 +03:00
if err != nil {
2022-10-24 22:29:17 +03:00
return fmt . Errorf ( "GetAssigneeIDsByIssue(%d): %w" , ctx . Issue . ID , err )
2016-07-15 19:36:39 +03:00
}
2019-11-18 11:08:20 +03:00
unfiltered = append ( unfiltered , ids ... )
2016-07-15 19:36:39 +03:00
2019-11-18 11:08:20 +03:00
// =========== Participants (i.e. commenters, reviewers) ===========
2022-11-19 11:12:33 +03:00
ids , err = issues_model . GetParticipantsIDsByIssueID ( ctx , ctx . Issue . ID )
2018-05-09 19:29:04 +03:00
if err != nil {
2022-10-24 22:29:17 +03:00
return fmt . Errorf ( "GetParticipantsIDsByIssueID(%d): %w" , ctx . Issue . ID , err )
2018-05-09 19:29:04 +03:00
}
2019-11-18 11:08:20 +03:00
unfiltered = append ( unfiltered , ids ... )
2018-05-09 19:29:04 +03:00
2019-11-18 11:08:20 +03:00
// =========== Issue watchers ===========
2022-06-13 12:37:59 +03:00
ids , err = issues_model . GetIssueWatchersIDs ( ctx , ctx . Issue . ID , true )
2019-11-18 11:08:20 +03:00
if err != nil {
2022-10-24 22:29:17 +03:00
return fmt . Errorf ( "GetIssueWatchersIDs(%d): %w" , ctx . Issue . ID , err )
2017-06-23 16:43:37 +03:00
}
2019-11-18 11:08:20 +03:00
unfiltered = append ( unfiltered , ids ... )
2017-06-23 16:43:37 +03:00
2019-11-18 11:08:20 +03:00
// =========== Repo watchers ===========
// Make repo watchers last, since it's likely the list with the most users
2023-10-11 07:24:07 +03:00
if ! ( ctx . Issue . IsPull && ctx . Issue . PullRequest . IsWorkInProgress ( ctx ) && ctx . ActionType != activities_model . ActionCreatePullRequest ) {
2022-03-22 18:22:54 +03:00
ids , err = repo_model . GetRepoWatchersIDs ( ctx , ctx . Issue . RepoID )
2021-06-23 07:14:22 +03:00
if err != nil {
2022-10-24 22:29:17 +03:00
return fmt . Errorf ( "GetRepoWatchersIDs(%d): %w" , ctx . Issue . RepoID , err )
2021-06-23 07:14:22 +03:00
}
unfiltered = append ( ids , unfiltered ... )
2016-07-15 19:36:39 +03:00
}
2017-03-16 04:34:24 +03:00
2022-10-12 08:18:26 +03:00
visited := make ( container . Set [ int64 ] , len ( unfiltered ) + len ( mentions ) + 1 )
2017-05-25 05:38:56 +03:00
2019-11-18 11:08:20 +03:00
// Avoid mailing the doer
2022-11-03 18:49:00 +03:00
if ctx . Doer . EmailNotificationsPreference != user_model . EmailNotificationsAndYourOwn && ! ctx . ForceDoerNotification {
2022-10-12 08:18:26 +03:00
visited . Add ( ctx . Doer . ID )
2022-07-28 11:30:12 +03:00
}
2021-01-02 20:04:02 +03:00
// =========== Mentions ===========
if err = mailIssueCommentBatch ( ctx , mentions , visited , true ) ; err != nil {
2022-10-24 22:29:17 +03:00
return fmt . Errorf ( "mailIssueCommentBatch() mentions: %w" , err )
2021-01-02 20:04:02 +03:00
}
2020-02-27 13:07:05 +03:00
// Avoid mailing explicit unwatched
2022-06-13 12:37:59 +03:00
ids , err = issues_model . GetIssueWatchersIDs ( ctx , ctx . Issue . ID , false )
2020-02-27 13:07:05 +03:00
if err != nil {
2022-10-24 22:29:17 +03:00
return fmt . Errorf ( "GetIssueWatchersIDs(%d): %w" , ctx . Issue . ID , err )
2020-02-27 13:07:05 +03:00
}
2022-10-12 08:18:26 +03:00
visited . AddMultiple ( ids ... )
2019-01-30 01:43:40 +03:00
2022-11-19 11:12:33 +03:00
unfilteredUsers , err := user_model . GetMaileableUsersByIDs ( ctx , unfiltered , false )
2021-04-02 13:25:13 +03:00
if err != nil {
return err
}
if err = mailIssueCommentBatch ( ctx , unfilteredUsers , visited , false ) ; err != nil {
2022-10-24 22:29:17 +03:00
return fmt . Errorf ( "mailIssueCommentBatch(): %w" , err )
2018-08-24 07:41:26 +03:00
}
2016-07-15 19:36:39 +03:00
2019-11-18 11:08:20 +03:00
return nil
}
2018-08-24 07:41:26 +03:00
2022-10-12 08:18:26 +03:00
func mailIssueCommentBatch ( ctx * mailCommentContext , users [ ] * user_model . User , visited container . Set [ int64 ] , fromMention bool ) error {
2021-11-09 22:57:58 +03:00
checkUnit := unit . TypeIssues
2021-04-02 13:25:13 +03:00
if ctx . Issue . IsPull {
2021-11-09 22:57:58 +03:00
checkUnit = unit . TypePullRequests
2021-04-02 13:25:13 +03:00
}
2021-11-24 12:49:20 +03:00
langMap := make ( map [ string ] [ ] * user_model . User )
2021-04-02 13:25:13 +03:00
for _ , user := range users {
2022-03-19 19:20:03 +03:00
if ! user . IsActive {
// Exclude deactivated users
continue
}
2021-04-02 13:25:13 +03:00
// At this point we exclude:
// user that don't have all mails enabled or users only get mail on mention and this is one ...
2021-11-24 12:49:20 +03:00
if ! ( user . EmailNotificationsPreference == user_model . EmailNotificationsEnabled ||
2022-07-28 11:30:12 +03:00
user . EmailNotificationsPreference == user_model . EmailNotificationsAndYourOwn ||
2021-11-24 12:49:20 +03:00
fromMention && user . EmailNotificationsPreference == user_model . EmailNotificationsOnMention ) {
2021-04-02 13:25:13 +03:00
continue
2019-11-18 11:08:20 +03:00
}
2020-12-03 23:25:49 +03:00
2021-04-02 13:25:13 +03:00
// if we have already visited this user we exclude them
2022-10-12 08:18:26 +03:00
if ! visited . Add ( user . ID ) {
2021-04-02 13:25:13 +03:00
continue
2021-01-09 20:34:08 +03:00
}
2021-04-02 13:25:13 +03:00
// test if this user is allowed to see the issue/pull
2022-08-25 05:31:57 +03:00
if ! access_model . CheckRepoUnitUser ( ctx , ctx . Issue . Repo , user , checkUnit ) {
2021-04-02 13:25:13 +03:00
continue
2020-12-03 23:25:49 +03:00
}
2021-05-22 09:47:16 +03:00
langMap [ user . Language ] = append ( langMap [ user . Language ] , user )
2021-04-02 13:25:13 +03:00
}
for lang , receivers := range langMap {
// because we know that the len(receivers) > 0 and we don't care about the order particularly
// working backwards from the last (possibly) incomplete batch. If len(receivers) can be 0 this
// starting condition will need to be changed slightly
for i := ( ( len ( receivers ) - 1 ) / MailBatchSize ) * MailBatchSize ; i >= 0 ; i -= MailBatchSize {
2021-04-20 01:25:08 +03:00
msgs , err := composeIssueCommentMessages ( ctx , lang , receivers [ i : ] , fromMention , "issue comments" )
if err != nil {
return err
}
2023-10-31 17:11:48 +03:00
SendAsync ( msgs ... )
2021-04-02 13:25:13 +03:00
receivers = receivers [ : i ]
2019-11-18 11:08:20 +03:00
}
2018-08-24 07:41:26 +03:00
}
2021-04-02 13:25:13 +03:00
2016-07-15 19:36:39 +03:00
return nil
}
// MailParticipants sends new issue thread created emails to repository watchers
// and mentioned people.
2022-11-19 11:12:33 +03:00
func MailParticipants ( ctx context . Context , issue * issues_model . Issue , doer * user_model . User , opType activities_model . ActionType , mentions [ ] * user_model . User ) error {
2021-08-12 10:26:33 +03:00
if setting . MailService == nil {
// No mail service configured
return nil
}
2021-05-30 12:38:38 +03:00
content := issue . Content
2022-08-25 05:31:57 +03:00
if opType == activities_model . ActionCloseIssue || opType == activities_model . ActionClosePullRequest ||
opType == activities_model . ActionReopenIssue || opType == activities_model . ActionReopenPullRequest ||
2022-11-03 18:49:00 +03:00
opType == activities_model . ActionMergePullRequest || opType == activities_model . ActionAutoMergePullRequest {
2021-05-30 12:38:38 +03:00
content = ""
}
2022-11-03 18:49:00 +03:00
forceDoerNotification := opType == activities_model . ActionAutoMergePullRequest
2021-04-02 13:25:13 +03:00
if err := mailIssueCommentToParticipants (
2019-11-18 11:08:20 +03:00
& mailCommentContext {
2022-11-19 11:12:33 +03:00
Context : ctx ,
2022-11-03 18:49:00 +03:00
Issue : issue ,
Doer : doer ,
ActionType : opType ,
Content : content ,
Comment : nil ,
ForceDoerNotification : forceDoerNotification ,
2021-04-02 13:25:13 +03:00
} , mentions ) ; err != nil {
2019-11-07 16:34:28 +03:00
log . Error ( "mailIssueCommentToParticipants: %v" , err )
2016-07-15 19:36:39 +03:00
}
return nil
}