issue.go 30 KB

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