2015-11-26 04:10:25 +03:00
// Copyright 2015 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
import (
2017-02-14 04:13:59 +03:00
"fmt"
2015-11-27 08:24:24 +03:00
"io/ioutil"
2017-02-14 04:13:59 +03:00
"net/url"
"path/filepath"
2015-11-27 09:50:38 +03:00
"strings"
2015-11-27 10:16:12 +03:00
"time"
2015-11-27 08:24:24 +03:00
2016-11-10 19:24:48 +03:00
"code.gitea.io/git"
2015-11-27 08:24:24 +03:00
2016-11-10 19:24:48 +03:00
"code.gitea.io/gitea/models"
"code.gitea.io/gitea/modules/auth"
"code.gitea.io/gitea/modules/base"
"code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/markdown"
2017-04-21 10:01:08 +03:00
"code.gitea.io/gitea/modules/markup"
2015-11-26 04:10:25 +03:00
)
const (
2016-11-21 13:03:42 +03:00
tplWikiStart base . TplName = "repo/wiki/start"
tplWikiView base . TplName = "repo/wiki/view"
tplWikiNew base . TplName = "repo/wiki/new"
tplWikiPages base . TplName = "repo/wiki/pages"
2015-11-26 04:10:25 +03:00
)
2016-11-21 13:03:42 +03:00
// MustEnableWiki check if wiki is enabled, if external then redirect
2016-03-11 19:56:52 +03:00
func MustEnableWiki ( ctx * context . Context ) {
2017-02-04 18:53:46 +03:00
if ! ctx . Repo . Repository . EnableUnit ( models . UnitTypeWiki ) &&
! ctx . Repo . Repository . EnableUnit ( models . UnitTypeExternalWiki ) {
2015-12-05 05:30:33 +03:00
ctx . Handle ( 404 , "MustEnableWiki" , nil )
2015-12-11 12:55:08 +03:00
return
}
2017-02-04 18:53:46 +03:00
unit , err := ctx . Repo . Repository . GetUnit ( models . UnitTypeExternalWiki )
if err == nil {
ctx . Redirect ( unit . ExternalWikiConfig ( ) . ExternalWikiURL )
2015-12-11 12:55:08 +03:00
return
2015-12-05 05:30:33 +03:00
}
}
2016-11-21 13:03:42 +03:00
// PageMeta wiki page meat information
2015-11-27 09:50:38 +03:00
type PageMeta struct {
2015-11-27 10:16:12 +03:00
Name string
URL string
Updated time . Time
2015-11-27 09:50:38 +03:00
}
2015-11-26 04:10:25 +03:00
2017-02-14 04:13:59 +03:00
func urlEncoded ( str string ) string {
u , err := url . Parse ( str )
if err != nil {
return str
}
return u . String ( )
}
func urlDecoded ( str string ) string {
res , err := url . QueryUnescape ( str )
if err != nil {
return str
}
return res
}
// commitTreeBlobEntry processes found file and checks if it matches search target
func commitTreeBlobEntry ( entry * git . TreeEntry , path string , targets [ ] string , textOnly bool ) * git . TreeEntry {
name := entry . Name ( )
ext := filepath . Ext ( name )
if ! textOnly || markdown . IsMarkdownFile ( name ) || ext == ".textile" {
for _ , target := range targets {
if matchName ( path , target ) || matchName ( urlEncoded ( path ) , target ) || matchName ( urlDecoded ( path ) , target ) {
return entry
}
pathNoExt := strings . TrimSuffix ( path , ext )
if matchName ( pathNoExt , target ) || matchName ( urlEncoded ( pathNoExt ) , target ) || matchName ( urlDecoded ( pathNoExt ) , target ) {
return entry
}
}
}
return nil
}
// commitTreeDirEntry is a recursive file tree traversal function
func commitTreeDirEntry ( repo * git . Repository , commit * git . Commit , entries [ ] * git . TreeEntry , prevPath string , targets [ ] string , textOnly bool ) ( * git . TreeEntry , error ) {
for i := range entries {
entry := entries [ i ]
var path string
if len ( prevPath ) == 0 {
path = entry . Name ( )
} else {
path = prevPath + "/" + entry . Name ( )
}
if entry . Type == git . ObjectBlob {
// File
if res := commitTreeBlobEntry ( entry , path , targets , textOnly ) ; res != nil {
return res , nil
}
} else if entry . IsDir ( ) {
// Directory
// Get our tree entry, handling all possible errors
var err error
var tree * git . Tree
if tree , err = repo . GetTree ( entry . ID . String ( ) ) ; tree == nil || err != nil {
if err == nil {
err = fmt . Errorf ( "repo.GetTree(%s) => nil" , entry . ID . String ( ) )
}
return nil , err
}
// Found us, get children entries
var ls git . Entries
if ls , err = tree . ListEntries ( ) ; err != nil {
return nil , err
}
// Call itself recursively to find needed entry
var te * git . TreeEntry
if te , err = commitTreeDirEntry ( repo , commit , ls , path , targets , textOnly ) ; err != nil {
return nil , err
}
if te != nil {
return te , nil
}
}
}
return nil , nil
}
// commitTreeEntry is a first step of commitTreeDirEntry, which should be never called directly
func commitTreeEntry ( repo * git . Repository , commit * git . Commit , targets [ ] string , textOnly bool ) ( * git . TreeEntry , error ) {
entries , err := commit . ListEntries ( )
if err != nil {
return nil , err
}
return commitTreeDirEntry ( repo , commit , entries , "" , targets , textOnly )
}
// findFile finds the best match for given filename in repo file tree
func findFile ( repo * git . Repository , commit * git . Commit , target string , textOnly bool ) ( * git . TreeEntry , error ) {
targets := [ ] string { target , urlEncoded ( target ) , urlDecoded ( target ) }
var entry * git . TreeEntry
var err error
if entry , err = commitTreeEntry ( repo , commit , targets , textOnly ) ; err != nil {
return nil , err
}
return entry , nil
}
// matchName matches generic name representation of the file with required one
func matchName ( target , name string ) bool {
if len ( target ) != len ( name ) {
return false
}
name = strings . ToLower ( name )
target = strings . ToLower ( target )
if name == target {
return true
}
target = strings . Replace ( target , " " , "?" , - 1 )
target = strings . Replace ( target , "-" , "?" , - 1 )
for i := range name {
ch := name [ i ]
reqCh := target [ i ]
if ch != reqCh {
if string ( reqCh ) != "?" {
return false
}
}
}
return true
}
func findWikiRepoCommit ( ctx * context . Context ) ( * git . Repository , * git . Commit , error ) {
2015-11-27 08:24:24 +03:00
wikiRepo , err := git . OpenRepository ( ctx . Repo . Repository . WikiPath ( ) )
if err != nil {
2017-02-14 04:13:59 +03:00
// ctx.Handle(500, "OpenRepository", err)
return nil , nil , err
2015-11-27 08:24:24 +03:00
}
2017-03-20 16:36:19 +03:00
if ! wikiRepo . IsBranchExist ( "master" ) {
return wikiRepo , nil , nil
}
2015-12-10 04:46:05 +03:00
commit , err := wikiRepo . GetBranchCommit ( "master" )
2015-11-27 08:24:24 +03:00
if err != nil {
2015-12-10 04:46:05 +03:00
ctx . Handle ( 500 , "GetBranchCommit" , err )
2017-02-14 04:13:59 +03:00
return wikiRepo , nil , err
}
return wikiRepo , commit , nil
}
func renderWikiPage ( ctx * context . Context , isViewPage bool ) ( * git . Repository , * git . TreeEntry ) {
wikiRepo , commit , err := findWikiRepoCommit ( ctx )
if err != nil {
return nil , nil
2015-11-27 09:50:38 +03:00
}
2017-03-20 16:36:19 +03:00
if commit == nil {
return wikiRepo , nil
}
2015-11-27 09:50:38 +03:00
// Get page list.
if isViewPage {
entries , err := commit . ListEntries ( )
if err != nil {
ctx . Handle ( 500 , "ListEntries" , err )
2017-02-14 04:13:59 +03:00
return nil , nil
2015-11-27 09:50:38 +03:00
}
2017-02-14 04:13:59 +03:00
pages := [ ] PageMeta { }
2015-11-27 09:50:38 +03:00
for i := range entries {
2017-02-14 04:13:59 +03:00
if entries [ i ] . Type == git . ObjectBlob {
name := entries [ i ] . Name ( )
ext := filepath . Ext ( name )
if markdown . IsMarkdownFile ( name ) || ext == ".textile" {
name = strings . TrimSuffix ( name , ext )
if name == "" || name == "_Sidebar" || name == "_Footer" || name == "_Header" {
continue
}
pages = append ( pages , PageMeta {
2017-03-08 03:34:22 +03:00
Name : models . ToWikiPageName ( name ) ,
2017-03-20 16:36:19 +03:00
URL : name ,
2017-02-14 04:13:59 +03:00
} )
}
2015-11-27 09:50:38 +03:00
}
}
ctx . Data [ "Pages" ] = pages
2015-11-27 08:24:24 +03:00
}
2015-11-27 09:50:38 +03:00
pageURL := ctx . Params ( ":page" )
if len ( pageURL ) == 0 {
pageURL = "Home"
2015-11-27 08:24:24 +03:00
}
2015-11-27 09:50:38 +03:00
ctx . Data [ "PageURL" ] = pageURL
pageName := models . ToWikiPageName ( pageURL )
ctx . Data [ "old_title" ] = pageName
ctx . Data [ "Title" ] = pageName
ctx . Data [ "title" ] = pageName
2015-11-27 08:24:24 +03:00
ctx . Data [ "RequireHighlightJS" ] = true
2017-02-14 04:13:59 +03:00
var entry * git . TreeEntry
if entry , err = findFile ( wikiRepo , commit , pageName , true ) ; err != nil {
ctx . Handle ( 500 , "findFile" , err )
return nil , nil
}
if entry == nil {
ctx . Redirect ( ctx . Repo . RepoLink + "/wiki/_pages" )
return nil , nil
2015-11-27 08:24:24 +03:00
}
2017-02-14 04:13:59 +03:00
blob := entry . Blob ( )
2015-11-27 08:24:24 +03:00
r , err := blob . Data ( )
if err != nil {
ctx . Handle ( 500 , "Data" , err )
2017-02-14 04:13:59 +03:00
return nil , nil
2015-11-27 08:24:24 +03:00
}
data , err := ioutil . ReadAll ( r )
if err != nil {
ctx . Handle ( 500 , "ReadAll" , err )
2017-02-14 04:13:59 +03:00
return nil , nil
}
sidebarPresent := false
sidebarContent := [ ] byte { }
sentry , err := findFile ( wikiRepo , commit , "_Sidebar" , true )
if err == nil && sentry != nil {
r , err = sentry . Blob ( ) . Data ( )
if err == nil {
dataSB , err := ioutil . ReadAll ( r )
if err == nil {
sidebarPresent = true
sidebarContent = dataSB
}
}
}
footerPresent := false
footerContent := [ ] byte { }
sentry , err = findFile ( wikiRepo , commit , "_Footer" , true )
if err == nil && sentry != nil {
r , err = sentry . Blob ( ) . Data ( )
if err == nil {
dataSB , err := ioutil . ReadAll ( r )
if err == nil {
footerPresent = true
footerContent = dataSB
}
}
2015-11-27 09:50:38 +03:00
}
if isViewPage {
2017-02-14 04:13:59 +03:00
metas := ctx . Repo . Repository . ComposeMetas ( )
ctx . Data [ "content" ] = markdown . RenderWiki ( data , ctx . Repo . RepoLink , metas )
ctx . Data [ "sidebarPresent" ] = sidebarPresent
ctx . Data [ "sidebarContent" ] = markdown . RenderWiki ( sidebarContent , ctx . Repo . RepoLink , metas )
ctx . Data [ "footerPresent" ] = footerPresent
ctx . Data [ "footerContent" ] = markdown . RenderWiki ( footerContent , ctx . Repo . RepoLink , metas )
2015-11-27 09:50:38 +03:00
} else {
ctx . Data [ "content" ] = string ( data )
2017-02-14 04:13:59 +03:00
ctx . Data [ "sidebarPresent" ] = false
ctx . Data [ "sidebarContent" ] = ""
ctx . Data [ "footerPresent" ] = false
ctx . Data [ "footerContent" ] = ""
2015-11-27 09:50:38 +03:00
}
2017-02-14 04:13:59 +03:00
return wikiRepo , entry
2015-11-27 09:50:38 +03:00
}
2017-02-14 04:13:59 +03:00
// Wiki renders single wiki page
2016-03-11 19:56:52 +03:00
func Wiki ( ctx * context . Context ) {
2015-11-27 09:50:38 +03:00
ctx . Data [ "PageIsWiki" ] = true
if ! ctx . Repo . Repository . HasWiki ( ) {
ctx . Data [ "Title" ] = ctx . Tr ( "repo.wiki" )
2016-11-21 13:03:42 +03:00
ctx . HTML ( 200 , tplWikiStart )
2015-11-27 09:50:38 +03:00
return
}
2017-02-14 04:13:59 +03:00
wikiRepo , entry := renderWikiPage ( ctx , true )
2015-11-27 09:50:38 +03:00
if ctx . Written ( ) {
2015-11-27 08:24:24 +03:00
return
}
2017-03-20 16:36:19 +03:00
if entry == nil {
ctx . Data [ "Title" ] = ctx . Tr ( "repo.wiki" )
ctx . HTML ( 200 , tplWikiStart )
return
}
2015-11-27 08:24:24 +03:00
2017-02-14 04:13:59 +03:00
ename := entry . Name ( )
2017-04-21 10:01:08 +03:00
if markup . Type ( ename ) != markdown . MarkupName {
2017-02-14 04:13:59 +03:00
ext := strings . ToUpper ( filepath . Ext ( ename ) )
ctx . Data [ "FormatWarning" ] = fmt . Sprintf ( "%s rendering is not supported at the moment. Rendered as Markdown." , ext )
}
2015-11-27 08:24:24 +03:00
// Get last change information.
2017-02-14 04:13:59 +03:00
lastCommit , err := wikiRepo . GetCommitByPath ( ename )
2015-11-27 08:24:24 +03:00
if err != nil {
ctx . Handle ( 500 , "GetCommitByPath" , err )
return
}
ctx . Data [ "Author" ] = lastCommit . Author
2016-11-21 13:03:42 +03:00
ctx . HTML ( 200 , tplWikiView )
2015-11-26 04:10:25 +03:00
}
2016-11-21 13:03:42 +03:00
// WikiPages render wiki pages list page
2016-03-11 19:56:52 +03:00
func WikiPages ( ctx * context . Context ) {
2015-11-27 10:16:12 +03:00
ctx . Data [ "Title" ] = ctx . Tr ( "repo.wiki.pages" )
ctx . Data [ "PageIsWiki" ] = true
if ! ctx . Repo . Repository . HasWiki ( ) {
ctx . Redirect ( ctx . Repo . RepoLink + "/wiki" )
return
}
2017-02-14 04:13:59 +03:00
wikiRepo , commit , err := findWikiRepoCommit ( ctx )
2015-11-27 10:16:12 +03:00
if err != nil {
return
}
entries , err := commit . ListEntries ( )
if err != nil {
ctx . Handle ( 500 , "ListEntries" , err )
return
}
pages := make ( [ ] PageMeta , 0 , len ( entries ) )
for i := range entries {
2017-02-14 04:13:59 +03:00
if entries [ i ] . Type == git . ObjectBlob {
2015-11-27 10:16:12 +03:00
c , err := wikiRepo . GetCommitByPath ( entries [ i ] . Name ( ) )
if err != nil {
ctx . Handle ( 500 , "GetCommit" , err )
return
}
2017-02-14 04:13:59 +03:00
name := entries [ i ] . Name ( )
ext := filepath . Ext ( name )
if markdown . IsMarkdownFile ( name ) || ext == ".textile" {
name = strings . TrimSuffix ( name , ext )
if name == "" {
continue
}
pages = append ( pages , PageMeta {
2017-03-08 03:34:22 +03:00
Name : models . ToWikiPageName ( name ) ,
2017-03-20 16:36:19 +03:00
URL : name ,
2017-02-14 04:13:59 +03:00
Updated : c . Author . When ,
} )
}
2015-11-27 10:16:12 +03:00
}
}
ctx . Data [ "Pages" ] = pages
2015-11-27 08:24:24 +03:00
2016-11-21 13:03:42 +03:00
ctx . HTML ( 200 , tplWikiPages )
2015-11-27 08:24:24 +03:00
}
2017-02-14 04:13:59 +03:00
// WikiRaw outputs raw blob requested by user (image for example)
func WikiRaw ( ctx * context . Context ) {
wikiRepo , commit , err := findWikiRepoCommit ( ctx )
if err != nil {
if wikiRepo != nil {
return
}
}
uri := ctx . Params ( "*" )
var entry * git . TreeEntry
if commit != nil {
entry , err = findFile ( wikiRepo , commit , uri , false )
}
if err != nil || entry == nil {
if entry == nil || commit == nil {
defBranch := ctx . Repo . Repository . DefaultBranch
if commit , err = ctx . Repo . GitRepo . GetBranchCommit ( defBranch ) ; commit == nil || err != nil {
ctx . Handle ( 500 , "GetBranchCommit" , err )
return
}
if entry , err = findFile ( ctx . Repo . GitRepo , commit , uri , false ) ; err != nil {
ctx . Handle ( 500 , "findFile" , err )
return
}
if entry == nil {
ctx . Handle ( 404 , "findFile" , nil )
return
}
} else {
ctx . Handle ( 500 , "findFile" , err )
return
}
}
if err = ServeBlob ( ctx , entry . Blob ( ) ) ; err != nil {
ctx . Handle ( 500 , "ServeBlob" , err )
}
}
2016-11-21 13:03:42 +03:00
// NewWiki render wiki create page
2016-03-11 19:56:52 +03:00
func NewWiki ( ctx * context . Context ) {
2015-11-26 04:10:25 +03:00
ctx . Data [ "Title" ] = ctx . Tr ( "repo.wiki.new_page" )
ctx . Data [ "PageIsWiki" ] = true
ctx . Data [ "RequireSimpleMDE" ] = true
2015-11-27 01:33:45 +03:00
if ! ctx . Repo . Repository . HasWiki ( ) {
2015-11-26 04:10:25 +03:00
ctx . Data [ "title" ] = "Home"
}
2016-11-21 13:03:42 +03:00
ctx . HTML ( 200 , tplWikiNew )
2015-11-26 04:10:25 +03:00
}
2016-11-21 13:03:42 +03:00
// NewWikiPost response fro wiki create request
2016-03-11 19:56:52 +03:00
func NewWikiPost ( ctx * context . Context , form auth . NewWikiForm ) {
2015-11-27 01:33:45 +03:00
ctx . Data [ "Title" ] = ctx . Tr ( "repo.wiki.new_page" )
ctx . Data [ "PageIsWiki" ] = true
ctx . Data [ "RequireSimpleMDE" ] = true
if ctx . HasError ( ) {
2016-11-21 13:03:42 +03:00
ctx . HTML ( 200 , tplWikiNew )
2015-11-27 01:33:45 +03:00
return
}
2017-01-21 15:50:51 +03:00
wikiPath := models . ToWikiPageURL ( form . Title )
if err := ctx . Repo . Repository . AddWikiPage ( ctx . User , wikiPath , form . Content , form . Message ) ; err != nil {
2015-11-27 09:50:38 +03:00
if models . IsErrWikiAlreadyExist ( err ) {
ctx . Data [ "Err_Title" ] = true
2016-11-21 13:03:42 +03:00
ctx . RenderWithErr ( ctx . Tr ( "repo.wiki.page_already_exists" ) , tplWikiNew , & form )
2015-11-27 09:50:38 +03:00
} else {
ctx . Handle ( 500 , "AddWikiPage" , err )
}
2015-11-27 01:33:45 +03:00
return
}
2017-01-21 15:50:51 +03:00
ctx . Redirect ( ctx . Repo . RepoLink + "/wiki/" + wikiPath )
2015-11-27 01:33:45 +03:00
}
2016-11-21 13:03:42 +03:00
// EditWiki render wiki modify page
2016-03-11 19:56:52 +03:00
func EditWiki ( ctx * context . Context ) {
2015-11-27 09:50:38 +03:00
ctx . Data [ "PageIsWiki" ] = true
ctx . Data [ "PageIsWikiEdit" ] = true
ctx . Data [ "RequireSimpleMDE" ] = true
if ! ctx . Repo . Repository . HasWiki ( ) {
ctx . Redirect ( ctx . Repo . RepoLink + "/wiki" )
return
}
renderWikiPage ( ctx , false )
if ctx . Written ( ) {
return
}
2016-11-21 13:03:42 +03:00
ctx . HTML ( 200 , tplWikiNew )
2015-11-27 09:50:38 +03:00
}
2016-11-21 13:03:42 +03:00
// EditWikiPost response fro wiki modify request
2016-03-11 19:56:52 +03:00
func EditWikiPost ( ctx * context . Context , form auth . NewWikiForm ) {
2015-11-27 09:50:38 +03:00
ctx . Data [ "Title" ] = ctx . Tr ( "repo.wiki.new_page" )
ctx . Data [ "PageIsWiki" ] = true
ctx . Data [ "RequireSimpleMDE" ] = true
if ctx . HasError ( ) {
2016-11-21 13:03:42 +03:00
ctx . HTML ( 200 , tplWikiNew )
2015-11-27 09:50:38 +03:00
return
}
2017-03-20 16:36:19 +03:00
oldWikiPath := models . ToWikiPageURL ( ctx . Params ( ":page" ) )
2017-01-21 15:50:51 +03:00
newWikiPath := models . ToWikiPageURL ( form . Title )
if err := ctx . Repo . Repository . EditWikiPage ( ctx . User , oldWikiPath , newWikiPath , form . Content , form . Message ) ; err != nil {
2015-11-27 09:50:38 +03:00
ctx . Handle ( 500 , "EditWikiPage" , err )
return
}
2017-01-21 15:50:51 +03:00
ctx . Redirect ( ctx . Repo . RepoLink + "/wiki/" + newWikiPath )
2015-11-26 04:10:25 +03:00
}
2016-03-04 01:06:50 +03:00
2016-11-21 13:03:42 +03:00
// DeleteWikiPagePost delete wiki page
2016-03-11 19:56:52 +03:00
func DeleteWikiPagePost ( ctx * context . Context ) {
2017-03-20 16:36:19 +03:00
pageURL := models . ToWikiPageURL ( ctx . Params ( ":page" ) )
2016-03-04 01:06:50 +03:00
if len ( pageURL ) == 0 {
pageURL = "Home"
}
2017-01-21 15:50:51 +03:00
if err := ctx . Repo . Repository . DeleteWikiPage ( ctx . User , pageURL ) ; err != nil {
2016-03-04 01:06:50 +03:00
ctx . Handle ( 500 , "DeleteWikiPage" , err )
return
}
ctx . JSON ( 200 , map [ string ] interface { } {
"redirect" : ctx . Repo . RepoLink + "/wiki/" ,
} )
}