code.gitea.io/gitea@v1.22.3/routers/web/repo/wiki.go (about)

     1  // Copyright 2015 The Gogs Authors. All rights reserved.
     2  // Copyright 2018 The Gitea Authors. All rights reserved.
     3  // SPDX-License-Identifier: MIT
     4  
     5  package repo
     6  
     7  import (
     8  	"bytes"
     9  	"fmt"
    10  	"io"
    11  	"net/http"
    12  	"net/url"
    13  	"path/filepath"
    14  	"strings"
    15  
    16  	git_model "code.gitea.io/gitea/models/git"
    17  	repo_model "code.gitea.io/gitea/models/repo"
    18  	"code.gitea.io/gitea/models/unit"
    19  	"code.gitea.io/gitea/modules/base"
    20  	"code.gitea.io/gitea/modules/charset"
    21  	"code.gitea.io/gitea/modules/git"
    22  	"code.gitea.io/gitea/modules/gitrepo"
    23  	"code.gitea.io/gitea/modules/log"
    24  	"code.gitea.io/gitea/modules/markup"
    25  	"code.gitea.io/gitea/modules/markup/markdown"
    26  	"code.gitea.io/gitea/modules/setting"
    27  	"code.gitea.io/gitea/modules/timeutil"
    28  	"code.gitea.io/gitea/modules/util"
    29  	"code.gitea.io/gitea/modules/web"
    30  	"code.gitea.io/gitea/routers/common"
    31  	"code.gitea.io/gitea/services/context"
    32  	"code.gitea.io/gitea/services/forms"
    33  	notify_service "code.gitea.io/gitea/services/notify"
    34  	wiki_service "code.gitea.io/gitea/services/wiki"
    35  )
    36  
    37  const (
    38  	tplWikiStart    base.TplName = "repo/wiki/start"
    39  	tplWikiView     base.TplName = "repo/wiki/view"
    40  	tplWikiRevision base.TplName = "repo/wiki/revision"
    41  	tplWikiNew      base.TplName = "repo/wiki/new"
    42  	tplWikiPages    base.TplName = "repo/wiki/pages"
    43  )
    44  
    45  // MustEnableWiki check if wiki is enabled, if external then redirect
    46  func MustEnableWiki(ctx *context.Context) {
    47  	if !ctx.Repo.CanRead(unit.TypeWiki) &&
    48  		!ctx.Repo.CanRead(unit.TypeExternalWiki) {
    49  		if log.IsTrace() {
    50  			log.Trace("Permission Denied: User %-v cannot read %-v or %-v of repo %-v\n"+
    51  				"User in repo has Permissions: %-+v",
    52  				ctx.Doer,
    53  				unit.TypeWiki,
    54  				unit.TypeExternalWiki,
    55  				ctx.Repo.Repository,
    56  				ctx.Repo.Permission)
    57  		}
    58  		ctx.NotFound("MustEnableWiki", nil)
    59  		return
    60  	}
    61  
    62  	unit, err := ctx.Repo.Repository.GetUnit(ctx, unit.TypeExternalWiki)
    63  	if err == nil {
    64  		ctx.Redirect(unit.ExternalWikiConfig().ExternalWikiURL)
    65  		return
    66  	}
    67  }
    68  
    69  // PageMeta wiki page meta information
    70  type PageMeta struct {
    71  	Name         string
    72  	SubURL       string
    73  	GitEntryName string
    74  	UpdatedUnix  timeutil.TimeStamp
    75  }
    76  
    77  // findEntryForFile finds the tree entry for a target filepath.
    78  func findEntryForFile(commit *git.Commit, target string) (*git.TreeEntry, error) {
    79  	entry, err := commit.GetTreeEntryByPath(target)
    80  	if err != nil && !git.IsErrNotExist(err) {
    81  		return nil, err
    82  	}
    83  	if entry != nil {
    84  		return entry, nil
    85  	}
    86  
    87  	// Then the unescaped, the shortest alternative
    88  	var unescapedTarget string
    89  	if unescapedTarget, err = url.QueryUnescape(target); err != nil {
    90  		return nil, err
    91  	}
    92  	return commit.GetTreeEntryByPath(unescapedTarget)
    93  }
    94  
    95  func findWikiRepoCommit(ctx *context.Context) (*git.Repository, *git.Commit, error) {
    96  	wikiGitRepo, errGitRepo := gitrepo.OpenWikiRepository(ctx, ctx.Repo.Repository)
    97  	if errGitRepo != nil {
    98  		ctx.ServerError("OpenRepository", errGitRepo)
    99  		return nil, nil, errGitRepo
   100  	}
   101  
   102  	commit, errCommit := wikiGitRepo.GetBranchCommit(ctx.Repo.Repository.DefaultWikiBranch)
   103  	if git.IsErrNotExist(errCommit) {
   104  		// if the default branch recorded in database is out of sync, then re-sync it
   105  		gitRepoDefaultBranch, errBranch := gitrepo.GetWikiDefaultBranch(ctx, ctx.Repo.Repository)
   106  		if errBranch != nil {
   107  			return wikiGitRepo, nil, errBranch
   108  		}
   109  		// update the default branch in the database
   110  		errDb := repo_model.UpdateRepositoryCols(ctx, &repo_model.Repository{ID: ctx.Repo.Repository.ID, DefaultWikiBranch: gitRepoDefaultBranch}, "default_wiki_branch")
   111  		if errDb != nil {
   112  			return wikiGitRepo, nil, errDb
   113  		}
   114  		ctx.Repo.Repository.DefaultWikiBranch = gitRepoDefaultBranch
   115  		// retry to get the commit from the correct default branch
   116  		commit, errCommit = wikiGitRepo.GetBranchCommit(ctx.Repo.Repository.DefaultWikiBranch)
   117  	}
   118  	if errCommit != nil {
   119  		return wikiGitRepo, nil, errCommit
   120  	}
   121  	return wikiGitRepo, commit, nil
   122  }
   123  
   124  // wikiContentsByEntry returns the contents of the wiki page referenced by the
   125  // given tree entry. Writes to ctx if an error occurs.
   126  func wikiContentsByEntry(ctx *context.Context, entry *git.TreeEntry) []byte {
   127  	reader, err := entry.Blob().DataAsync()
   128  	if err != nil {
   129  		ctx.ServerError("Blob.Data", err)
   130  		return nil
   131  	}
   132  	defer reader.Close()
   133  	content, err := io.ReadAll(reader)
   134  	if err != nil {
   135  		ctx.ServerError("ReadAll", err)
   136  		return nil
   137  	}
   138  	return content
   139  }
   140  
   141  // wikiEntryByName returns the entry of a wiki page, along with a boolean
   142  // indicating whether the entry exists. Writes to ctx if an error occurs.
   143  // The last return value indicates whether the file should be returned as a raw file
   144  func wikiEntryByName(ctx *context.Context, commit *git.Commit, wikiName wiki_service.WebPath) (*git.TreeEntry, string, bool, bool) {
   145  	isRaw := false
   146  	gitFilename := wiki_service.WebPathToGitPath(wikiName)
   147  	entry, err := findEntryForFile(commit, gitFilename)
   148  	if err != nil && !git.IsErrNotExist(err) {
   149  		ctx.ServerError("findEntryForFile", err)
   150  		return nil, "", false, false
   151  	}
   152  	if entry == nil {
   153  		// check if the file without ".md" suffix exists
   154  		gitFilename := strings.TrimSuffix(gitFilename, ".md")
   155  		entry, err = findEntryForFile(commit, gitFilename)
   156  		if err != nil && !git.IsErrNotExist(err) {
   157  			ctx.ServerError("findEntryForFile", err)
   158  			return nil, "", false, false
   159  		}
   160  		isRaw = true
   161  	}
   162  	if entry == nil {
   163  		return nil, "", true, false
   164  	}
   165  	return entry, gitFilename, false, isRaw
   166  }
   167  
   168  // wikiContentsByName returns the contents of a wiki page, along with a boolean
   169  // indicating whether the page exists. Writes to ctx if an error occurs.
   170  func wikiContentsByName(ctx *context.Context, commit *git.Commit, wikiName wiki_service.WebPath) ([]byte, *git.TreeEntry, string, bool) {
   171  	entry, gitFilename, noEntry, _ := wikiEntryByName(ctx, commit, wikiName)
   172  	if entry == nil {
   173  		return nil, nil, "", true
   174  	}
   175  	return wikiContentsByEntry(ctx, entry), entry, gitFilename, noEntry
   176  }
   177  
   178  func renderViewPage(ctx *context.Context) (*git.Repository, *git.TreeEntry) {
   179  	wikiRepo, commit, err := findWikiRepoCommit(ctx)
   180  	if err != nil {
   181  		if wikiRepo != nil {
   182  			wikiRepo.Close()
   183  		}
   184  		if !git.IsErrNotExist(err) {
   185  			ctx.ServerError("GetBranchCommit", err)
   186  		}
   187  		return nil, nil
   188  	}
   189  
   190  	// Get page list.
   191  	entries, err := commit.ListEntries()
   192  	if err != nil {
   193  		if wikiRepo != nil {
   194  			wikiRepo.Close()
   195  		}
   196  		ctx.ServerError("ListEntries", err)
   197  		return nil, nil
   198  	}
   199  	pages := make([]PageMeta, 0, len(entries))
   200  	for _, entry := range entries {
   201  		if !entry.IsRegular() {
   202  			continue
   203  		}
   204  		wikiName, err := wiki_service.GitPathToWebPath(entry.Name())
   205  		if err != nil {
   206  			if repo_model.IsErrWikiInvalidFileName(err) {
   207  				continue
   208  			}
   209  			if wikiRepo != nil {
   210  				wikiRepo.Close()
   211  			}
   212  			ctx.ServerError("WikiFilenameToName", err)
   213  			return nil, nil
   214  		} else if wikiName == "_Sidebar" || wikiName == "_Footer" {
   215  			continue
   216  		}
   217  		_, displayName := wiki_service.WebPathToUserTitle(wikiName)
   218  		pages = append(pages, PageMeta{
   219  			Name:         displayName,
   220  			SubURL:       wiki_service.WebPathToURLPath(wikiName),
   221  			GitEntryName: entry.Name(),
   222  		})
   223  	}
   224  	ctx.Data["Pages"] = pages
   225  
   226  	// get requested page name
   227  	pageName := wiki_service.WebPathFromRequest(ctx.PathParamRaw("*"))
   228  	if len(pageName) == 0 {
   229  		pageName = "Home"
   230  	}
   231  
   232  	_, displayName := wiki_service.WebPathToUserTitle(pageName)
   233  	ctx.Data["PageURL"] = wiki_service.WebPathToURLPath(pageName)
   234  	ctx.Data["old_title"] = displayName
   235  	ctx.Data["Title"] = displayName
   236  	ctx.Data["title"] = displayName
   237  
   238  	isSideBar := pageName == "_Sidebar"
   239  	isFooter := pageName == "_Footer"
   240  
   241  	// lookup filename in wiki - get gitTree entry , real filename
   242  	entry, pageFilename, noEntry, isRaw := wikiEntryByName(ctx, commit, pageName)
   243  	if noEntry {
   244  		ctx.Redirect(ctx.Repo.RepoLink + "/wiki/?action=_pages")
   245  	}
   246  	if isRaw {
   247  		ctx.Redirect(util.URLJoin(ctx.Repo.RepoLink, "wiki/raw", string(pageName)))
   248  	}
   249  	if entry == nil || ctx.Written() {
   250  		if wikiRepo != nil {
   251  			wikiRepo.Close()
   252  		}
   253  		return nil, nil
   254  	}
   255  
   256  	// get filecontent
   257  	data := wikiContentsByEntry(ctx, entry)
   258  	if ctx.Written() {
   259  		if wikiRepo != nil {
   260  			wikiRepo.Close()
   261  		}
   262  		return nil, nil
   263  	}
   264  
   265  	var sidebarContent []byte
   266  	if !isSideBar {
   267  		sidebarContent, _, _, _ = wikiContentsByName(ctx, commit, "_Sidebar")
   268  		if ctx.Written() {
   269  			if wikiRepo != nil {
   270  				wikiRepo.Close()
   271  			}
   272  			return nil, nil
   273  		}
   274  	} else {
   275  		sidebarContent = data
   276  	}
   277  
   278  	var footerContent []byte
   279  	if !isFooter {
   280  		footerContent, _, _, _ = wikiContentsByName(ctx, commit, "_Footer")
   281  		if ctx.Written() {
   282  			if wikiRepo != nil {
   283  				wikiRepo.Close()
   284  			}
   285  			return nil, nil
   286  		}
   287  	} else {
   288  		footerContent = data
   289  	}
   290  
   291  	rctx := &markup.RenderContext{
   292  		Ctx:   ctx,
   293  		Metas: ctx.Repo.Repository.ComposeDocumentMetas(ctx),
   294  		Links: markup.Links{
   295  			Base: ctx.Repo.RepoLink,
   296  		},
   297  		IsWiki: true,
   298  	}
   299  	buf := &strings.Builder{}
   300  
   301  	renderFn := func(data []byte) (escaped *charset.EscapeStatus, output string, err error) {
   302  		markupRd, markupWr := io.Pipe()
   303  		defer markupWr.Close()
   304  		done := make(chan struct{})
   305  		go func() {
   306  			// We allow NBSP here this is rendered
   307  			escaped, _ = charset.EscapeControlReader(markupRd, buf, ctx.Locale, charset.RuneNBSP)
   308  			output = buf.String()
   309  			buf.Reset()
   310  			close(done)
   311  		}()
   312  
   313  		err = markdown.Render(rctx, bytes.NewReader(data), markupWr)
   314  		_ = markupWr.CloseWithError(err)
   315  		<-done
   316  		return escaped, output, err
   317  	}
   318  
   319  	ctx.Data["EscapeStatus"], ctx.Data["content"], err = renderFn(data)
   320  	if err != nil {
   321  		if wikiRepo != nil {
   322  			wikiRepo.Close()
   323  		}
   324  		ctx.ServerError("Render", err)
   325  		return nil, nil
   326  	}
   327  
   328  	if rctx.SidebarTocNode != nil {
   329  		sb := &strings.Builder{}
   330  		err = markdown.SpecializedMarkdown().Renderer().Render(sb, nil, rctx.SidebarTocNode)
   331  		if err != nil {
   332  			log.Error("Failed to render wiki sidebar TOC: %v", err)
   333  		} else {
   334  			ctx.Data["sidebarTocContent"] = sb.String()
   335  		}
   336  	}
   337  
   338  	if !isSideBar {
   339  		buf.Reset()
   340  		ctx.Data["sidebarEscapeStatus"], ctx.Data["sidebarContent"], err = renderFn(sidebarContent)
   341  		if err != nil {
   342  			if wikiRepo != nil {
   343  				wikiRepo.Close()
   344  			}
   345  			ctx.ServerError("Render", err)
   346  			return nil, nil
   347  		}
   348  		ctx.Data["sidebarPresent"] = sidebarContent != nil
   349  	} else {
   350  		ctx.Data["sidebarPresent"] = false
   351  	}
   352  
   353  	if !isFooter {
   354  		buf.Reset()
   355  		ctx.Data["footerEscapeStatus"], ctx.Data["footerContent"], err = renderFn(footerContent)
   356  		if err != nil {
   357  			if wikiRepo != nil {
   358  				wikiRepo.Close()
   359  			}
   360  			ctx.ServerError("Render", err)
   361  			return nil, nil
   362  		}
   363  		ctx.Data["footerPresent"] = footerContent != nil
   364  	} else {
   365  		ctx.Data["footerPresent"] = false
   366  	}
   367  
   368  	// get commit count - wiki revisions
   369  	commitsCount, _ := wikiRepo.FileCommitsCount(ctx.Repo.Repository.DefaultWikiBranch, pageFilename)
   370  	ctx.Data["CommitCount"] = commitsCount
   371  
   372  	return wikiRepo, entry
   373  }
   374  
   375  func renderRevisionPage(ctx *context.Context) (*git.Repository, *git.TreeEntry) {
   376  	wikiRepo, commit, err := findWikiRepoCommit(ctx)
   377  	if err != nil {
   378  		if wikiRepo != nil {
   379  			wikiRepo.Close()
   380  		}
   381  		if !git.IsErrNotExist(err) {
   382  			ctx.ServerError("GetBranchCommit", err)
   383  		}
   384  		return nil, nil
   385  	}
   386  
   387  	// get requested pagename
   388  	pageName := wiki_service.WebPathFromRequest(ctx.PathParamRaw("*"))
   389  	if len(pageName) == 0 {
   390  		pageName = "Home"
   391  	}
   392  
   393  	_, displayName := wiki_service.WebPathToUserTitle(pageName)
   394  	ctx.Data["PageURL"] = wiki_service.WebPathToURLPath(pageName)
   395  	ctx.Data["old_title"] = displayName
   396  	ctx.Data["Title"] = displayName
   397  	ctx.Data["title"] = displayName
   398  
   399  	ctx.Data["Username"] = ctx.Repo.Owner.Name
   400  	ctx.Data["Reponame"] = ctx.Repo.Repository.Name
   401  
   402  	// lookup filename in wiki - get filecontent, gitTree entry , real filename
   403  	data, entry, pageFilename, noEntry := wikiContentsByName(ctx, commit, pageName)
   404  	if noEntry {
   405  		ctx.Redirect(ctx.Repo.RepoLink + "/wiki/?action=_pages")
   406  	}
   407  	if entry == nil || ctx.Written() {
   408  		if wikiRepo != nil {
   409  			wikiRepo.Close()
   410  		}
   411  		return nil, nil
   412  	}
   413  
   414  	ctx.Data["content"] = string(data)
   415  	ctx.Data["sidebarPresent"] = false
   416  	ctx.Data["sidebarContent"] = ""
   417  	ctx.Data["footerPresent"] = false
   418  	ctx.Data["footerContent"] = ""
   419  
   420  	// get commit count - wiki revisions
   421  	commitsCount, _ := wikiRepo.FileCommitsCount(ctx.Repo.Repository.DefaultWikiBranch, pageFilename)
   422  	ctx.Data["CommitCount"] = commitsCount
   423  
   424  	// get page
   425  	page := ctx.FormInt("page")
   426  	if page <= 1 {
   427  		page = 1
   428  	}
   429  
   430  	// get Commit Count
   431  	commitsHistory, err := wikiRepo.CommitsByFileAndRange(
   432  		git.CommitsByFileAndRangeOptions{
   433  			Revision: ctx.Repo.Repository.DefaultWikiBranch,
   434  			File:     pageFilename,
   435  			Page:     page,
   436  		})
   437  	if err != nil {
   438  		if wikiRepo != nil {
   439  			wikiRepo.Close()
   440  		}
   441  		ctx.ServerError("CommitsByFileAndRange", err)
   442  		return nil, nil
   443  	}
   444  	ctx.Data["Commits"] = git_model.ConvertFromGitCommit(ctx, commitsHistory, ctx.Repo.Repository)
   445  
   446  	pager := context.NewPagination(int(commitsCount), setting.Git.CommitsRangeSize, page, 5)
   447  	pager.SetDefaultParams(ctx)
   448  	pager.AddParamString("action", "_revision")
   449  	ctx.Data["Page"] = pager
   450  
   451  	return wikiRepo, entry
   452  }
   453  
   454  func renderEditPage(ctx *context.Context) {
   455  	wikiRepo, commit, err := findWikiRepoCommit(ctx)
   456  	defer func() {
   457  		if wikiRepo != nil {
   458  			_ = wikiRepo.Close()
   459  		}
   460  	}()
   461  	if err != nil {
   462  		if !git.IsErrNotExist(err) {
   463  			ctx.ServerError("GetBranchCommit", err)
   464  		}
   465  		return
   466  	}
   467  
   468  	// get requested pagename
   469  	pageName := wiki_service.WebPathFromRequest(ctx.PathParamRaw("*"))
   470  	if len(pageName) == 0 {
   471  		pageName = "Home"
   472  	}
   473  
   474  	_, displayName := wiki_service.WebPathToUserTitle(pageName)
   475  	ctx.Data["PageURL"] = wiki_service.WebPathToURLPath(pageName)
   476  	ctx.Data["old_title"] = displayName
   477  	ctx.Data["Title"] = displayName
   478  	ctx.Data["title"] = displayName
   479  
   480  	// lookup filename in wiki -  gitTree entry , real filename
   481  	entry, _, noEntry, isRaw := wikiEntryByName(ctx, commit, pageName)
   482  	if noEntry {
   483  		ctx.Redirect(ctx.Repo.RepoLink + "/wiki/?action=_pages")
   484  	}
   485  	if isRaw {
   486  		ctx.Error(http.StatusForbidden, "Editing of raw wiki files is not allowed")
   487  	}
   488  	if entry == nil || ctx.Written() {
   489  		return
   490  	}
   491  
   492  	// get filecontent
   493  	data := wikiContentsByEntry(ctx, entry)
   494  	if ctx.Written() {
   495  		return
   496  	}
   497  
   498  	ctx.Data["content"] = string(data)
   499  	ctx.Data["sidebarPresent"] = false
   500  	ctx.Data["sidebarContent"] = ""
   501  	ctx.Data["footerPresent"] = false
   502  	ctx.Data["footerContent"] = ""
   503  }
   504  
   505  // WikiPost renders post of wiki page
   506  func WikiPost(ctx *context.Context) {
   507  	switch ctx.FormString("action") {
   508  	case "_new":
   509  		if !ctx.Repo.CanWrite(unit.TypeWiki) {
   510  			ctx.NotFound(ctx.Req.URL.RequestURI(), nil)
   511  			return
   512  		}
   513  		NewWikiPost(ctx)
   514  		return
   515  	case "_delete":
   516  		if !ctx.Repo.CanWrite(unit.TypeWiki) {
   517  			ctx.NotFound(ctx.Req.URL.RequestURI(), nil)
   518  			return
   519  		}
   520  		DeleteWikiPagePost(ctx)
   521  		return
   522  	}
   523  
   524  	if !ctx.Repo.CanWrite(unit.TypeWiki) {
   525  		ctx.NotFound(ctx.Req.URL.RequestURI(), nil)
   526  		return
   527  	}
   528  	EditWikiPost(ctx)
   529  }
   530  
   531  // Wiki renders single wiki page
   532  func Wiki(ctx *context.Context) {
   533  	ctx.Data["CanWriteWiki"] = ctx.Repo.CanWrite(unit.TypeWiki) && !ctx.Repo.Repository.IsArchived
   534  
   535  	switch ctx.FormString("action") {
   536  	case "_pages":
   537  		WikiPages(ctx)
   538  		return
   539  	case "_revision":
   540  		WikiRevision(ctx)
   541  		return
   542  	case "_edit":
   543  		if !ctx.Repo.CanWrite(unit.TypeWiki) {
   544  			ctx.NotFound(ctx.Req.URL.RequestURI(), nil)
   545  			return
   546  		}
   547  		EditWiki(ctx)
   548  		return
   549  	case "_new":
   550  		if !ctx.Repo.CanWrite(unit.TypeWiki) {
   551  			ctx.NotFound(ctx.Req.URL.RequestURI(), nil)
   552  			return
   553  		}
   554  		NewWiki(ctx)
   555  		return
   556  	}
   557  
   558  	if !ctx.Repo.Repository.HasWiki() {
   559  		ctx.Data["Title"] = ctx.Tr("repo.wiki")
   560  		ctx.HTML(http.StatusOK, tplWikiStart)
   561  		return
   562  	}
   563  
   564  	wikiRepo, entry := renderViewPage(ctx)
   565  	defer func() {
   566  		if wikiRepo != nil {
   567  			wikiRepo.Close()
   568  		}
   569  	}()
   570  	if ctx.Written() {
   571  		return
   572  	}
   573  	if entry == nil {
   574  		ctx.Data["Title"] = ctx.Tr("repo.wiki")
   575  		ctx.HTML(http.StatusOK, tplWikiStart)
   576  		return
   577  	}
   578  
   579  	wikiPath := entry.Name()
   580  	if markup.DetectMarkupTypeByFileName(wikiPath) != markdown.MarkupName {
   581  		ext := strings.ToUpper(filepath.Ext(wikiPath))
   582  		ctx.Data["FormatWarning"] = fmt.Sprintf("%s rendering is not supported at the moment. Rendered as Markdown.", ext)
   583  	}
   584  	// Get last change information.
   585  	lastCommit, err := wikiRepo.GetCommitByPath(wikiPath)
   586  	if err != nil {
   587  		ctx.ServerError("GetCommitByPath", err)
   588  		return
   589  	}
   590  	ctx.Data["Author"] = lastCommit.Author
   591  
   592  	ctx.HTML(http.StatusOK, tplWikiView)
   593  }
   594  
   595  // WikiRevision renders file revision list of wiki page
   596  func WikiRevision(ctx *context.Context) {
   597  	ctx.Data["CanWriteWiki"] = ctx.Repo.CanWrite(unit.TypeWiki) && !ctx.Repo.Repository.IsArchived
   598  
   599  	if !ctx.Repo.Repository.HasWiki() {
   600  		ctx.Data["Title"] = ctx.Tr("repo.wiki")
   601  		ctx.HTML(http.StatusOK, tplWikiStart)
   602  		return
   603  	}
   604  
   605  	wikiRepo, entry := renderRevisionPage(ctx)
   606  	defer func() {
   607  		if wikiRepo != nil {
   608  			wikiRepo.Close()
   609  		}
   610  	}()
   611  
   612  	if ctx.Written() {
   613  		return
   614  	}
   615  	if entry == nil {
   616  		ctx.Data["Title"] = ctx.Tr("repo.wiki")
   617  		ctx.HTML(http.StatusOK, tplWikiStart)
   618  		return
   619  	}
   620  
   621  	// Get last change information.
   622  	wikiPath := entry.Name()
   623  	lastCommit, err := wikiRepo.GetCommitByPath(wikiPath)
   624  	if err != nil {
   625  		ctx.ServerError("GetCommitByPath", err)
   626  		return
   627  	}
   628  	ctx.Data["Author"] = lastCommit.Author
   629  
   630  	ctx.HTML(http.StatusOK, tplWikiRevision)
   631  }
   632  
   633  // WikiPages render wiki pages list page
   634  func WikiPages(ctx *context.Context) {
   635  	if !ctx.Repo.Repository.HasWiki() {
   636  		ctx.Redirect(ctx.Repo.RepoLink + "/wiki")
   637  		return
   638  	}
   639  
   640  	ctx.Data["Title"] = ctx.Tr("repo.wiki.pages")
   641  	ctx.Data["CanWriteWiki"] = ctx.Repo.CanWrite(unit.TypeWiki) && !ctx.Repo.Repository.IsArchived
   642  
   643  	wikiRepo, commit, err := findWikiRepoCommit(ctx)
   644  	defer func() {
   645  		if wikiRepo != nil {
   646  			_ = wikiRepo.Close()
   647  		}
   648  	}()
   649  	if err != nil {
   650  		ctx.Redirect(ctx.Repo.RepoLink + "/wiki")
   651  		return
   652  	}
   653  
   654  	entries, err := commit.ListEntries()
   655  	if err != nil {
   656  		ctx.ServerError("ListEntries", err)
   657  		return
   658  	}
   659  	pages := make([]PageMeta, 0, len(entries))
   660  	for _, entry := range entries {
   661  		if !entry.IsRegular() {
   662  			continue
   663  		}
   664  		c, err := wikiRepo.GetCommitByPath(entry.Name())
   665  		if err != nil {
   666  			ctx.ServerError("GetCommit", err)
   667  			return
   668  		}
   669  		wikiName, err := wiki_service.GitPathToWebPath(entry.Name())
   670  		if err != nil {
   671  			if repo_model.IsErrWikiInvalidFileName(err) {
   672  				continue
   673  			}
   674  			ctx.ServerError("WikiFilenameToName", err)
   675  			return
   676  		}
   677  		_, displayName := wiki_service.WebPathToUserTitle(wikiName)
   678  		pages = append(pages, PageMeta{
   679  			Name:         displayName,
   680  			SubURL:       wiki_service.WebPathToURLPath(wikiName),
   681  			GitEntryName: entry.Name(),
   682  			UpdatedUnix:  timeutil.TimeStamp(c.Author.When.Unix()),
   683  		})
   684  	}
   685  	ctx.Data["Pages"] = pages
   686  
   687  	ctx.HTML(http.StatusOK, tplWikiPages)
   688  }
   689  
   690  // WikiRaw outputs raw blob requested by user (image for example)
   691  func WikiRaw(ctx *context.Context) {
   692  	wikiRepo, commit, err := findWikiRepoCommit(ctx)
   693  	defer func() {
   694  		if wikiRepo != nil {
   695  			wikiRepo.Close()
   696  		}
   697  	}()
   698  
   699  	if err != nil {
   700  		if git.IsErrNotExist(err) {
   701  			ctx.NotFound("findEntryForFile", nil)
   702  			return
   703  		}
   704  		ctx.ServerError("findEntryForfile", err)
   705  		return
   706  	}
   707  
   708  	providedWebPath := wiki_service.WebPathFromRequest(ctx.PathParamRaw("*"))
   709  	providedGitPath := wiki_service.WebPathToGitPath(providedWebPath)
   710  	var entry *git.TreeEntry
   711  	if commit != nil {
   712  		// Try to find a file with that name
   713  		entry, err = findEntryForFile(commit, providedGitPath)
   714  		if err != nil && !git.IsErrNotExist(err) {
   715  			ctx.ServerError("findFile", err)
   716  			return
   717  		}
   718  
   719  		if entry == nil {
   720  			// Try to find a wiki page with that name
   721  			providedGitPath = strings.TrimSuffix(providedGitPath, ".md")
   722  			entry, err = findEntryForFile(commit, providedGitPath)
   723  			if err != nil && !git.IsErrNotExist(err) {
   724  				ctx.ServerError("findFile", err)
   725  				return
   726  			}
   727  		}
   728  	}
   729  
   730  	if entry != nil {
   731  		if err = common.ServeBlob(ctx.Base, ctx.Repo.TreePath, entry.Blob(), nil); err != nil {
   732  			ctx.ServerError("ServeBlob", err)
   733  		}
   734  		return
   735  	}
   736  
   737  	ctx.NotFound("findEntryForFile", nil)
   738  }
   739  
   740  // NewWiki render wiki create page
   741  func NewWiki(ctx *context.Context) {
   742  	ctx.Data["Title"] = ctx.Tr("repo.wiki.new_page")
   743  
   744  	if !ctx.Repo.Repository.HasWiki() {
   745  		ctx.Data["title"] = "Home"
   746  	}
   747  	if ctx.FormString("title") != "" {
   748  		ctx.Data["title"] = ctx.FormString("title")
   749  	}
   750  
   751  	ctx.HTML(http.StatusOK, tplWikiNew)
   752  }
   753  
   754  // NewWikiPost response for wiki create request
   755  func NewWikiPost(ctx *context.Context) {
   756  	form := web.GetForm(ctx).(*forms.NewWikiForm)
   757  	ctx.Data["Title"] = ctx.Tr("repo.wiki.new_page")
   758  
   759  	if ctx.HasError() {
   760  		ctx.HTML(http.StatusOK, tplWikiNew)
   761  		return
   762  	}
   763  
   764  	if util.IsEmptyString(form.Title) {
   765  		ctx.RenderWithErr(ctx.Tr("repo.issues.new.title_empty"), tplWikiNew, form)
   766  		return
   767  	}
   768  
   769  	wikiName := wiki_service.UserTitleToWebPath("", form.Title)
   770  
   771  	if len(form.Message) == 0 {
   772  		form.Message = ctx.Locale.TrString("repo.editor.add", form.Title)
   773  	}
   774  
   775  	if err := wiki_service.AddWikiPage(ctx, ctx.Doer, ctx.Repo.Repository, wikiName, form.Content, form.Message); err != nil {
   776  		if repo_model.IsErrWikiReservedName(err) {
   777  			ctx.Data["Err_Title"] = true
   778  			ctx.RenderWithErr(ctx.Tr("repo.wiki.reserved_page", wikiName), tplWikiNew, &form)
   779  		} else if repo_model.IsErrWikiAlreadyExist(err) {
   780  			ctx.Data["Err_Title"] = true
   781  			ctx.RenderWithErr(ctx.Tr("repo.wiki.page_already_exists"), tplWikiNew, &form)
   782  		} else {
   783  			ctx.ServerError("AddWikiPage", err)
   784  		}
   785  		return
   786  	}
   787  
   788  	notify_service.NewWikiPage(ctx, ctx.Doer, ctx.Repo.Repository, string(wikiName), form.Message)
   789  
   790  	ctx.Redirect(ctx.Repo.RepoLink + "/wiki/" + wiki_service.WebPathToURLPath(wikiName))
   791  }
   792  
   793  // EditWiki render wiki modify page
   794  func EditWiki(ctx *context.Context) {
   795  	ctx.Data["PageIsWikiEdit"] = true
   796  
   797  	if !ctx.Repo.Repository.HasWiki() {
   798  		ctx.Redirect(ctx.Repo.RepoLink + "/wiki")
   799  		return
   800  	}
   801  
   802  	renderEditPage(ctx)
   803  	if ctx.Written() {
   804  		return
   805  	}
   806  
   807  	ctx.HTML(http.StatusOK, tplWikiNew)
   808  }
   809  
   810  // EditWikiPost response for wiki modify request
   811  func EditWikiPost(ctx *context.Context) {
   812  	form := web.GetForm(ctx).(*forms.NewWikiForm)
   813  	ctx.Data["Title"] = ctx.Tr("repo.wiki.new_page")
   814  
   815  	if ctx.HasError() {
   816  		ctx.HTML(http.StatusOK, tplWikiNew)
   817  		return
   818  	}
   819  
   820  	oldWikiName := wiki_service.WebPathFromRequest(ctx.PathParamRaw("*"))
   821  	newWikiName := wiki_service.UserTitleToWebPath("", form.Title)
   822  
   823  	if len(form.Message) == 0 {
   824  		form.Message = ctx.Locale.TrString("repo.editor.update", form.Title)
   825  	}
   826  
   827  	if err := wiki_service.EditWikiPage(ctx, ctx.Doer, ctx.Repo.Repository, oldWikiName, newWikiName, form.Content, form.Message); err != nil {
   828  		ctx.ServerError("EditWikiPage", err)
   829  		return
   830  	}
   831  
   832  	notify_service.EditWikiPage(ctx, ctx.Doer, ctx.Repo.Repository, string(newWikiName), form.Message)
   833  
   834  	ctx.Redirect(ctx.Repo.RepoLink + "/wiki/" + wiki_service.WebPathToURLPath(newWikiName))
   835  }
   836  
   837  // DeleteWikiPagePost delete wiki page
   838  func DeleteWikiPagePost(ctx *context.Context) {
   839  	wikiName := wiki_service.WebPathFromRequest(ctx.PathParamRaw("*"))
   840  	if len(wikiName) == 0 {
   841  		wikiName = "Home"
   842  	}
   843  
   844  	if err := wiki_service.DeleteWikiPage(ctx, ctx.Doer, ctx.Repo.Repository, wikiName); err != nil {
   845  		ctx.ServerError("DeleteWikiPage", err)
   846  		return
   847  	}
   848  
   849  	notify_service.DeleteWikiPage(ctx, ctx.Doer, ctx.Repo.Repository, string(wikiName))
   850  
   851  	ctx.JSONRedirect(ctx.Repo.RepoLink + "/wiki/")
   852  }