You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

helper.go 8.3KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271
  1. // Copyright 2018 The Gitea Authors. All rights reserved.
  2. // Copyright 2014 The Gogs Authors. All rights reserved.
  3. // SPDX-License-Identifier: MIT
  4. package templates
  5. import (
  6. "fmt"
  7. "html"
  8. "html/template"
  9. "net/url"
  10. "slices"
  11. "strings"
  12. "time"
  13. user_model "code.gitea.io/gitea/models/user"
  14. "code.gitea.io/gitea/modules/base"
  15. "code.gitea.io/gitea/modules/markup"
  16. "code.gitea.io/gitea/modules/setting"
  17. "code.gitea.io/gitea/modules/svg"
  18. "code.gitea.io/gitea/modules/templates/eval"
  19. "code.gitea.io/gitea/modules/timeutil"
  20. "code.gitea.io/gitea/modules/util"
  21. "code.gitea.io/gitea/services/gitdiff"
  22. "code.gitea.io/gitea/services/webtheme"
  23. )
  24. // NewFuncMap returns functions for injecting to templates
  25. func NewFuncMap() template.FuncMap {
  26. return map[string]any{
  27. "ctx": func() any { return nil }, // template context function
  28. "DumpVar": dumpVar,
  29. // -----------------------------------------------------------------
  30. // html/template related functions
  31. "dict": dict, // it's lowercase because this name has been widely used. Our other functions should have uppercase names.
  32. "Iif": Iif,
  33. "Eval": Eval,
  34. "SafeHTML": SafeHTML,
  35. "HTMLFormat": HTMLFormat,
  36. "HTMLEscape": HTMLEscape,
  37. "QueryEscape": QueryEscape,
  38. "JSEscape": JSEscapeSafe,
  39. "SanitizeHTML": SanitizeHTML,
  40. "URLJoin": util.URLJoin,
  41. "DotEscape": DotEscape,
  42. "PathEscape": url.PathEscape,
  43. "PathEscapeSegments": util.PathEscapeSegments,
  44. // utils
  45. "StringUtils": NewStringUtils,
  46. "SliceUtils": NewSliceUtils,
  47. "JsonUtils": NewJsonUtils,
  48. // -----------------------------------------------------------------
  49. // svg / avatar / icon / color
  50. "svg": svg.RenderHTML,
  51. "EntryIcon": base.EntryIcon,
  52. "MigrationIcon": MigrationIcon,
  53. "ActionIcon": ActionIcon,
  54. "SortArrow": SortArrow,
  55. "ContrastColor": util.ContrastColor,
  56. // -----------------------------------------------------------------
  57. // time / number / format
  58. "FileSize": base.FileSize,
  59. "CountFmt": base.FormatNumberSI,
  60. "TimeSince": timeutil.TimeSince,
  61. "TimeSinceUnix": timeutil.TimeSinceUnix,
  62. "DateTime": timeutil.DateTime,
  63. "Sec2Time": util.SecToTime,
  64. "SecToTimeExact": util.SecToTimeExact,
  65. "TimeEstimateToStr": util.TimeEstimateToStr,
  66. "LoadTimes": func(startTime time.Time) string {
  67. return fmt.Sprint(time.Since(startTime).Nanoseconds()/1e6) + "ms"
  68. },
  69. // -----------------------------------------------------------------
  70. // setting
  71. "AppName": func() string {
  72. return setting.AppName
  73. },
  74. "AppSubUrl": func() string {
  75. return setting.AppSubURL
  76. },
  77. "AssetUrlPrefix": func() string {
  78. return setting.StaticURLPrefix + "/assets"
  79. },
  80. "AppUrl": func() string {
  81. // The usage of AppUrl should be avoided as much as possible,
  82. // because the AppURL(ROOT_URL) may not match user's visiting site and the ROOT_URL in app.ini may be incorrect.
  83. // And it's difficult for Gitea to guess absolute URL correctly with zero configuration,
  84. // because Gitea doesn't know whether the scheme is HTTP or HTTPS unless the reverse proxy could tell Gitea.
  85. return setting.AppURL
  86. },
  87. "AppVer": func() string {
  88. return setting.AppVer
  89. },
  90. "AppDomain": func() string { // documented in mail-templates.md
  91. return setting.Domain
  92. },
  93. "AssetVersion": func() string {
  94. return setting.AssetVersion
  95. },
  96. "DefaultShowFullName": func() bool {
  97. return setting.UI.DefaultShowFullName
  98. },
  99. "ShowFooterTemplateLoadTime": func() bool {
  100. return setting.Other.ShowFooterTemplateLoadTime
  101. },
  102. "ShowFooterPoweredBy": func() bool {
  103. return setting.Other.ShowFooterPoweredBy
  104. },
  105. "AllowedReactions": func() []string {
  106. return setting.UI.Reactions
  107. },
  108. "CustomEmojis": func() map[string]string {
  109. return setting.UI.CustomEmojisMap
  110. },
  111. "MetaAuthor": func() string {
  112. return setting.UI.Meta.Author
  113. },
  114. "MetaDescription": func() string {
  115. return setting.UI.Meta.Description
  116. },
  117. "MetaKeywords": func() string {
  118. return setting.UI.Meta.Keywords
  119. },
  120. "EnableTimetracking": func() bool {
  121. return setting.Service.EnableTimetracking
  122. },
  123. "DisableGitHooks": func() bool {
  124. return setting.DisableGitHooks
  125. },
  126. "DisableWebhooks": func() bool {
  127. return setting.DisableWebhooks
  128. },
  129. "DisableImportLocal": func() bool {
  130. return !setting.ImportLocalPaths
  131. },
  132. "UserThemeName": UserThemeName,
  133. "NotificationSettings": func() map[string]any {
  134. return map[string]any{
  135. "MinTimeout": int(setting.UI.Notification.MinTimeout / time.Millisecond),
  136. "TimeoutStep": int(setting.UI.Notification.TimeoutStep / time.Millisecond),
  137. "MaxTimeout": int(setting.UI.Notification.MaxTimeout / time.Millisecond),
  138. "EventSourceUpdateTime": int(setting.UI.Notification.EventSourceUpdateTime / time.Millisecond),
  139. }
  140. },
  141. "MermaidMaxSourceCharacters": func() int {
  142. return setting.MermaidMaxSourceCharacters
  143. },
  144. // -----------------------------------------------------------------
  145. // render
  146. "RenderCommitMessage": RenderCommitMessage,
  147. "RenderCommitMessageLinkSubject": RenderCommitMessageLinkSubject,
  148. "RenderCommitBody": RenderCommitBody,
  149. "RenderCodeBlock": RenderCodeBlock,
  150. "RenderIssueTitle": RenderIssueTitle,
  151. "RenderEmoji": RenderEmoji,
  152. "ReactionToEmoji": ReactionToEmoji,
  153. "RenderMarkdownToHtml": RenderMarkdownToHtml,
  154. "RenderLabel": RenderLabel,
  155. "RenderLabels": RenderLabels,
  156. // -----------------------------------------------------------------
  157. // misc
  158. "ShortSha": base.ShortSha,
  159. "ActionContent2Commits": ActionContent2Commits,
  160. "IsMultilineCommitMessage": IsMultilineCommitMessage,
  161. "CommentMustAsDiff": gitdiff.CommentMustAsDiff,
  162. "MirrorRemoteAddress": mirrorRemoteAddress,
  163. "FilenameIsImage": FilenameIsImage,
  164. "TabSizeClass": TabSizeClass,
  165. }
  166. }
  167. func HTMLFormat(s string, rawArgs ...any) template.HTML {
  168. args := slices.Clone(rawArgs)
  169. for i, v := range args {
  170. switch v := v.(type) {
  171. case nil, bool, int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64, float32, float64, template.HTML:
  172. // for most basic types (including template.HTML which is safe), just do nothing and use it
  173. case string:
  174. args[i] = template.HTMLEscapeString(v)
  175. case fmt.Stringer:
  176. args[i] = template.HTMLEscapeString(v.String())
  177. default:
  178. args[i] = template.HTMLEscapeString(fmt.Sprint(v))
  179. }
  180. }
  181. return template.HTML(fmt.Sprintf(s, args...))
  182. }
  183. // SafeHTML render raw as HTML
  184. func SafeHTML(s any) template.HTML {
  185. switch v := s.(type) {
  186. case string:
  187. return template.HTML(v)
  188. case template.HTML:
  189. return v
  190. }
  191. panic(fmt.Sprintf("unexpected type %T", s))
  192. }
  193. // SanitizeHTML sanitizes the input by pre-defined markdown rules
  194. func SanitizeHTML(s string) template.HTML {
  195. return template.HTML(markup.Sanitize(s))
  196. }
  197. func HTMLEscape(s any) template.HTML {
  198. switch v := s.(type) {
  199. case string:
  200. return template.HTML(html.EscapeString(v))
  201. case template.HTML:
  202. return v
  203. }
  204. panic(fmt.Sprintf("unexpected type %T", s))
  205. }
  206. func JSEscapeSafe(s string) template.HTML {
  207. return template.HTML(template.JSEscapeString(s))
  208. }
  209. func QueryEscape(s string) template.URL {
  210. return template.URL(url.QueryEscape(s))
  211. }
  212. // DotEscape wraps a dots in names with ZWJ [U+200D] in order to prevent autolinkers from detecting these as urls
  213. func DotEscape(raw string) string {
  214. return strings.ReplaceAll(raw, ".", "\u200d.\u200d")
  215. }
  216. // Iif is an "inline-if", similar util.Iif[T] but templates need the non-generic version,
  217. // and it could be simply used as "{{Iif expr trueVal}}" (omit the falseVal).
  218. func Iif(condition bool, vals ...any) any {
  219. if condition {
  220. return vals[0]
  221. } else if len(vals) > 1 {
  222. return vals[1]
  223. }
  224. return nil
  225. }
  226. // Eval the expression and return the result, see the comment of eval.Expr for details.
  227. // To use this helper function in templates, pass each token as a separate parameter.
  228. //
  229. // {{ $int64 := Eval $var "+" 1 }}
  230. // {{ $float64 := Eval $var "+" 1.0 }}
  231. //
  232. // Golang's template supports comparable int types, so the int64 result can be used in later statements like {{if lt $int64 10}}
  233. func Eval(tokens ...any) (any, error) {
  234. n, err := eval.Expr(tokens...)
  235. return n.Value, err
  236. }
  237. func UserThemeName(user *user_model.User) string {
  238. if user == nil || user.Theme == "" {
  239. return setting.UI.DefaultTheme
  240. }
  241. if webtheme.IsThemeAvailable(user.Theme) {
  242. return user.Theme
  243. }
  244. return setting.UI.DefaultTheme
  245. }