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 }