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

issue.go 32 kB

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