Skip to content

Start using template context function #26254

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Aug 8, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 12 additions & 3 deletions modules/context/context.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
package context

import (
"context"
"html"
"html/template"
"io"
Expand All @@ -31,14 +32,16 @@ import (

// Render represents a template render
type Render interface {
TemplateLookup(tmpl string) (templates.TemplateExecutor, error)
HTML(w io.Writer, status int, name string, data any) error
TemplateLookup(tmpl string, templateCtx context.Context) (templates.TemplateExecutor, error)
HTML(w io.Writer, status int, name string, data any, templateCtx context.Context) error
}

// Context represents context of a request.
type Context struct {
*Base

TemplateContext TemplateContext

Render Render
PageData map[string]any // data used by JavaScript modules in one page, it's `window.config.pageData`

Expand All @@ -60,6 +63,8 @@ type Context struct {
Package *Package
}

type TemplateContext map[string]any

func init() {
web.RegisterResponseStatusProvider[*Context](func(req *http.Request) web_types.ResponseStatusProvider {
return req.Context().Value(WebContextKey).(*Context)
Expand Down Expand Up @@ -133,8 +138,12 @@ func Contexter() func(next http.Handler) http.Handler {
}
defer baseCleanUp()

// TODO: "install.go" also shares the same logic, which should be refactored to a general function
ctx.TemplateContext = NewTemplateContext(ctx)
ctx.TemplateContext["Locale"] = ctx.Locale

ctx.Data.MergeFrom(middleware.CommonTemplateContextData())
ctx.Data["Context"] = &ctx
ctx.Data["Context"] = ctx // TODO: use "ctx" in template and remove this
ctx.Data["CurrentURL"] = setting.AppSubURL + req.URL.RequestURI()
ctx.Data["Link"] = ctx.Link
ctx.Data["locale"] = ctx.Locale
Expand Down
4 changes: 2 additions & 2 deletions modules/context/context_response.go
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ func (ctx *Context) HTML(status int, name base.TplName) {
return strconv.FormatInt(time.Since(tmplStartTime).Nanoseconds()/1e6, 10) + "ms"
}

err := ctx.Render.HTML(ctx.Resp, status, string(name), ctx.Data)
err := ctx.Render.HTML(ctx.Resp, status, string(name), ctx.Data, ctx.TemplateContext)
if err == nil {
return
}
Expand All @@ -93,7 +93,7 @@ func (ctx *Context) HTML(status int, name base.TplName) {
// RenderToString renders the template content to a string
func (ctx *Context) RenderToString(name base.TplName, data map[string]any) (string, error) {
var buf strings.Builder
err := ctx.Render.HTML(&buf, http.StatusOK, string(name), data)
err := ctx.Render.HTML(&buf, http.StatusOK, string(name), data, ctx.TemplateContext)
return buf.String(), err
}

Expand Down
49 changes: 49 additions & 0 deletions modules/context/context_template.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
// Copyright 2023 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT

package context

import (
"context"
"errors"
"time"

"code.gitea.io/gitea/modules/log"
)

var _ context.Context = TemplateContext(nil)

func NewTemplateContext(ctx context.Context) TemplateContext {
return TemplateContext{"_ctx": ctx}
}

func (c TemplateContext) parentContext() context.Context {
return c["_ctx"].(context.Context)
}

func (c TemplateContext) Deadline() (deadline time.Time, ok bool) {
return c.parentContext().Deadline()
}

func (c TemplateContext) Done() <-chan struct{} {
return c.parentContext().Done()
}

func (c TemplateContext) Err() error {
return c.parentContext().Err()
}

func (c TemplateContext) Value(key any) any {
return c.parentContext().Value(key)
}

// DataRaceCheck checks whether the template context function "ctx()" returns the consistent context
// as the current template's rendering context (request context), to help to find data race issues as early as possible.
// When the code is proven to be correct and stable, this function should be removed.
func (c TemplateContext) DataRaceCheck(dataCtx context.Context) (string, error) {
if c.parentContext() != dataCtx {
log.Error("TemplateContext.DataRaceCheck: parent context mismatch\n%s", log.Stack(2))
return "", errors.New("parent context mismatch")
}
return "", nil
}
2 changes: 2 additions & 0 deletions modules/templates/helper.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ import (
// NewFuncMap returns functions for injecting to templates
func NewFuncMap() template.FuncMap {
return map[string]any{
"ctx": func() any { return nil }, // template context function

"DumpVar": dumpVar,

// -----------------------------------------------------------------
Expand Down
12 changes: 7 additions & 5 deletions modules/templates/htmlrenderer.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ package templates
import (
"bufio"
"bytes"
"context"
"errors"
"fmt"
"io"
Expand Down Expand Up @@ -39,27 +40,28 @@ var (

var ErrTemplateNotInitialized = errors.New("template system is not initialized, check your log for errors")

func (h *HTMLRender) HTML(w io.Writer, status int, name string, data any) error {
func (h *HTMLRender) HTML(w io.Writer, status int, name string, data any, ctx context.Context) error { //nolint:revive
if respWriter, ok := w.(http.ResponseWriter); ok {
if respWriter.Header().Get("Content-Type") == "" {
respWriter.Header().Set("Content-Type", "text/html; charset=utf-8")
}
respWriter.WriteHeader(status)
}
t, err := h.TemplateLookup(name)
t, err := h.TemplateLookup(name, ctx)
if err != nil {
return texttemplate.ExecError{Name: name, Err: err}
}
return t.Execute(w, data)
}

func (h *HTMLRender) TemplateLookup(name string) (TemplateExecutor, error) {
func (h *HTMLRender) TemplateLookup(name string, ctx context.Context) (TemplateExecutor, error) { //nolint:revive
tmpls := h.templates.Load()
if tmpls == nil {
return nil, ErrTemplateNotInitialized
}

return tmpls.Executor(name, NewFuncMap())
m := NewFuncMap()
m["ctx"] = func() any { return ctx }
return tmpls.Executor(name, m)
}

func (h *HTMLRender) CompileTemplates() error {
Expand Down
4 changes: 2 additions & 2 deletions modules/test/context_tests.go
Original file line number Diff line number Diff line change
Expand Up @@ -150,11 +150,11 @@ func LoadGitRepo(t *testing.T, ctx *context.Context) {

type mockRender struct{}

func (tr *mockRender) TemplateLookup(tmpl string) (templates.TemplateExecutor, error) {
func (tr *mockRender) TemplateLookup(tmpl string, _ gocontext.Context) (templates.TemplateExecutor, error) {
return nil, nil
}

func (tr *mockRender) HTML(w io.Writer, status int, _ string, _ any) error {
func (tr *mockRender) HTML(w io.Writer, status int, _ string, _ any, _ gocontext.Context) error {
if resp, ok := w.(http.ResponseWriter); ok {
resp.WriteHeader(status)
}
Expand Down
2 changes: 1 addition & 1 deletion routers/common/errpage.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ func RenderPanicErrorPage(w http.ResponseWriter, req *http.Request, err any) {
data["ErrorMsg"] = "PANIC: " + combinedErr
}

err = templates.HTMLRenderer().HTML(w, http.StatusInternalServerError, string(tplStatus500), data)
err = templates.HTMLRenderer().HTML(w, http.StatusInternalServerError, string(tplStatus500), data, nil)
if err != nil {
log.Error("Error occurs again when rendering error page: %v", err)
w.WriteHeader(http.StatusInternalServerError)
Expand Down
4 changes: 4 additions & 0 deletions routers/install/install.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,9 +68,13 @@ func Contexter() func(next http.Handler) http.Handler {
}
defer baseCleanUp()

ctx.TemplateContext = context.NewTemplateContext(ctx)
ctx.TemplateContext["Locale"] = ctx.Locale

ctx.AppendContextValue(context.WebContextKey, ctx)
ctx.Data.MergeFrom(middleware.CommonTemplateContextData())
ctx.Data.MergeFrom(middleware.ContextData{
"Context": ctx, // TODO: use "ctx" in template and remove this
"locale": ctx.Locale,
"Title": ctx.Locale.Tr("install.install"),
"PageIsInstall": true,
Expand Down
2 changes: 1 addition & 1 deletion routers/web/auth/oauth.go
Original file line number Diff line number Diff line change
Expand Up @@ -578,7 +578,7 @@ func GrantApplicationOAuth(ctx *context.Context) {

// OIDCWellKnown generates JSON so OIDC clients know Gitea's capabilities
func OIDCWellKnown(ctx *context.Context) {
t, err := ctx.Render.TemplateLookup("user/auth/oidc_wellknown")
t, err := ctx.Render.TemplateLookup("user/auth/oidc_wellknown", nil)
if err != nil {
ctx.ServerError("unable to find template", err)
return
Expand Down
2 changes: 1 addition & 1 deletion routers/web/swagger_json.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ const tplSwaggerV1Json base.TplName = "swagger/v1_json"

// SwaggerV1Json render swagger v1 json
func SwaggerV1Json(ctx *context.Context) {
t, err := ctx.Render.TemplateLookup(string(tplSwaggerV1Json))
t, err := ctx.Render.TemplateLookup(string(tplSwaggerV1Json), nil)
if err != nil {
ctx.ServerError("unable to find template", err)
return
Expand Down
4 changes: 3 additions & 1 deletion templates/base/footer.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@
{{end}}
{{end}}
<script src="{{AssetUrlPrefix}}/js/index.js?v={{AssetVersion}}" onerror="alert('Failed to load asset files from ' + this.src + '. Please make sure the asset files can be accessed.')"></script>
{{template "custom/footer" .}}

{{template "custom/footer" .}}
{{ctx.DataRaceCheck $.Context}}
</body>
</html>
13 changes: 7 additions & 6 deletions templates/base/head.tmpl
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<!DOCTYPE html>
<html lang="{{.locale.Lang}}" class="theme-{{if .SignedUser.Theme}}{{.SignedUser.Theme}}{{else}}{{DefaultTheme}}{{end}}">
<html lang="{{ctx.Locale.Lang}}" class="theme-{{if .SignedUser.Theme}}{{.SignedUser.Theme}}{{else}}{{DefaultTheme}}{{end}}">
<head>
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>{{if .Title}}{{.Title | RenderEmojiPlain}} - {{end}}{{if .Repository.Name}}{{.Repository.Name}} - {{end}}{{AppName}}</title>
Expand Down Expand Up @@ -28,7 +28,7 @@
{{if .PageIsUserProfile}}
<meta property="og:title" content="{{.ContextUser.DisplayName}}">
<meta property="og:type" content="profile">
<meta property="og:image" content="{{.ContextUser.AvatarLink $.Context}}">
<meta property="og:image" content="{{.ContextUser.AvatarLink ctx}}">
<meta property="og:url" content="{{.ContextUser.HTMLURL}}">
{{if .ContextUser.Description}}
<meta property="og:description" content="{{.ContextUser.Description}}">
Expand All @@ -48,10 +48,10 @@
{{end}}
{{end}}
<meta property="og:type" content="object">
{{if (.Repository.AvatarLink $.Context)}}
<meta property="og:image" content="{{.Repository.AvatarLink $.Context}}">
{{if (.Repository.AvatarLink ctx)}}
<meta property="og:image" content="{{.Repository.AvatarLink ctx}}">
{{else}}
<meta property="og:image" content="{{.Repository.Owner.AvatarLink $.Context}}">
<meta property="og:image" content="{{.Repository.Owner.AvatarLink ctx}}">
{{end}}
{{else}}
<meta property="og:title" content="{{AppName}}">
Expand All @@ -65,10 +65,11 @@
{{template "custom/header" .}}
</head>
<body>
{{ctx.DataRaceCheck $.Context}}
{{template "custom/body_outer_pre" .}}

<div class="full height">
<noscript>{{.locale.Tr "enable_javascript"}}</noscript>
<noscript>{{ctx.Locale.Tr "enable_javascript"}}</noscript>

{{template "custom/body_inner_pre" .}}

Expand Down