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

936 lines
25 KiB

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
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
  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. "fmt"
  7. "net/url"
  8. "strings"
  9. "time"
  10. "github.com/Unknwon/com"
  11. "github.com/go-martini/martini"
  12. "github.com/gogits/gogs/models"
  13. "github.com/gogits/gogs/modules/auth"
  14. "github.com/gogits/gogs/modules/base"
  15. "github.com/gogits/gogs/modules/log"
  16. "github.com/gogits/gogs/modules/mailer"
  17. "github.com/gogits/gogs/modules/middleware"
  18. "github.com/gogits/gogs/modules/setting"
  19. )
  20. func Issues(ctx *middleware.Context) {
  21. ctx.Data["Title"] = "Issues"
  22. ctx.Data["IsRepoToolbarIssues"] = true
  23. ctx.Data["IsRepoToolbarIssuesList"] = true
  24. viewType := ctx.Query("type")
  25. types := []string{"assigned", "created_by", "mentioned"}
  26. if !com.IsSliceContainsStr(types, viewType) {
  27. viewType = "all"
  28. }
  29. isShowClosed := ctx.Query("state") == "closed"
  30. if viewType != "all" && !ctx.IsSigned {
  31. ctx.SetCookie("redirect_to", "/"+url.QueryEscape(ctx.Req.RequestURI))
  32. ctx.Redirect("/user/login")
  33. return
  34. }
  35. var assigneeId, posterId int64
  36. var filterMode int
  37. switch viewType {
  38. case "assigned":
  39. assigneeId = ctx.User.Id
  40. filterMode = models.FM_ASSIGN
  41. case "created_by":
  42. posterId = ctx.User.Id
  43. filterMode = models.FM_CREATE
  44. case "mentioned":
  45. filterMode = models.FM_MENTION
  46. }
  47. var mid int64
  48. midx, _ := base.StrTo(ctx.Query("milestone")).Int64()
  49. if midx > 0 {
  50. mile, err := models.GetMilestoneByIndex(ctx.Repo.Repository.Id, midx)
  51. if err != nil {
  52. ctx.Handle(500, "issue.Issues(GetMilestoneByIndex): %v", err)
  53. return
  54. }
  55. mid = mile.Id
  56. }
  57. selectLabels := ctx.Query("labels")
  58. labels, err := models.GetLabels(ctx.Repo.Repository.Id)
  59. if err != nil {
  60. ctx.Handle(500, "issue.Issues(GetLabels): %v", err)
  61. return
  62. }
  63. for _, l := range labels {
  64. l.CalOpenIssues()
  65. }
  66. ctx.Data["Labels"] = labels
  67. page, _ := base.StrTo(ctx.Query("page")).Int()
  68. // Get issues.
  69. issues, err := models.GetIssues(assigneeId, ctx.Repo.Repository.Id, posterId, mid, page,
  70. isShowClosed, selectLabels, ctx.Query("sortType"))
  71. if err != nil {
  72. ctx.Handle(500, "issue.Issues(GetIssues): %v", err)
  73. return
  74. }
  75. // Get issue-user pairs.
  76. pairs, err := models.GetIssueUserPairs(ctx.Repo.Repository.Id, posterId, isShowClosed)
  77. if err != nil {
  78. ctx.Handle(500, "issue.Issues(GetIssueUserPairs): %v", err)
  79. return
  80. }
  81. // Get posters.
  82. for i := range issues {
  83. if err = issues[i].GetLabels(); err != nil {
  84. ctx.Handle(500, "issue.Issues(GetLabels)", fmt.Errorf("[#%d]%v", issues[i].Id, err))
  85. return
  86. }
  87. idx := models.PairsContains(pairs, issues[i].Id)
  88. if filterMode == models.FM_MENTION && (idx == -1 || !pairs[idx].IsMentioned) {
  89. continue
  90. }
  91. if idx > -1 {
  92. issues[i].IsRead = pairs[idx].IsRead
  93. } else {
  94. issues[i].IsRead = true
  95. }
  96. if err = issues[i].GetPoster(); err != nil {
  97. ctx.Handle(500, "issue.Issues(GetPoster)", fmt.Errorf("[#%d]%v", issues[i].Id, err))
  98. return
  99. }
  100. }
  101. var uid int64 = -1
  102. if ctx.User != nil {
  103. uid = ctx.User.Id
  104. }
  105. issueStats := models.GetIssueStats(ctx.Repo.Repository.Id, uid, isShowClosed, filterMode)
  106. ctx.Data["IssueStats"] = issueStats
  107. ctx.Data["SelectLabels"], _ = base.StrTo(selectLabels).Int64()
  108. ctx.Data["ViewType"] = viewType
  109. ctx.Data["Issues"] = issues
  110. ctx.Data["IsShowClosed"] = isShowClosed
  111. if isShowClosed {
  112. ctx.Data["State"] = "closed"
  113. ctx.Data["ShowCount"] = issueStats.ClosedCount
  114. } else {
  115. ctx.Data["ShowCount"] = issueStats.OpenCount
  116. }
  117. ctx.HTML(200, "issue/list")
  118. }
  119. func CreateIssue(ctx *middleware.Context, params martini.Params) {
  120. ctx.Data["Title"] = "Create issue"
  121. ctx.Data["IsRepoToolbarIssues"] = true
  122. ctx.Data["IsRepoToolbarIssuesList"] = false
  123. var err error
  124. // Get all milestones.
  125. ctx.Data["OpenMilestones"], err = models.GetMilestones(ctx.Repo.Repository.Id, false)
  126. if err != nil {
  127. ctx.Handle(500, "issue.ViewIssue(GetMilestones.1): %v", err)
  128. return
  129. }
  130. ctx.Data["ClosedMilestones"], err = models.GetMilestones(ctx.Repo.Repository.Id, true)
  131. if err != nil {
  132. ctx.Handle(500, "issue.ViewIssue(GetMilestones.2): %v", err)
  133. return
  134. }
  135. us, err := models.GetCollaborators(strings.TrimPrefix(ctx.Repo.RepoLink, "/"))
  136. if err != nil {
  137. ctx.Handle(500, "issue.CreateIssue(GetCollaborators)", err)
  138. return
  139. }
  140. ctx.Data["Collaborators"] = us
  141. ctx.HTML(200, "issue/create")
  142. }
  143. func CreateIssuePost(ctx *middleware.Context, params martini.Params, form auth.CreateIssueForm) {
  144. ctx.Data["Title"] = "Create issue"
  145. ctx.Data["IsRepoToolbarIssues"] = true
  146. ctx.Data["IsRepoToolbarIssuesList"] = false
  147. var err error
  148. // Get all milestones.
  149. ctx.Data["OpenMilestones"], err = models.GetMilestones(ctx.Repo.Repository.Id, false)
  150. if err != nil {
  151. ctx.Handle(500, "issue.ViewIssue(GetMilestones.1): %v", err)
  152. return
  153. }
  154. ctx.Data["ClosedMilestones"], err = models.GetMilestones(ctx.Repo.Repository.Id, true)
  155. if err != nil {
  156. ctx.Handle(500, "issue.ViewIssue(GetMilestones.2): %v", err)
  157. return
  158. }
  159. us, err := models.GetCollaborators(strings.TrimPrefix(ctx.Repo.RepoLink, "/"))
  160. if err != nil {
  161. ctx.Handle(500, "issue.CreateIssue(GetCollaborators)", err)
  162. return
  163. }
  164. ctx.Data["Collaborators"] = us
  165. if ctx.HasError() {
  166. ctx.HTML(200, "issue/create")
  167. return
  168. }
  169. // Only collaborators can assign.
  170. if !ctx.Repo.IsOwner {
  171. form.AssigneeId = 0
  172. }
  173. issue := &models.Issue{
  174. RepoId: ctx.Repo.Repository.Id,
  175. Index: int64(ctx.Repo.Repository.NumIssues) + 1,
  176. Name: form.IssueName,
  177. PosterId: ctx.User.Id,
  178. MilestoneId: form.MilestoneId,
  179. AssigneeId: form.AssigneeId,
  180. LabelIds: form.Labels,
  181. Content: form.Content,
  182. }
  183. if err := models.NewIssue(issue); err != nil {
  184. ctx.Handle(500, "issue.CreateIssue(NewIssue)", err)
  185. return
  186. } else if err := models.NewIssueUserPairs(issue.RepoId, issue.Id, ctx.Repo.Owner.Id,
  187. ctx.User.Id, form.AssigneeId, ctx.Repo.Repository.Name); err != nil {
  188. ctx.Handle(500, "issue.CreateIssue(NewIssueUserPairs)", err)
  189. return
  190. }
  191. // Update mentions.
  192. ms := base.MentionPattern.FindAllString(issue.Content, -1)
  193. if len(ms) > 0 {
  194. for i := range ms {
  195. ms[i] = ms[i][1:]
  196. }
  197. ids := models.GetUserIdsByNames(ms)
  198. if err := models.UpdateIssueUserPairsByMentions(ids, issue.Id); err != nil {
  199. ctx.Handle(500, "issue.CreateIssue(UpdateIssueUserPairsByMentions)", err)
  200. return
  201. }
  202. }
  203. act := &models.Action{
  204. ActUserId: ctx.User.Id,
  205. ActUserName: ctx.User.Name,
  206. ActEmail: ctx.User.Email,
  207. OpType: models.OP_CREATE_ISSUE,
  208. Content: fmt.Sprintf("%d|%s", issue.Index, issue.Name),
  209. RepoId: ctx.Repo.Repository.Id,
  210. RepoUserName: ctx.Repo.Owner.Name,
  211. RepoName: ctx.Repo.Repository.Name,
  212. RefName: ctx.Repo.BranchName,
  213. IsPrivate: ctx.Repo.Repository.IsPrivate,
  214. }
  215. // Notify watchers.
  216. if err := models.NotifyWatchers(act); err != nil {
  217. ctx.Handle(500, "issue.CreateIssue(NotifyWatchers)", err)
  218. return
  219. }
  220. // Mail watchers and mentions.
  221. if setting.Service.NotifyMail {
  222. tos, err := mailer.SendIssueNotifyMail(ctx.User, ctx.Repo.Owner, ctx.Repo.Repository, issue)
  223. if err != nil {
  224. ctx.Handle(500, "issue.CreateIssue(SendIssueNotifyMail)", err)
  225. return
  226. }
  227. tos = append(tos, ctx.User.LowerName)
  228. newTos := make([]string, 0, len(ms))
  229. for _, m := range ms {
  230. if com.IsSliceContainsStr(tos, m) {
  231. continue
  232. }
  233. newTos = append(newTos, m)
  234. }
  235. if err = mailer.SendIssueMentionMail(ctx.Render, ctx.User, ctx.Repo.Owner,
  236. ctx.Repo.Repository, issue, models.GetUserEmailsByNames(newTos)); err != nil {
  237. ctx.Handle(500, "issue.CreateIssue(SendIssueMentionMail)", err)
  238. return
  239. }
  240. }
  241. log.Trace("%d Issue created: %d", ctx.Repo.Repository.Id, issue.Id)
  242. ctx.Redirect(fmt.Sprintf("/%s/%s/issues/%d", params["username"], params["reponame"], issue.Index))
  243. }
  244. func checkLabels(labels, allLabels []*models.Label) {
  245. for _, l := range labels {
  246. for _, l2 := range allLabels {
  247. if l.Id == l2.Id {
  248. l2.IsChecked = true
  249. break
  250. }
  251. }
  252. }
  253. }
  254. func ViewIssue(ctx *middleware.Context, params martini.Params) {
  255. idx, _ := base.StrTo(params["index"]).Int64()
  256. if idx == 0 {
  257. ctx.Handle(404, "issue.ViewIssue", nil)
  258. return
  259. }
  260. issue, err := models.GetIssueByIndex(ctx.Repo.Repository.Id, idx)
  261. if err != nil {
  262. if err == models.ErrIssueNotExist {
  263. ctx.Handle(404, "issue.ViewIssue(GetIssueByIndex)", err)
  264. } else {
  265. ctx.Handle(500, "issue.ViewIssue(GetIssueByIndex)", err)
  266. }
  267. return
  268. }
  269. // Get labels.
  270. if err = issue.GetLabels(); err != nil {
  271. ctx.Handle(500, "issue.ViewIssue(GetLabels)", err)
  272. return
  273. }
  274. labels, err := models.GetLabels(ctx.Repo.Repository.Id)
  275. if err != nil {
  276. ctx.Handle(500, "issue.ViewIssue(GetLabels.2)", err)
  277. return
  278. }
  279. checkLabels(issue.Labels, labels)
  280. ctx.Data["Labels"] = labels
  281. // Get assigned milestone.
  282. if issue.MilestoneId > 0 {
  283. ctx.Data["Milestone"], err = models.GetMilestoneById(issue.MilestoneId)
  284. if err != nil {
  285. if err == models.ErrMilestoneNotExist {
  286. log.Warn("issue.ViewIssue(GetMilestoneById): %v", err)
  287. } else {
  288. ctx.Handle(500, "issue.ViewIssue(GetMilestoneById)", err)
  289. return
  290. }
  291. }
  292. }
  293. // Get all milestones.
  294. ctx.Data["OpenMilestones"], err = models.GetMilestones(ctx.Repo.Repository.Id, false)
  295. if err != nil {
  296. ctx.Handle(500, "issue.ViewIssue(GetMilestones.1): %v", err)
  297. return
  298. }
  299. ctx.Data["ClosedMilestones"], err = models.GetMilestones(ctx.Repo.Repository.Id, true)
  300. if err != nil {
  301. ctx.Handle(500, "issue.ViewIssue(GetMilestones.2): %v", err)
  302. return
  303. }
  304. // Get all collaborators.
  305. ctx.Data["Collaborators"], err = models.GetCollaborators(strings.TrimPrefix(ctx.Repo.RepoLink, "/"))
  306. if err != nil {
  307. ctx.Handle(500, "issue.CreateIssue(GetCollaborators)", err)
  308. return
  309. }
  310. if ctx.IsSigned {
  311. // Update issue-user.
  312. if err = models.UpdateIssueUserPairByRead(ctx.User.Id, issue.Id); err != nil {
  313. ctx.Handle(500, "issue.ViewIssue(UpdateIssueUserPairByRead): %v", err)
  314. return
  315. }
  316. }
  317. // Get poster and Assignee.
  318. if err = issue.GetPoster(); err != nil {
  319. ctx.Handle(500, "issue.ViewIssue(GetPoster): %v", err)
  320. return
  321. } else if err = issue.GetAssignee(); err != nil {
  322. ctx.Handle(500, "issue.ViewIssue(GetAssignee): %v", err)
  323. return
  324. }
  325. issue.RenderedContent = string(base.RenderMarkdown([]byte(issue.Content), ctx.Repo.RepoLink))
  326. // Get comments.
  327. comments, err := models.GetIssueComments(issue.Id)
  328. if err != nil {
  329. ctx.Handle(500, "issue.ViewIssue(GetIssueComments): %v", err)
  330. return
  331. }
  332. // Get posters.
  333. for i := range comments {
  334. u, err := models.GetUserById(comments[i].PosterId)
  335. if err != nil {
  336. ctx.Handle(500, "issue.ViewIssue(GetUserById.2): %v", err)
  337. return
  338. }
  339. comments[i].Poster = u
  340. comments[i].Content = string(base.RenderMarkdown([]byte(comments[i].Content), ctx.Repo.RepoLink))
  341. }
  342. ctx.Data["Title"] = issue.Name
  343. ctx.Data["Issue"] = issue
  344. ctx.Data["Comments"] = comments
  345. ctx.Data["IsIssueOwner"] = ctx.Repo.IsOwner || (ctx.IsSigned && issue.PosterId == ctx.User.Id)
  346. ctx.Data["IsRepoToolbarIssues"] = true
  347. ctx.Data["IsRepoToolbarIssuesList"] = false
  348. ctx.HTML(200, "issue/view")
  349. }
  350. func UpdateIssue(ctx *middleware.Context, params martini.Params, form auth.CreateIssueForm) {
  351. idx, _ := base.StrTo(params["index"]).Int64()
  352. if idx <= 0 {
  353. ctx.Error(404)
  354. return
  355. }
  356. issue, err := models.GetIssueByIndex(ctx.Repo.Repository.Id, idx)
  357. if err != nil {
  358. if err == models.ErrIssueNotExist {
  359. ctx.Handle(404, "issue.UpdateIssue", err)
  360. } else {
  361. ctx.Handle(500, "issue.UpdateIssue(GetIssueByIndex)", err)
  362. }
  363. return
  364. }
  365. if ctx.User.Id != issue.PosterId && !ctx.Repo.IsOwner {
  366. ctx.Error(403)
  367. return
  368. }
  369. issue.Name = form.IssueName
  370. issue.MilestoneId = form.MilestoneId
  371. issue.AssigneeId = form.AssigneeId
  372. issue.LabelIds = form.Labels
  373. issue.Content = form.Content
  374. // try get content from text, ignore conflict with preview ajax
  375. if form.Content == "" {
  376. issue.Content = ctx.Query("text")
  377. }
  378. if err = models.UpdateIssue(issue); err != nil {
  379. ctx.Handle(500, "issue.UpdateIssue(UpdateIssue)", err)
  380. return
  381. }
  382. ctx.JSON(200, map[string]interface{}{
  383. "ok": true,
  384. "title": issue.Name,
  385. "content": string(base.RenderMarkdown([]byte(issue.Content), ctx.Repo.RepoLink)),
  386. })
  387. }
  388. func UpdateIssueLabel(ctx *middleware.Context, params martini.Params) {
  389. if !ctx.Repo.IsOwner {
  390. ctx.Error(403)
  391. return
  392. }
  393. idx, _ := base.StrTo(params["index"]).Int64()
  394. if idx <= 0 {
  395. ctx.Error(404)
  396. return
  397. }
  398. issue, err := models.GetIssueByIndex(ctx.Repo.Repository.Id, idx)
  399. if err != nil {
  400. if err == models.ErrIssueNotExist {
  401. ctx.Handle(404, "issue.UpdateIssueLabel(GetIssueByIndex)", err)
  402. } else {
  403. ctx.Handle(500, "issue.UpdateIssueLabel(GetIssueByIndex)", err)
  404. }
  405. return
  406. }
  407. isAttach := ctx.Query("action") == "attach"
  408. labelStrId := ctx.Query("id")
  409. labelId, _ := base.StrTo(labelStrId).Int64()
  410. label, err := models.GetLabelById(labelId)
  411. if err != nil {
  412. if err == models.ErrLabelNotExist {
  413. ctx.Handle(404, "issue.UpdateIssueLabel(GetLabelById)", err)
  414. } else {
  415. ctx.Handle(500, "issue.UpdateIssueLabel(GetLabelById)", err)
  416. }
  417. return
  418. }
  419. isHad := strings.Contains(issue.LabelIds, "$"+labelStrId+"|")
  420. isNeedUpdate := false
  421. if isAttach {
  422. if !isHad {
  423. issue.LabelIds += "$" + labelStrId + "|"
  424. isNeedUpdate = true
  425. }
  426. } else {
  427. if isHad {
  428. issue.LabelIds = strings.Replace(issue.LabelIds, "$"+labelStrId+"|", "", -1)
  429. isNeedUpdate = true
  430. }
  431. }
  432. if isNeedUpdate {
  433. if err = models.UpdateIssue(issue); err != nil {
  434. ctx.Handle(500, "issue.UpdateIssueLabel(UpdateIssue)", err)
  435. return
  436. }
  437. if isAttach {
  438. label.NumIssues++
  439. if issue.IsClosed {
  440. label.NumClosedIssues++
  441. }
  442. } else {
  443. label.NumIssues--
  444. if issue.IsClosed {
  445. label.NumClosedIssues--
  446. }
  447. }
  448. if err = models.UpdateLabel(label); err != nil {
  449. ctx.Handle(500, "issue.UpdateIssueLabel(UpdateLabel)", err)
  450. return
  451. }
  452. }
  453. ctx.JSON(200, map[string]interface{}{
  454. "ok": true,
  455. })
  456. }
  457. func UpdateIssueMilestone(ctx *middleware.Context) {
  458. if !ctx.Repo.IsOwner {
  459. ctx.Error(403)
  460. return
  461. }
  462. issueId, err := base.StrTo(ctx.Query("issue")).Int64()
  463. if err != nil {
  464. ctx.Error(404)
  465. return
  466. }
  467. issue, err := models.GetIssueById(issueId)
  468. if err != nil {
  469. if err == models.ErrIssueNotExist {
  470. ctx.Handle(404, "issue.UpdateIssueMilestone(GetIssueById)", err)
  471. } else {
  472. ctx.Handle(500, "issue.UpdateIssueMilestone(GetIssueById)", err)
  473. }
  474. return
  475. }
  476. oldMid := issue.MilestoneId
  477. mid, _ := base.StrTo(ctx.Query("milestone")).Int64()
  478. if oldMid == mid {
  479. ctx.JSON(200, map[string]interface{}{
  480. "ok": true,
  481. })
  482. return
  483. }
  484. // Not check for invalid milestone id and give responsibility to owners.
  485. issue.MilestoneId = mid
  486. if err = models.ChangeMilestoneAssign(oldMid, mid, issue); err != nil {
  487. ctx.Handle(500, "issue.UpdateIssueMilestone(ChangeMilestoneAssign)", err)
  488. return
  489. } else if err = models.UpdateIssue(issue); err != nil {
  490. ctx.Handle(500, "issue.UpdateIssueMilestone(UpdateIssue)", err)
  491. return
  492. }
  493. ctx.JSON(200, map[string]interface{}{
  494. "ok": true,
  495. })
  496. }
  497. func UpdateAssignee(ctx *middleware.Context) {
  498. if !ctx.Repo.IsOwner {
  499. ctx.Error(403)
  500. return
  501. }
  502. issueId, err := base.StrTo(ctx.Query("issue")).Int64()
  503. if err != nil {
  504. ctx.Error(404)
  505. return
  506. }
  507. issue, err := models.GetIssueById(issueId)
  508. if err != nil {
  509. if err == models.ErrIssueNotExist {
  510. ctx.Handle(404, "issue.UpdateAssignee(GetIssueById)", err)
  511. } else {
  512. ctx.Handle(500, "issue.UpdateAssignee(GetIssueById)", err)
  513. }
  514. return
  515. }
  516. aid, _ := base.StrTo(ctx.Query("assigneeid")).Int64()
  517. // Not check for invalid assignne id and give responsibility to owners.
  518. issue.AssigneeId = aid
  519. if err = models.UpdateIssueUserPairByAssignee(aid, issue.Id); err != nil {
  520. ctx.Handle(500, "issue.UpdateAssignee(UpdateIssueUserPairByAssignee): %v", err)
  521. return
  522. } else if err = models.UpdateIssue(issue); err != nil {
  523. ctx.Handle(500, "issue.UpdateAssignee(UpdateIssue)", err)
  524. return
  525. }
  526. ctx.JSON(200, map[string]interface{}{
  527. "ok": true,
  528. })
  529. }
  530. func Comment(ctx *middleware.Context, params martini.Params) {
  531. index, err := base.StrTo(ctx.Query("issueIndex")).Int64()
  532. if err != nil {
  533. ctx.Handle(404, "issue.Comment(get index)", err)
  534. return
  535. }
  536. issue, err := models.GetIssueByIndex(ctx.Repo.Repository.Id, index)
  537. if err != nil {
  538. if err == models.ErrIssueNotExist {
  539. ctx.Handle(404, "issue.Comment", err)
  540. } else {
  541. ctx.Handle(200, "issue.Comment(get issue)", err)
  542. }
  543. return
  544. }
  545. // Check if issue owner changes the status of issue.
  546. var newStatus string
  547. if ctx.Repo.IsOwner || issue.PosterId == ctx.User.Id {
  548. newStatus = ctx.Query("change_status")
  549. }
  550. if len(newStatus) > 0 {
  551. if (strings.Contains(newStatus, "Reopen") && issue.IsClosed) ||
  552. (strings.Contains(newStatus, "Close") && !issue.IsClosed) {
  553. issue.IsClosed = !issue.IsClosed
  554. if err = models.UpdateIssue(issue); err != nil {
  555. ctx.Handle(500, "issue.Comment(UpdateIssue)", err)
  556. return
  557. } else if err = models.UpdateIssueUserPairsByStatus(issue.Id, issue.IsClosed); err != nil {
  558. ctx.Handle(500, "issue.Comment(UpdateIssueUserPairsByStatus)", err)
  559. return
  560. }
  561. cmtType := models.IT_CLOSE
  562. if !issue.IsClosed {
  563. cmtType = models.IT_REOPEN
  564. }
  565. if err = models.CreateComment(ctx.User.Id, ctx.Repo.Repository.Id, issue.Id, 0, 0, cmtType, ""); err != nil {
  566. ctx.Handle(200, "issue.Comment(create status change comment)", err)
  567. return
  568. }
  569. log.Trace("%s Issue(%d) status changed: %v", ctx.Req.RequestURI, issue.Id, !issue.IsClosed)
  570. }
  571. }
  572. var ms []string
  573. content := ctx.Query("content")
  574. if len(content) > 0 {
  575. switch params["action"] {
  576. case "new":
  577. if err = models.CreateComment(ctx.User.Id, ctx.Repo.Repository.Id, issue.Id, 0, 0, models.IT_PLAIN, content); err != nil {
  578. ctx.Handle(500, "issue.Comment(create comment)", err)
  579. return
  580. }
  581. // Update mentions.
  582. ms = base.MentionPattern.FindAllString(issue.Content, -1)
  583. if len(ms) > 0 {
  584. for i := range ms {
  585. ms[i] = ms[i][1:]
  586. }
  587. ids := models.GetUserIdsByNames(ms)
  588. if err := models.UpdateIssueUserPairsByMentions(ids, issue.Id); err != nil {
  589. ctx.Handle(500, "issue.CreateIssue(UpdateIssueUserPairsByMentions)", err)
  590. return
  591. }
  592. }
  593. log.Trace("%s Comment created: %d", ctx.Req.RequestURI, issue.Id)
  594. default:
  595. ctx.Handle(404, "issue.Comment", err)
  596. return
  597. }
  598. }
  599. // Notify watchers.
  600. if err = models.NotifyWatchers(&models.Action{ActUserId: ctx.User.Id, ActUserName: ctx.User.Name, ActEmail: ctx.User.Email,
  601. OpType: models.OP_COMMENT_ISSUE, Content: fmt.Sprintf("%d|%s", issue.Index, strings.Split(content, "\n")[0]),
  602. RepoId: ctx.Repo.Repository.Id, RepoName: ctx.Repo.Repository.Name, RefName: ""}); err != nil {
  603. ctx.Handle(500, "issue.CreateIssue(NotifyWatchers)", err)
  604. return
  605. }
  606. // Mail watchers and mentions.
  607. if setting.Service.NotifyMail {
  608. issue.Content = content
  609. tos, err := mailer.SendIssueNotifyMail(ctx.User, ctx.Repo.Owner, ctx.Repo.Repository, issue)
  610. if err != nil {
  611. ctx.Handle(500, "issue.Comment(SendIssueNotifyMail)", err)
  612. return
  613. }
  614. tos = append(tos, ctx.User.LowerName)
  615. newTos := make([]string, 0, len(ms))
  616. for _, m := range ms {
  617. if com.IsSliceContainsStr(tos, m) {
  618. continue
  619. }
  620. newTos = append(newTos, m)
  621. }
  622. if err = mailer.SendIssueMentionMail(ctx.Render, ctx.User, ctx.Repo.Owner,
  623. ctx.Repo.Repository, issue, models.GetUserEmailsByNames(newTos)); err != nil {
  624. ctx.Handle(500, "issue.Comment(SendIssueMentionMail)", err)
  625. return
  626. }
  627. }
  628. ctx.Redirect(fmt.Sprintf("%s/issues/%d", ctx.Repo.RepoLink, index))
  629. }
  630. func NewLabel(ctx *middleware.Context, form auth.CreateLabelForm) {
  631. if ctx.HasError() {
  632. Issues(ctx)
  633. return
  634. }
  635. l := &models.Label{
  636. RepoId: ctx.Repo.Repository.Id,
  637. Name: form.Title,
  638. Color: form.Color,
  639. }
  640. if err := models.NewLabel(l); err != nil {
  641. ctx.Handle(500, "issue.NewLabel(NewLabel)", err)
  642. return
  643. }
  644. ctx.Redirect(ctx.Repo.RepoLink + "/issues")
  645. }
  646. func UpdateLabel(ctx *middleware.Context, params martini.Params, form auth.CreateLabelForm) {
  647. id, _ := base.StrTo(ctx.Query("id")).Int64()
  648. if id == 0 {
  649. ctx.Error(404)
  650. return
  651. }
  652. l := &models.Label{
  653. Id: id,
  654. Name: form.Title,
  655. Color: form.Color,
  656. }
  657. if err := models.UpdateLabel(l); err != nil {
  658. ctx.Handle(500, "issue.UpdateLabel(UpdateLabel)", err)
  659. return
  660. }
  661. ctx.Redirect(ctx.Repo.RepoLink + "/issues")
  662. }
  663. func DeleteLabel(ctx *middleware.Context) {
  664. removes := ctx.Query("remove")
  665. if len(strings.TrimSpace(removes)) == 0 {
  666. ctx.JSON(200, map[string]interface{}{
  667. "ok": true,
  668. })
  669. return
  670. }
  671. strIds := strings.Split(removes, ",")
  672. for _, strId := range strIds {
  673. if err := models.DeleteLabel(ctx.Repo.Repository.Id, strId); err != nil {
  674. ctx.Handle(500, "issue.DeleteLabel(DeleteLabel)", err)
  675. return
  676. }
  677. }
  678. ctx.JSON(200, map[string]interface{}{
  679. "ok": true,
  680. })
  681. }
  682. func Milestones(ctx *middleware.Context) {
  683. ctx.Data["Title"] = "Milestones"
  684. ctx.Data["IsRepoToolbarIssues"] = true
  685. ctx.Data["IsRepoToolbarIssuesList"] = true
  686. isShowClosed := ctx.Query("state") == "closed"
  687. miles, err := models.GetMilestones(ctx.Repo.Repository.Id, isShowClosed)
  688. if err != nil {
  689. ctx.Handle(500, "issue.Milestones(GetMilestones)", err)
  690. return
  691. }
  692. for _, m := range miles {
  693. m.RenderedContent = string(base.RenderSpecialLink([]byte(m.Content), ctx.Repo.RepoLink))
  694. m.CalOpenIssues()
  695. }
  696. ctx.Data["Milestones"] = miles
  697. if isShowClosed {
  698. ctx.Data["State"] = "closed"
  699. } else {
  700. ctx.Data["State"] = "open"
  701. }
  702. ctx.HTML(200, "issue/milestone")
  703. }
  704. func NewMilestone(ctx *middleware.Context) {
  705. ctx.Data["Title"] = "New Milestone"
  706. ctx.Data["IsRepoToolbarIssues"] = true
  707. ctx.Data["IsRepoToolbarIssuesList"] = true
  708. ctx.HTML(200, "issue/milestone_new")
  709. }
  710. func NewMilestonePost(ctx *middleware.Context, form auth.CreateMilestoneForm) {
  711. ctx.Data["Title"] = "New Milestone"
  712. ctx.Data["IsRepoToolbarIssues"] = true
  713. ctx.Data["IsRepoToolbarIssuesList"] = true
  714. var deadline time.Time
  715. var err error
  716. if len(form.Deadline) == 0 {
  717. form.Deadline = "12/31/9999"
  718. }
  719. deadline, err = time.Parse("01/02/2006", form.Deadline)
  720. if err != nil {
  721. ctx.Handle(500, "issue.NewMilestonePost(time.Parse)", err)
  722. return
  723. }
  724. mile := &models.Milestone{
  725. RepoId: ctx.Repo.Repository.Id,
  726. Index: int64(ctx.Repo.Repository.NumMilestones) + 1,
  727. Name: form.Title,
  728. Content: form.Content,
  729. Deadline: deadline,
  730. }
  731. if err = models.NewMilestone(mile); err != nil {
  732. ctx.Handle(500, "issue.NewMilestonePost(NewMilestone)", err)
  733. return
  734. }
  735. ctx.Redirect(ctx.Repo.RepoLink + "/issues/milestones")
  736. }
  737. func UpdateMilestone(ctx *middleware.Context, params martini.Params) {
  738. ctx.Data["Title"] = "Update Milestone"
  739. ctx.Data["IsRepoToolbarIssues"] = true
  740. ctx.Data["IsRepoToolbarIssuesList"] = true
  741. idx, _ := base.StrTo(params["index"]).Int64()
  742. if idx == 0 {
  743. ctx.Handle(404, "issue.UpdateMilestone", nil)
  744. return
  745. }
  746. mile, err := models.GetMilestoneByIndex(ctx.Repo.Repository.Id, idx)
  747. if err != nil {
  748. if err == models.ErrMilestoneNotExist {
  749. ctx.Handle(404, "issue.UpdateMilestone(GetMilestoneByIndex)", err)
  750. } else {
  751. ctx.Handle(500, "issue.UpdateMilestone(GetMilestoneByIndex)", err)
  752. }
  753. return
  754. }
  755. action := params["action"]
  756. if len(action) > 0 {
  757. switch action {
  758. case "open":
  759. if mile.IsClosed {
  760. if err = models.ChangeMilestoneStatus(mile, false); err != nil {
  761. ctx.Handle(500, "issue.UpdateMilestone(ChangeMilestoneStatus)", err)
  762. return
  763. }
  764. }
  765. case "close":
  766. if !mile.IsClosed {
  767. mile.ClosedDate = time.Now()
  768. if err = models.ChangeMilestoneStatus(mile, true); err != nil {
  769. ctx.Handle(500, "issue.UpdateMilestone(ChangeMilestoneStatus)", err)
  770. return
  771. }
  772. }
  773. case "delete":
  774. if err = models.DeleteMilestone(mile); err != nil {
  775. ctx.Handle(500, "issue.UpdateMilestone(DeleteMilestone)", err)
  776. return
  777. }
  778. }
  779. ctx.Redirect(ctx.Repo.RepoLink + "/issues/milestones")
  780. return
  781. }
  782. mile.DeadlineString = mile.Deadline.UTC().Format("01/02/2006")
  783. if mile.DeadlineString == "12/31/9999" {
  784. mile.DeadlineString = ""
  785. }
  786. ctx.Data["Milestone"] = mile
  787. ctx.HTML(200, "issue/milestone_edit")
  788. }
  789. func UpdateMilestonePost(ctx *middleware.Context, params martini.Params, form auth.CreateMilestoneForm) {
  790. ctx.Data["Title"] = "Update Milestone"
  791. ctx.Data["IsRepoToolbarIssues"] = true
  792. ctx.Data["IsRepoToolbarIssuesList"] = true
  793. idx, _ := base.StrTo(params["index"]).Int64()
  794. if idx == 0 {
  795. ctx.Handle(404, "issue.UpdateMilestonePost", nil)
  796. return
  797. }
  798. mile, err := models.GetMilestoneByIndex(ctx.Repo.Repository.Id, idx)
  799. if err != nil {
  800. if err == models.ErrMilestoneNotExist {
  801. ctx.Handle(404, "issue.UpdateMilestonePost(GetMilestoneByIndex)", err)
  802. } else {
  803. ctx.Handle(500, "issue.UpdateMilestonePost(GetMilestoneByIndex)", err)
  804. }
  805. return
  806. }
  807. var deadline time.Time
  808. if len(form.Deadline) == 0 {
  809. form.Deadline = "12/31/9999"
  810. }
  811. deadline, err = time.Parse("01/02/2006", form.Deadline)
  812. if err != nil {
  813. ctx.Handle(500, "issue.UpdateMilestonePost(time.Parse)", err)
  814. return
  815. }
  816. mile.Name = form.Title
  817. mile.Content = form.Content
  818. mile.Deadline = deadline
  819. if err = models.UpdateMilestone(mile); err != nil {
  820. ctx.Handle(500, "issue.UpdateMilestonePost(UpdateMilestone)", err)
  821. return
  822. }
  823. ctx.Redirect(ctx.Repo.RepoLink + "/issues/milestones")
  824. }