Files
Jonathan Miller c572fcfe04
PR RC Release / Build RC Release (pull_request) Failing after 0s
Branch Policy Check / Verify merge target (pull_request) Failing after 0s
chore(core): rename Go module from code.gitea.io/gitea to MokoGitea namespace
Rename the Go module path from code.gitea.io/gitea to
git.mokoconsulting.tech/MokoConsulting/MokoGitea across the entire
codebase.

Scope:
- go.mod module declaration
- 2,235 Go source files (import paths)
- Dockerfile WORKDIR and COPY paths
- Swagger API templates
- golangci.yml linter config

External dependencies (code.gitea.io/gitea-vet, code.gitea.io/sdk/gitea,
gitea.com/gitea/act, etc.) are intentionally NOT renamed — they are
separate upstream modules.

Closes #132

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-25 00:22:38 -05:00

249 lines
6.4 KiB
Go

// Copyright 2022 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package templates
import (
"html/template"
"io"
"net/url"
"regexp"
"slices"
"strings"
"sync"
texttmpl "text/template"
"git.mokoconsulting.tech/MokoConsulting/MokoGitea/modules/base"
"git.mokoconsulting.tech/MokoConsulting/MokoGitea/modules/graceful"
"git.mokoconsulting.tech/MokoConsulting/MokoGitea/modules/log"
"git.mokoconsulting.tech/MokoConsulting/MokoGitea/modules/setting"
"git.mokoconsulting.tech/MokoConsulting/MokoGitea/modules/util"
)
type MailRender struct {
TemplateNames []string
BodyTemplates struct {
HasTemplate func(name string) bool
ExecuteTemplate func(w io.Writer, name string, data any) error
}
// FIXME: MAIL-TEMPLATE-SUBJECT: only "issue" related messages support using subject from templates
// It is an incomplete implementation from "Use templates for issue e-mail subject and body" https://github.com/go-gitea/gitea/pull/8329
SubjectTemplates *texttmpl.Template
tmplRenderer *tmplRender
mockedBodyTemplates map[string]*template.Template
}
// dotEscape wraps a dots in names with ZWJ [U+200D] in order to prevent auto-linkers from detecting these as urls
func dotEscape(raw string) string {
return strings.ReplaceAll(raw, ".", "\u200d.\u200d")
}
// mailSubjectTextFuncMap returns functions for injecting to text templates, it's only used for mail subject
func mailSubjectTextFuncMap() texttmpl.FuncMap {
return texttmpl.FuncMap{
"dict": dict,
"Eval": evalTokens,
"EllipsisString": util.EllipsisDisplayString,
"AppName": func() string {
return setting.AppName
},
"AppDomain": func() string { // documented in mail-templates.md
return setting.Domain
},
}
}
func mailBodyFuncMap() template.FuncMap {
// Some of them are documented in mail-templates.md
return template.FuncMap{
"DumpVar": dumpVar,
"NIL": func() any { return nil },
// html/template related functions
"dict": dict,
"Iif": iif,
"Eval": evalTokens,
"HTMLFormat": htmlFormat,
"QueryEscape": queryEscape,
"QueryBuild": QueryBuild,
// deprecated, use "HTMLFormat" instead, but some user custom mail templates still use it
// see: https://github.com/go-gitea/gitea/issues/36049
"SanitizeHTML": sanitizeHTML,
"PathEscape": url.PathEscape,
"PathEscapeSegments": util.PathEscapeSegments,
"DotEscape": dotEscape,
// utils
"StringUtils": NewStringUtils,
"SliceUtils": NewSliceUtils,
"JsonUtils": NewJsonUtils,
// time / number / format
"ShortSha": base.ShortSha,
"FileSize": base.FileSize,
// setting
"AppName": func() string {
return setting.AppName
},
"AppUrl": func() string {
return setting.AppURL
},
"AppDomain": func() string {
return setting.Domain
},
}
}
var mailSubjectSplit = regexp.MustCompile(`(?m)^-{3,}\s*$`)
func newMailRenderer() (*MailRender, error) {
subjectTemplates := texttmpl.New("")
subjectTemplates.Funcs(mailSubjectTextFuncMap())
renderer := &MailRender{
SubjectTemplates: subjectTemplates,
}
assetFS := AssetFS()
renderer.tmplRenderer = &tmplRender{
collectTemplateNames: func() ([]string, error) {
names, err := assetFS.ListAllFiles(".", true)
if err != nil {
return nil, err
}
names = slices.DeleteFunc(names, func(file string) bool {
return !strings.HasPrefix(file, "mail/") || !strings.HasSuffix(file, ".tmpl")
})
for i, name := range names {
names[i] = strings.TrimSuffix(strings.TrimPrefix(name, "mail/"), ".tmpl")
}
renderer.TemplateNames = names
return names, nil
},
readTemplateContent: func(name string) ([]byte, error) {
content, err := assetFS.ReadFile("mail/" + name + ".tmpl")
if err != nil {
return nil, err
}
var subjectContent []byte
bodyContent := content
loc := mailSubjectSplit.FindIndex(content)
if loc != nil {
subjectContent, bodyContent = content[0:loc[0]], content[loc[1]:]
}
_, err = renderer.SubjectTemplates.New(name).Parse(string(subjectContent))
if err != nil {
return nil, err
}
return bodyContent, nil
},
}
renderer.BodyTemplates.HasTemplate = func(name string) bool {
if renderer.mockedBodyTemplates[name] != nil {
return true
}
return renderer.tmplRenderer.Templates().HasTemplate(name)
}
staticFuncMap := mailBodyFuncMap()
renderer.BodyTemplates.ExecuteTemplate = func(w io.Writer, name string, data any) error {
if t, ok := renderer.mockedBodyTemplates[name]; ok {
return t.Execute(w, data)
}
t, err := renderer.tmplRenderer.Templates().Executor(name, staticFuncMap)
if err != nil {
return err
}
return t.Execute(w, data)
}
err := renderer.tmplRenderer.recompileTemplates(staticFuncMap)
if err != nil {
return nil, err
}
return renderer, nil
}
func (r *MailRender) MockTemplate(name, subject, body string) func() {
if r.mockedBodyTemplates == nil {
r.mockedBodyTemplates = make(map[string]*template.Template)
}
oldSubject := r.SubjectTemplates
r.SubjectTemplates, _ = r.SubjectTemplates.Clone()
texttmpl.Must(r.SubjectTemplates.New(name).Parse(subject))
oldBody, hasOldBody := r.mockedBodyTemplates[name]
mockFuncMap := mailBodyFuncMap()
r.mockedBodyTemplates[name] = template.Must(template.New(name).Funcs(mockFuncMap).Parse(body))
return func() {
r.SubjectTemplates = oldSubject
if hasOldBody {
r.mockedBodyTemplates[name] = oldBody
} else {
delete(r.mockedBodyTemplates, name)
}
}
}
var (
globalMailRenderer *MailRender
globalMailRendererMu sync.RWMutex
)
func MailRendererReload() error {
globalMailRendererMu.Lock()
defer globalMailRendererMu.Unlock()
r, err := newMailRenderer()
if err != nil {
return err
}
globalMailRenderer = r
return nil
}
func MailRenderer() *MailRender {
globalMailRendererMu.RLock()
r := globalMailRenderer
globalMailRendererMu.RUnlock()
if r != nil {
return r
}
globalMailRendererMu.Lock()
defer globalMailRendererMu.Unlock()
if globalMailRenderer != nil {
return globalMailRenderer
}
var err error
globalMailRenderer, err = newMailRenderer()
if err != nil {
log.Fatal("Failed to initialize mail renderer: %v", err)
}
if !setting.IsProd {
go AssetFS().WatchLocalChanges(graceful.GetManager().ShutdownContext(), func() {
globalMailRendererMu.Lock()
defer globalMailRendererMu.Unlock()
r, err := newMailRenderer()
if err != nil {
log.Error("Mail template error: %v", err)
return
}
globalMailRenderer = r
})
}
return globalMailRenderer
}