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 30 kB

11 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
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
10 years ago
10 years ago
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191
  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. "github.com/gogits/gogs/models"
  17. "github.com/gogits/gogs/modules/auth"
  18. "github.com/gogits/gogs/modules/base"
  19. "github.com/gogits/gogs/modules/log"
  20. "github.com/gogits/gogs/modules/mailer"
  21. "github.com/gogits/gogs/modules/middleware"
  22. "github.com/gogits/gogs/modules/setting"
  23. )
  24. const (
  25. ISSUES base.TplName = "repo/issue/list"
  26. ISSUE_CREATE base.TplName = "repo/issue/create"
  27. ISSUE_VIEW base.TplName = "repo/issue/view"
  28. LABELS base.TplName = "repo/issue/labels"
  29. MILESTONE base.TplName = "repo/issue/milestones"
  30. MILESTONE_NEW base.TplName = "repo/issue/milestone_new"
  31. MILESTONE_EDIT base.TplName = "repo/issue/milestone_edit"
  32. )
  33. var (
  34. ErrFileTypeForbidden = errors.New("File type is not allowed")
  35. ErrTooManyFiles = errors.New("Maximum number of files to upload exceeded")
  36. )
  37. func RetrieveLabels(ctx *middleware.Context) {
  38. labels, err := models.GetLabels(ctx.Repo.Repository.Id)
  39. if err != nil {
  40. ctx.Handle(500, "RetrieveLabels.GetLabels: %v", err)
  41. return
  42. }
  43. for _, l := range labels {
  44. l.CalOpenIssues()
  45. }
  46. ctx.Data["Labels"] = labels
  47. ctx.Data["NumLabels"] = len(labels)
  48. }
  49. func Issues(ctx *middleware.Context) {
  50. ctx.Data["Title"] = ctx.Tr("repo.issues")
  51. ctx.Data["PageIsIssueList"] = true
  52. viewType := ctx.Query("type")
  53. types := []string{"assigned", "created_by", "mentioned"}
  54. if !com.IsSliceContainsStr(types, viewType) {
  55. viewType = "all"
  56. }
  57. // Must sign in to see issues about you.
  58. if viewType != "all" && !ctx.IsSigned {
  59. ctx.SetCookie("redirect_to", "/"+url.QueryEscape(setting.AppSubUrl+ctx.Req.RequestURI), 0, setting.AppSubUrl)
  60. ctx.Redirect(setting.AppSubUrl + "/user/login")
  61. return
  62. }
  63. var assigneeID, posterID int64
  64. filterMode := models.FM_ALL
  65. switch viewType {
  66. case "assigned":
  67. assigneeID = ctx.User.Id
  68. filterMode = models.FM_ASSIGN
  69. case "created_by":
  70. posterID = ctx.User.Id
  71. filterMode = models.FM_CREATE
  72. case "mentioned":
  73. filterMode = models.FM_MENTION
  74. }
  75. var uid int64 = -1
  76. if ctx.IsSigned {
  77. uid = ctx.User.Id
  78. }
  79. repo := ctx.Repo.Repository
  80. selectLabels := ctx.Query("labels")
  81. milestoneID := ctx.QueryInt64("milestone")
  82. isShowClosed := ctx.Query("state") == "closed"
  83. issueStats := models.GetIssueStats(repo.Id, uid, com.StrTo(selectLabels).MustInt64(), milestoneID, isShowClosed, filterMode)
  84. page := ctx.QueryInt("page")
  85. if page <= 1 {
  86. page = 1
  87. }
  88. var total int
  89. if !isShowClosed {
  90. total = int(issueStats.OpenCount)
  91. } else {
  92. total = int(issueStats.ClosedCount)
  93. }
  94. ctx.Data["Page"] = paginater.New(total, setting.IssuePagingNum, page, 5)
  95. // Get issues.
  96. issues, err := models.Issues(uid, assigneeID, repo.Id, posterID, milestoneID,
  97. page, isShowClosed, filterMode == models.FM_MENTION, selectLabels, ctx.Query("sortType"))
  98. if err != nil {
  99. ctx.Handle(500, "GetIssues: %v", err)
  100. return
  101. }
  102. // Get issue-user pairs.
  103. pairs, err := models.GetIssueUserPairs(repo.Id, posterID, isShowClosed)
  104. if err != nil {
  105. ctx.Handle(500, "GetIssueUserPairs: %v", err)
  106. return
  107. }
  108. // Get posters.
  109. for i := range issues {
  110. if err = issues[i].GetPoster(); err != nil {
  111. ctx.Handle(500, "GetPoster", fmt.Errorf("[#%d]%v", issues[i].ID, err))
  112. return
  113. }
  114. if err = issues[i].GetLabels(); err != nil {
  115. ctx.Handle(500, "GetLabels", fmt.Errorf("[#%d]%v", issues[i].ID, err))
  116. return
  117. }
  118. if !ctx.IsSigned {
  119. issues[i].IsRead = true
  120. continue
  121. }
  122. // Check read status.
  123. idx := models.PairsContains(pairs, issues[i].ID, ctx.User.Id)
  124. if idx > -1 {
  125. issues[i].IsRead = pairs[idx].IsRead
  126. } else {
  127. issues[i].IsRead = true
  128. }
  129. }
  130. ctx.Data["Issues"] = issues
  131. // Get milestones.
  132. miles, err := models.GetAllRepoMilestones(repo.Id)
  133. if err != nil {
  134. ctx.Handle(500, "GetAllRepoMilestones: %v", err)
  135. return
  136. }
  137. ctx.Data["Milestones"] = miles
  138. ctx.Data["IssueStats"] = issueStats
  139. ctx.Data["SelectLabels"] = com.StrTo(selectLabels).MustInt64()
  140. ctx.Data["ViewType"] = viewType
  141. ctx.Data["MilestoneID"] = milestoneID
  142. ctx.Data["IsShowClosed"] = isShowClosed
  143. if isShowClosed {
  144. ctx.Data["State"] = "closed"
  145. } else {
  146. ctx.Data["State"] = "open"
  147. }
  148. ctx.HTML(200, ISSUES)
  149. }
  150. func CreateIssue(ctx *middleware.Context) {
  151. ctx.Data["Title"] = "Create issue"
  152. ctx.Data["IsRepoToolbarIssues"] = true
  153. ctx.Data["IsRepoToolbarIssuesList"] = false
  154. ctx.Data["AttachmentsEnabled"] = setting.AttachmentEnabled
  155. var (
  156. repo = ctx.Repo.Repository
  157. err error
  158. )
  159. // Get all milestones.
  160. ctx.Data["OpenMilestones"], err = models.GetMilestones(repo.Id, -1, false)
  161. if err != nil {
  162. ctx.Handle(500, "GetMilestones.1: %v", err)
  163. return
  164. }
  165. ctx.Data["ClosedMilestones"], err = models.GetMilestones(repo.Id, -1, true)
  166. if err != nil {
  167. ctx.Handle(500, "GetMilestones.2: %v", err)
  168. return
  169. }
  170. us, err := repo.GetCollaborators()
  171. if err != nil {
  172. ctx.Handle(500, "GetCollaborators", err)
  173. return
  174. }
  175. ctx.Data["AllowedTypes"] = setting.AttachmentAllowedTypes
  176. ctx.Data["Collaborators"] = us
  177. ctx.HTML(200, ISSUE_CREATE)
  178. }
  179. func CreateIssuePost(ctx *middleware.Context, form auth.CreateIssueForm) {
  180. send := func(status int, data interface{}, err error) {
  181. if err != nil {
  182. log.Error(4, "issue.CreateIssuePost(?): %s", err)
  183. ctx.JSON(status, map[string]interface{}{
  184. "ok": false,
  185. "status": status,
  186. "error": err.Error(),
  187. })
  188. } else {
  189. ctx.JSON(status, map[string]interface{}{
  190. "ok": true,
  191. "status": status,
  192. "data": data,
  193. })
  194. }
  195. }
  196. var err error
  197. // Get all milestones.
  198. _, err = models.GetMilestones(ctx.Repo.Repository.Id, -1, false)
  199. if err != nil {
  200. send(500, nil, err)
  201. return
  202. }
  203. _, err = models.GetMilestones(ctx.Repo.Repository.Id, -1, true)
  204. if err != nil {
  205. send(500, nil, err)
  206. return
  207. }
  208. _, err = ctx.Repo.Repository.GetCollaborators()
  209. if err != nil {
  210. send(500, nil, err)
  211. return
  212. }
  213. if ctx.HasError() {
  214. send(400, nil, errors.New(ctx.Flash.ErrorMsg))
  215. return
  216. }
  217. // Only collaborators can assign.
  218. if !ctx.Repo.IsOwner() {
  219. form.AssigneeId = 0
  220. }
  221. issue := &models.Issue{
  222. RepoID: ctx.Repo.Repository.Id,
  223. Index: int64(ctx.Repo.Repository.NumIssues) + 1,
  224. Name: form.IssueName,
  225. PosterID: ctx.User.Id,
  226. MilestoneID: form.MilestoneId,
  227. AssigneeID: form.AssigneeId,
  228. LabelIds: form.Labels,
  229. Content: form.Content,
  230. }
  231. if err := models.NewIssue(issue); err != nil {
  232. send(500, nil, err)
  233. return
  234. } else if err := models.NewIssueUserPairs(ctx.Repo.Repository, issue.ID, ctx.Repo.Owner.Id,
  235. ctx.User.Id, form.AssigneeId); err != nil {
  236. send(500, nil, err)
  237. return
  238. }
  239. if setting.AttachmentEnabled {
  240. uploadFiles(ctx, issue.ID, 0)
  241. }
  242. // Update mentions.
  243. ms := base.MentionPattern.FindAllString(issue.Content, -1)
  244. if len(ms) > 0 {
  245. for i := range ms {
  246. ms[i] = ms[i][1:]
  247. }
  248. if err := models.UpdateMentions(ms, issue.ID); err != nil {
  249. send(500, nil, err)
  250. return
  251. }
  252. }
  253. act := &models.Action{
  254. ActUserID: ctx.User.Id,
  255. ActUserName: ctx.User.Name,
  256. ActEmail: ctx.User.Email,
  257. OpType: models.CREATE_ISSUE,
  258. Content: fmt.Sprintf("%d|%s", issue.Index, issue.Name),
  259. RepoID: ctx.Repo.Repository.Id,
  260. RepoUserName: ctx.Repo.Owner.Name,
  261. RepoName: ctx.Repo.Repository.Name,
  262. RefName: ctx.Repo.BranchName,
  263. IsPrivate: ctx.Repo.Repository.IsPrivate,
  264. }
  265. // Notify watchers.
  266. if err := models.NotifyWatchers(act); err != nil {
  267. send(500, nil, err)
  268. return
  269. }
  270. // Mail watchers and mentions.
  271. if setting.Service.EnableNotifyMail {
  272. tos, err := mailer.SendIssueNotifyMail(ctx.User, ctx.Repo.Owner, ctx.Repo.Repository, issue)
  273. if err != nil {
  274. send(500, nil, err)
  275. return
  276. }
  277. tos = append(tos, ctx.User.LowerName)
  278. newTos := make([]string, 0, len(ms))
  279. for _, m := range ms {
  280. if com.IsSliceContainsStr(tos, m) {
  281. continue
  282. }
  283. newTos = append(newTos, m)
  284. }
  285. if err = mailer.SendIssueMentionMail(ctx.Render, ctx.User, ctx.Repo.Owner,
  286. ctx.Repo.Repository, issue, models.GetUserEmailsByNames(newTos)); err != nil {
  287. send(500, nil, err)
  288. return
  289. }
  290. }
  291. log.Trace("%d Issue created: %d", ctx.Repo.Repository.Id, issue.ID)
  292. send(200, fmt.Sprintf("%s/%s/%s/issues/%d", setting.AppSubUrl, ctx.Params(":username"), ctx.Params(":reponame"), issue.Index), nil)
  293. }
  294. func checkLabels(labels, allLabels []*models.Label) {
  295. for _, l := range labels {
  296. for _, l2 := range allLabels {
  297. if l.ID == l2.ID {
  298. l2.IsChecked = true
  299. break
  300. }
  301. }
  302. }
  303. }
  304. func ViewIssue(ctx *middleware.Context) {
  305. ctx.Data["AttachmentsEnabled"] = setting.AttachmentEnabled
  306. idx := com.StrTo(ctx.Params(":index")).MustInt64()
  307. if idx == 0 {
  308. ctx.Handle(404, "issue.ViewIssue", nil)
  309. return
  310. }
  311. issue, err := models.GetIssueByIndex(ctx.Repo.Repository.Id, idx)
  312. if err != nil {
  313. if err == models.ErrIssueNotExist {
  314. ctx.Handle(404, "GetIssueByIndex", err)
  315. } else {
  316. ctx.Handle(500, "GetIssueByIndex", err)
  317. }
  318. return
  319. }
  320. // Get labels.
  321. if err = issue.GetLabels(); err != nil {
  322. ctx.Handle(500, "GetLabels", err)
  323. return
  324. }
  325. labels, err := models.GetLabels(ctx.Repo.Repository.Id)
  326. if err != nil {
  327. ctx.Handle(500, "GetLabels.2", err)
  328. return
  329. }
  330. checkLabels(issue.Labels, labels)
  331. ctx.Data["Labels"] = labels
  332. // Get assigned milestone.
  333. if issue.MilestoneID > 0 {
  334. ctx.Data["Milestone"], err = models.GetMilestoneByID(issue.MilestoneID)
  335. if err != nil {
  336. if models.IsErrMilestoneNotExist(err) {
  337. log.Warn("GetMilestoneById: %v", err)
  338. } else {
  339. ctx.Handle(500, "GetMilestoneById", err)
  340. return
  341. }
  342. }
  343. }
  344. // Get all milestones.
  345. ctx.Data["OpenMilestones"], err = models.GetMilestones(ctx.Repo.Repository.Id, -1, false)
  346. if err != nil {
  347. ctx.Handle(500, "GetMilestones.1: %v", err)
  348. return
  349. }
  350. ctx.Data["ClosedMilestones"], err = models.GetMilestones(ctx.Repo.Repository.Id, -1, true)
  351. if err != nil {
  352. ctx.Handle(500, "GetMilestones.2: %v", err)
  353. return
  354. }
  355. // Get all collaborators.
  356. ctx.Data["Collaborators"], err = ctx.Repo.Repository.GetCollaborators()
  357. if err != nil {
  358. ctx.Handle(500, "GetCollaborators", err)
  359. return
  360. }
  361. if ctx.IsSigned {
  362. // Update issue-user.
  363. if err = models.UpdateIssueUserPairByRead(ctx.User.Id, issue.ID); err != nil {
  364. ctx.Handle(500, "UpdateIssueUserPairByRead: %v", err)
  365. return
  366. }
  367. }
  368. // Get poster and Assignee.
  369. if err = issue.GetPoster(); err != nil {
  370. ctx.Handle(500, "GetPoster: %v", err)
  371. return
  372. } else if err = issue.GetAssignee(); err != nil {
  373. ctx.Handle(500, "GetAssignee: %v", err)
  374. return
  375. }
  376. issue.RenderedContent = string(base.RenderMarkdown([]byte(issue.Content), ctx.Repo.RepoLink))
  377. // Get comments.
  378. comments, err := models.GetIssueComments(issue.ID)
  379. if err != nil {
  380. ctx.Handle(500, "GetIssueComments: %v", err)
  381. return
  382. }
  383. // Get posters.
  384. for i := range comments {
  385. u, err := models.GetUserById(comments[i].PosterId)
  386. if err != nil {
  387. ctx.Handle(500, "GetUserById.2: %v", err)
  388. return
  389. }
  390. comments[i].Poster = u
  391. if comments[i].Type == models.COMMENT_TYPE_COMMENT {
  392. comments[i].Content = string(base.RenderMarkdown([]byte(comments[i].Content), ctx.Repo.RepoLink))
  393. }
  394. }
  395. ctx.Data["AllowedTypes"] = setting.AttachmentAllowedTypes
  396. ctx.Data["Title"] = issue.Name
  397. ctx.Data["Issue"] = issue
  398. ctx.Data["Comments"] = comments
  399. ctx.Data["IsIssueOwner"] = ctx.Repo.IsOwner() || (ctx.IsSigned && issue.PosterID == ctx.User.Id)
  400. ctx.Data["IsRepoToolbarIssues"] = true
  401. ctx.Data["IsRepoToolbarIssuesList"] = false
  402. ctx.HTML(200, ISSUE_VIEW)
  403. }
  404. func UpdateIssue(ctx *middleware.Context, form auth.CreateIssueForm) {
  405. idx := com.StrTo(ctx.Params(":index")).MustInt64()
  406. if idx <= 0 {
  407. ctx.Error(404)
  408. return
  409. }
  410. issue, err := models.GetIssueByIndex(ctx.Repo.Repository.Id, idx)
  411. if err != nil {
  412. if err == models.ErrIssueNotExist {
  413. ctx.Handle(404, "issue.UpdateIssue", err)
  414. } else {
  415. ctx.Handle(500, "issue.UpdateIssue(GetIssueByIndex)", err)
  416. }
  417. return
  418. }
  419. if ctx.User.Id != issue.PosterID && !ctx.Repo.IsOwner() {
  420. ctx.Error(403)
  421. return
  422. }
  423. issue.Name = form.IssueName
  424. //issue.MilestoneId = form.MilestoneId
  425. //issue.AssigneeId = form.AssigneeId
  426. //issue.LabelIds = form.Labels
  427. issue.Content = form.Content
  428. // try get content from text, ignore conflict with preview ajax
  429. if form.Content == "" {
  430. issue.Content = ctx.Query("text")
  431. }
  432. if err = models.UpdateIssue(issue); err != nil {
  433. ctx.Handle(500, "issue.UpdateIssue(UpdateIssue)", err)
  434. return
  435. }
  436. ctx.JSON(200, map[string]interface{}{
  437. "ok": true,
  438. "title": issue.Name,
  439. "content": string(base.RenderMarkdown([]byte(issue.Content), ctx.Repo.RepoLink)),
  440. })
  441. }
  442. func UpdateIssueLabel(ctx *middleware.Context) {
  443. if !ctx.Repo.IsOwner() {
  444. ctx.Error(403)
  445. return
  446. }
  447. idx := com.StrTo(ctx.Params(":index")).MustInt64()
  448. if idx <= 0 {
  449. ctx.Error(404)
  450. return
  451. }
  452. issue, err := models.GetIssueByIndex(ctx.Repo.Repository.Id, idx)
  453. if err != nil {
  454. if err == models.ErrIssueNotExist {
  455. ctx.Handle(404, "issue.UpdateIssueLabel(GetIssueByIndex)", err)
  456. } else {
  457. ctx.Handle(500, "issue.UpdateIssueLabel(GetIssueByIndex)", err)
  458. }
  459. return
  460. }
  461. isAttach := ctx.Query("action") == "attach"
  462. labelStrId := ctx.Query("id")
  463. labelId := com.StrTo(labelStrId).MustInt64()
  464. label, err := models.GetLabelById(labelId)
  465. if err != nil {
  466. if err == models.ErrLabelNotExist {
  467. ctx.Handle(404, "issue.UpdateIssueLabel(GetLabelById)", err)
  468. } else {
  469. ctx.Handle(500, "issue.UpdateIssueLabel(GetLabelById)", err)
  470. }
  471. return
  472. }
  473. isHad := strings.Contains(issue.LabelIds, "$"+labelStrId+"|")
  474. isNeedUpdate := false
  475. if isAttach {
  476. if !isHad {
  477. issue.LabelIds += "$" + labelStrId + "|"
  478. isNeedUpdate = true
  479. }
  480. } else {
  481. if isHad {
  482. issue.LabelIds = strings.Replace(issue.LabelIds, "$"+labelStrId+"|", "", -1)
  483. isNeedUpdate = true
  484. }
  485. }
  486. if isNeedUpdate {
  487. if err = models.UpdateIssue(issue); err != nil {
  488. ctx.Handle(500, "issue.UpdateIssueLabel(UpdateIssue)", err)
  489. return
  490. }
  491. if isAttach {
  492. label.NumIssues++
  493. if issue.IsClosed {
  494. label.NumClosedIssues++
  495. }
  496. } else {
  497. label.NumIssues--
  498. if issue.IsClosed {
  499. label.NumClosedIssues--
  500. }
  501. }
  502. if err = models.UpdateLabel(label); err != nil {
  503. ctx.Handle(500, "issue.UpdateIssueLabel(UpdateLabel)", err)
  504. return
  505. }
  506. }
  507. ctx.JSON(200, map[string]interface{}{
  508. "ok": true,
  509. })
  510. }
  511. func UpdateIssueMilestone(ctx *middleware.Context) {
  512. if !ctx.Repo.IsOwner() {
  513. ctx.Error(403)
  514. return
  515. }
  516. issueId := com.StrTo(ctx.Query("issue")).MustInt64()
  517. if issueId == 0 {
  518. ctx.Error(404)
  519. return
  520. }
  521. issue, err := models.GetIssueById(issueId)
  522. if err != nil {
  523. if err == models.ErrIssueNotExist {
  524. ctx.Handle(404, "issue.UpdateIssueMilestone(GetIssueById)", err)
  525. } else {
  526. ctx.Handle(500, "issue.UpdateIssueMilestone(GetIssueById)", err)
  527. }
  528. return
  529. }
  530. oldMid := issue.MilestoneID
  531. mid := com.StrTo(ctx.Query("milestoneid")).MustInt64()
  532. if oldMid == mid {
  533. ctx.JSON(200, map[string]interface{}{
  534. "ok": true,
  535. })
  536. return
  537. }
  538. // Not check for invalid milestone id and give responsibility to owners.
  539. issue.MilestoneID = mid
  540. if err = models.ChangeMilestoneAssign(oldMid, mid, issue); err != nil {
  541. ctx.Handle(500, "issue.UpdateIssueMilestone(ChangeMilestoneAssign)", err)
  542. return
  543. } else if err = models.UpdateIssue(issue); err != nil {
  544. ctx.Handle(500, "issue.UpdateIssueMilestone(UpdateIssue)", err)
  545. return
  546. }
  547. ctx.JSON(200, map[string]interface{}{
  548. "ok": true,
  549. })
  550. }
  551. func UpdateAssignee(ctx *middleware.Context) {
  552. if !ctx.Repo.IsOwner() {
  553. ctx.Error(403)
  554. return
  555. }
  556. issueId := com.StrTo(ctx.Query("issue")).MustInt64()
  557. if issueId == 0 {
  558. ctx.Error(404)
  559. return
  560. }
  561. issue, err := models.GetIssueById(issueId)
  562. if err != nil {
  563. if err == models.ErrIssueNotExist {
  564. ctx.Handle(404, "GetIssueById", err)
  565. } else {
  566. ctx.Handle(500, "GetIssueById", err)
  567. }
  568. return
  569. }
  570. aid := com.StrTo(ctx.Query("assigneeid")).MustInt64()
  571. // Not check for invalid assignee id and give responsibility to owners.
  572. issue.AssigneeID = aid
  573. if err = models.UpdateIssueUserPairByAssignee(aid, issue.ID); err != nil {
  574. ctx.Handle(500, "UpdateIssueUserPairByAssignee: %v", err)
  575. return
  576. } else if err = models.UpdateIssue(issue); err != nil {
  577. ctx.Handle(500, "UpdateIssue", err)
  578. return
  579. }
  580. ctx.JSON(200, map[string]interface{}{
  581. "ok": true,
  582. })
  583. }
  584. func uploadFiles(ctx *middleware.Context, issueId, commentId int64) {
  585. if !setting.AttachmentEnabled {
  586. return
  587. }
  588. allowedTypes := strings.Split(setting.AttachmentAllowedTypes, "|")
  589. attachments := ctx.Req.MultipartForm.File["attachments"]
  590. if len(attachments) > setting.AttachmentMaxFiles {
  591. ctx.Handle(400, "issue.Comment", ErrTooManyFiles)
  592. return
  593. }
  594. for _, header := range attachments {
  595. file, err := header.Open()
  596. if err != nil {
  597. ctx.Handle(500, "issue.Comment(header.Open)", err)
  598. return
  599. }
  600. defer file.Close()
  601. buf := make([]byte, 1024)
  602. n, _ := file.Read(buf)
  603. if n > 0 {
  604. buf = buf[:n]
  605. }
  606. fileType := http.DetectContentType(buf)
  607. fmt.Println(fileType)
  608. allowed := false
  609. for _, t := range allowedTypes {
  610. t := strings.Trim(t, " ")
  611. if t == "*/*" || t == fileType {
  612. allowed = true
  613. break
  614. }
  615. }
  616. if !allowed {
  617. ctx.Handle(400, "issue.Comment", ErrFileTypeForbidden)
  618. return
  619. }
  620. out, err := ioutil.TempFile(setting.AttachmentPath, "attachment_")
  621. if err != nil {
  622. ctx.Handle(500, "ioutil.TempFile", err)
  623. return
  624. }
  625. defer out.Close()
  626. out.Write(buf)
  627. _, err = io.Copy(out, file)
  628. if err != nil {
  629. ctx.Handle(500, "io.Copy", err)
  630. return
  631. }
  632. _, err = models.CreateAttachment(issueId, commentId, header.Filename, out.Name())
  633. if err != nil {
  634. ctx.Handle(500, "CreateAttachment", err)
  635. return
  636. }
  637. }
  638. }
  639. func Comment(ctx *middleware.Context) {
  640. send := func(status int, data interface{}, err error) {
  641. if err != nil {
  642. log.Error(4, "issue.Comment(?): %s", err)
  643. ctx.JSON(status, map[string]interface{}{
  644. "ok": false,
  645. "status": status,
  646. "error": err.Error(),
  647. })
  648. } else {
  649. ctx.JSON(status, map[string]interface{}{
  650. "ok": true,
  651. "status": status,
  652. "data": data,
  653. })
  654. }
  655. }
  656. index := com.StrTo(ctx.Query("issueIndex")).MustInt64()
  657. if index == 0 {
  658. ctx.Error(404)
  659. return
  660. }
  661. issue, err := models.GetIssueByIndex(ctx.Repo.Repository.Id, index)
  662. if err != nil {
  663. if err == models.ErrIssueNotExist {
  664. send(404, nil, err)
  665. } else {
  666. send(200, nil, err)
  667. }
  668. return
  669. }
  670. // Check if issue owner changes the status of issue.
  671. var newStatus string
  672. if ctx.Repo.IsOwner() || issue.PosterID == ctx.User.Id {
  673. newStatus = ctx.Query("change_status")
  674. }
  675. if len(newStatus) > 0 {
  676. if (strings.Contains(newStatus, "Reopen") && issue.IsClosed) ||
  677. (strings.Contains(newStatus, "Close") && !issue.IsClosed) {
  678. issue.IsClosed = !issue.IsClosed
  679. if err = models.UpdateIssue(issue); err != nil {
  680. send(500, nil, err)
  681. return
  682. } else if err = models.UpdateIssueUserPairsByStatus(issue.ID, issue.IsClosed); err != nil {
  683. send(500, nil, err)
  684. return
  685. }
  686. if err = issue.GetLabels(); err != nil {
  687. send(500, nil, err)
  688. return
  689. }
  690. for _, label := range issue.Labels {
  691. if issue.IsClosed {
  692. label.NumClosedIssues++
  693. } else {
  694. label.NumClosedIssues--
  695. }
  696. if err = models.UpdateLabel(label); err != nil {
  697. send(500, nil, err)
  698. return
  699. }
  700. }
  701. // Change open/closed issue counter for the associated milestone
  702. if issue.MilestoneID > 0 {
  703. if err = models.ChangeMilestoneIssueStats(issue); err != nil {
  704. send(500, nil, err)
  705. }
  706. }
  707. cmtType := models.COMMENT_TYPE_CLOSE
  708. if !issue.IsClosed {
  709. cmtType = models.COMMENT_TYPE_REOPEN
  710. }
  711. if _, err = models.CreateComment(ctx.User.Id, ctx.Repo.Repository.Id, issue.ID, 0, 0, cmtType, "", nil); err != nil {
  712. send(200, nil, err)
  713. return
  714. }
  715. log.Trace("%s Issue(%d) status changed: %v", ctx.Req.RequestURI, issue.ID, !issue.IsClosed)
  716. }
  717. }
  718. var comment *models.Comment
  719. var ms []string
  720. content := ctx.Query("content")
  721. // Fix #321. Allow empty comments, as long as we have attachments.
  722. if len(content) > 0 || len(ctx.Req.MultipartForm.File["attachments"]) > 0 {
  723. switch ctx.Params(":action") {
  724. case "new":
  725. if comment, err = models.CreateComment(ctx.User.Id, ctx.Repo.Repository.Id, issue.ID, 0, 0, models.COMMENT_TYPE_COMMENT, content, nil); err != nil {
  726. send(500, nil, err)
  727. return
  728. }
  729. // Update mentions.
  730. ms = base.MentionPattern.FindAllString(issue.Content, -1)
  731. if len(ms) > 0 {
  732. for i := range ms {
  733. ms[i] = ms[i][1:]
  734. }
  735. if err := models.UpdateMentions(ms, issue.ID); err != nil {
  736. send(500, nil, err)
  737. return
  738. }
  739. }
  740. log.Trace("%s Comment created: %d", ctx.Req.RequestURI, issue.ID)
  741. default:
  742. ctx.Handle(404, "issue.Comment", err)
  743. return
  744. }
  745. }
  746. if comment != nil {
  747. uploadFiles(ctx, issue.ID, comment.Id)
  748. }
  749. // Notify watchers.
  750. act := &models.Action{
  751. ActUserID: ctx.User.Id,
  752. ActUserName: ctx.User.LowerName,
  753. ActEmail: ctx.User.Email,
  754. OpType: models.COMMENT_ISSUE,
  755. Content: fmt.Sprintf("%d|%s", issue.Index, strings.Split(content, "\n")[0]),
  756. RepoID: ctx.Repo.Repository.Id,
  757. RepoUserName: ctx.Repo.Owner.LowerName,
  758. RepoName: ctx.Repo.Repository.LowerName,
  759. IsPrivate: ctx.Repo.Repository.IsPrivate,
  760. }
  761. if err = models.NotifyWatchers(act); err != nil {
  762. send(500, nil, err)
  763. return
  764. }
  765. // Mail watchers and mentions.
  766. if setting.Service.EnableNotifyMail {
  767. issue.Content = content
  768. tos, err := mailer.SendIssueNotifyMail(ctx.User, ctx.Repo.Owner, ctx.Repo.Repository, issue)
  769. if err != nil {
  770. send(500, nil, err)
  771. return
  772. }
  773. tos = append(tos, ctx.User.LowerName)
  774. newTos := make([]string, 0, len(ms))
  775. for _, m := range ms {
  776. if com.IsSliceContainsStr(tos, m) {
  777. continue
  778. }
  779. newTos = append(newTos, m)
  780. }
  781. if err = mailer.SendIssueMentionMail(ctx.Render, ctx.User, ctx.Repo.Owner,
  782. ctx.Repo.Repository, issue, models.GetUserEmailsByNames(newTos)); err != nil {
  783. send(500, nil, err)
  784. return
  785. }
  786. }
  787. send(200, fmt.Sprintf("%s/issues/%d", ctx.Repo.RepoLink, index), nil)
  788. }
  789. func Labels(ctx *middleware.Context) {
  790. ctx.Data["Title"] = ctx.Tr("repo.labels")
  791. ctx.Data["PageIsLabels"] = true
  792. ctx.HTML(200, LABELS)
  793. }
  794. func NewLabel(ctx *middleware.Context, form auth.CreateLabelForm) {
  795. ctx.Data["Title"] = ctx.Tr("repo.labels")
  796. ctx.Data["PageIsLabels"] = true
  797. if ctx.HasError() {
  798. ctx.Flash.Error(ctx.Data["ErrorMsg"].(string))
  799. ctx.Redirect(ctx.Repo.RepoLink + "/labels")
  800. return
  801. }
  802. l := &models.Label{
  803. RepoId: ctx.Repo.Repository.Id,
  804. Name: form.Title,
  805. Color: form.Color,
  806. }
  807. if err := models.NewLabel(l); err != nil {
  808. ctx.Handle(500, "NewLabel", err)
  809. return
  810. }
  811. ctx.Redirect(ctx.Repo.RepoLink + "/labels")
  812. }
  813. func UpdateLabel(ctx *middleware.Context, form auth.CreateLabelForm) {
  814. l, err := models.GetLabelById(form.ID)
  815. if err != nil {
  816. switch err {
  817. case models.ErrLabelNotExist:
  818. ctx.Error(404)
  819. default:
  820. ctx.Handle(500, "UpdateLabel", err)
  821. }
  822. return
  823. }
  824. l.Name = form.Title
  825. l.Color = form.Color
  826. if err := models.UpdateLabel(l); err != nil {
  827. ctx.Handle(500, "UpdateLabel", err)
  828. return
  829. }
  830. ctx.Redirect(ctx.Repo.RepoLink + "/labels")
  831. }
  832. func DeleteLabel(ctx *middleware.Context) {
  833. if err := models.DeleteLabel(ctx.Repo.Repository.Id, ctx.QueryInt64("id")); err != nil {
  834. ctx.Flash.Error("DeleteLabel: " + err.Error())
  835. } else {
  836. ctx.Flash.Success(ctx.Tr("repo.issues.label_deletion_success"))
  837. }
  838. ctx.JSON(200, map[string]interface{}{
  839. "redirect": ctx.Repo.RepoLink + "/labels",
  840. })
  841. return
  842. }
  843. func Milestones(ctx *middleware.Context) {
  844. ctx.Data["Title"] = ctx.Tr("repo.milestones")
  845. ctx.Data["PageIsMilestones"] = true
  846. isShowClosed := ctx.Query("state") == "closed"
  847. openCount, closedCount := models.MilestoneStats(ctx.Repo.Repository.Id)
  848. ctx.Data["OpenCount"] = openCount
  849. ctx.Data["ClosedCount"] = closedCount
  850. page := ctx.QueryInt("page")
  851. if page <= 1 {
  852. page = 1
  853. }
  854. var total int
  855. if !isShowClosed {
  856. total = int(openCount)
  857. } else {
  858. total = int(closedCount)
  859. }
  860. ctx.Data["Page"] = paginater.New(total, setting.IssuePagingNum, page, 5)
  861. miles, err := models.GetMilestones(ctx.Repo.Repository.Id, page, isShowClosed)
  862. if err != nil {
  863. ctx.Handle(500, "GetMilestones", err)
  864. return
  865. }
  866. for _, m := range miles {
  867. m.RenderedContent = string(base.RenderMarkdown([]byte(m.Content), ctx.Repo.RepoLink))
  868. m.CalOpenIssues()
  869. }
  870. ctx.Data["Milestones"] = miles
  871. if isShowClosed {
  872. ctx.Data["State"] = "closed"
  873. } else {
  874. ctx.Data["State"] = "open"
  875. }
  876. ctx.Data["IsShowClosed"] = isShowClosed
  877. ctx.HTML(200, MILESTONE)
  878. }
  879. func NewMilestone(ctx *middleware.Context) {
  880. ctx.Data["Title"] = ctx.Tr("repo.milestones.new")
  881. ctx.Data["PageIsMilestones"] = true
  882. ctx.Data["DateLang"] = setting.DateLang(ctx.Locale.Language())
  883. ctx.HTML(200, MILESTONE_NEW)
  884. }
  885. func NewMilestonePost(ctx *middleware.Context, form auth.CreateMilestoneForm) {
  886. ctx.Data["Title"] = ctx.Tr("repo.milestones.new")
  887. ctx.Data["PageIsMilestones"] = true
  888. ctx.Data["DateLang"] = setting.DateLang(ctx.Locale.Language())
  889. if ctx.HasError() {
  890. ctx.HTML(200, MILESTONE_NEW)
  891. return
  892. }
  893. if len(form.Deadline) == 0 {
  894. form.Deadline = "9999-12-31"
  895. }
  896. deadline, err := time.Parse("2006-01-02", form.Deadline)
  897. if err != nil {
  898. ctx.Data["Err_Deadline"] = true
  899. ctx.RenderWithErr(ctx.Tr("repo.milestones.invalid_due_date_format"), MILESTONE_NEW, &form)
  900. return
  901. }
  902. if err = models.NewMilestone(&models.Milestone{
  903. RepoID: ctx.Repo.Repository.Id,
  904. Name: form.Title,
  905. Content: form.Content,
  906. Deadline: deadline,
  907. }); err != nil {
  908. ctx.Handle(500, "NewMilestone", err)
  909. return
  910. }
  911. ctx.Flash.Success(ctx.Tr("repo.milestones.create_success", form.Title))
  912. ctx.Redirect(ctx.Repo.RepoLink + "/milestones")
  913. }
  914. func EditMilestone(ctx *middleware.Context) {
  915. ctx.Data["Title"] = ctx.Tr("repo.milestones.edit")
  916. ctx.Data["PageIsMilestones"] = true
  917. ctx.Data["PageIsEditMilestone"] = true
  918. ctx.Data["DateLang"] = setting.DateLang(ctx.Locale.Language())
  919. m, err := models.GetMilestoneByID(ctx.ParamsInt64(":id"))
  920. if err != nil {
  921. if models.IsErrMilestoneNotExist(err) {
  922. ctx.Handle(404, "GetMilestoneByID", nil)
  923. } else {
  924. ctx.Handle(500, "GetMilestoneByID", err)
  925. }
  926. return
  927. }
  928. ctx.Data["title"] = m.Name
  929. ctx.Data["content"] = m.Content
  930. if len(m.DeadlineString) > 0 {
  931. ctx.Data["deadline"] = m.DeadlineString
  932. }
  933. ctx.HTML(200, MILESTONE_NEW)
  934. }
  935. func EditMilestonePost(ctx *middleware.Context, form auth.CreateMilestoneForm) {
  936. ctx.Data["Title"] = ctx.Tr("repo.milestones.edit")
  937. ctx.Data["PageIsMilestones"] = true
  938. ctx.Data["PageIsEditMilestone"] = true
  939. ctx.Data["DateLang"] = setting.DateLang(ctx.Locale.Language())
  940. if ctx.HasError() {
  941. ctx.HTML(200, MILESTONE_NEW)
  942. return
  943. }
  944. if len(form.Deadline) == 0 {
  945. form.Deadline = "9999-12-31"
  946. }
  947. deadline, err := time.Parse("2006-01-02", form.Deadline)
  948. if err != nil {
  949. ctx.Data["Err_Deadline"] = true
  950. ctx.RenderWithErr(ctx.Tr("repo.milestones.invalid_due_date_format"), MILESTONE_NEW, &form)
  951. return
  952. }
  953. m, err := models.GetMilestoneByID(ctx.ParamsInt64(":id"))
  954. if err != nil {
  955. if models.IsErrMilestoneNotExist(err) {
  956. ctx.Handle(404, "GetMilestoneByID", nil)
  957. } else {
  958. ctx.Handle(500, "GetMilestoneByID", err)
  959. }
  960. return
  961. }
  962. m.Name = form.Title
  963. m.Content = form.Content
  964. m.Deadline = deadline
  965. if err = models.UpdateMilestone(m); err != nil {
  966. ctx.Handle(500, "UpdateMilestone", err)
  967. return
  968. }
  969. ctx.Flash.Success(ctx.Tr("repo.milestones.edit_success", m.Name))
  970. ctx.Redirect(ctx.Repo.RepoLink + "/milestones")
  971. }
  972. func ChangeMilestonStatus(ctx *middleware.Context) {
  973. m, err := models.GetMilestoneByID(ctx.ParamsInt64(":id"))
  974. if err != nil {
  975. if models.IsErrMilestoneNotExist(err) {
  976. ctx.Handle(404, "GetMilestoneByID", err)
  977. } else {
  978. ctx.Handle(500, "GetMilestoneByID", err)
  979. }
  980. return
  981. }
  982. switch ctx.Params(":action") {
  983. case "open":
  984. if m.IsClosed {
  985. if err = models.ChangeMilestoneStatus(m, false); err != nil {
  986. ctx.Handle(500, "ChangeMilestoneStatus", err)
  987. return
  988. }
  989. }
  990. ctx.Redirect(ctx.Repo.RepoLink + "/milestones?state=open")
  991. case "close":
  992. if !m.IsClosed {
  993. m.ClosedDate = time.Now()
  994. if err = models.ChangeMilestoneStatus(m, true); err != nil {
  995. ctx.Handle(500, "ChangeMilestoneStatus", err)
  996. return
  997. }
  998. }
  999. ctx.Redirect(ctx.Repo.RepoLink + "/milestones?state=closed")
  1000. default:
  1001. ctx.Redirect(ctx.Repo.RepoLink + "/milestones")
  1002. }
  1003. }
  1004. func DeleteMilestone(ctx *middleware.Context) {
  1005. if err := models.DeleteMilestoneByID(ctx.QueryInt64("id")); err != nil {
  1006. ctx.Flash.Error("DeleteMilestone: " + err.Error())
  1007. } else {
  1008. ctx.Flash.Success(ctx.Tr("repo.milestones.deletion_success"))
  1009. }
  1010. ctx.JSON(200, map[string]interface{}{
  1011. "redirect": ctx.Repo.RepoLink + "/milestones",
  1012. })
  1013. }
  1014. func IssueGetAttachment(ctx *middleware.Context) {
  1015. id := com.StrTo(ctx.Params(":id")).MustInt64()
  1016. if id == 0 {
  1017. ctx.Error(404)
  1018. return
  1019. }
  1020. attachment, err := models.GetAttachmentById(id)
  1021. if err != nil {
  1022. ctx.Handle(404, "models.GetAttachmentById", err)
  1023. return
  1024. }
  1025. // Fix #312. Attachments with , in their name are not handled correctly by Google Chrome.
  1026. // We must put the name in " manually.
  1027. ctx.ServeFile(attachment.Path, "\""+attachment.Name+"\"")
  1028. }
  1029. func PullRequest2(ctx *middleware.Context) {
  1030. ctx.HTML(200, "repo/pr2/list")
  1031. }