You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1299 lines
33 KiB

10 years ago
9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
Squashed commit of the following: commit 0afcb843d7ffd596991c4885cab768273a6eb42c Author: Richard Mahn <richard_mahn@wycliffeassociates.org> Date: Sun Jul 31 17:13:29 2016 -0600 Removed Upload stats as the upload table is just a temporary table commit 7ecd73ff5535612d79d471409173ee7f1fcfa157 Author: Richard Mahn <richard_mahn@wycliffeassociates.org> Date: Sun Jul 31 08:42:41 2016 -0600 Fix for CodeMirror mode commit c29b9ab531e2e7af0fb5db24dc17e51027dd1174 Author: Richard Mahn <richard_mahn@wycliffeassociates.org> Date: Sun Jul 31 08:03:33 2016 -0600 Made tabbing in editor use spaces commit 23af384c53206a8a40e11e45bf49d7a149c4adcd Author: Richard Mahn <richard_mahn@wycliffeassociates.org> Date: Sun Jul 31 07:56:46 2016 -0600 Fix for data-url commit cfb8a97591cb6fc0a92e49563b7b764c524db0e9 Merge: 7fc8a89 991ce42 Author: Richard Mahn <richard_mahn@wycliffeassociates.org> Date: Sun Jul 31 07:42:53 2016 -0600 Merge remote-tracking branch 'gogits/develop' into feature-create-and-edit-repo-file Conflicts: modules/bindata/bindata.go public/js/gogs.js commit 7fc8a89cb495478225b02d613e647f99a1489634 Merge: fd3d86c c03d040 Author: Richard Mahn <richard_mahn@wycliffeassociates.org> Date: Sun Jul 31 07:40:00 2016 -0600 Merge branch 'feature-create-and-edit-repo-file' of github.com:richmahn/gogs into feature-create-and-edit-repo-file commit fd3d86ca6bbc02cfda566a504ffd6b03db4f75ef Author: Richard Mahn <richard_mahn@wycliffeassociates.org> Date: Sun Jul 31 07:39:44 2016 -0600 Code cleanup commit c03d0401c1049eeeccc32ab1f9c3303c130be5ee Author: Richard Mahn <richard_mahn@wycliffeassociates.org> Date: Fri Jul 29 15:38:23 2016 -0600 Code cleanup commit 98e1206ccf9f9a4503c020e3a7830cf9f861dfae Author: Richard Mahn <richard_mahn@wycliffeassociates.org> Date: Thu Jul 28 18:36:01 2016 -0600 Code cleanup and fixes commit c2895dc742f25f8412879c9fa15e18f27f42f194 Author: Richard Mahn <richard_mahn@wycliffeassociates.org> Date: Thu Jul 28 18:24:04 2016 -0600 Fixes per Unknwon's requests commit 6aa7e46b21ad4c96e562daa2eac26a8fb408f8ef Merge: 889e9fa ad7ea88 Author: Richard Mahn <richard_mahn@wycliffeassociates.org> Date: Thu Jul 28 17:13:43 2016 -0600 Merge remote-tracking branch 'gogits/develop' into feature-create-and-edit-repo-file Conflicts: modules/bindata/bindata.go modules/setting/setting.go commit 889e9faf1bd8559a4979c8f46005d488c1a234d4 Author: Richard Mahn <richard_mahn@wycliffeassociates.org> Date: Fri Jul 22 14:09:18 2016 -0600 Fix in gogs.js commit 47603edf223f147b114be65f3bd27bc1e88827a5 Merge: bb57912 cf85e9e Author: Richard Mahn <richard_mahn@wycliffeassociates.org> Date: Fri Jul 22 14:07:36 2016 -0600 Merge remote-tracking branch 'gogits/develop' into feature-create-and-edit-repo-file Conflicts: modules/bindata/bindata.go public/js/gogs.js commit bb5791255867a71c11a77b639db050ad09c597a4 Author: Richard Mahn <richard_mahn@wycliffeassociates.org> Date: Fri Jul 22 14:02:18 2016 -0600 Update for using CodeMirror mode addon commit d10d128c51039be19e2af9c66c63db66a9f2ec6d Author: Richard Mahn <richard_mahn@wycliffeassociates.org> Date: Tue Jul 19 16:12:57 2016 -0600 Update for Edit commit 34a34982025144e3225e389f7849eb6273c1d576 Merge: fa1b752 1c7dcdd Author: Richard Mahn <richard_mahn@wycliffeassociates.org> Date: Tue Jul 19 11:52:02 2016 -0600 Merge remote-tracking branch 'gogits/develop' into feature-create-and-edit-repo-file Conflicts: modules/bindata/bindata.go commit fa1b752be29cd455c5184ddac2ffe80b3489763e Author: Richard Mahn <richard_mahn@wycliffeassociates.org> Date: Fri Jul 15 18:35:42 2016 -0600 Feature for editing, creating, uploading and deleting files
8 years ago
9 years ago
Squashed commit of the following: commit 0afcb843d7ffd596991c4885cab768273a6eb42c Author: Richard Mahn <richard_mahn@wycliffeassociates.org> Date: Sun Jul 31 17:13:29 2016 -0600 Removed Upload stats as the upload table is just a temporary table commit 7ecd73ff5535612d79d471409173ee7f1fcfa157 Author: Richard Mahn <richard_mahn@wycliffeassociates.org> Date: Sun Jul 31 08:42:41 2016 -0600 Fix for CodeMirror mode commit c29b9ab531e2e7af0fb5db24dc17e51027dd1174 Author: Richard Mahn <richard_mahn@wycliffeassociates.org> Date: Sun Jul 31 08:03:33 2016 -0600 Made tabbing in editor use spaces commit 23af384c53206a8a40e11e45bf49d7a149c4adcd Author: Richard Mahn <richard_mahn@wycliffeassociates.org> Date: Sun Jul 31 07:56:46 2016 -0600 Fix for data-url commit cfb8a97591cb6fc0a92e49563b7b764c524db0e9 Merge: 7fc8a89 991ce42 Author: Richard Mahn <richard_mahn@wycliffeassociates.org> Date: Sun Jul 31 07:42:53 2016 -0600 Merge remote-tracking branch 'gogits/develop' into feature-create-and-edit-repo-file Conflicts: modules/bindata/bindata.go public/js/gogs.js commit 7fc8a89cb495478225b02d613e647f99a1489634 Merge: fd3d86c c03d040 Author: Richard Mahn <richard_mahn@wycliffeassociates.org> Date: Sun Jul 31 07:40:00 2016 -0600 Merge branch 'feature-create-and-edit-repo-file' of github.com:richmahn/gogs into feature-create-and-edit-repo-file commit fd3d86ca6bbc02cfda566a504ffd6b03db4f75ef Author: Richard Mahn <richard_mahn@wycliffeassociates.org> Date: Sun Jul 31 07:39:44 2016 -0600 Code cleanup commit c03d0401c1049eeeccc32ab1f9c3303c130be5ee Author: Richard Mahn <richard_mahn@wycliffeassociates.org> Date: Fri Jul 29 15:38:23 2016 -0600 Code cleanup commit 98e1206ccf9f9a4503c020e3a7830cf9f861dfae Author: Richard Mahn <richard_mahn@wycliffeassociates.org> Date: Thu Jul 28 18:36:01 2016 -0600 Code cleanup and fixes commit c2895dc742f25f8412879c9fa15e18f27f42f194 Author: Richard Mahn <richard_mahn@wycliffeassociates.org> Date: Thu Jul 28 18:24:04 2016 -0600 Fixes per Unknwon's requests commit 6aa7e46b21ad4c96e562daa2eac26a8fb408f8ef Merge: 889e9fa ad7ea88 Author: Richard Mahn <richard_mahn@wycliffeassociates.org> Date: Thu Jul 28 17:13:43 2016 -0600 Merge remote-tracking branch 'gogits/develop' into feature-create-and-edit-repo-file Conflicts: modules/bindata/bindata.go modules/setting/setting.go commit 889e9faf1bd8559a4979c8f46005d488c1a234d4 Author: Richard Mahn <richard_mahn@wycliffeassociates.org> Date: Fri Jul 22 14:09:18 2016 -0600 Fix in gogs.js commit 47603edf223f147b114be65f3bd27bc1e88827a5 Merge: bb57912 cf85e9e Author: Richard Mahn <richard_mahn@wycliffeassociates.org> Date: Fri Jul 22 14:07:36 2016 -0600 Merge remote-tracking branch 'gogits/develop' into feature-create-and-edit-repo-file Conflicts: modules/bindata/bindata.go public/js/gogs.js commit bb5791255867a71c11a77b639db050ad09c597a4 Author: Richard Mahn <richard_mahn@wycliffeassociates.org> Date: Fri Jul 22 14:02:18 2016 -0600 Update for using CodeMirror mode addon commit d10d128c51039be19e2af9c66c63db66a9f2ec6d Author: Richard Mahn <richard_mahn@wycliffeassociates.org> Date: Tue Jul 19 16:12:57 2016 -0600 Update for Edit commit 34a34982025144e3225e389f7849eb6273c1d576 Merge: fa1b752 1c7dcdd Author: Richard Mahn <richard_mahn@wycliffeassociates.org> Date: Tue Jul 19 11:52:02 2016 -0600 Merge remote-tracking branch 'gogits/develop' into feature-create-and-edit-repo-file Conflicts: modules/bindata/bindata.go commit fa1b752be29cd455c5184ddac2ffe80b3489763e Author: Richard Mahn <richard_mahn@wycliffeassociates.org> Date: Fri Jul 15 18:35:42 2016 -0600 Feature for editing, creating, uploading and deleting files
8 years ago
9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
Squashed commit of the following: commit 0afcb843d7ffd596991c4885cab768273a6eb42c Author: Richard Mahn <richard_mahn@wycliffeassociates.org> Date: Sun Jul 31 17:13:29 2016 -0600 Removed Upload stats as the upload table is just a temporary table commit 7ecd73ff5535612d79d471409173ee7f1fcfa157 Author: Richard Mahn <richard_mahn@wycliffeassociates.org> Date: Sun Jul 31 08:42:41 2016 -0600 Fix for CodeMirror mode commit c29b9ab531e2e7af0fb5db24dc17e51027dd1174 Author: Richard Mahn <richard_mahn@wycliffeassociates.org> Date: Sun Jul 31 08:03:33 2016 -0600 Made tabbing in editor use spaces commit 23af384c53206a8a40e11e45bf49d7a149c4adcd Author: Richard Mahn <richard_mahn@wycliffeassociates.org> Date: Sun Jul 31 07:56:46 2016 -0600 Fix for data-url commit cfb8a97591cb6fc0a92e49563b7b764c524db0e9 Merge: 7fc8a89 991ce42 Author: Richard Mahn <richard_mahn@wycliffeassociates.org> Date: Sun Jul 31 07:42:53 2016 -0600 Merge remote-tracking branch 'gogits/develop' into feature-create-and-edit-repo-file Conflicts: modules/bindata/bindata.go public/js/gogs.js commit 7fc8a89cb495478225b02d613e647f99a1489634 Merge: fd3d86c c03d040 Author: Richard Mahn <richard_mahn@wycliffeassociates.org> Date: Sun Jul 31 07:40:00 2016 -0600 Merge branch 'feature-create-and-edit-repo-file' of github.com:richmahn/gogs into feature-create-and-edit-repo-file commit fd3d86ca6bbc02cfda566a504ffd6b03db4f75ef Author: Richard Mahn <richard_mahn@wycliffeassociates.org> Date: Sun Jul 31 07:39:44 2016 -0600 Code cleanup commit c03d0401c1049eeeccc32ab1f9c3303c130be5ee Author: Richard Mahn <richard_mahn@wycliffeassociates.org> Date: Fri Jul 29 15:38:23 2016 -0600 Code cleanup commit 98e1206ccf9f9a4503c020e3a7830cf9f861dfae Author: Richard Mahn <richard_mahn@wycliffeassociates.org> Date: Thu Jul 28 18:36:01 2016 -0600 Code cleanup and fixes commit c2895dc742f25f8412879c9fa15e18f27f42f194 Author: Richard Mahn <richard_mahn@wycliffeassociates.org> Date: Thu Jul 28 18:24:04 2016 -0600 Fixes per Unknwon's requests commit 6aa7e46b21ad4c96e562daa2eac26a8fb408f8ef Merge: 889e9fa ad7ea88 Author: Richard Mahn <richard_mahn@wycliffeassociates.org> Date: Thu Jul 28 17:13:43 2016 -0600 Merge remote-tracking branch 'gogits/develop' into feature-create-and-edit-repo-file Conflicts: modules/bindata/bindata.go modules/setting/setting.go commit 889e9faf1bd8559a4979c8f46005d488c1a234d4 Author: Richard Mahn <richard_mahn@wycliffeassociates.org> Date: Fri Jul 22 14:09:18 2016 -0600 Fix in gogs.js commit 47603edf223f147b114be65f3bd27bc1e88827a5 Merge: bb57912 cf85e9e Author: Richard Mahn <richard_mahn@wycliffeassociates.org> Date: Fri Jul 22 14:07:36 2016 -0600 Merge remote-tracking branch 'gogits/develop' into feature-create-and-edit-repo-file Conflicts: modules/bindata/bindata.go public/js/gogs.js commit bb5791255867a71c11a77b639db050ad09c597a4 Author: Richard Mahn <richard_mahn@wycliffeassociates.org> Date: Fri Jul 22 14:02:18 2016 -0600 Update for using CodeMirror mode addon commit d10d128c51039be19e2af9c66c63db66a9f2ec6d Author: Richard Mahn <richard_mahn@wycliffeassociates.org> Date: Tue Jul 19 16:12:57 2016 -0600 Update for Edit commit 34a34982025144e3225e389f7849eb6273c1d576 Merge: fa1b752 1c7dcdd Author: Richard Mahn <richard_mahn@wycliffeassociates.org> Date: Tue Jul 19 11:52:02 2016 -0600 Merge remote-tracking branch 'gogits/develop' into feature-create-and-edit-repo-file Conflicts: modules/bindata/bindata.go commit fa1b752be29cd455c5184ddac2ffe80b3489763e Author: Richard Mahn <richard_mahn@wycliffeassociates.org> Date: Fri Jul 15 18:35:42 2016 -0600 Feature for editing, creating, uploading and deleting files
8 years ago
9 years ago
9 years ago
8 years ago
8 years ago
9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
  1. // Copyright 2014 The Gogs 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. package repo
  5. import (
  6. "errors"
  7. "fmt"
  8. "io"
  9. "io/ioutil"
  10. "net/http"
  11. "net/url"
  12. "strings"
  13. "time"
  14. "github.com/Unknwon/com"
  15. "github.com/Unknwon/paginater"
  16. "code.gitea.io/git"
  17. "code.gitea.io/gitea/models"
  18. "code.gitea.io/gitea/modules/auth"
  19. "code.gitea.io/gitea/modules/base"
  20. "code.gitea.io/gitea/modules/context"
  21. "code.gitea.io/gitea/modules/log"
  22. "code.gitea.io/gitea/modules/markdown"
  23. "code.gitea.io/gitea/modules/setting"
  24. )
  25. const (
  26. tplIssues base.TplName = "repo/issue/list"
  27. tplIssueNew base.TplName = "repo/issue/new"
  28. tplIssueView base.TplName = "repo/issue/view"
  29. tplLabels base.TplName = "repo/issue/labels"
  30. tplMilestone base.TplName = "repo/issue/milestones"
  31. tplMilestoneNew base.TplName = "repo/issue/milestone_new"
  32. tplMilestoneEdit base.TplName = "repo/issue/milestone_edit"
  33. issueTemplateKey = "IssueTemplate"
  34. )
  35. var (
  36. // ErrFileTypeForbidden not allowed file type error
  37. ErrFileTypeForbidden = errors.New("File type is not allowed")
  38. // ErrTooManyFiles upload too many files
  39. ErrTooManyFiles = errors.New("Maximum number of files to upload exceeded")
  40. // IssueTemplateCandidates issue templates
  41. IssueTemplateCandidates = []string{
  42. "ISSUE_TEMPLATE.md",
  43. ".gogs/ISSUE_TEMPLATE.md",
  44. ".github/ISSUE_TEMPLATE.md",
  45. }
  46. )
  47. // MustEnableIssues check if repository enable internal issues
  48. func MustEnableIssues(ctx *context.Context) {
  49. if !ctx.Repo.Repository.EnableIssues {
  50. ctx.Handle(404, "MustEnableIssues", nil)
  51. return
  52. }
  53. if ctx.Repo.Repository.EnableExternalTracker {
  54. ctx.Redirect(ctx.Repo.Repository.ExternalTrackerURL)
  55. return
  56. }
  57. }
  58. // MustAllowPulls check if repository enable pull requests
  59. func MustAllowPulls(ctx *context.Context) {
  60. if !ctx.Repo.Repository.AllowsPulls() {
  61. ctx.Handle(404, "MustAllowPulls", nil)
  62. return
  63. }
  64. // User can send pull request if owns a forked repository.
  65. if ctx.IsSigned && ctx.User.HasForkedRepo(ctx.Repo.Repository.ID) {
  66. ctx.Repo.PullRequest.Allowed = true
  67. ctx.Repo.PullRequest.HeadInfo = ctx.User.Name + ":" + ctx.Repo.BranchName
  68. }
  69. }
  70. // RetrieveLabels find all the labels of a repository
  71. func RetrieveLabels(ctx *context.Context) {
  72. labels, err := models.GetLabelsByRepoID(ctx.Repo.Repository.ID, ctx.Query("sort"))
  73. if err != nil {
  74. ctx.Handle(500, "RetrieveLabels.GetLabels", err)
  75. return
  76. }
  77. for _, l := range labels {
  78. l.CalOpenIssues()
  79. }
  80. ctx.Data["Labels"] = labels
  81. ctx.Data["NumLabels"] = len(labels)
  82. ctx.Data["SortType"] = ctx.Query("sort")
  83. }
  84. // Issues render issues page
  85. func Issues(ctx *context.Context) {
  86. isPullList := ctx.Params(":type") == "pulls"
  87. if isPullList {
  88. MustAllowPulls(ctx)
  89. if ctx.Written() {
  90. return
  91. }
  92. ctx.Data["Title"] = ctx.Tr("repo.pulls")
  93. ctx.Data["PageIsPullList"] = true
  94. } else {
  95. MustEnableIssues(ctx)
  96. if ctx.Written() {
  97. return
  98. }
  99. ctx.Data["Title"] = ctx.Tr("repo.issues")
  100. ctx.Data["PageIsIssueList"] = true
  101. }
  102. viewType := ctx.Query("type")
  103. sortType := ctx.Query("sort")
  104. types := []string{"assigned", "created_by", "mentioned"}
  105. if !com.IsSliceContainsStr(types, viewType) {
  106. viewType = "all"
  107. }
  108. // Must sign in to see issues about you.
  109. if viewType != "all" && !ctx.IsSigned {
  110. ctx.SetCookie("redirect_to", "/"+url.QueryEscape(setting.AppSubURL+ctx.Req.RequestURI), 0, setting.AppSubURL)
  111. ctx.Redirect(setting.AppSubURL + "/user/login")
  112. return
  113. }
  114. var (
  115. assigneeID = ctx.QueryInt64("assignee")
  116. posterID int64
  117. mentionedID int64
  118. forceEmpty bool
  119. )
  120. switch viewType {
  121. case "assigned":
  122. if assigneeID > 0 && ctx.User.ID != assigneeID {
  123. // two different assignees, must be empty
  124. forceEmpty = true
  125. } else {
  126. assigneeID = ctx.User.ID
  127. }
  128. case "created_by":
  129. posterID = ctx.User.ID
  130. case "mentioned":
  131. mentionedID = ctx.User.ID
  132. }
  133. repo := ctx.Repo.Repository
  134. selectLabels := ctx.Query("labels")
  135. milestoneID := ctx.QueryInt64("milestone")
  136. isShowClosed := ctx.Query("state") == "closed"
  137. var issueStats *models.IssueStats
  138. if forceEmpty {
  139. issueStats = &models.IssueStats{}
  140. } else {
  141. issueStats = models.GetIssueStats(&models.IssueStatsOptions{
  142. RepoID: repo.ID,
  143. Labels: selectLabels,
  144. MilestoneID: milestoneID,
  145. AssigneeID: assigneeID,
  146. MentionedID: mentionedID,
  147. IsPull: isPullList,
  148. })
  149. }
  150. page := ctx.QueryInt("page")
  151. if page <= 1 {
  152. page = 1
  153. }
  154. var total int
  155. if !isShowClosed {
  156. total = int(issueStats.OpenCount)
  157. } else {
  158. total = int(issueStats.ClosedCount)
  159. }
  160. pager := paginater.New(total, setting.UI.IssuePagingNum, page, 5)
  161. ctx.Data["Page"] = pager
  162. var issues []*models.Issue
  163. if forceEmpty {
  164. issues = []*models.Issue{}
  165. } else {
  166. var err error
  167. issues, err = models.Issues(&models.IssuesOptions{
  168. AssigneeID: assigneeID,
  169. RepoID: repo.ID,
  170. PosterID: posterID,
  171. MentionedID: mentionedID,
  172. MilestoneID: milestoneID,
  173. Page: pager.Current(),
  174. IsClosed: isShowClosed,
  175. IsPull: isPullList,
  176. Labels: selectLabels,
  177. SortType: sortType,
  178. })
  179. if err != nil {
  180. ctx.Handle(500, "Issues", err)
  181. return
  182. }
  183. }
  184. // Get issue-user relations.
  185. pairs, err := models.GetIssueUsers(repo.ID, posterID, isShowClosed)
  186. if err != nil {
  187. ctx.Handle(500, "GetIssueUsers", err)
  188. return
  189. }
  190. // Get posters.
  191. for i := range issues {
  192. if !ctx.IsSigned {
  193. issues[i].IsRead = true
  194. continue
  195. }
  196. // Check read status.
  197. idx := models.PairsContains(pairs, issues[i].ID, ctx.User.ID)
  198. if idx > -1 {
  199. issues[i].IsRead = pairs[idx].IsRead
  200. } else {
  201. issues[i].IsRead = true
  202. }
  203. }
  204. ctx.Data["Issues"] = issues
  205. // Get milestones.
  206. ctx.Data["Milestones"], err = models.GetMilestonesByRepoID(repo.ID)
  207. if err != nil {
  208. ctx.Handle(500, "GetAllRepoMilestones", err)
  209. return
  210. }
  211. // Get assignees.
  212. ctx.Data["Assignees"], err = repo.GetAssignees()
  213. if err != nil {
  214. ctx.Handle(500, "GetAssignees", err)
  215. return
  216. }
  217. if ctx.QueryInt64("assignee") == 0 {
  218. assigneeID = 0 // Reset ID to prevent unexpected selection of assignee.
  219. }
  220. ctx.Data["IssueStats"] = issueStats
  221. ctx.Data["SelectLabels"] = com.StrTo(selectLabels).MustInt64()
  222. ctx.Data["ViewType"] = viewType
  223. ctx.Data["SortType"] = sortType
  224. ctx.Data["MilestoneID"] = milestoneID
  225. ctx.Data["AssigneeID"] = assigneeID
  226. ctx.Data["IsShowClosed"] = isShowClosed
  227. if isShowClosed {
  228. ctx.Data["State"] = "closed"
  229. } else {
  230. ctx.Data["State"] = "open"
  231. }
  232. ctx.HTML(200, tplIssues)
  233. }
  234. func renderAttachmentSettings(ctx *context.Context) {
  235. ctx.Data["RequireDropzone"] = true
  236. ctx.Data["IsAttachmentEnabled"] = setting.AttachmentEnabled
  237. ctx.Data["AttachmentAllowedTypes"] = setting.AttachmentAllowedTypes
  238. ctx.Data["AttachmentMaxSize"] = setting.AttachmentMaxSize
  239. ctx.Data["AttachmentMaxFiles"] = setting.AttachmentMaxFiles
  240. }
  241. // RetrieveRepoMilestonesAndAssignees find all the milestones and assignees of a repository
  242. func RetrieveRepoMilestonesAndAssignees(ctx *context.Context, repo *models.Repository) {
  243. var err error
  244. ctx.Data["OpenMilestones"], err = models.GetMilestones(repo.ID, -1, false, "")
  245. if err != nil {
  246. ctx.Handle(500, "GetMilestones", err)
  247. return
  248. }
  249. ctx.Data["ClosedMilestones"], err = models.GetMilestones(repo.ID, -1, true, "")
  250. if err != nil {
  251. ctx.Handle(500, "GetMilestones", err)
  252. return
  253. }
  254. ctx.Data["Assignees"], err = repo.GetAssignees()
  255. if err != nil {
  256. ctx.Handle(500, "GetAssignees", err)
  257. return
  258. }
  259. }
  260. // RetrieveRepoMetas find all the meta information of a repository
  261. func RetrieveRepoMetas(ctx *context.Context, repo *models.Repository) []*models.Label {
  262. if !ctx.Repo.IsWriter() {
  263. return nil
  264. }
  265. labels, err := models.GetLabelsByRepoID(repo.ID, "")
  266. if err != nil {
  267. ctx.Handle(500, "GetLabelsByRepoID", err)
  268. return nil
  269. }
  270. ctx.Data["Labels"] = labels
  271. RetrieveRepoMilestonesAndAssignees(ctx, repo)
  272. if ctx.Written() {
  273. return nil
  274. }
  275. return labels
  276. }
  277. func getFileContentFromDefaultBranch(ctx *context.Context, filename string) (string, bool) {
  278. var r io.Reader
  279. var bytes []byte
  280. if ctx.Repo.Commit == nil {
  281. var err error
  282. ctx.Repo.Commit, err = ctx.Repo.GitRepo.GetBranchCommit(ctx.Repo.Repository.DefaultBranch)
  283. if err != nil {
  284. return "", false
  285. }
  286. }
  287. entry, err := ctx.Repo.Commit.GetTreeEntryByPath(filename)
  288. if err != nil {
  289. return "", false
  290. }
  291. r, err = entry.Blob().Data()
  292. if err != nil {
  293. return "", false
  294. }
  295. bytes, err = ioutil.ReadAll(r)
  296. if err != nil {
  297. return "", false
  298. }
  299. return string(bytes), true
  300. }
  301. func setTemplateIfExists(ctx *context.Context, ctxDataKey string, possibleFiles []string) {
  302. for _, filename := range possibleFiles {
  303. content, found := getFileContentFromDefaultBranch(ctx, filename)
  304. if found {
  305. ctx.Data[ctxDataKey] = content
  306. return
  307. }
  308. }
  309. }
  310. // NewIssue render createing issue page
  311. func NewIssue(ctx *context.Context) {
  312. ctx.Data["Title"] = ctx.Tr("repo.issues.new")
  313. ctx.Data["PageIsIssueList"] = true
  314. ctx.Data["RequireHighlightJS"] = true
  315. ctx.Data["RequireSimpleMDE"] = true
  316. setTemplateIfExists(ctx, issueTemplateKey, IssueTemplateCandidates)
  317. renderAttachmentSettings(ctx)
  318. RetrieveRepoMetas(ctx, ctx.Repo.Repository)
  319. if ctx.Written() {
  320. return
  321. }
  322. ctx.HTML(200, tplIssueNew)
  323. }
  324. // ValidateRepoMetas check and returns repository's meta informations
  325. func ValidateRepoMetas(ctx *context.Context, form auth.CreateIssueForm) ([]int64, int64, int64) {
  326. var (
  327. repo = ctx.Repo.Repository
  328. err error
  329. )
  330. labels := RetrieveRepoMetas(ctx, ctx.Repo.Repository)
  331. if ctx.Written() {
  332. return nil, 0, 0
  333. }
  334. if !ctx.Repo.IsWriter() {
  335. return nil, 0, 0
  336. }
  337. // Check labels.
  338. labelIDs, err := base.StringsToInt64s(strings.Split(form.LabelIDs, ","))
  339. if err != nil {
  340. return nil, 0, 0
  341. }
  342. labelIDMark := base.Int64sToMap(labelIDs)
  343. hasSelected := false
  344. for i := range labels {
  345. if labelIDMark[labels[i].ID] {
  346. labels[i].IsChecked = true
  347. hasSelected = true
  348. }
  349. }
  350. ctx.Data["HasSelectedLabel"] = hasSelected
  351. ctx.Data["label_ids"] = form.LabelIDs
  352. ctx.Data["Labels"] = labels
  353. // Check milestone.
  354. milestoneID := form.MilestoneID
  355. if milestoneID > 0 {
  356. ctx.Data["Milestone"], err = repo.GetMilestoneByID(milestoneID)
  357. if err != nil {
  358. ctx.Handle(500, "GetMilestoneByID", err)
  359. return nil, 0, 0
  360. }
  361. ctx.Data["milestone_id"] = milestoneID
  362. }
  363. // Check assignee.
  364. assigneeID := form.AssigneeID
  365. if assigneeID > 0 {
  366. ctx.Data["Assignee"], err = repo.GetAssigneeByID(assigneeID)
  367. if err != nil {
  368. ctx.Handle(500, "GetAssigneeByID", err)
  369. return nil, 0, 0
  370. }
  371. ctx.Data["assignee_id"] = assigneeID
  372. }
  373. return labelIDs, milestoneID, assigneeID
  374. }
  375. // NewIssuePost response for creating new issue
  376. func NewIssuePost(ctx *context.Context, form auth.CreateIssueForm) {
  377. ctx.Data["Title"] = ctx.Tr("repo.issues.new")
  378. ctx.Data["PageIsIssueList"] = true
  379. ctx.Data["RequireHighlightJS"] = true
  380. ctx.Data["RequireSimpleMDE"] = true
  381. renderAttachmentSettings(ctx)
  382. var (
  383. repo = ctx.Repo.Repository
  384. attachments []string
  385. )
  386. labelIDs, milestoneID, assigneeID := ValidateRepoMetas(ctx, form)
  387. if ctx.Written() {
  388. return
  389. }
  390. if setting.AttachmentEnabled {
  391. attachments = form.Files
  392. }
  393. if ctx.HasError() {
  394. ctx.HTML(200, tplIssueNew)
  395. return
  396. }
  397. issue := &models.Issue{
  398. RepoID: repo.ID,
  399. Title: form.Title,
  400. PosterID: ctx.User.ID,
  401. Poster: ctx.User,
  402. MilestoneID: milestoneID,
  403. AssigneeID: assigneeID,
  404. Content: form.Content,
  405. }
  406. if err := models.NewIssue(repo, issue, labelIDs, attachments); err != nil {
  407. ctx.Handle(500, "NewIssue", err)
  408. return
  409. }
  410. log.Trace("Issue created: %d/%d", repo.ID, issue.ID)
  411. ctx.Redirect(ctx.Repo.RepoLink + "/issues/" + com.ToStr(issue.Index))
  412. }
  413. // UploadIssueAttachment response for uploading issue's attachment
  414. func UploadIssueAttachment(ctx *context.Context) {
  415. if !setting.AttachmentEnabled {
  416. ctx.Error(404, "attachment is not enabled")
  417. return
  418. }
  419. file, header, err := ctx.Req.FormFile("file")
  420. if err != nil {
  421. ctx.Error(500, fmt.Sprintf("FormFile: %v", err))
  422. return
  423. }
  424. defer file.Close()
  425. buf := make([]byte, 1024)
  426. n, _ := file.Read(buf)
  427. if n > 0 {
  428. buf = buf[:n]
  429. }
  430. fileType := http.DetectContentType(buf)
  431. allowedTypes := strings.Split(setting.AttachmentAllowedTypes, ",")
  432. allowed := false
  433. for _, t := range allowedTypes {
  434. t := strings.Trim(t, " ")
  435. if t == "*/*" || t == fileType {
  436. allowed = true
  437. break
  438. }
  439. }
  440. if !allowed {
  441. ctx.Error(400, ErrFileTypeForbidden.Error())
  442. return
  443. }
  444. attach, err := models.NewAttachment(header.Filename, buf, file)
  445. if err != nil {
  446. ctx.Error(500, fmt.Sprintf("NewAttachment: %v", err))
  447. return
  448. }
  449. log.Trace("New attachment uploaded: %s", attach.UUID)
  450. ctx.JSON(200, map[string]string{
  451. "uuid": attach.UUID,
  452. })
  453. }
  454. // ViewIssue render issue view page
  455. func ViewIssue(ctx *context.Context) {
  456. ctx.Data["RequireHighlightJS"] = true
  457. ctx.Data["RequireDropzone"] = true
  458. renderAttachmentSettings(ctx)
  459. issue, err := models.GetIssueByIndex(ctx.Repo.Repository.ID, ctx.ParamsInt64(":index"))
  460. if err != nil {
  461. if models.IsErrIssueNotExist(err) {
  462. ctx.Handle(404, "GetIssueByIndex", err)
  463. } else {
  464. ctx.Handle(500, "GetIssueByIndex", err)
  465. }
  466. return
  467. }
  468. ctx.Data["Title"] = fmt.Sprintf("#%d - %s", issue.Index, issue.Title)
  469. // Make sure type and URL matches.
  470. if ctx.Params(":type") == "issues" && issue.IsPull {
  471. ctx.Redirect(ctx.Repo.RepoLink + "/pulls/" + com.ToStr(issue.Index))
  472. return
  473. } else if ctx.Params(":type") == "pulls" && !issue.IsPull {
  474. ctx.Redirect(ctx.Repo.RepoLink + "/issues/" + com.ToStr(issue.Index))
  475. return
  476. }
  477. if issue.IsPull {
  478. MustAllowPulls(ctx)
  479. if ctx.Written() {
  480. return
  481. }
  482. ctx.Data["PageIsPullList"] = true
  483. ctx.Data["PageIsPullConversation"] = true
  484. } else {
  485. MustEnableIssues(ctx)
  486. if ctx.Written() {
  487. return
  488. }
  489. ctx.Data["PageIsIssueList"] = true
  490. }
  491. issue.RenderedContent = string(markdown.Render([]byte(issue.Content), ctx.Repo.RepoLink,
  492. ctx.Repo.Repository.ComposeMetas()))
  493. repo := ctx.Repo.Repository
  494. // Get more information if it's a pull request.
  495. if issue.IsPull {
  496. if issue.PullRequest.HasMerged {
  497. ctx.Data["DisableStatusChange"] = issue.PullRequest.HasMerged
  498. PrepareMergedViewPullInfo(ctx, issue)
  499. } else {
  500. PrepareViewPullInfo(ctx, issue)
  501. }
  502. if ctx.Written() {
  503. return
  504. }
  505. }
  506. // Metas.
  507. // Check labels.
  508. labelIDMark := make(map[int64]bool)
  509. for i := range issue.Labels {
  510. labelIDMark[issue.Labels[i].ID] = true
  511. }
  512. labels, err := models.GetLabelsByRepoID(repo.ID, "")
  513. if err != nil {
  514. ctx.Handle(500, "GetLabelsByRepoID", err)
  515. return
  516. }
  517. hasSelected := false
  518. for i := range labels {
  519. if labelIDMark[labels[i].ID] {
  520. labels[i].IsChecked = true
  521. hasSelected = true
  522. }
  523. }
  524. ctx.Data["HasSelectedLabel"] = hasSelected
  525. ctx.Data["Labels"] = labels
  526. // Check milestone and assignee.
  527. if ctx.Repo.IsWriter() {
  528. RetrieveRepoMilestonesAndAssignees(ctx, repo)
  529. if ctx.Written() {
  530. return
  531. }
  532. }
  533. if ctx.IsSigned {
  534. // Update issue-user.
  535. if err = issue.ReadBy(ctx.User.ID); err != nil {
  536. ctx.Handle(500, "ReadBy", err)
  537. return
  538. }
  539. }
  540. var (
  541. tag models.CommentTag
  542. ok bool
  543. marked = make(map[int64]models.CommentTag)
  544. comment *models.Comment
  545. participants = make([]*models.User, 1, 10)
  546. )
  547. // Render comments and and fetch participants.
  548. participants[0] = issue.Poster
  549. for _, comment = range issue.Comments {
  550. if comment.Type == models.CommentTypeComment {
  551. comment.RenderedContent = string(markdown.Render([]byte(comment.Content), ctx.Repo.RepoLink,
  552. ctx.Repo.Repository.ComposeMetas()))
  553. // Check tag.
  554. tag, ok = marked[comment.PosterID]
  555. if ok {
  556. comment.ShowTag = tag
  557. continue
  558. }
  559. if repo.IsOwnedBy(comment.PosterID) ||
  560. (repo.Owner.IsOrganization() && repo.Owner.IsOwnedBy(comment.PosterID)) {
  561. comment.ShowTag = models.CommentTagOwner
  562. } else if comment.Poster.IsWriterOfRepo(repo) {
  563. comment.ShowTag = models.CommentTagWriter
  564. } else if comment.PosterID == issue.PosterID {
  565. comment.ShowTag = models.CommentTagPoster
  566. }
  567. marked[comment.PosterID] = comment.ShowTag
  568. isAdded := false
  569. for j := range participants {
  570. if comment.Poster == participants[j] {
  571. isAdded = true
  572. break
  573. }
  574. }
  575. if !isAdded && !issue.IsPoster(comment.Poster.ID) {
  576. participants = append(participants, comment.Poster)
  577. }
  578. }
  579. }
  580. if issue.IsPull {
  581. pull := issue.PullRequest
  582. canDelete := false
  583. if ctx.IsSigned && pull.HeadBranch != "master" {
  584. if err := pull.GetHeadRepo(); err != nil {
  585. log.Error(4, "GetHeadRepo: %v", err)
  586. } else if ctx.User.IsWriterOfRepo(pull.HeadRepo) {
  587. canDelete = true
  588. deleteBranchURL := pull.HeadRepo.Link() + "/branches/" + pull.HeadBranch + "/delete"
  589. ctx.Data["DeleteBranchLink"] = fmt.Sprintf("%s?commit=%s&redirect_to=%s", deleteBranchURL, pull.MergedCommitID, ctx.Data["Link"])
  590. }
  591. }
  592. ctx.Data["IsPullBranchDeletable"] = canDelete && git.IsBranchExist(pull.HeadRepo.RepoPath(), pull.HeadBranch)
  593. }
  594. ctx.Data["Participants"] = participants
  595. ctx.Data["NumParticipants"] = len(participants)
  596. ctx.Data["Issue"] = issue
  597. ctx.Data["IsIssueOwner"] = ctx.Repo.IsWriter() || (ctx.IsSigned && issue.IsPoster(ctx.User.ID))
  598. ctx.Data["SignInLink"] = setting.AppSubURL + "/user/login?redirect_to=" + ctx.Data["Link"].(string)
  599. ctx.HTML(200, tplIssueView)
  600. }
  601. func getActionIssue(ctx *context.Context) *models.Issue {
  602. issue, err := models.GetIssueByIndex(ctx.Repo.Repository.ID, ctx.ParamsInt64(":index"))
  603. if err != nil {
  604. if models.IsErrIssueNotExist(err) {
  605. ctx.Error(404, "GetIssueByIndex")
  606. } else {
  607. ctx.Handle(500, "GetIssueByIndex", err)
  608. }
  609. return nil
  610. }
  611. return issue
  612. }
  613. // UpdateIssueTitle change issue's title
  614. func UpdateIssueTitle(ctx *context.Context) {
  615. issue := getActionIssue(ctx)
  616. if ctx.Written() {
  617. return
  618. }
  619. if !ctx.IsSigned || (!issue.IsPoster(ctx.User.ID) && !ctx.Repo.IsWriter()) {
  620. ctx.Error(403)
  621. return
  622. }
  623. title := ctx.QueryTrim("title")
  624. if len(title) == 0 {
  625. ctx.Error(204)
  626. return
  627. }
  628. if err := issue.ChangeTitle(ctx.User, title); err != nil {
  629. ctx.Handle(500, "ChangeTitle", err)
  630. return
  631. }
  632. ctx.JSON(200, map[string]interface{}{
  633. "title": issue.Title,
  634. })
  635. }
  636. // UpdateIssueContent change issue's content
  637. func UpdateIssueContent(ctx *context.Context) {
  638. issue := getActionIssue(ctx)
  639. if ctx.Written() {
  640. return
  641. }
  642. if !ctx.IsSigned || (ctx.User.ID != issue.PosterID && !ctx.Repo.IsWriter()) {
  643. ctx.Error(403)
  644. return
  645. }
  646. content := ctx.Query("content")
  647. if err := issue.ChangeContent(ctx.User, content); err != nil {
  648. ctx.Handle(500, "ChangeContent", err)
  649. return
  650. }
  651. ctx.JSON(200, map[string]interface{}{
  652. "content": string(markdown.Render([]byte(issue.Content), ctx.Query("context"), ctx.Repo.Repository.ComposeMetas())),
  653. })
  654. }
  655. // UpdateIssueLabel change issue's labels
  656. func UpdateIssueLabel(ctx *context.Context) {
  657. issue := getActionIssue(ctx)
  658. if ctx.Written() {
  659. return
  660. }
  661. if ctx.Query("action") == "clear" {
  662. if err := issue.ClearLabels(ctx.User); err != nil {
  663. ctx.Handle(500, "ClearLabels", err)
  664. return
  665. }
  666. } else {
  667. isAttach := ctx.Query("action") == "attach"
  668. label, err := models.GetLabelByID(ctx.QueryInt64("id"))
  669. if err != nil {
  670. if models.IsErrLabelNotExist(err) {
  671. ctx.Error(404, "GetLabelByID")
  672. } else {
  673. ctx.Handle(500, "GetLabelByID", err)
  674. }
  675. return
  676. }
  677. if isAttach && !issue.HasLabel(label.ID) {
  678. if err = issue.AddLabel(ctx.User, label); err != nil {
  679. ctx.Handle(500, "AddLabel", err)
  680. return
  681. }
  682. } else if !isAttach && issue.HasLabel(label.ID) {
  683. if err = issue.RemoveLabel(ctx.User, label); err != nil {
  684. ctx.Handle(500, "RemoveLabel", err)
  685. return
  686. }
  687. }
  688. }
  689. ctx.JSON(200, map[string]interface{}{
  690. "ok": true,
  691. })
  692. }
  693. // UpdateIssueMilestone change issue's milestone
  694. func UpdateIssueMilestone(ctx *context.Context) {
  695. issue := getActionIssue(ctx)
  696. if ctx.Written() {
  697. return
  698. }
  699. oldMilestoneID := issue.MilestoneID
  700. milestoneID := ctx.QueryInt64("id")
  701. if oldMilestoneID == milestoneID {
  702. ctx.JSON(200, map[string]interface{}{
  703. "ok": true,
  704. })
  705. return
  706. }
  707. // Not check for invalid milestone id and give responsibility to owners.
  708. issue.MilestoneID = milestoneID
  709. if err := models.ChangeMilestoneAssign(issue, oldMilestoneID); err != nil {
  710. ctx.Handle(500, "ChangeMilestoneAssign", err)
  711. return
  712. }
  713. ctx.JSON(200, map[string]interface{}{
  714. "ok": true,
  715. })
  716. }
  717. // UpdateIssueAssignee change issue's assignee
  718. func UpdateIssueAssignee(ctx *context.Context) {
  719. issue := getActionIssue(ctx)
  720. if ctx.Written() {
  721. return
  722. }
  723. assigneeID := ctx.QueryInt64("id")
  724. if issue.AssigneeID == assigneeID {
  725. ctx.JSON(200, map[string]interface{}{
  726. "ok": true,
  727. })
  728. return
  729. }
  730. if err := issue.ChangeAssignee(ctx.User, assigneeID); err != nil {
  731. ctx.Handle(500, "ChangeAssignee", err)
  732. return
  733. }
  734. ctx.JSON(200, map[string]interface{}{
  735. "ok": true,
  736. })
  737. }
  738. // NewComment create a comment for issue
  739. func NewComment(ctx *context.Context, form auth.CreateCommentForm) {
  740. issue, err := models.GetIssueByIndex(ctx.Repo.Repository.ID, ctx.ParamsInt64(":index"))
  741. if err != nil {
  742. ctx.NotFoundOrServerError("GetIssueByIndex", models.IsErrIssueNotExist, err)
  743. return
  744. }
  745. var attachments []string
  746. if setting.AttachmentEnabled {
  747. attachments = form.Files
  748. }
  749. if ctx.HasError() {
  750. ctx.Flash.Error(ctx.Data["ErrorMsg"].(string))
  751. ctx.Redirect(fmt.Sprintf("%s/issues/%d", ctx.Repo.RepoLink, issue.Index))
  752. return
  753. }
  754. var comment *models.Comment
  755. defer func() {
  756. // Check if issue admin/poster changes the status of issue.
  757. if (ctx.Repo.IsWriter() || (ctx.IsSigned && issue.IsPoster(ctx.User.ID))) &&
  758. (form.Status == "reopen" || form.Status == "close") &&
  759. !(issue.IsPull && issue.PullRequest.HasMerged) {
  760. // Duplication and conflict check should apply to reopen pull request.
  761. var pr *models.PullRequest
  762. if form.Status == "reopen" && issue.IsPull {
  763. pull := issue.PullRequest
  764. pr, err = models.GetUnmergedPullRequest(pull.HeadRepoID, pull.BaseRepoID, pull.HeadBranch, pull.BaseBranch)
  765. if err != nil {
  766. if !models.IsErrPullRequestNotExist(err) {
  767. ctx.Handle(500, "GetUnmergedPullRequest", err)
  768. return
  769. }
  770. }
  771. // Regenerate patch and test conflict.
  772. if pr == nil {
  773. if err = issue.PullRequest.UpdatePatch(); err != nil {
  774. ctx.Handle(500, "UpdatePatch", err)
  775. return
  776. }
  777. issue.PullRequest.AddToTaskQueue()
  778. }
  779. }
  780. if pr != nil {
  781. ctx.Flash.Info(ctx.Tr("repo.pulls.open_unmerged_pull_exists", pr.Index))
  782. } else {
  783. if err = issue.ChangeStatus(ctx.User, ctx.Repo.Repository, form.Status == "close"); err != nil {
  784. log.Error(4, "ChangeStatus: %v", err)
  785. } else {
  786. log.Trace("Issue [%d] status changed to closed: %v", issue.ID, issue.IsClosed)
  787. }
  788. }
  789. }
  790. // Redirect to comment hashtag if there is any actual content.
  791. typeName := "issues"
  792. if issue.IsPull {
  793. typeName = "pulls"
  794. }
  795. if comment != nil {
  796. ctx.Redirect(fmt.Sprintf("%s/%s/%d#%s", ctx.Repo.RepoLink, typeName, issue.Index, comment.HashTag()))
  797. } else {
  798. ctx.Redirect(fmt.Sprintf("%s/%s/%d", ctx.Repo.RepoLink, typeName, issue.Index))
  799. }
  800. }()
  801. // Fix #321: Allow empty comments, as long as we have attachments.
  802. if len(form.Content) == 0 && len(attachments) == 0 {
  803. return
  804. }
  805. comment, err = models.CreateIssueComment(ctx.User, ctx.Repo.Repository, issue, form.Content, attachments)
  806. if err != nil {
  807. ctx.Handle(500, "CreateIssueComment", err)
  808. return
  809. }
  810. log.Trace("Comment created: %d/%d/%d", ctx.Repo.Repository.ID, issue.ID, comment.ID)
  811. }
  812. // UpdateCommentContent change comment of issue's content
  813. func UpdateCommentContent(ctx *context.Context) {
  814. comment, err := models.GetCommentByID(ctx.ParamsInt64(":id"))
  815. if err != nil {
  816. ctx.NotFoundOrServerError("GetCommentByID", models.IsErrCommentNotExist, err)
  817. return
  818. }
  819. if !ctx.IsSigned || (ctx.User.ID != comment.PosterID && !ctx.Repo.IsAdmin()) {
  820. ctx.Error(403)
  821. return
  822. } else if comment.Type != models.CommentTypeComment {
  823. ctx.Error(204)
  824. return
  825. }
  826. comment.Content = ctx.Query("content")
  827. if len(comment.Content) == 0 {
  828. ctx.JSON(200, map[string]interface{}{
  829. "content": "",
  830. })
  831. return
  832. }
  833. if err = models.UpdateComment(comment); err != nil {
  834. ctx.Handle(500, "UpdateComment", err)
  835. return
  836. }
  837. ctx.JSON(200, map[string]interface{}{
  838. "content": string(markdown.Render([]byte(comment.Content), ctx.Query("context"), ctx.Repo.Repository.ComposeMetas())),
  839. })
  840. }
  841. // DeleteComment delete comment of issue
  842. func DeleteComment(ctx *context.Context) {
  843. comment, err := models.GetCommentByID(ctx.ParamsInt64(":id"))
  844. if err != nil {
  845. ctx.NotFoundOrServerError("GetCommentByID", models.IsErrCommentNotExist, err)
  846. return
  847. }
  848. if !ctx.IsSigned || (ctx.User.ID != comment.PosterID && !ctx.Repo.IsAdmin()) {
  849. ctx.Error(403)
  850. return
  851. } else if comment.Type != models.CommentTypeComment {
  852. ctx.Error(204)
  853. return
  854. }
  855. if err = models.DeleteCommentByID(comment.ID); err != nil {
  856. ctx.Handle(500, "DeleteCommentByID", err)
  857. return
  858. }
  859. ctx.Status(200)
  860. }
  861. // Labels render issue's labels page
  862. func Labels(ctx *context.Context) {
  863. ctx.Data["Title"] = ctx.Tr("repo.labels")
  864. ctx.Data["PageIsIssueList"] = true
  865. ctx.Data["PageIsLabels"] = true
  866. ctx.Data["RequireMinicolors"] = true
  867. ctx.Data["LabelTemplates"] = models.LabelTemplates
  868. ctx.HTML(200, tplLabels)
  869. }
  870. // InitializeLabels init labels for a repository
  871. func InitializeLabels(ctx *context.Context, form auth.InitializeLabelsForm) {
  872. if ctx.HasError() {
  873. ctx.Redirect(ctx.Repo.RepoLink + "/labels")
  874. return
  875. }
  876. list, err := models.GetLabelTemplateFile(form.TemplateName)
  877. if err != nil {
  878. ctx.Flash.Error(ctx.Tr("repo.issues.label_templates.fail_to_load_file", form.TemplateName, err))
  879. ctx.Redirect(ctx.Repo.RepoLink + "/labels")
  880. return
  881. }
  882. labels := make([]*models.Label, len(list))
  883. for i := 0; i < len(list); i++ {
  884. labels[i] = &models.Label{
  885. RepoID: ctx.Repo.Repository.ID,
  886. Name: list[i][0],
  887. Color: list[i][1],
  888. }
  889. }
  890. if err := models.NewLabels(labels...); err != nil {
  891. ctx.Handle(500, "NewLabels", err)
  892. return
  893. }
  894. ctx.Redirect(ctx.Repo.RepoLink + "/labels")
  895. }
  896. // NewLabel create new label for repository
  897. func NewLabel(ctx *context.Context, form auth.CreateLabelForm) {
  898. ctx.Data["Title"] = ctx.Tr("repo.labels")
  899. ctx.Data["PageIsLabels"] = true
  900. if ctx.HasError() {
  901. ctx.Flash.Error(ctx.Data["ErrorMsg"].(string))
  902. ctx.Redirect(ctx.Repo.RepoLink + "/labels")
  903. return
  904. }
  905. l := &models.Label{
  906. RepoID: ctx.Repo.Repository.ID,
  907. Name: form.Title,
  908. Color: form.Color,
  909. }
  910. if err := models.NewLabels(l); err != nil {
  911. ctx.Handle(500, "NewLabel", err)
  912. return
  913. }
  914. ctx.Redirect(ctx.Repo.RepoLink + "/labels")
  915. }
  916. // UpdateLabel update a label's name and color
  917. func UpdateLabel(ctx *context.Context, form auth.CreateLabelForm) {
  918. l, err := models.GetLabelByID(form.ID)
  919. if err != nil {
  920. switch {
  921. case models.IsErrLabelNotExist(err):
  922. ctx.Error(404)
  923. default:
  924. ctx.Handle(500, "UpdateLabel", err)
  925. }
  926. return
  927. }
  928. l.Name = form.Title
  929. l.Color = form.Color
  930. if err := models.UpdateLabel(l); err != nil {
  931. ctx.Handle(500, "UpdateLabel", err)
  932. return
  933. }
  934. ctx.Redirect(ctx.Repo.RepoLink + "/labels")
  935. }
  936. // DeleteLabel delete a label
  937. func DeleteLabel(ctx *context.Context) {
  938. if err := models.DeleteLabel(ctx.Repo.Repository.ID, ctx.QueryInt64("id")); err != nil {
  939. ctx.Flash.Error("DeleteLabel: " + err.Error())
  940. } else {
  941. ctx.Flash.Success(ctx.Tr("repo.issues.label_deletion_success"))
  942. }
  943. ctx.JSON(200, map[string]interface{}{
  944. "redirect": ctx.Repo.RepoLink + "/labels",
  945. })
  946. return
  947. }
  948. // Milestones render milestones page
  949. func Milestones(ctx *context.Context) {
  950. ctx.Data["Title"] = ctx.Tr("repo.milestones")
  951. ctx.Data["PageIsIssueList"] = true
  952. ctx.Data["PageIsMilestones"] = true
  953. isShowClosed := ctx.Query("state") == "closed"
  954. openCount, closedCount := models.MilestoneStats(ctx.Repo.Repository.ID)
  955. ctx.Data["OpenCount"] = openCount
  956. ctx.Data["ClosedCount"] = closedCount
  957. sortType := ctx.Query("sort")
  958. page := ctx.QueryInt("page")
  959. if page <= 1 {
  960. page = 1
  961. }
  962. var total int
  963. if !isShowClosed {
  964. total = int(openCount)
  965. } else {
  966. total = int(closedCount)
  967. }
  968. ctx.Data["Page"] = paginater.New(total, setting.UI.IssuePagingNum, page, 5)
  969. miles, err := models.GetMilestones(ctx.Repo.Repository.ID, page, isShowClosed, sortType)
  970. if err != nil {
  971. ctx.Handle(500, "GetMilestones", err)
  972. return
  973. }
  974. for _, m := range miles {
  975. m.RenderedContent = string(markdown.Render([]byte(m.Content), ctx.Repo.RepoLink, ctx.Repo.Repository.ComposeMetas()))
  976. }
  977. ctx.Data["Milestones"] = miles
  978. if isShowClosed {
  979. ctx.Data["State"] = "closed"
  980. } else {
  981. ctx.Data["State"] = "open"
  982. }
  983. ctx.Data["SortType"] = sortType
  984. ctx.Data["IsShowClosed"] = isShowClosed
  985. ctx.HTML(200, tplMilestone)
  986. }
  987. // NewMilestone render creating milestone page
  988. func NewMilestone(ctx *context.Context) {
  989. ctx.Data["Title"] = ctx.Tr("repo.milestones.new")
  990. ctx.Data["PageIsIssueList"] = true
  991. ctx.Data["PageIsMilestones"] = true
  992. ctx.Data["RequireDatetimepicker"] = true
  993. ctx.Data["DateLang"] = setting.DateLang(ctx.Locale.Language())
  994. ctx.HTML(200, tplMilestoneNew)
  995. }
  996. // NewMilestonePost response for creating milestone
  997. func NewMilestonePost(ctx *context.Context, form auth.CreateMilestoneForm) {
  998. ctx.Data["Title"] = ctx.Tr("repo.milestones.new")
  999. ctx.Data["PageIsIssueList"] = true
  1000. ctx.Data["PageIsMilestones"] = true
  1001. ctx.Data["RequireDatetimepicker"] = true
  1002. ctx.Data["DateLang"] = setting.DateLang(ctx.Locale.Language())
  1003. if ctx.HasError() {
  1004. ctx.HTML(200, tplMilestoneNew)
  1005. return
  1006. }
  1007. if len(form.Deadline) == 0 {
  1008. form.Deadline = "9999-12-31"
  1009. }
  1010. deadline, err := time.ParseInLocation("2006-01-02", form.Deadline, time.Local)
  1011. if err != nil {
  1012. ctx.Data["Err_Deadline"] = true
  1013. ctx.RenderWithErr(ctx.Tr("repo.milestones.invalid_due_date_format"), tplMilestoneNew, &form)
  1014. return
  1015. }
  1016. if err = models.NewMilestone(&models.Milestone{
  1017. RepoID: ctx.Repo.Repository.ID,
  1018. Name: form.Title,
  1019. Content: form.Content,
  1020. Deadline: deadline,
  1021. }); err != nil {
  1022. ctx.Handle(500, "NewMilestone", err)
  1023. return
  1024. }
  1025. ctx.Flash.Success(ctx.Tr("repo.milestones.create_success", form.Title))
  1026. ctx.Redirect(ctx.Repo.RepoLink + "/milestones")
  1027. }
  1028. // EditMilestone render edting milestone page
  1029. func EditMilestone(ctx *context.Context) {
  1030. ctx.Data["Title"] = ctx.Tr("repo.milestones.edit")
  1031. ctx.Data["PageIsMilestones"] = true
  1032. ctx.Data["PageIsEditMilestone"] = true
  1033. ctx.Data["RequireDatetimepicker"] = true
  1034. ctx.Data["DateLang"] = setting.DateLang(ctx.Locale.Language())
  1035. m, err := models.GetMilestoneByRepoID(ctx.Repo.Repository.ID, ctx.ParamsInt64(":id"))
  1036. if err != nil {
  1037. if models.IsErrMilestoneNotExist(err) {
  1038. ctx.Handle(404, "", nil)
  1039. } else {
  1040. ctx.Handle(500, "GetMilestoneByRepoID", err)
  1041. }
  1042. return
  1043. }
  1044. ctx.Data["title"] = m.Name
  1045. ctx.Data["content"] = m.Content
  1046. if len(m.DeadlineString) > 0 {
  1047. ctx.Data["deadline"] = m.DeadlineString
  1048. }
  1049. ctx.HTML(200, tplMilestoneNew)
  1050. }
  1051. // EditMilestonePost response for edting milestone
  1052. func EditMilestonePost(ctx *context.Context, form auth.CreateMilestoneForm) {
  1053. ctx.Data["Title"] = ctx.Tr("repo.milestones.edit")
  1054. ctx.Data["PageIsMilestones"] = true
  1055. ctx.Data["PageIsEditMilestone"] = true
  1056. ctx.Data["RequireDatetimepicker"] = true
  1057. ctx.Data["DateLang"] = setting.DateLang(ctx.Locale.Language())
  1058. if ctx.HasError() {
  1059. ctx.HTML(200, tplMilestoneNew)
  1060. return
  1061. }
  1062. if len(form.Deadline) == 0 {
  1063. form.Deadline = "9999-12-31"
  1064. }
  1065. deadline, err := time.ParseInLocation("2006-01-02", form.Deadline, time.Local)
  1066. if err != nil {
  1067. ctx.Data["Err_Deadline"] = true
  1068. ctx.RenderWithErr(ctx.Tr("repo.milestones.invalid_due_date_format"), tplMilestoneNew, &form)
  1069. return
  1070. }
  1071. m, err := models.GetMilestoneByRepoID(ctx.Repo.Repository.ID, ctx.ParamsInt64(":id"))
  1072. if err != nil {
  1073. if models.IsErrMilestoneNotExist(err) {
  1074. ctx.Handle(404, "", nil)
  1075. } else {
  1076. ctx.Handle(500, "GetMilestoneByRepoID", err)
  1077. }
  1078. return
  1079. }
  1080. m.Name = form.Title
  1081. m.Content = form.Content
  1082. m.Deadline = deadline
  1083. if err = models.UpdateMilestone(m); err != nil {
  1084. ctx.Handle(500, "UpdateMilestone", err)
  1085. return
  1086. }
  1087. ctx.Flash.Success(ctx.Tr("repo.milestones.edit_success", m.Name))
  1088. ctx.Redirect(ctx.Repo.RepoLink + "/milestones")
  1089. }
  1090. // ChangeMilestonStatus response for change a milestone's status
  1091. func ChangeMilestonStatus(ctx *context.Context) {
  1092. m, err := models.GetMilestoneByRepoID(ctx.Repo.Repository.ID, ctx.ParamsInt64(":id"))
  1093. if err != nil {
  1094. if models.IsErrMilestoneNotExist(err) {
  1095. ctx.Handle(404, "", err)
  1096. } else {
  1097. ctx.Handle(500, "GetMilestoneByRepoID", err)
  1098. }
  1099. return
  1100. }
  1101. switch ctx.Params(":action") {
  1102. case "open":
  1103. if m.IsClosed {
  1104. if err = models.ChangeMilestoneStatus(m, false); err != nil {
  1105. ctx.Handle(500, "ChangeMilestoneStatus", err)
  1106. return
  1107. }
  1108. }
  1109. ctx.Redirect(ctx.Repo.RepoLink + "/milestones?state=open")
  1110. case "close":
  1111. if !m.IsClosed {
  1112. m.ClosedDate = time.Now()
  1113. if err = models.ChangeMilestoneStatus(m, true); err != nil {
  1114. ctx.Handle(500, "ChangeMilestoneStatus", err)
  1115. return
  1116. }
  1117. }
  1118. ctx.Redirect(ctx.Repo.RepoLink + "/milestones?state=closed")
  1119. default:
  1120. ctx.Redirect(ctx.Repo.RepoLink + "/milestones")
  1121. }
  1122. }
  1123. // DeleteMilestone delete a milestone
  1124. func DeleteMilestone(ctx *context.Context) {
  1125. if err := models.DeleteMilestoneByRepoID(ctx.Repo.Repository.ID, ctx.QueryInt64("id")); err != nil {
  1126. ctx.Flash.Error("DeleteMilestoneByRepoID: " + err.Error())
  1127. } else {
  1128. ctx.Flash.Success(ctx.Tr("repo.milestones.deletion_success"))
  1129. }
  1130. ctx.JSON(200, map[string]interface{}{
  1131. "redirect": ctx.Repo.RepoLink + "/milestones",
  1132. })
  1133. }