2022-08-28 12:43:25 +03:00
// Copyright 2022 The Gitea Authors. All rights reserved.
2022-11-27 21:20:29 +03:00
// SPDX-License-Identifier: MIT
2022-08-28 12:43:25 +03:00
package templates
import (
"context"
2024-03-03 07:57:22 +03:00
"fmt"
2022-08-28 12:43:25 +03:00
"html/template"
2023-04-30 15:22:23 +03:00
"regexp"
2022-08-28 12:43:25 +03:00
"strings"
texttmpl "text/template"
2023-04-08 16:15:22 +03:00
"code.gitea.io/gitea/modules/base"
2022-08-28 12:43:25 +03:00
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
)
2023-04-30 15:22:23 +03:00
var mailSubjectSplit = regexp . MustCompile ( ` (?m)^- { 3,}\s*$ ` )
2023-04-08 16:15:22 +03:00
// mailSubjectTextFuncMap returns functions for injecting to text templates, it's only used for mail subject
func mailSubjectTextFuncMap ( ) texttmpl . FuncMap {
return texttmpl . FuncMap {
"dict" : dict ,
2024-06-19 01:32:45 +03:00
"Eval" : evalTokens ,
2023-04-08 16:15:22 +03:00
"EllipsisString" : base . EllipsisString ,
"AppName" : func ( ) string {
return setting . AppName
} ,
"AppDomain" : func ( ) string { // documented in mail-templates.md
return setting . Domain
} ,
}
}
2024-03-03 07:57:22 +03:00
func buildSubjectBodyTemplate ( stpl * texttmpl . Template , btpl * template . Template , name string , content [ ] byte ) error {
2023-04-08 16:15:22 +03:00
// Split template into subject and body
var subjectContent [ ] byte
bodyContent := content
loc := mailSubjectSplit . FindIndex ( content )
if loc != nil {
subjectContent = content [ 0 : loc [ 0 ] ]
bodyContent = content [ loc [ 1 ] : ]
}
2024-03-03 07:57:22 +03:00
if _ , err := stpl . New ( name ) . Parse ( string ( subjectContent ) ) ; err != nil {
return fmt . Errorf ( "failed to parse template [%s/subject]: %w" , name , err )
2023-04-08 16:15:22 +03:00
}
2024-03-03 07:57:22 +03:00
if _ , err := btpl . New ( name ) . Parse ( string ( bodyContent ) ) ; err != nil {
return fmt . Errorf ( "failed to parse template [%s/body]: %w" , name , err )
2023-04-08 16:15:22 +03:00
}
2024-03-03 07:57:22 +03:00
return nil
2023-04-08 16:15:22 +03:00
}
2022-08-28 12:43:25 +03:00
// Mailer provides the templates required for sending notification mails.
func Mailer ( ctx context . Context ) ( * texttmpl . Template , * template . Template ) {
2023-04-08 16:15:22 +03:00
subjectTemplates := texttmpl . New ( "" )
bodyTemplates := template . New ( "" )
subjectTemplates . Funcs ( mailSubjectTextFuncMap ( ) )
2023-04-30 15:22:23 +03:00
bodyTemplates . Funcs ( NewFuncMap ( ) )
2022-08-28 12:43:25 +03:00
2023-04-12 13:16:45 +03:00
assetFS := AssetFS ( )
2023-05-25 06:47:30 +03:00
refreshTemplates := func ( firstRun bool ) {
if ! firstRun {
log . Trace ( "Reloading mail templates" )
}
2023-04-12 13:16:45 +03:00
assetPaths , err := ListMailTemplateAssetNames ( assetFS )
if err != nil {
log . Error ( "Failed to list mail templates: %v" , err )
return
2022-08-28 12:43:25 +03:00
}
2023-04-12 13:16:45 +03:00
for _ , assetPath := range assetPaths {
content , layerName , err := assetFS . ReadLayeredFile ( assetPath )
2022-08-28 12:43:25 +03:00
if err != nil {
2023-04-12 13:16:45 +03:00
log . Warn ( "Failed to read mail template %s by %s: %v" , assetPath , layerName , err )
continue
2022-08-28 12:43:25 +03:00
}
2023-04-12 13:16:45 +03:00
tmplName := strings . TrimPrefix ( strings . TrimSuffix ( assetPath , ".tmpl" ) , "mail/" )
2023-05-25 06:47:30 +03:00
if firstRun {
log . Trace ( "Adding mail template %s: %s by %s" , tmplName , assetPath , layerName )
}
2024-03-03 07:57:22 +03:00
if err = buildSubjectBodyTemplate ( subjectTemplates , bodyTemplates , tmplName , content ) ; err != nil {
if firstRun {
log . Fatal ( "Failed to parse mail template, err: %v" , err )
}
2024-04-22 14:48:42 +03:00
log . Error ( "Failed to parse mail template, err: %v" , err )
2024-03-03 07:57:22 +03:00
}
2022-08-28 12:43:25 +03:00
}
}
2023-05-25 06:47:30 +03:00
refreshTemplates ( true )
2022-08-28 12:43:25 +03:00
if ! setting . IsProd {
// Now subjectTemplates and bodyTemplates are both synchronized
// thus it is safe to call refresh from a different goroutine
2023-05-25 06:47:30 +03:00
go assetFS . WatchLocalChanges ( ctx , func ( ) {
refreshTemplates ( false )
} )
2022-08-28 12:43:25 +03:00
}
return subjectTemplates , bodyTemplates
}