Skip to content

Commit 1f90147

Browse files
guillep2k6543
authored andcommitted
Use templates for issue e-mail subject and body (#8329)
* Add template capability for issue mail subject * Remove test string * Fix trim subject length * Add comment to template and run make fmt * Add information for the template * Rename defaultMailSubject() to fallbackMailSubject() * General rewrite of the mail template code * Fix .Doer name * Use text/template for subject instead of html * Fix subject Re: prefix * Fix mail tests * Fix static templates * [skip ci] Updated translations via Crowdin * Expose db.SetMaxOpenConns and allow non MySQL dbs to set conn pool params (#8528) * Expose db.SetMaxOpenConns and allow other dbs to set their connection params * Add note about port exhaustion Co-Authored-By: guillep2k <18600385+guillep2k@users.noreply.github.com> * Prevent .code-view from overriding font on icon fonts (#8614) * Correct some outdated statements in the contributing guidelines (#8612) * More information for drone-cli in CONTRIBUTING.md * Increases the version of drone-cli to 1.2.0 * Adds a note for the Docker Toolbox on Windows Signed-off-by: LukBukkit <luk.bukkit@gmail.com> * Fix the url for the blog repository (now on gitea.com) Signed-off-by: LukBukkit <luk.bukkit@gmail.com> * Remove TrN due to lack of lang context * Redo templates to match previous code * Fix extra character in template * Unify PR & Issue tempaltes, fix format * Remove default subject * Add template tests * Fix template * Remove replaced function * Provide User as models.User for better consistency * Add docs * Fix doc inaccuracies, improve examples * Change mail footer to math AppName * Add test for mail subject/body template separation * Add support for code review comments * Update docs/content/doc/advanced/mail-templates-us.md Co-Authored-By: 6543 <24977596+6543@users.noreply.github.com>
1 parent d5b1e6b commit 1f90147

File tree

13 files changed

+780
-161
lines changed

13 files changed

+780
-161
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,272 @@
1+
---
2+
date: "2019-10-23T17:00:00-03:00"
3+
title: "Mail templates"
4+
slug: "mail-templates"
5+
weight: 45
6+
toc: true
7+
draft: false
8+
menu:
9+
sidebar:
10+
parent: "advanced"
11+
name: "Mail templates"
12+
weight: 45
13+
identifier: "mail-templates"
14+
---
15+
16+
# Mail templates
17+
18+
To craft the e-mail subject and contents for certain operations, Gitea can be customized by using templates. The templates
19+
for these functions are located under the [`custom` directory](https://docs.gitea.io/en-us/customizing-gitea/).
20+
Gitea has an internal template that serves as default in case there's no custom alternative.
21+
22+
Custom templates are loaded when Gitea starts. Changes made to them are not recognized until Gitea is restarted again.
23+
24+
## Mail notifications supporting templates
25+
26+
Currently, the following notification events make use of templates:
27+
28+
| Action name | Usage |
29+
|---------------|--------------------------------------------------------------------------------------------------------------|
30+
| `new` | A new issue or pull request was created. |
31+
| `comment` | A new comment was created in an existing issue or pull request. |
32+
| `close` | An issue or pull request was closed. |
33+
| `reopen` | An issue or pull request was reopened. |
34+
| `review` | The head comment of a review in a pull request. |
35+
| `code` | A single comment on the code of a pull request. |
36+
| `assigned` | Used was assigned to an issue or pull request. |
37+
| `default` | Any action not included in the above categories, or when the corresponding category template is not present. |
38+
39+
The path for the template of a particular message type is:
40+
41+
```
42+
custom/templates/mail/{action type}/{action name}.tmpl
43+
```
44+
45+
Where `{action type}` is one of `issue` or `pull` (for pull requests), and `{action name}` is one of the names listed above.
46+
47+
For example, the specific template for a mail regarding a comment in a pull request is:
48+
```
49+
custom/templates/mail/pull/comment.tmpl
50+
```
51+
52+
However, creating templates for each and every action type/name combination is not required.
53+
A fallback system is used to choose the appropriate template for an event. The _first existing_
54+
template on this list is used:
55+
56+
* The specific template for the desired **action type** and **action name**.
57+
* The template for action type `issue` and the desired **action name**.
58+
* The template for the desired **action type**, action name `default`.
59+
* The template for action type `issue`, action name `default`.
60+
61+
The only mandatory template is action type `issue`, action name `default`, which is already embedded in Gitea
62+
unless it's overridden by the user in the `custom` directory.
63+
64+
## Template syntax
65+
66+
Mail templates are UTF-8 encoded text files that need to follow one of the following formats:
67+
68+
```
69+
Text and macros for the subject line
70+
------------
71+
Text and macros for the mail body
72+
```
73+
74+
or
75+
76+
```
77+
Text and macros for the mail body
78+
```
79+
80+
Specifying a _subject_ section is optional (and therefore also the dash line separator). When used, the separator between
81+
_subject_ and _mail body_ templates requires at least three dashes; no other characters are allowed in the separator line.
82+
83+
84+
_Subject_ and _mail body_ are parsed by [Golang's template engine](https://golang.org/pkg/text/template/) and
85+
are provided with a _metadata context_ assembled for each notification. The context contains the following elements:
86+
87+
| Name | Type | Available | Usage |
88+
|--------------------|----------------|---------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
89+
| `.FallbackSubject` | string | Always | A default subject line. See Below. |
90+
| `.Subject` | string | Only in body | The _subject_, once resolved. |
91+
| `.Body` | string | Always | The message of the issue, pull request or comment, parsed from Markdown into HTML and sanitized. Do not confuse with the _mail body_ |
92+
| `.Link` | string | Always | The address of the originating issue, pull request or comment. |
93+
| `.Issue` | models.Issue | Always | The issue (or pull request) originating the notification. To get data specific to a pull request (e.g. `HasMerged`), `.Issue.PullRequest` can be used, but care should be taken as this field will be `nil` if the issue is *not* a pull request. |
94+
| `.Comment` | models.Comment | If applicable | If the notification is from a comment added to an issue or pull request, this will contain the information about the comment. |
95+
| `.IsPull` | bool | Always | `true` if the mail notification is associated with a pull request (i.e. `.Issue.PullRequest` is not `nil`). |
96+
| `.Repo` | string | Always | Name of the repository, including owner name (e.g. `mike/stuff`) |
97+
| `.User` | models.User | Always | Owner of the repository from which the event originated. To get the user name (e.g. `mike`),`.User.Name` can be used. |
98+
| `.Doer` | models.User | Always | User that executed the action triggering the notification event. To get the user name (e.g. `rhonda`), `.Doer.Name` can be used. |
99+
| `.IsMention` | bool | Always | `true` if this notification was only generated because the user was mentioned in the comment, while not being subscribed to the source. It will be `false` if the recipient was subscribed to the issue or repository. |
100+
| `.SubjectPrefix` | string | Always | `Re: ` if the notification is about other than issue or pull request creation; otherwise an empty string. |
101+
| `.ActionType` | string | Always | `"issue"` or `"pull"`. Will correspond to the actual _action type_ independently of which template was selected. |
102+
| `.ActionName` | string | Always | It will be one of the action types described above (`new`, `comment`, etc.), and will correspond to the actual _action name_ independently of which template was selected. |
103+
104+
All names are case sensitive.
105+
106+
### The _subject_ part of the template
107+
108+
The template engine used for the mail _subject_ is golang's [`text/template`](https://golang.org/pkg/text/template/).
109+
Please refer to the linked documentation for details about its syntax.
110+
111+
The _subject_ is built using the following steps:
112+
113+
* A template is selected according to the type of notification and to what templates are present.
114+
* The template is parsed and resolved (e.g. `{{.Issue.Index}}` is converted to the number of the issue
115+
or pull request).
116+
* All space-like characters (e.g. `TAB`, `LF`, etc.) are converted to normal spaces.
117+
* All leading, trailing and redundant spaces are removed.
118+
* The string is truncated to its first 256 runes (characters).
119+
120+
If the end result is an empty string, **or** no subject template was available (i.e. the selected template
121+
did not include a subject part), Gitea's **internal default** will be used.
122+
123+
The internal default (fallback) subject is the equivalent of:
124+
125+
```
126+
{{.SubjectPrefix}}[{{.Repo}}] {{.Issue.Title}} (#.Issue.Index)
127+
```
128+
129+
For example: `Re: [mike/stuff] New color palette (#38)`
130+
131+
Gitea's default subject can also be found in the template _metadata_ as `.FallbackSubject` from any of
132+
the two templates, even if a valid subject template is present.
133+
134+
### The _mail body_ part of the template
135+
136+
The template engine used for the _mail body_ is golang's [`html/template`](https://golang.org/pkg/html/template/).
137+
Please refer to the linked documentation for details about its syntax.
138+
139+
The _mail body_ is parsed after the mail subject, so there is an additional _metadata_ field which is
140+
the actual rendered subject, after all considerations.
141+
142+
The expected result is HTML (including structural elements like`<html>`, `<body>`, etc.). Styling
143+
through `<style>` blocks, `class` and `style` attributes is possible. However, `html/template`
144+
does some [automatic escaping](https://golang.org/pkg/html/template/#hdr-Contexts) that should be considered.
145+
146+
Attachments (such as images or external style sheets) are not supported. However, other templates can
147+
be referenced too, for example to provide the contents of a `<style>` element in a centralized fashion.
148+
The external template must be placed under `custom/mail` and referenced relative to that directory.
149+
For example, `custom/mail/styles/base.tmpl` can be included using `{{template styles/base}}`.
150+
151+
The mail is sent with `Content-Type: multipart/alternative`, so the body is sent in both HTML
152+
and text formats. The latter is obtained by stripping the HTML markup.
153+
154+
## Troubleshooting
155+
156+
How a mail is rendered is directly dependent on the capabilities of the mail application. Many mail
157+
clients don't even support HTML, so they show the text version included in the generated mail.
158+
159+
If the template fails to render, it will be noticed only at the moment the mail is sent.
160+
A default subject is used if the subject template fails, and whatever was rendered successfully
161+
from the the _mail body_ is used, disregarding the rest.
162+
163+
Please check [Gitea's logs](https://docs.gitea.io/en-us/logging-configuration/) for error messages in case of trouble.
164+
165+
## Example
166+
167+
`custom/templates/mail/issue/default.tmpl`:
168+
169+
```
170+
[{{.Repo}}] @{{.Doer.Name}}
171+
{{if eq .ActionName "new"}}
172+
created
173+
{{else if eq .ActionName "comment"}}
174+
commented on
175+
{{else if eq .ActionName "close"}}
176+
closed
177+
{{else if eq .ActionName "reopen"}}
178+
reopened
179+
{{else}}
180+
updated
181+
{{end}}
182+
{{if eq .ActionType "issue"}}
183+
issue
184+
{{else}}
185+
pull request
186+
{{end}}
187+
#{{.Issue.Index}}: {{.Issue.Title}}
188+
------------
189+
<!DOCTYPE html>
190+
<html>
191+
<head>
192+
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
193+
<title>{{.Subject}}</title>
194+
</head>
195+
196+
<body>
197+
{{if .IsMention}}
198+
<p>
199+
You are receiving this because @{{.Doer.Name}} mentioned you.
200+
</p>
201+
{{end}}
202+
<p>
203+
<p>
204+
<a href="{{AppURL}}/{{.Doer.LowerName}}">@{{.Doer.Name}}</a>
205+
{{if not (eq .Doer.FullName "")}}
206+
({{.Doer.FullName}})
207+
{{end}}
208+
{{if eq .ActionName "new"}}
209+
created
210+
{{else if eq .ActionName "close"}}
211+
closed
212+
{{else if eq .ActionName "reopen"}}
213+
reopened
214+
{{else}}
215+
updated
216+
{{end}}
217+
<a href="{{.Link}}">{{.Repo}}#{{.Issue.Index}}</a>.
218+
</p>
219+
{{if not (eq .Body "")}}
220+
<h3>Message content:</h3>
221+
<hr>
222+
{{.Body | Str2html}}
223+
{{end}}
224+
</p>
225+
<hr>
226+
<p>
227+
<a href="{{.Link}}">View it on Gitea</a>.
228+
</p>
229+
</body>
230+
</html>
231+
```
232+
233+
This template produces something along these lines:
234+
235+
#### Subject
236+
237+
> [mike/stuff] @rhonda commented on pull request #38: New color palette
238+
239+
#### Mail body
240+
241+
> [@rhonda](#) (Rhonda Myers) updated [mike/stuff#38](#).
242+
>
243+
> #### Message content:
244+
>
245+
> \__________________________________________________________________
246+
>
247+
> Mike, I think we should tone down the blues a little.
248+
> \__________________________________________________________________
249+
>
250+
> [View it on Gitea](#).
251+
252+
## Advanced
253+
254+
The template system contains several functions that can be used to further process and format
255+
the messages. Here's a list of some of them:
256+
257+
| Name | Parameters | Available | Usage |
258+
|----------------------|-------------|-----------|---------------------------------------------------------------------|
259+
| `AppUrl` | - | Any | Gitea's URL |
260+
| `AppName` | - | Any | Set from `app.ini`, usually "Gitea" |
261+
| `AppDomain` | - | Any | Gitea's host name |
262+
| `EllipsisString` | string, int | Any | Truncates a string to the specified length; adds ellipsis as needed |
263+
| `Str2html` | string | Body only | Sanitizes text by removing any HTML tags from it. |
264+
265+
These are _functions_, not metadata, so they have to be used:
266+
267+
```
268+
Like this: {{Str2html "Escape<my>text"}}
269+
Or this: {{"Escape<my>text" | Str2html}}
270+
Or this: {{AppUrl}}
271+
But not like this: {{.AppUrl}}
272+
```

modules/templates/dynamic.go

+11-22
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import (
1111
"io/ioutil"
1212
"path"
1313
"strings"
14+
texttmpl "text/template"
1415

1516
"code.gitea.io/gitea/modules/log"
1617
"code.gitea.io/gitea/modules/setting"
@@ -20,7 +21,8 @@ import (
2021
)
2122

2223
var (
23-
templates = template.New("")
24+
subjectTemplates = texttmpl.New("")
25+
bodyTemplates = template.New("")
2426
)
2527

2628
// HTMLRenderer implements the macaron handler for serving HTML templates.
@@ -59,9 +61,12 @@ func JSRenderer() macaron.Handler {
5961
}
6062

6163
// Mailer provides the templates required for sending notification mails.
62-
func Mailer() *template.Template {
64+
func Mailer() (*texttmpl.Template, *template.Template) {
65+
for _, funcs := range NewTextFuncMap() {
66+
subjectTemplates.Funcs(funcs)
67+
}
6368
for _, funcs := range NewFuncMap() {
64-
templates.Funcs(funcs)
69+
bodyTemplates.Funcs(funcs)
6570
}
6671

6772
staticDir := path.Join(setting.StaticRootPath, "templates", "mail")
@@ -84,15 +89,7 @@ func Mailer() *template.Template {
8489
continue
8590
}
8691

87-
_, err = templates.New(
88-
strings.TrimSuffix(
89-
filePath,
90-
".tmpl",
91-
),
92-
).Parse(string(content))
93-
if err != nil {
94-
log.Warn("Failed to parse template %v", err)
95-
}
92+
buildSubjectBodyTemplate(subjectTemplates, bodyTemplates, strings.TrimSuffix(filePath, ".tmpl"), content)
9693
}
9794
}
9895
}
@@ -117,18 +114,10 @@ func Mailer() *template.Template {
117114
continue
118115
}
119116

120-
_, err = templates.New(
121-
strings.TrimSuffix(
122-
filePath,
123-
".tmpl",
124-
),
125-
).Parse(string(content))
126-
if err != nil {
127-
log.Warn("Failed to parse template %v", err)
128-
}
117+
buildSubjectBodyTemplate(subjectTemplates, bodyTemplates, strings.TrimSuffix(filePath, ".tmpl"), content)
129118
}
130119
}
131120
}
132121

133-
return templates
122+
return subjectTemplates, bodyTemplates
134123
}

0 commit comments

Comments
 (0)