diff --git a/modules/context/context.go b/modules/context/context.go
index cd7fcebe55da5..db8e92dc571db 100644
--- a/modules/context/context.go
+++ b/modules/context/context.go
@@ -31,6 +31,7 @@ import (
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/templates"
+ "code.gitea.io/gitea/modules/timeutil"
"code.gitea.io/gitea/modules/translation"
"code.gitea.io/gitea/modules/typesniffer"
"code.gitea.io/gitea/modules/util"
@@ -216,6 +217,26 @@ func (ctx *Context) RedirectToFirst(location ...string) {
const tplStatus500 base.TplName = "status/500"
+func (ctx *Context) newCtxFuncMap() template.FuncMap {
+ return template.FuncMap{
+ "CtxLocale": func(s string) string {
+ return ctx.Locale.Tr(s)
+ },
+ "CtxDateTime": func(format string, datetime any) template.HTML {
+ username := "(anonymous)"
+ if ctx.Doer != nil {
+ username = ctx.Doer.Name
+ }
+ s := fmt.Sprintf("PoC demo for %q: %s", html.EscapeString(username), timeutil.DateTime(format, datetime))
+ return template.HTML(s)
+ },
+ }
+}
+
+func (ctx *Context) renderHTML(w io.Writer, status int, name base.TplName, data map[string]any) error {
+ return ctx.Render.(*templates.HTMLRender).HTMLCtxFuncs(ctx.Resp, status, string(name), templates.BaseVars().Merge(data), ctx.newCtxFuncMap())
+}
+
// HTML calls Context.HTML and renders the template to HTTP response
func (ctx *Context) HTML(status int, name base.TplName) {
log.Debug("Template: %s", name)
@@ -226,7 +247,7 @@ func (ctx *Context) HTML(status int, name base.TplName) {
ctx.Data["TemplateLoadTimes"] = func() string {
return strconv.FormatInt(time.Since(tmplStartTime).Nanoseconds()/1e6, 10) + "ms"
}
- if err := ctx.Render.HTML(ctx.Resp, status, string(name), templates.BaseVars().Merge(ctx.Data)); err != nil {
+ if err := ctx.renderHTML(ctx.Resp, status, name, ctx.Data); err != nil {
if status == http.StatusInternalServerError && name == tplStatus500 {
ctx.PlainText(http.StatusInternalServerError, "Unable to find HTML templates, the template system is not initialized, or Gitea can't find your template files.")
return
@@ -239,7 +260,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]interface{}) (string, error) {
var buf strings.Builder
- err := ctx.Render.HTML(&buf, http.StatusOK, string(name), data)
+ err := ctx.renderHTML(&buf, http.StatusOK, name, data)
return buf.String(), err
}
diff --git a/modules/templates/helper.go b/modules/templates/helper.go
index 4abd94d46e59e..f19b36965e1d8 100644
--- a/modules/templates/helper.go
+++ b/modules/templates/helper.go
@@ -30,6 +30,10 @@ func NewFuncMap() template.FuncMap {
return map[string]interface{}{
"DumpVar": dumpVar,
+ // ctx func
+ "CtxLocale": func(v ...any) any { panic("not implemented") },
+ "CtxDateTime": func(v ...any) any { panic("not implemented") },
+
// -----------------------------------------------------------------
// html/template related functions
"dict": dict, // it's lowercase because this name has been widely used. Our other functions should have uppercase names.
diff --git a/modules/templates/htmlrenderer.go b/modules/templates/htmlrenderer.go
index d60be887278a5..41ab04a7ece45 100644
--- a/modules/templates/htmlrenderer.go
+++ b/modules/templates/htmlrenderer.go
@@ -8,6 +8,7 @@ import (
"bytes"
"errors"
"fmt"
+ "html/template"
"io"
"net/http"
"path/filepath"
@@ -53,6 +54,24 @@ func (h *HTMLRender) HTML(w io.Writer, status int, name string, data interface{}
return t.Execute(w, data)
}
+func (h *HTMLRender) HTMLCtxFuncs(w io.Writer, status int, name string, data interface{}, ctxFuncMap template.FuncMap) error {
+ 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)
+ }
+ tmpls := h.templates.Load()
+ if tmpls == nil {
+ return ErrTemplateNotInitialized
+ }
+ st, err := tmpls.Executor(name, NewFuncMap(), ctxFuncMap)
+ if err != nil {
+ return texttemplate.ExecError{Name: name, Err: err}
+ }
+ return st.Execute(w, data)
+}
+
func (h *HTMLRender) TemplateLookup(name string) (TemplateExecutor, error) {
tmpls := h.templates.Load()
if tmpls == nil {
diff --git a/modules/templates/scopedtmpl/scopedtmpl.go b/modules/templates/scopedtmpl/scopedtmpl.go
index a8b67ad0f908c..30df9714fe436 100644
--- a/modules/templates/scopedtmpl/scopedtmpl.go
+++ b/modules/templates/scopedtmpl/scopedtmpl.go
@@ -62,7 +62,7 @@ func (t *ScopedTemplate) Freeze() {
t.all.Funcs(m)
}
-func (t *ScopedTemplate) Executor(name string, funcMap template.FuncMap) (TemplateExecutor, error) {
+func (t *ScopedTemplate) Executor(name string, funcMaps ...template.FuncMap) (TemplateExecutor, error) {
t.scopedMu.RLock()
scopedTmplSet, ok := t.scopedTemplateSets[name]
t.scopedMu.RUnlock()
@@ -84,7 +84,7 @@ func (t *ScopedTemplate) Executor(name string, funcMap template.FuncMap) (Templa
if scopedTmplSet == nil {
return nil, fmt.Errorf("template %s not found", name)
}
- return scopedTmplSet.newExecutor(funcMap), nil
+ return scopedTmplSet.newExecutor(funcMaps...), nil
}
type scopedTemplateSet struct {
@@ -216,14 +216,14 @@ func newScopedTemplateSet(all *template.Template, name string) (*scopedTemplateS
return ts, collectErr
}
-func (ts *scopedTemplateSet) newExecutor(funcMap map[string]any) TemplateExecutor {
+func (ts *scopedTemplateSet) newExecutor(funcMaps ...template.FuncMap) TemplateExecutor {
tmpl := texttemplate.New("")
tmplPtr := ptr[textTemplate](tmpl)
tmplPtr.execFuncs = map[string]reflect.Value{}
for k, v := range ts.execFuncs {
tmplPtr.execFuncs[k] = v
}
- if funcMap != nil {
+ for _, funcMap := range funcMaps {
tmpl.Funcs(funcMap)
}
// after escapeTemplate, the html templates are also escaped text templates, so it could be added to the text template directly
diff --git a/templates/devtest/ctxfunc.tmpl b/templates/devtest/ctxfunc.tmpl
new file mode 100644
index 0000000000000..573f30851fff7
--- /dev/null
+++ b/templates/devtest/ctxfunc.tmpl
@@ -0,0 +1,2 @@
+Locale: {{CtxLocale "home"}}
+DateTime: {{CtxDateTime "full" "2001-02-03"}}