gitea

Зеркало из https://github.com/go-gitea/gitea
Форк
0
/
helper.go 
296 строк · 8.9 Кб
1
// Copyright 2018 The Gitea Authors. All rights reserved.
2
// Copyright 2014 The Gogs Authors. All rights reserved.
3
// SPDX-License-Identifier: MIT
4

5
package templates
6

7
import (
8
	"fmt"
9
	"html"
10
	"html/template"
11
	"net/url"
12
	"reflect"
13
	"slices"
14
	"strings"
15
	"time"
16

17
	user_model "code.gitea.io/gitea/models/user"
18
	"code.gitea.io/gitea/modules/base"
19
	"code.gitea.io/gitea/modules/markup"
20
	"code.gitea.io/gitea/modules/setting"
21
	"code.gitea.io/gitea/modules/svg"
22
	"code.gitea.io/gitea/modules/templates/eval"
23
	"code.gitea.io/gitea/modules/timeutil"
24
	"code.gitea.io/gitea/modules/util"
25
	"code.gitea.io/gitea/services/gitdiff"
26
	"code.gitea.io/gitea/services/webtheme"
27
)
28

29
// NewFuncMap returns functions for injecting to templates
30
func NewFuncMap() template.FuncMap {
31
	return map[string]any{
32
		"ctx": func() any { return nil }, // template context function
33

34
		"DumpVar": dumpVar,
35

36
		// -----------------------------------------------------------------
37
		// html/template related functions
38
		"dict":         dict, // it's lowercase because this name has been widely used. Our other functions should have uppercase names.
39
		"Iif":          iif,
40
		"Eval":         evalTokens,
41
		"SafeHTML":     safeHTML,
42
		"HTMLFormat":   HTMLFormat,
43
		"HTMLEscape":   htmlEscape,
44
		"QueryEscape":  queryEscape,
45
		"JSEscape":     jsEscapeSafe,
46
		"SanitizeHTML": SanitizeHTML,
47
		"URLJoin":      util.URLJoin,
48
		"DotEscape":    dotEscape,
49

50
		"PathEscape":         url.PathEscape,
51
		"PathEscapeSegments": util.PathEscapeSegments,
52

53
		// utils
54
		"StringUtils": NewStringUtils,
55
		"SliceUtils":  NewSliceUtils,
56
		"JsonUtils":   NewJsonUtils,
57

58
		// -----------------------------------------------------------------
59
		// svg / avatar / icon / color
60
		"svg":           svg.RenderHTML,
61
		"EntryIcon":     base.EntryIcon,
62
		"MigrationIcon": migrationIcon,
63
		"ActionIcon":    actionIcon,
64
		"SortArrow":     sortArrow,
65
		"ContrastColor": util.ContrastColor,
66

67
		// -----------------------------------------------------------------
68
		// time / number / format
69
		"FileSize":      base.FileSize,
70
		"CountFmt":      base.FormatNumberSI,
71
		"TimeSince":     timeutil.TimeSince,
72
		"TimeSinceUnix": timeutil.TimeSinceUnix,
73
		"DateTime":      timeutil.DateTime,
74
		"Sec2Time":      util.SecToTime,
75
		"LoadTimes": func(startTime time.Time) string {
76
			return fmt.Sprint(time.Since(startTime).Nanoseconds()/1e6) + "ms"
77
		},
78

79
		// -----------------------------------------------------------------
80
		// setting
81
		"AppName": func() string {
82
			return setting.AppName
83
		},
84
		"AppSubUrl": func() string {
85
			return setting.AppSubURL
86
		},
87
		"AssetUrlPrefix": func() string {
88
			return setting.StaticURLPrefix + "/assets"
89
		},
90
		"AppUrl": func() string {
91
			// The usage of AppUrl should be avoided as much as possible,
92
			// because the AppURL(ROOT_URL) may not match user's visiting site and the ROOT_URL in app.ini may be incorrect.
93
			// And it's difficult for Gitea to guess absolute URL correctly with zero configuration,
94
			// because Gitea doesn't know whether the scheme is HTTP or HTTPS unless the reverse proxy could tell Gitea.
95
			return setting.AppURL
96
		},
97
		"AppVer": func() string {
98
			return setting.AppVer
99
		},
100
		"AppDomain": func() string { // documented in mail-templates.md
101
			return setting.Domain
102
		},
103
		"AssetVersion": func() string {
104
			return setting.AssetVersion
105
		},
106
		"DefaultShowFullName": func() bool {
107
			return setting.UI.DefaultShowFullName
108
		},
109
		"ShowFooterTemplateLoadTime": func() bool {
110
			return setting.Other.ShowFooterTemplateLoadTime
111
		},
112
		"ShowFooterPoweredBy": func() bool {
113
			return setting.Other.ShowFooterPoweredBy
114
		},
115
		"AllowedReactions": func() []string {
116
			return setting.UI.Reactions
117
		},
118
		"CustomEmojis": func() map[string]string {
119
			return setting.UI.CustomEmojisMap
120
		},
121
		"MetaAuthor": func() string {
122
			return setting.UI.Meta.Author
123
		},
124
		"MetaDescription": func() string {
125
			return setting.UI.Meta.Description
126
		},
127
		"MetaKeywords": func() string {
128
			return setting.UI.Meta.Keywords
129
		},
130
		"EnableTimetracking": func() bool {
131
			return setting.Service.EnableTimetracking
132
		},
133
		"DisableGitHooks": func() bool {
134
			return setting.DisableGitHooks
135
		},
136
		"DisableWebhooks": func() bool {
137
			return setting.DisableWebhooks
138
		},
139
		"DisableImportLocal": func() bool {
140
			return !setting.ImportLocalPaths
141
		},
142
		"UserThemeName": userThemeName,
143
		"NotificationSettings": func() map[string]any {
144
			return map[string]any{
145
				"MinTimeout":            int(setting.UI.Notification.MinTimeout / time.Millisecond),
146
				"TimeoutStep":           int(setting.UI.Notification.TimeoutStep / time.Millisecond),
147
				"MaxTimeout":            int(setting.UI.Notification.MaxTimeout / time.Millisecond),
148
				"EventSourceUpdateTime": int(setting.UI.Notification.EventSourceUpdateTime / time.Millisecond),
149
			}
150
		},
151
		"MermaidMaxSourceCharacters": func() int {
152
			return setting.MermaidMaxSourceCharacters
153
		},
154

155
		// -----------------------------------------------------------------
156
		// render
157
		"RenderCommitMessage":            RenderCommitMessage,
158
		"RenderCommitMessageLinkSubject": renderCommitMessageLinkSubject,
159

160
		"RenderCommitBody": renderCommitBody,
161
		"RenderCodeBlock":  renderCodeBlock,
162
		"RenderIssueTitle": renderIssueTitle,
163
		"RenderEmoji":      renderEmoji,
164
		"ReactionToEmoji":  reactionToEmoji,
165

166
		"RenderMarkdownToHtml": RenderMarkdownToHtml,
167
		"RenderLabel":          renderLabel,
168
		"RenderLabels":         RenderLabels,
169

170
		// -----------------------------------------------------------------
171
		// misc
172
		"ShortSha":                 base.ShortSha,
173
		"ActionContent2Commits":    ActionContent2Commits,
174
		"IsMultilineCommitMessage": isMultilineCommitMessage,
175
		"CommentMustAsDiff":        gitdiff.CommentMustAsDiff,
176
		"MirrorRemoteAddress":      mirrorRemoteAddress,
177

178
		"FilenameIsImage": filenameIsImage,
179
		"TabSizeClass":    tabSizeClass,
180
	}
181
}
182

183
func HTMLFormat(s string, rawArgs ...any) template.HTML {
184
	args := slices.Clone(rawArgs)
185
	for i, v := range args {
186
		switch v := v.(type) {
187
		case nil, bool, int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64, float32, float64, template.HTML:
188
			// for most basic types (including template.HTML which is safe), just do nothing and use it
189
		case string:
190
			args[i] = template.HTMLEscapeString(v)
191
		case fmt.Stringer:
192
			args[i] = template.HTMLEscapeString(v.String())
193
		default:
194
			args[i] = template.HTMLEscapeString(fmt.Sprint(v))
195
		}
196
	}
197
	return template.HTML(fmt.Sprintf(s, args...))
198
}
199

200
// safeHTML render raw as HTML
201
func safeHTML(s any) template.HTML {
202
	switch v := s.(type) {
203
	case string:
204
		return template.HTML(v)
205
	case template.HTML:
206
		return v
207
	}
208
	panic(fmt.Sprintf("unexpected type %T", s))
209
}
210

211
// SanitizeHTML sanitizes the input by pre-defined markdown rules
212
func SanitizeHTML(s string) template.HTML {
213
	return template.HTML(markup.Sanitize(s))
214
}
215

216
func htmlEscape(s any) template.HTML {
217
	switch v := s.(type) {
218
	case string:
219
		return template.HTML(html.EscapeString(v))
220
	case template.HTML:
221
		return v
222
	}
223
	panic(fmt.Sprintf("unexpected type %T", s))
224
}
225

226
func jsEscapeSafe(s string) template.HTML {
227
	return template.HTML(template.JSEscapeString(s))
228
}
229

230
func queryEscape(s string) template.URL {
231
	return template.URL(url.QueryEscape(s))
232
}
233

234
// dotEscape wraps a dots in names with ZWJ [U+200D] in order to prevent auto-linkers from detecting these as urls
235
func dotEscape(raw string) string {
236
	return strings.ReplaceAll(raw, ".", "\u200d.\u200d")
237
}
238

239
// iif is an "inline-if", similar util.Iif[T] but templates need the non-generic version,
240
// and it could be simply used as "{{iif expr trueVal}}" (omit the falseVal).
241
func iif(condition any, vals ...any) any {
242
	if isTemplateTruthy(condition) {
243
		return vals[0]
244
	} else if len(vals) > 1 {
245
		return vals[1]
246
	}
247
	return nil
248
}
249

250
func isTemplateTruthy(v any) bool {
251
	if v == nil {
252
		return false
253
	}
254

255
	rv := reflect.ValueOf(v)
256
	switch rv.Kind() {
257
	case reflect.Bool:
258
		return rv.Bool()
259
	case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
260
		return rv.Int() != 0
261
	case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
262
		return rv.Uint() != 0
263
	case reflect.Float32, reflect.Float64:
264
		return rv.Float() != 0
265
	case reflect.Complex64, reflect.Complex128:
266
		return rv.Complex() != 0
267
	case reflect.String, reflect.Slice, reflect.Array, reflect.Map:
268
		return rv.Len() > 0
269
	case reflect.Struct:
270
		return true
271
	default:
272
		return !rv.IsNil()
273
	}
274
}
275

276
// evalTokens evaluates the expression by tokens and returns the result, see the comment of eval.Expr for details.
277
// To use this helper function in templates, pass each token as a separate parameter.
278
//
279
//	{{ $int64 := Eval $var "+" 1 }}
280
//	{{ $float64 := Eval $var "+" 1.0 }}
281
//
282
// Golang's template supports comparable int types, so the int64 result can be used in later statements like {{if lt $int64 10}}
283
func evalTokens(tokens ...any) (any, error) {
284
	n, err := eval.Expr(tokens...)
285
	return n.Value, err
286
}
287

288
func userThemeName(user *user_model.User) string {
289
	if user == nil || user.Theme == "" {
290
		return setting.UI.DefaultTheme
291
	}
292
	if webtheme.IsThemeAvailable(user.Theme) {
293
		return user.Theme
294
	}
295
	return setting.UI.DefaultTheme
296
}
297

Использование cookies

Мы используем файлы cookie в соответствии с Политикой конфиденциальности и Политикой использования cookies.

Нажимая кнопку «Принимаю», Вы даете АО «СберТех» согласие на обработку Ваших персональных данных в целях совершенствования нашего веб-сайта и Сервиса GitVerse, а также повышения удобства их использования.

Запретить использование cookies Вы можете самостоятельно в настройках Вашего браузера.