2014-03-22 13:50:50 -04:00
// Copyright 2014 The Gogs Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package repo
2014-07-26 02:28:04 -04:00
import (
"errors"
"fmt"
"io"
"io/ioutil"
"net/http"
"net/url"
"strings"
"time"
"github.com/Unknwon/com"
2015-07-28 03:14:37 +08:00
"github.com/Unknwon/paginater"
2014-07-26 02:28:04 -04:00
"github.com/gogits/gogs/models"
"github.com/gogits/gogs/modules/auth"
"github.com/gogits/gogs/modules/base"
"github.com/gogits/gogs/modules/log"
"github.com/gogits/gogs/modules/mailer"
"github.com/gogits/gogs/modules/middleware"
"github.com/gogits/gogs/modules/setting"
)
const (
ISSUES base . TplName = "repo/issue/list"
ISSUE_CREATE base . TplName = "repo/issue/create"
ISSUE_VIEW base . TplName = "repo/issue/view"
2015-07-24 21:02:49 +08:00
LABELS base . TplName = "repo/issue/labels"
2014-07-26 02:28:04 -04:00
MILESTONE base . TplName = "repo/issue/milestone"
MILESTONE_NEW base . TplName = "repo/issue/milestone_new"
MILESTONE_EDIT base . TplName = "repo/issue/milestone_edit"
)
var (
ErrFileTypeForbidden = errors . New ( "File type is not allowed" )
ErrTooManyFiles = errors . New ( "Maximum number of files to upload exceeded" )
)
2015-07-24 21:02:49 +08:00
func RetrieveLabels ( ctx * middleware . Context ) {
labels , err := models . GetLabels ( ctx . Repo . Repository . Id )
if err != nil {
ctx . Handle ( 500 , "RetrieveLabels.GetLabels: %v" , err )
return
}
for _ , l := range labels {
l . CalOpenIssues ( )
}
ctx . Data [ "Labels" ] = labels
ctx . Data [ "NumLabels" ] = len ( labels )
}
2014-07-26 02:28:04 -04:00
func Issues ( ctx * middleware . Context ) {
2015-07-24 04:50:05 +08:00
ctx . Data [ "Title" ] = ctx . Tr ( "repo.issues" )
ctx . Data [ "PageIsIssueList" ] = true
2014-07-26 02:28:04 -04:00
viewType := ctx . Query ( "type" )
types := [ ] string { "assigned" , "created_by" , "mentioned" }
if ! com . IsSliceContainsStr ( types , viewType ) {
viewType = "all"
}
isShowClosed := ctx . Query ( "state" ) == "closed"
2015-07-24 16:42:47 +08:00
// Must sign in to see issues about you.
2014-07-26 02:28:04 -04:00
if viewType != "all" && ! ctx . IsSigned {
2014-09-21 14:07:00 +02:00
ctx . SetCookie ( "redirect_to" , "/" + url . QueryEscape ( setting . AppSubUrl + ctx . Req . RequestURI ) , 0 , setting . AppSubUrl )
2014-09-19 20:11:34 -04:00
ctx . Redirect ( setting . AppSubUrl + "/user/login" )
2014-07-26 02:28:04 -04:00
return
}
2015-07-25 02:52:25 +08:00
var assigneeID , posterID int64
filterMode := models . FM_ALL
2014-07-26 02:28:04 -04:00
switch viewType {
case "assigned" :
2015-07-25 02:52:25 +08:00
assigneeID = ctx . User . Id
2014-07-26 02:28:04 -04:00
filterMode = models . FM_ASSIGN
case "created_by" :
2015-07-25 02:52:25 +08:00
posterID = ctx . User . Id
2014-07-26 02:28:04 -04:00
filterMode = models . FM_CREATE
case "mentioned" :
filterMode = models . FM_MENTION
}
2015-07-25 02:52:25 +08:00
var uid int64 = - 1
if ctx . IsSigned {
uid = ctx . User . Id
2014-07-26 02:28:04 -04:00
}
2015-07-25 02:52:25 +08:00
repo := ctx . Repo . Repository
2015-07-25 07:59:54 +08:00
selectLabels := ctx . Query ( "labels" )
2015-07-25 02:52:25 +08:00
milestoneID := ctx . QueryInt64 ( "milestone" )
2015-07-25 07:59:54 +08:00
issueStats := models . GetIssueStats ( repo . Id , uid , com . StrTo ( selectLabels ) . MustInt64 ( ) , isShowClosed , filterMode )
2015-07-24 16:42:47 +08:00
page := ctx . QueryInt ( "page" )
if page <= 1 {
page = 1
}
2015-07-28 03:14:37 +08:00
var total int
if ! isShowClosed {
total = int ( issueStats . OpenCount )
} else {
total = int ( issueStats . ClosedCount )
2015-07-24 16:42:47 +08:00
}
2015-07-28 03:14:37 +08:00
ctx . Data [ "Page" ] = paginater . New ( total , setting . IssuePagingNum , page , 5 )
2014-07-26 02:28:04 -04:00
// Get issues.
2015-07-25 02:52:25 +08:00
issues , err := models . GetIssues ( uid , assigneeID , repo . Id , posterID , milestoneID ,
page , isShowClosed , filterMode == models . FM_MENTION , selectLabels , ctx . Query ( "sortType" ) )
2014-07-26 02:28:04 -04:00
if err != nil {
2015-07-24 16:42:47 +08:00
ctx . Handle ( 500 , "GetIssues: %v" , err )
2014-07-26 02:28:04 -04:00
return
}
// Get issue-user pairs.
2015-07-25 02:52:25 +08:00
pairs , err := models . GetIssueUserPairs ( repo . Id , posterID , isShowClosed )
2014-07-26 02:28:04 -04:00
if err != nil {
2015-07-24 16:42:47 +08:00
ctx . Handle ( 500 , "GetIssueUserPairs: %v" , err )
2014-07-26 02:28:04 -04:00
return
}
// Get posters.
for i := range issues {
2015-07-25 02:52:25 +08:00
if err = issues [ i ] . GetPoster ( ) ; err != nil {
ctx . Handle ( 500 , "GetPoster" , fmt . Errorf ( "[#%d]%v" , issues [ i ] . ID , err ) )
return
}
2014-07-26 02:28:04 -04:00
if err = issues [ i ] . GetLabels ( ) ; err != nil {
2015-07-24 21:02:49 +08:00
ctx . Handle ( 500 , "GetLabels" , fmt . Errorf ( "[#%d]%v" , issues [ i ] . ID , err ) )
2014-07-26 02:28:04 -04:00
return
}
2015-07-25 02:52:25 +08:00
if ! ctx . IsSigned {
issues [ i ] . IsRead = true
continue
2014-07-26 02:28:04 -04:00
}
2015-07-25 02:52:25 +08:00
// Check read status.
idx := models . PairsContains ( pairs , issues [ i ] . ID , ctx . User . Id )
if idx > - 1 {
issues [ i ] . IsRead = pairs [ idx ] . IsRead
} else {
issues [ i ] . IsRead = true
2014-07-26 02:28:04 -04:00
}
}
ctx . Data [ "IssueStats" ] = issueStats
2015-07-24 16:42:47 +08:00
ctx . Data [ "SelectLabels" ] = com . StrTo ( selectLabels ) . MustInt64 ( )
2014-07-26 02:28:04 -04:00
ctx . Data [ "ViewType" ] = viewType
ctx . Data [ "Issues" ] = issues
ctx . Data [ "IsShowClosed" ] = isShowClosed
if isShowClosed {
ctx . Data [ "State" ] = "closed"
} else {
2015-07-25 02:52:25 +08:00
ctx . Data [ "State" ] = "open"
2014-07-26 02:28:04 -04:00
}
2015-07-25 02:52:25 +08:00
2014-07-26 02:28:04 -04:00
ctx . HTML ( 200 , ISSUES )
}
func CreateIssue ( ctx * middleware . Context ) {
ctx . Data [ "Title" ] = "Create issue"
ctx . Data [ "IsRepoToolbarIssues" ] = true
ctx . Data [ "IsRepoToolbarIssuesList" ] = false
ctx . Data [ "AttachmentsEnabled" ] = setting . AttachmentEnabled
var err error
// Get all milestones.
2015-08-03 17:42:09 +08:00
ctx . Data [ "OpenMilestones" ] , err = models . Milestones ( ctx . Repo . Repository . Id , false )
2014-07-26 02:28:04 -04:00
if err != nil {
ctx . Handle ( 500 , "issue.ViewIssue(GetMilestones.1): %v" , err )
return
}
2015-08-03 17:42:09 +08:00
ctx . Data [ "ClosedMilestones" ] , err = models . Milestones ( ctx . Repo . Repository . Id , true )
2014-07-26 02:28:04 -04:00
if err != nil {
ctx . Handle ( 500 , "issue.ViewIssue(GetMilestones.2): %v" , err )
return
}
2015-01-23 09:54:16 +02:00
us , err := ctx . Repo . Repository . GetCollaborators ( )
2014-07-26 02:28:04 -04:00
if err != nil {
ctx . Handle ( 500 , "issue.CreateIssue(GetCollaborators)" , err )
return
}
ctx . Data [ "AllowedTypes" ] = setting . AttachmentAllowedTypes
ctx . Data [ "Collaborators" ] = us
ctx . HTML ( 200 , ISSUE_CREATE )
}
func CreateIssuePost ( ctx * middleware . Context , form auth . CreateIssueForm ) {
send := func ( status int , data interface { } , err error ) {
if err != nil {
log . Error ( 4 , "issue.CreateIssuePost(?): %s" , err )
ctx . JSON ( status , map [ string ] interface { } {
"ok" : false ,
"status" : status ,
"error" : err . Error ( ) ,
} )
} else {
ctx . JSON ( status , map [ string ] interface { } {
"ok" : true ,
"status" : status ,
"data" : data ,
} )
}
}
var err error
// Get all milestones.
2015-08-03 17:42:09 +08:00
_ , err = models . Milestones ( ctx . Repo . Repository . Id , false )
2014-07-26 02:28:04 -04:00
if err != nil {
send ( 500 , nil , err )
return
}
2015-08-03 17:42:09 +08:00
_ , err = models . Milestones ( ctx . Repo . Repository . Id , true )
2014-07-26 02:28:04 -04:00
if err != nil {
send ( 500 , nil , err )
return
}
2015-01-23 09:54:16 +02:00
_ , err = ctx . Repo . Repository . GetCollaborators ( )
2014-07-26 02:28:04 -04:00
if err != nil {
send ( 500 , nil , err )
return
}
if ctx . HasError ( ) {
send ( 400 , nil , errors . New ( ctx . Flash . ErrorMsg ) )
return
}
// Only collaborators can assign.
2015-02-16 12:51:56 +02:00
if ! ctx . Repo . IsOwner ( ) {
2014-07-26 02:28:04 -04:00
form . AssigneeId = 0
}
issue := & models . Issue {
RepoId : ctx . Repo . Repository . Id ,
Index : int64 ( ctx . Repo . Repository . NumIssues ) + 1 ,
Name : form . IssueName ,
PosterId : ctx . User . Id ,
MilestoneId : form . MilestoneId ,
AssigneeId : form . AssigneeId ,
LabelIds : form . Labels ,
Content : form . Content ,
}
if err := models . NewIssue ( issue ) ; err != nil {
send ( 500 , nil , err )
return
2015-07-24 21:02:49 +08:00
} else if err := models . NewIssueUserPairs ( ctx . Repo . Repository , issue . ID , ctx . Repo . Owner . Id ,
2015-01-23 09:54:16 +02:00
ctx . User . Id , form . AssigneeId ) ; err != nil {
2014-07-26 02:28:04 -04:00
send ( 500 , nil , err )
return
}
if setting . AttachmentEnabled {
2015-07-24 21:02:49 +08:00
uploadFiles ( ctx , issue . ID , 0 )
2014-07-26 02:28:04 -04:00
}
// Update mentions.
ms := base . MentionPattern . FindAllString ( issue . Content , - 1 )
if len ( ms ) > 0 {
for i := range ms {
ms [ i ] = ms [ i ] [ 1 : ]
}
2015-07-24 21:02:49 +08:00
if err := models . UpdateMentions ( ms , issue . ID ) ; err != nil {
2014-07-26 02:28:04 -04:00
send ( 500 , nil , err )
return
}
}
act := & models . Action {
2015-03-17 21:51:39 -04:00
ActUserID : ctx . User . Id ,
2014-07-26 02:28:04 -04:00
ActUserName : ctx . User . Name ,
ActEmail : ctx . User . Email ,
OpType : models . CREATE_ISSUE ,
Content : fmt . Sprintf ( "%d|%s" , issue . Index , issue . Name ) ,
2015-03-17 21:51:39 -04:00
RepoID : ctx . Repo . Repository . Id ,
2014-07-26 02:28:04 -04:00
RepoUserName : ctx . Repo . Owner . Name ,
RepoName : ctx . Repo . Repository . Name ,
RefName : ctx . Repo . BranchName ,
IsPrivate : ctx . Repo . Repository . IsPrivate ,
}
// Notify watchers.
if err := models . NotifyWatchers ( act ) ; err != nil {
send ( 500 , nil , err )
return
}
// Mail watchers and mentions.
if setting . Service . EnableNotifyMail {
tos , err := mailer . SendIssueNotifyMail ( ctx . User , ctx . Repo . Owner , ctx . Repo . Repository , issue )
if err != nil {
send ( 500 , nil , err )
return
}
tos = append ( tos , ctx . User . LowerName )
newTos := make ( [ ] string , 0 , len ( ms ) )
for _ , m := range ms {
if com . IsSliceContainsStr ( tos , m ) {
continue
}
newTos = append ( newTos , m )
}
if err = mailer . SendIssueMentionMail ( ctx . Render , ctx . User , ctx . Repo . Owner ,
ctx . Repo . Repository , issue , models . GetUserEmailsByNames ( newTos ) ) ; err != nil {
send ( 500 , nil , err )
return
}
}
2015-07-24 21:02:49 +08:00
log . Trace ( "%d Issue created: %d" , ctx . Repo . Repository . Id , issue . ID )
2014-07-26 02:28:04 -04:00
2014-09-19 20:11:34 -04:00
send ( 200 , fmt . Sprintf ( "%s/%s/%s/issues/%d" , setting . AppSubUrl , ctx . Params ( ":username" ) , ctx . Params ( ":reponame" ) , issue . Index ) , nil )
2014-07-26 02:28:04 -04:00
}
func checkLabels ( labels , allLabels [ ] * models . Label ) {
for _ , l := range labels {
for _ , l2 := range allLabels {
2015-07-24 21:02:49 +08:00
if l . ID == l2 . ID {
2014-07-26 02:28:04 -04:00
l2 . IsChecked = true
break
}
}
}
}
func ViewIssue ( ctx * middleware . Context ) {
ctx . Data [ "AttachmentsEnabled" ] = setting . AttachmentEnabled
idx := com . StrTo ( ctx . Params ( ":index" ) ) . MustInt64 ( )
if idx == 0 {
ctx . Handle ( 404 , "issue.ViewIssue" , nil )
return
}
issue , err := models . GetIssueByIndex ( ctx . Repo . Repository . Id , idx )
if err != nil {
if err == models . ErrIssueNotExist {
ctx . Handle ( 404 , "issue.ViewIssue(GetIssueByIndex)" , err )
} else {
ctx . Handle ( 500 , "issue.ViewIssue(GetIssueByIndex)" , err )
}
return
}
// Get labels.
if err = issue . GetLabels ( ) ; err != nil {
ctx . Handle ( 500 , "issue.ViewIssue(GetLabels)" , err )
return
}
labels , err := models . GetLabels ( ctx . Repo . Repository . Id )
if err != nil {
ctx . Handle ( 500 , "issue.ViewIssue(GetLabels.2)" , err )
return
}
checkLabels ( issue . Labels , labels )
ctx . Data [ "Labels" ] = labels
// Get assigned milestone.
if issue . MilestoneId > 0 {
ctx . Data [ "Milestone" ] , err = models . GetMilestoneById ( issue . MilestoneId )
if err != nil {
if err == models . ErrMilestoneNotExist {
log . Warn ( "issue.ViewIssue(GetMilestoneById): %v" , err )
} else {
ctx . Handle ( 500 , "issue.ViewIssue(GetMilestoneById)" , err )
return
}
}
}
// Get all milestones.
2015-08-03 17:42:09 +08:00
ctx . Data [ "OpenMilestones" ] , err = models . Milestones ( ctx . Repo . Repository . Id , false )
2014-07-26 02:28:04 -04:00
if err != nil {
ctx . Handle ( 500 , "issue.ViewIssue(GetMilestones.1): %v" , err )
return
}
2015-08-03 17:42:09 +08:00
ctx . Data [ "ClosedMilestones" ] , err = models . Milestones ( ctx . Repo . Repository . Id , true )
2014-07-26 02:28:04 -04:00
if err != nil {
ctx . Handle ( 500 , "issue.ViewIssue(GetMilestones.2): %v" , err )
return
}
// Get all collaborators.
2015-01-23 09:54:16 +02:00
ctx . Data [ "Collaborators" ] , err = ctx . Repo . Repository . GetCollaborators ( )
2014-07-26 02:28:04 -04:00
if err != nil {
ctx . Handle ( 500 , "issue.CreateIssue(GetCollaborators)" , err )
return
}
if ctx . IsSigned {
// Update issue-user.
2015-07-24 21:02:49 +08:00
if err = models . UpdateIssueUserPairByRead ( ctx . User . Id , issue . ID ) ; err != nil {
2014-07-26 02:28:04 -04:00
ctx . Handle ( 500 , "issue.ViewIssue(UpdateIssueUserPairByRead): %v" , err )
return
}
}
// Get poster and Assignee.
if err = issue . GetPoster ( ) ; err != nil {
ctx . Handle ( 500 , "issue.ViewIssue(GetPoster): %v" , err )
return
} else if err = issue . GetAssignee ( ) ; err != nil {
ctx . Handle ( 500 , "issue.ViewIssue(GetAssignee): %v" , err )
return
}
issue . RenderedContent = string ( base . RenderMarkdown ( [ ] byte ( issue . Content ) , ctx . Repo . RepoLink ) )
// Get comments.
2015-07-24 21:02:49 +08:00
comments , err := models . GetIssueComments ( issue . ID )
2014-07-26 02:28:04 -04:00
if err != nil {
ctx . Handle ( 500 , "issue.ViewIssue(GetIssueComments): %v" , err )
return
}
// Get posters.
for i := range comments {
u , err := models . GetUserById ( comments [ i ] . PosterId )
if err != nil {
ctx . Handle ( 500 , "issue.ViewIssue(GetUserById.2): %v" , err )
return
}
comments [ i ] . Poster = u
2015-02-06 20:47:21 -05:00
if comments [ i ] . Type == models . COMMENT_TYPE_COMMENT {
2014-07-26 02:28:04 -04:00
comments [ i ] . Content = string ( base . RenderMarkdown ( [ ] byte ( comments [ i ] . Content ) , ctx . Repo . RepoLink ) )
}
}
ctx . Data [ "AllowedTypes" ] = setting . AttachmentAllowedTypes
ctx . Data [ "Title" ] = issue . Name
ctx . Data [ "Issue" ] = issue
ctx . Data [ "Comments" ] = comments
2015-02-16 12:51:56 +02:00
ctx . Data [ "IsIssueOwner" ] = ctx . Repo . IsOwner ( ) || ( ctx . IsSigned && issue . PosterId == ctx . User . Id )
2014-07-26 02:28:04 -04:00
ctx . Data [ "IsRepoToolbarIssues" ] = true
ctx . Data [ "IsRepoToolbarIssuesList" ] = false
ctx . HTML ( 200 , ISSUE_VIEW )
}
func UpdateIssue ( ctx * middleware . Context , form auth . CreateIssueForm ) {
idx := com . StrTo ( ctx . Params ( ":index" ) ) . MustInt64 ( )
if idx <= 0 {
ctx . Error ( 404 )
return
}
issue , err := models . GetIssueByIndex ( ctx . Repo . Repository . Id , idx )
if err != nil {
if err == models . ErrIssueNotExist {
ctx . Handle ( 404 , "issue.UpdateIssue" , err )
} else {
ctx . Handle ( 500 , "issue.UpdateIssue(GetIssueByIndex)" , err )
}
return
}
2015-02-16 12:51:56 +02:00
if ctx . User . Id != issue . PosterId && ! ctx . Repo . IsOwner ( ) {
2014-07-26 02:28:04 -04:00
ctx . Error ( 403 )
return
}
issue . Name = form . IssueName
2014-10-22 14:52:49 +08:00
//issue.MilestoneId = form.MilestoneId
//issue.AssigneeId = form.AssigneeId
//issue.LabelIds = form.Labels
2014-07-26 02:28:04 -04:00
issue . Content = form . Content
// try get content from text, ignore conflict with preview ajax
if form . Content == "" {
issue . Content = ctx . Query ( "text" )
}
if err = models . UpdateIssue ( issue ) ; err != nil {
ctx . Handle ( 500 , "issue.UpdateIssue(UpdateIssue)" , err )
return
}
ctx . JSON ( 200 , map [ string ] interface { } {
"ok" : true ,
"title" : issue . Name ,
"content" : string ( base . RenderMarkdown ( [ ] byte ( issue . Content ) , ctx . Repo . RepoLink ) ) ,
} )
}
func UpdateIssueLabel ( ctx * middleware . Context ) {
2015-02-16 12:51:56 +02:00
if ! ctx . Repo . IsOwner ( ) {
2014-07-26 02:28:04 -04:00
ctx . Error ( 403 )
return
}
idx := com . StrTo ( ctx . Params ( ":index" ) ) . MustInt64 ( )
if idx <= 0 {
ctx . Error ( 404 )
return
}
issue , err := models . GetIssueByIndex ( ctx . Repo . Repository . Id , idx )
if err != nil {
if err == models . ErrIssueNotExist {
ctx . Handle ( 404 , "issue.UpdateIssueLabel(GetIssueByIndex)" , err )
} else {
ctx . Handle ( 500 , "issue.UpdateIssueLabel(GetIssueByIndex)" , err )
}
return
}
isAttach := ctx . Query ( "action" ) == "attach"
labelStrId := ctx . Query ( "id" )
labelId := com . StrTo ( labelStrId ) . MustInt64 ( )
label , err := models . GetLabelById ( labelId )
if err != nil {
if err == models . ErrLabelNotExist {
ctx . Handle ( 404 , "issue.UpdateIssueLabel(GetLabelById)" , err )
} else {
ctx . Handle ( 500 , "issue.UpdateIssueLabel(GetLabelById)" , err )
}
return
}
isHad := strings . Contains ( issue . LabelIds , "$" + labelStrId + "|" )
isNeedUpdate := false
if isAttach {
if ! isHad {
issue . LabelIds += "$" + labelStrId + "|"
isNeedUpdate = true
}
} else {
if isHad {
issue . LabelIds = strings . Replace ( issue . LabelIds , "$" + labelStrId + "|" , "" , - 1 )
isNeedUpdate = true
}
}
if isNeedUpdate {
if err = models . UpdateIssue ( issue ) ; err != nil {
ctx . Handle ( 500 , "issue.UpdateIssueLabel(UpdateIssue)" , err )
return
}
if isAttach {
label . NumIssues ++
if issue . IsClosed {
label . NumClosedIssues ++
}
} else {
label . NumIssues --
if issue . IsClosed {
label . NumClosedIssues --
}
}
2015-02-23 20:32:44 +01:00
2014-07-26 02:28:04 -04:00
if err = models . UpdateLabel ( label ) ; err != nil {
ctx . Handle ( 500 , "issue.UpdateIssueLabel(UpdateLabel)" , err )
return
}
}
ctx . JSON ( 200 , map [ string ] interface { } {
"ok" : true ,
} )
}
func UpdateIssueMilestone ( ctx * middleware . Context ) {
2015-02-16 12:51:56 +02:00
if ! ctx . Repo . IsOwner ( ) {
2014-07-26 02:28:04 -04:00
ctx . Error ( 403 )
return
}
2014-10-21 16:27:01 +08:00
issueId := com . StrTo ( ctx . Query ( "issue" ) ) . MustInt64 ( )
2014-07-26 02:28:04 -04:00
if issueId == 0 {
ctx . Error ( 404 )
return
}
issue , err := models . GetIssueById ( issueId )
if err != nil {
if err == models . ErrIssueNotExist {
ctx . Handle ( 404 , "issue.UpdateIssueMilestone(GetIssueById)" , err )
} else {
ctx . Handle ( 500 , "issue.UpdateIssueMilestone(GetIssueById)" , err )
}
return
}
oldMid := issue . MilestoneId
2014-09-03 00:04:22 +07:00
mid := com . StrTo ( ctx . Query ( "milestoneid" ) ) . MustInt64 ( )
2014-07-26 02:28:04 -04:00
if oldMid == mid {
ctx . JSON ( 200 , map [ string ] interface { } {
"ok" : true ,
} )
return
}
// Not check for invalid milestone id and give responsibility to owners.
issue . MilestoneId = mid
if err = models . ChangeMilestoneAssign ( oldMid , mid , issue ) ; err != nil {
ctx . Handle ( 500 , "issue.UpdateIssueMilestone(ChangeMilestoneAssign)" , err )
return
} else if err = models . UpdateIssue ( issue ) ; err != nil {
ctx . Handle ( 500 , "issue.UpdateIssueMilestone(UpdateIssue)" , err )
return
}
ctx . JSON ( 200 , map [ string ] interface { } {
"ok" : true ,
} )
}
func UpdateAssignee ( ctx * middleware . Context ) {
2015-02-16 12:51:56 +02:00
if ! ctx . Repo . IsOwner ( ) {
2014-07-26 02:28:04 -04:00
ctx . Error ( 403 )
return
}
2014-10-21 16:27:01 +08:00
issueId := com . StrTo ( ctx . Query ( "issue" ) ) . MustInt64 ( )
2014-07-26 02:28:04 -04:00
if issueId == 0 {
ctx . Error ( 404 )
return
}
issue , err := models . GetIssueById ( issueId )
if err != nil {
if err == models . ErrIssueNotExist {
ctx . Handle ( 404 , "GetIssueById" , err )
} else {
ctx . Handle ( 500 , "GetIssueById" , err )
}
return
}
2014-09-03 00:04:22 +07:00
aid := com . StrTo ( ctx . Query ( "assigneeid" ) ) . MustInt64 ( )
2014-12-06 20:22:48 -05:00
// Not check for invalid assignee id and give responsibility to owners.
2014-07-26 02:28:04 -04:00
issue . AssigneeId = aid
2015-07-24 21:02:49 +08:00
if err = models . UpdateIssueUserPairByAssignee ( aid , issue . ID ) ; err != nil {
2014-07-26 02:28:04 -04:00
ctx . Handle ( 500 , "UpdateIssueUserPairByAssignee: %v" , err )
return
} else if err = models . UpdateIssue ( issue ) ; err != nil {
ctx . Handle ( 500 , "UpdateIssue" , err )
return
}
ctx . JSON ( 200 , map [ string ] interface { } {
"ok" : true ,
} )
}
func uploadFiles ( ctx * middleware . Context , issueId , commentId int64 ) {
if ! setting . AttachmentEnabled {
return
}
allowedTypes := strings . Split ( setting . AttachmentAllowedTypes , "|" )
attachments := ctx . Req . MultipartForm . File [ "attachments" ]
if len ( attachments ) > setting . AttachmentMaxFiles {
ctx . Handle ( 400 , "issue.Comment" , ErrTooManyFiles )
return
}
for _ , header := range attachments {
file , err := header . Open ( )
if err != nil {
ctx . Handle ( 500 , "issue.Comment(header.Open)" , err )
return
}
defer file . Close ( )
buf := make ( [ ] byte , 1024 )
n , _ := file . Read ( buf )
if n > 0 {
buf = buf [ : n ]
}
fileType := http . DetectContentType ( buf )
fmt . Println ( fileType )
allowed := false
for _ , t := range allowedTypes {
t := strings . Trim ( t , " " )
if t == "*/*" || t == fileType {
allowed = true
break
}
}
if ! allowed {
ctx . Handle ( 400 , "issue.Comment" , ErrFileTypeForbidden )
return
}
out , err := ioutil . TempFile ( setting . AttachmentPath , "attachment_" )
if err != nil {
ctx . Handle ( 500 , "ioutil.TempFile" , err )
return
}
defer out . Close ( )
out . Write ( buf )
_ , err = io . Copy ( out , file )
if err != nil {
ctx . Handle ( 500 , "io.Copy" , err )
return
}
_ , err = models . CreateAttachment ( issueId , commentId , header . Filename , out . Name ( ) )
if err != nil {
ctx . Handle ( 500 , "CreateAttachment" , err )
return
}
}
}
func Comment ( ctx * middleware . Context ) {
send := func ( status int , data interface { } , err error ) {
if err != nil {
log . Error ( 4 , "issue.Comment(?): %s" , err )
ctx . JSON ( status , map [ string ] interface { } {
"ok" : false ,
"status" : status ,
"error" : err . Error ( ) ,
} )
} else {
ctx . JSON ( status , map [ string ] interface { } {
"ok" : true ,
"status" : status ,
"data" : data ,
} )
}
}
index := com . StrTo ( ctx . Query ( "issueIndex" ) ) . MustInt64 ( )
if index == 0 {
ctx . Error ( 404 )
return
}
issue , err := models . GetIssueByIndex ( ctx . Repo . Repository . Id , index )
if err != nil {
if err == models . ErrIssueNotExist {
send ( 404 , nil , err )
} else {
send ( 200 , nil , err )
}
return
}
// Check if issue owner changes the status of issue.
var newStatus string
2015-02-16 12:51:56 +02:00
if ctx . Repo . IsOwner ( ) || issue . PosterId == ctx . User . Id {
2014-07-26 02:28:04 -04:00
newStatus = ctx . Query ( "change_status" )
}
if len ( newStatus ) > 0 {
if ( strings . Contains ( newStatus , "Reopen" ) && issue . IsClosed ) ||
( strings . Contains ( newStatus , "Close" ) && ! issue . IsClosed ) {
issue . IsClosed = ! issue . IsClosed
if err = models . UpdateIssue ( issue ) ; err != nil {
send ( 500 , nil , err )
return
2015-07-24 21:02:49 +08:00
} else if err = models . UpdateIssueUserPairsByStatus ( issue . ID , issue . IsClosed ) ; err != nil {
2014-07-26 02:28:04 -04:00
send ( 500 , nil , err )
return
}
2015-02-23 20:32:44 +01:00
if err = issue . GetLabels ( ) ; err != nil {
send ( 500 , nil , err )
return
}
for _ , label := range issue . Labels {
if issue . IsClosed {
label . NumClosedIssues ++
} else {
label . NumClosedIssues --
}
if err = models . UpdateLabel ( label ) ; err != nil {
send ( 500 , nil , err )
return
}
}
2014-07-26 02:28:04 -04:00
// Change open/closed issue counter for the associated milestone
if issue . MilestoneId > 0 {
if err = models . ChangeMilestoneIssueStats ( issue ) ; err != nil {
send ( 500 , nil , err )
}
}
2015-02-06 20:47:21 -05:00
cmtType := models . COMMENT_TYPE_CLOSE
2014-07-26 02:28:04 -04:00
if ! issue . IsClosed {
2015-02-06 20:47:21 -05:00
cmtType = models . COMMENT_TYPE_REOPEN
2014-07-26 02:28:04 -04:00
}
2015-07-24 21:02:49 +08:00
if _ , err = models . CreateComment ( ctx . User . Id , ctx . Repo . Repository . Id , issue . ID , 0 , 0 , cmtType , "" , nil ) ; err != nil {
2014-07-26 02:28:04 -04:00
send ( 200 , nil , err )
return
}
2015-07-24 21:02:49 +08:00
log . Trace ( "%s Issue(%d) status changed: %v" , ctx . Req . RequestURI , issue . ID , ! issue . IsClosed )
2014-07-26 02:28:04 -04:00
}
}
var comment * models . Comment
var ms [ ] string
content := ctx . Query ( "content" )
// Fix #321. Allow empty comments, as long as we have attachments.
if len ( content ) > 0 || len ( ctx . Req . MultipartForm . File [ "attachments" ] ) > 0 {
switch ctx . Params ( ":action" ) {
case "new" :
2015-07-24 21:02:49 +08:00
if comment , err = models . CreateComment ( ctx . User . Id , ctx . Repo . Repository . Id , issue . ID , 0 , 0 , models . COMMENT_TYPE_COMMENT , content , nil ) ; err != nil {
2014-07-26 02:28:04 -04:00
send ( 500 , nil , err )
return
}
// Update mentions.
ms = base . MentionPattern . FindAllString ( issue . Content , - 1 )
if len ( ms ) > 0 {
for i := range ms {
ms [ i ] = ms [ i ] [ 1 : ]
}
2015-07-24 21:02:49 +08:00
if err := models . UpdateMentions ( ms , issue . ID ) ; err != nil {
2014-07-26 02:28:04 -04:00
send ( 500 , nil , err )
return
}
}
2015-07-24 21:02:49 +08:00
log . Trace ( "%s Comment created: %d" , ctx . Req . RequestURI , issue . ID )
2014-07-26 02:28:04 -04:00
default :
ctx . Handle ( 404 , "issue.Comment" , err )
return
}
}
if comment != nil {
2015-07-24 21:02:49 +08:00
uploadFiles ( ctx , issue . ID , comment . Id )
2014-07-26 02:28:04 -04:00
}
// Notify watchers.
act := & models . Action {
2015-03-17 21:51:39 -04:00
ActUserID : ctx . User . Id ,
2014-07-26 02:28:04 -04:00
ActUserName : ctx . User . LowerName ,
ActEmail : ctx . User . Email ,
OpType : models . COMMENT_ISSUE ,
Content : fmt . Sprintf ( "%d|%s" , issue . Index , strings . Split ( content , "\n" ) [ 0 ] ) ,
2015-03-17 21:51:39 -04:00
RepoID : ctx . Repo . Repository . Id ,
2014-07-26 02:28:04 -04:00
RepoUserName : ctx . Repo . Owner . LowerName ,
RepoName : ctx . Repo . Repository . LowerName ,
2015-07-10 16:51:25 +08:00
IsPrivate : ctx . Repo . Repository . IsPrivate ,
2014-07-26 02:28:04 -04:00
}
if err = models . NotifyWatchers ( act ) ; err != nil {
send ( 500 , nil , err )
return
}
// Mail watchers and mentions.
if setting . Service . EnableNotifyMail {
issue . Content = content
tos , err := mailer . SendIssueNotifyMail ( ctx . User , ctx . Repo . Owner , ctx . Repo . Repository , issue )
if err != nil {
send ( 500 , nil , err )
return
}
tos = append ( tos , ctx . User . LowerName )
newTos := make ( [ ] string , 0 , len ( ms ) )
for _ , m := range ms {
if com . IsSliceContainsStr ( tos , m ) {
continue
}
newTos = append ( newTos , m )
}
if err = mailer . SendIssueMentionMail ( ctx . Render , ctx . User , ctx . Repo . Owner ,
ctx . Repo . Repository , issue , models . GetUserEmailsByNames ( newTos ) ) ; err != nil {
send ( 500 , nil , err )
return
}
}
send ( 200 , fmt . Sprintf ( "%s/issues/%d" , ctx . Repo . RepoLink , index ) , nil )
}
2015-07-24 21:02:49 +08:00
func Labels ( ctx * middleware . Context ) {
ctx . Data [ "Title" ] = ctx . Tr ( "repo.labels" )
ctx . Data [ "PageIsLabels" ] = true
ctx . HTML ( 200 , LABELS )
}
2014-07-26 02:28:04 -04:00
func NewLabel ( ctx * middleware . Context , form auth . CreateLabelForm ) {
2015-07-24 21:02:49 +08:00
ctx . Data [ "Title" ] = ctx . Tr ( "repo.labels" )
ctx . Data [ "PageIsLabels" ] = true
2014-07-26 02:28:04 -04:00
if ctx . HasError ( ) {
2015-07-24 21:02:49 +08:00
ctx . Flash . Error ( ctx . Data [ "ErrorMsg" ] . ( string ) )
ctx . Redirect ( ctx . Repo . RepoLink + "/labels" )
2014-07-26 02:28:04 -04:00
return
}
l := & models . Label {
RepoId : ctx . Repo . Repository . Id ,
Name : form . Title ,
Color : form . Color ,
}
if err := models . NewLabel ( l ) ; err != nil {
2015-07-24 21:02:49 +08:00
ctx . Handle ( 500 , "NewLabel" , err )
2014-07-26 02:28:04 -04:00
return
}
2015-07-24 21:02:49 +08:00
ctx . Redirect ( ctx . Repo . RepoLink + "/labels" )
2014-07-26 02:28:04 -04:00
}
func UpdateLabel ( ctx * middleware . Context , form auth . CreateLabelForm ) {
2015-07-24 23:13:42 +08:00
l , err := models . GetLabelById ( form . ID )
if err != nil {
switch err {
case models . ErrLabelNotExist :
ctx . Error ( 404 )
default :
ctx . Handle ( 500 , "UpdateLabel" , err )
}
2014-07-26 02:28:04 -04:00
return
}
2015-07-24 23:13:42 +08:00
l . Name = form . Title
l . Color = form . Color
2014-07-26 02:28:04 -04:00
if err := models . UpdateLabel ( l ) ; err != nil {
2015-07-24 23:13:42 +08:00
ctx . Handle ( 500 , "UpdateLabel" , err )
2014-07-26 02:28:04 -04:00
return
}
2015-07-24 23:13:42 +08:00
ctx . Redirect ( ctx . Repo . RepoLink + "/labels" )
2014-07-26 02:28:04 -04:00
}
func DeleteLabel ( ctx * middleware . Context ) {
2015-07-24 23:13:42 +08:00
id := ctx . QueryInt64 ( "id" )
if id > 0 {
if err := models . DeleteLabel ( ctx . Repo . Repository . Id , id ) ; err != nil {
ctx . Flash . Error ( "DeleteLabel: " + err . Error ( ) )
} else {
ctx . Flash . Success ( ctx . Tr ( "repo.issues.label_deletion_success" ) )
2014-07-26 02:28:04 -04:00
}
}
ctx . JSON ( 200 , map [ string ] interface { } {
2015-07-24 23:13:42 +08:00
"redirect" : ctx . Repo . RepoLink + "/labels" ,
2014-07-26 02:28:04 -04:00
} )
2015-07-24 23:13:42 +08:00
return
2014-07-26 02:28:04 -04:00
}
func Milestones ( ctx * middleware . Context ) {
2015-08-03 17:42:09 +08:00
ctx . Data [ "Title" ] = ctx . Tr ( "repo.milestones" )
ctx . Data [ "PageIsMilestones" ] = true
2014-07-26 02:28:04 -04:00
isShowClosed := ctx . Query ( "state" ) == "closed"
2015-08-03 17:42:09 +08:00
miles , err := models . Milestones ( ctx . Repo . Repository . Id , isShowClosed )
2014-07-26 02:28:04 -04:00
if err != nil {
2015-07-30 20:00:57 +08:00
ctx . Handle ( 500 , "GetMilestones" , err )
2014-07-26 02:28:04 -04:00
return
}
for _ , m := range miles {
2014-09-05 09:10:58 +07:00
m . RenderedContent = string ( base . RenderMarkdown ( [ ] byte ( m . Content ) , ctx . Repo . RepoLink ) )
2014-07-26 02:28:04 -04:00
m . CalOpenIssues ( )
}
ctx . Data [ "Milestones" ] = miles
2015-08-03 17:42:09 +08:00
openCount , closedCount := models . MilestoneStats ( ctx . Repo . Repository . Id )
ctx . Data [ "OpenCount" ] = openCount
ctx . Data [ "ClosedCount" ] = closedCount
2014-07-26 02:28:04 -04:00
if isShowClosed {
ctx . Data [ "State" ] = "closed"
} else {
ctx . Data [ "State" ] = "open"
}
2015-08-03 17:42:09 +08:00
ctx . Data [ "IsShowClosed" ] = isShowClosed
2014-07-26 02:28:04 -04:00
ctx . HTML ( 200 , MILESTONE )
}
func NewMilestone ( ctx * middleware . Context ) {
ctx . Data [ "Title" ] = "New Milestone"
ctx . Data [ "IsRepoToolbarIssues" ] = true
ctx . Data [ "IsRepoToolbarIssuesList" ] = true
ctx . HTML ( 200 , MILESTONE_NEW )
}
func NewMilestonePost ( ctx * middleware . Context , form auth . CreateMilestoneForm ) {
ctx . Data [ "Title" ] = "New Milestone"
ctx . Data [ "IsRepoToolbarIssues" ] = true
ctx . Data [ "IsRepoToolbarIssuesList" ] = true
if ctx . HasError ( ) {
ctx . HTML ( 200 , MILESTONE_NEW )
return
}
var deadline time . Time
var err error
if len ( form . Deadline ) == 0 {
form . Deadline = "12/31/9999"
}
deadline , err = time . Parse ( "01/02/2006" , form . Deadline )
if err != nil {
2015-07-30 20:00:57 +08:00
ctx . Handle ( 500 , "time.Parse" , err )
2014-07-26 02:28:04 -04:00
return
}
mile := & models . Milestone {
RepoId : ctx . Repo . Repository . Id ,
Index : int64 ( ctx . Repo . Repository . NumMilestones ) + 1 ,
Name : form . Title ,
Content : form . Content ,
Deadline : deadline ,
}
if err = models . NewMilestone ( mile ) ; err != nil {
2015-07-30 20:00:57 +08:00
ctx . Handle ( 500 , "NewMilestone" , err )
2014-07-26 02:28:04 -04:00
return
}
2015-07-30 20:00:57 +08:00
ctx . Redirect ( ctx . Repo . RepoLink + "/milestones" )
2014-07-26 02:28:04 -04:00
}
func UpdateMilestone ( ctx * middleware . Context ) {
ctx . Data [ "Title" ] = "Update Milestone"
ctx . Data [ "IsRepoToolbarIssues" ] = true
ctx . Data [ "IsRepoToolbarIssuesList" ] = true
2015-07-30 20:00:57 +08:00
idx := ctx . ParamsInt64 ( ":index" )
2014-07-26 02:28:04 -04:00
if idx == 0 {
ctx . Handle ( 404 , "issue.UpdateMilestone" , nil )
return
}
mile , err := models . GetMilestoneByIndex ( ctx . Repo . Repository . Id , idx )
if err != nil {
if err == models . ErrMilestoneNotExist {
2015-07-30 20:00:57 +08:00
ctx . Handle ( 404 , "GetMilestoneByIndex" , err )
2014-07-26 02:28:04 -04:00
} else {
2015-07-30 20:00:57 +08:00
ctx . Handle ( 500 , "GetMilestoneByIndex" , err )
2014-07-26 02:28:04 -04:00
}
return
}
action := ctx . Params ( ":action" )
if len ( action ) > 0 {
switch action {
case "open" :
if mile . IsClosed {
if err = models . ChangeMilestoneStatus ( mile , false ) ; err != nil {
2015-07-30 20:00:57 +08:00
ctx . Handle ( 500 , "ChangeMilestoneStatus" , err )
2014-07-26 02:28:04 -04:00
return
}
}
case "close" :
if ! mile . IsClosed {
mile . ClosedDate = time . Now ( )
if err = models . ChangeMilestoneStatus ( mile , true ) ; err != nil {
2015-07-30 20:00:57 +08:00
ctx . Handle ( 500 , "ChangeMilestoneStatus" , err )
2014-07-26 02:28:04 -04:00
return
}
}
case "delete" :
if err = models . DeleteMilestone ( mile ) ; err != nil {
2015-07-30 20:00:57 +08:00
ctx . Handle ( 500 , "DeleteMilestone" , err )
2014-07-26 02:28:04 -04:00
return
}
}
2015-07-30 20:00:57 +08:00
ctx . Redirect ( ctx . Repo . RepoLink + "/milestones" )
2014-07-26 02:28:04 -04:00
return
}
mile . DeadlineString = mile . Deadline . UTC ( ) . Format ( "01/02/2006" )
if mile . DeadlineString == "12/31/9999" {
mile . DeadlineString = ""
}
ctx . Data [ "Milestone" ] = mile
ctx . HTML ( 200 , MILESTONE_EDIT )
}
func UpdateMilestonePost ( ctx * middleware . Context , form auth . CreateMilestoneForm ) {
ctx . Data [ "Title" ] = "Update Milestone"
ctx . Data [ "IsRepoToolbarIssues" ] = true
ctx . Data [ "IsRepoToolbarIssuesList" ] = true
2015-07-30 20:00:57 +08:00
idx := ctx . ParamsInt64 ( ":index" )
2014-07-26 02:28:04 -04:00
if idx == 0 {
ctx . Handle ( 404 , "issue.UpdateMilestonePost" , nil )
return
}
mile , err := models . GetMilestoneByIndex ( ctx . Repo . Repository . Id , idx )
if err != nil {
if err == models . ErrMilestoneNotExist {
2015-07-30 20:00:57 +08:00
ctx . Handle ( 404 , "GetMilestoneByIndex" , err )
2014-07-26 02:28:04 -04:00
} else {
2015-07-30 20:00:57 +08:00
ctx . Handle ( 500 , "GetMilestoneByIndex" , err )
2014-07-26 02:28:04 -04:00
}
return
}
if ctx . HasError ( ) {
ctx . HTML ( 200 , MILESTONE_EDIT )
return
}
var deadline time . Time
if len ( form . Deadline ) == 0 {
form . Deadline = "12/31/9999"
}
deadline , err = time . Parse ( "01/02/2006" , form . Deadline )
if err != nil {
2015-07-30 20:00:57 +08:00
ctx . Handle ( 500 , "time.Parse" , err )
2014-07-26 02:28:04 -04:00
return
}
mile . Name = form . Title
mile . Content = form . Content
mile . Deadline = deadline
if err = models . UpdateMilestone ( mile ) ; err != nil {
2015-07-30 20:00:57 +08:00
ctx . Handle ( 500 , "UpdateMilestone" , err )
2014-07-26 02:28:04 -04:00
return
}
2015-07-30 20:00:57 +08:00
ctx . Redirect ( ctx . Repo . RepoLink + "/milestones" )
2014-07-26 02:28:04 -04:00
}
func IssueGetAttachment ( ctx * middleware . Context ) {
id := com . StrTo ( ctx . Params ( ":id" ) ) . MustInt64 ( )
if id == 0 {
ctx . Error ( 404 )
return
}
attachment , err := models . GetAttachmentById ( id )
if err != nil {
2015-07-30 20:00:57 +08:00
ctx . Handle ( 404 , "models.GetAttachmentById" , err )
2014-07-26 02:28:04 -04:00
return
}
// Fix #312. Attachments with , in their name are not handled correctly by Google Chrome.
// We must put the name in " manually.
ctx . ServeFile ( attachment . Path , "\"" + attachment . Name + "\"" )
}
2014-09-27 19:03:07 +08:00
2015-02-06 20:47:21 -05:00
func PullRequest2 ( ctx * middleware . Context ) {
ctx . HTML ( 200 , "repo/pr2/list" )
2014-11-17 23:07:34 +08:00
}
2015-02-06 20:47:21 -05:00
func Milestones2 ( ctx * middleware . Context ) {
ctx . HTML ( 200 , "repo/milestone2/list" )
2014-11-24 22:33:04 +08:00
}