1// Copyright 2019 The Gitea Authors. All rights reserved.
2// Use of this source code is governed by a MIT-style
3// license that can be found in the LICENSE file.
4
5package issue
6
7import (
8	"code.gitea.io/gitea/models"
9	"code.gitea.io/gitea/models/db"
10	repo_model "code.gitea.io/gitea/models/repo"
11	user_model "code.gitea.io/gitea/models/user"
12	"code.gitea.io/gitea/modules/git"
13	"code.gitea.io/gitea/modules/notification"
14	"code.gitea.io/gitea/modules/util"
15)
16
17// NewIssue creates new issue with labels for repository.
18func NewIssue(repo *repo_model.Repository, issue *models.Issue, labelIDs []int64, uuids []string, assigneeIDs []int64) error {
19	if err := models.NewIssue(repo, issue, labelIDs, uuids); err != nil {
20		return err
21	}
22
23	for _, assigneeID := range assigneeIDs {
24		if err := AddAssigneeIfNotAssigned(issue, issue.Poster, assigneeID); err != nil {
25			return err
26		}
27	}
28
29	mentions, err := issue.FindAndUpdateIssueMentions(db.DefaultContext, issue.Poster, issue.Content)
30	if err != nil {
31		return err
32	}
33
34	notification.NotifyNewIssue(issue, mentions)
35	if len(issue.Labels) > 0 {
36		notification.NotifyIssueChangeLabels(issue.Poster, issue, issue.Labels, nil)
37	}
38	if issue.Milestone != nil {
39		notification.NotifyIssueChangeMilestone(issue.Poster, issue, 0)
40	}
41
42	return nil
43}
44
45// ChangeTitle changes the title of this issue, as the given user.
46func ChangeTitle(issue *models.Issue, doer *user_model.User, title string) (err error) {
47	oldTitle := issue.Title
48	issue.Title = title
49
50	if err = issue.ChangeTitle(doer, oldTitle); err != nil {
51		return
52	}
53
54	notification.NotifyIssueChangeTitle(doer, issue, oldTitle)
55
56	return nil
57}
58
59// ChangeIssueRef changes the branch of this issue, as the given user.
60func ChangeIssueRef(issue *models.Issue, doer *user_model.User, ref string) error {
61	oldRef := issue.Ref
62	issue.Ref = ref
63
64	if err := issue.ChangeRef(doer, oldRef); err != nil {
65		return err
66	}
67
68	notification.NotifyIssueChangeRef(doer, issue, oldRef)
69
70	return nil
71}
72
73// UpdateAssignees is a helper function to add or delete one or multiple issue assignee(s)
74// Deleting is done the GitHub way (quote from their api documentation):
75// https://developer.github.com/v3/issues/#edit-an-issue
76// "assignees" (array): Logins for Users to assign to this issue.
77// Pass one or more user logins to replace the set of assignees on this Issue.
78// Send an empty array ([]) to clear all assignees from the Issue.
79func UpdateAssignees(issue *models.Issue, oneAssignee string, multipleAssignees []string, doer *user_model.User) (err error) {
80	var allNewAssignees []*user_model.User
81
82	// Keep the old assignee thingy for compatibility reasons
83	if oneAssignee != "" {
84		// Prevent double adding assignees
85		var isDouble bool
86		for _, assignee := range multipleAssignees {
87			if assignee == oneAssignee {
88				isDouble = true
89				break
90			}
91		}
92
93		if !isDouble {
94			multipleAssignees = append(multipleAssignees, oneAssignee)
95		}
96	}
97
98	// Loop through all assignees to add them
99	for _, assigneeName := range multipleAssignees {
100		assignee, err := user_model.GetUserByName(assigneeName)
101		if err != nil {
102			return err
103		}
104
105		allNewAssignees = append(allNewAssignees, assignee)
106	}
107
108	// Delete all old assignees not passed
109	if err = DeleteNotPassedAssignee(issue, doer, allNewAssignees); err != nil {
110		return err
111	}
112
113	// Add all new assignees
114	// Update the assignee. The function will check if the user exists, is already
115	// assigned (which he shouldn't as we deleted all assignees before) and
116	// has access to the repo.
117	for _, assignee := range allNewAssignees {
118		// Extra method to prevent double adding (which would result in removing)
119		err = AddAssigneeIfNotAssigned(issue, doer, assignee.ID)
120		if err != nil {
121			return err
122		}
123	}
124
125	return
126}
127
128// AddAssigneeIfNotAssigned adds an assignee only if he isn't already assigned to the issue.
129// Also checks for access of assigned user
130func AddAssigneeIfNotAssigned(issue *models.Issue, doer *user_model.User, assigneeID int64) (err error) {
131	assignee, err := user_model.GetUserByID(assigneeID)
132	if err != nil {
133		return err
134	}
135
136	// Check if the user is already assigned
137	isAssigned, err := models.IsUserAssignedToIssue(issue, assignee)
138	if err != nil {
139		return err
140	}
141	if isAssigned {
142		// nothing to to
143		return nil
144	}
145
146	valid, err := models.CanBeAssigned(assignee, issue.Repo, issue.IsPull)
147	if err != nil {
148		return err
149	}
150	if !valid {
151		return models.ErrUserDoesNotHaveAccessToRepo{UserID: assigneeID, RepoName: issue.Repo.Name}
152	}
153
154	_, _, err = ToggleAssignee(issue, doer, assigneeID)
155	if err != nil {
156		return err
157	}
158
159	return nil
160}
161
162// GetRefEndNamesAndURLs retrieves the ref end names (e.g. refs/heads/branch-name -> branch-name)
163// and their respective URLs.
164func GetRefEndNamesAndURLs(issues []*models.Issue, repoLink string) (map[int64]string, map[int64]string) {
165	var issueRefEndNames = make(map[int64]string, len(issues))
166	var issueRefURLs = make(map[int64]string, len(issues))
167	for _, issue := range issues {
168		if issue.Ref != "" {
169			issueRefEndNames[issue.ID] = git.RefEndName(issue.Ref)
170			issueRefURLs[issue.ID] = git.RefURL(repoLink, util.PathEscapeSegments(issue.Ref))
171		}
172	}
173	return issueRefEndNames, issueRefURLs
174}
175