2018-09-29 11:33:54 +03:00
// Copyright 2018 The Gitea Authors. All rights reserved.
2014-04-10 14:20:58 -04:00
// Copyright 2014 The Gogs Authors. All rights reserved.
2022-11-27 13:20:29 -05:00
// SPDX-License-Identifier: MIT
2014-04-10 14:20:58 -04:00
2016-12-06 18:58:31 +01:00
package templates
2014-04-10 14:20:58 -04:00
import (
"fmt"
2018-02-27 08:09:18 +01:00
"html"
2014-04-10 14:20:58 -04:00
"html/template"
2017-11-28 01:43:51 -08:00
"net/url"
2024-06-11 21:07:10 +08:00
"reflect"
2014-04-10 14:20:58 -04:00
"strings"
"time"
2014-05-25 20:11:25 -04:00
2023-10-06 09:46:36 +02:00
user_model "code.gitea.io/gitea/models/user"
2016-11-10 17:24:48 +01:00
"code.gitea.io/gitea/modules/base"
2024-11-18 13:25:42 +08:00
"code.gitea.io/gitea/modules/htmlutil"
2017-09-17 01:17:57 +08:00
"code.gitea.io/gitea/modules/markup"
2016-11-10 17:24:48 +01:00
"code.gitea.io/gitea/modules/setting"
2020-07-12 11:10:56 +02:00
"code.gitea.io/gitea/modules/svg"
Use a general Eval function for expressions in templates. (#23927)
One of the proposals in #23328
This PR introduces a simple expression calculator
(templates/eval/eval.go), it can do basic expression calculations.
Many untested template helper functions like `Mul` `Add` can be replaced
by this new approach.
Then these `Add` / `Mul` / `percentage` / `Subtract` / `DiffStatsWidth`
could all use this `Eval`.
And it provides enhancements for Golang templates, and improves
readability.
Some examples:
----
* Before: `{{Add (Mul $glyph.Row 12) 12}}`
* After: `{{Eval $glyph.Row "*" 12 "+" 12}}`
----
* Before: `{{if lt (Add $i 1) (len $.Topics)}}`
* After: `{{if Eval $i "+" 1 "<" (len $.Topics)}}`
## FAQ
### Why not use an existing expression package?
We need a highly customized expression engine:
* do the calculation on the fly, without pre-compiling
* deal with int/int64/float64 types, to make the result could be used in
Golang template.
* make the syntax could be used in the Golang template directly
* do not introduce too much complex or strange syntax, we just need a
simple calculator.
* it needs to strictly follow Golang template's behavior, for example,
Golang template treats all non-zero values as truth, but many 3rd
packages don't do so.
### What's the benefit?
* Developers don't need to add more `Add`/`Mul`/`Sub`-like functions,
they were getting more and more.
Now, only one `Eval` is enough for all cases.
* The new code reads better than old `{{Add (Mul $glyph.Row 12) 12}}`,
the old one isn't familiar to most procedural programming developers
(eg, the Golang expression syntax).
* The `Eval` is fully covered by tests, many old `Add`/`Mul`-like
functions were never tested.
### The performance?
It doesn't use `reflect`, it doesn't need to parse or compile when used
in Golang template, the performance is as fast as native Go template.
### Is it too complex? Could it be unstable?
The expression calculator program is a common homework for computer
science students, and it's widely used as a teaching and practicing
purpose for developers. The algorithm is pretty well-known.
The behavior can be clearly defined, it is stable.
2023-04-07 21:25:49 +08:00
"code.gitea.io/gitea/modules/templates/eval"
2019-08-15 22:46:21 +08:00
"code.gitea.io/gitea/modules/util"
2019-09-06 10:20:09 +08:00
"code.gitea.io/gitea/services/gitdiff"
2024-04-24 00:18:41 +08:00
"code.gitea.io/gitea/services/webtheme"
2014-04-10 14:20:58 -04:00
)
2016-11-25 14:23:48 +08:00
// NewFuncMap returns functions for injecting to templates
2023-04-30 20:22:23 +08:00
func NewFuncMap ( ) template . FuncMap {
2023-07-04 20:36:08 +02:00
return map [ string ] any {
2023-08-08 09:22:47 +08:00
"ctx" : func ( ) any { return nil } , // template context function
2023-04-29 20:02:29 +08:00
"DumpVar" : dumpVar ,
2024-11-11 04:07:54 +08:00
"NIL" : func ( ) any { return nil } ,
2023-04-29 20:02:29 +08:00
2023-04-08 21:15:22 +08:00
// -----------------------------------------------------------------
// html/template related functions
2024-03-01 18:16:19 +08:00
"dict" : dict , // it's lowercase because this name has been widely used. Our other functions should have uppercase names.
2024-06-19 06:32:45 +08:00
"Iif" : iif ,
"Eval" : evalTokens ,
"SafeHTML" : safeHTML ,
2024-11-18 13:25:42 +08:00
"HTMLFormat" : htmlutil . HTMLFormat ,
2024-06-19 06:32:45 +08:00
"HTMLEscape" : htmlEscape ,
"QueryEscape" : queryEscape ,
Refactor issue filter (labels, poster, assignee) (#32771)
Rewrite a lot of legacy strange code, remove duplicate code, remove
jquery, and make these filters reusable.
Let's forget the old code, new code affects:
* issue list open/close switch
* issue list filter (label, author, assignee)
* milestone list open/close switch
* milestone issue list filter (label, author, assignee)
* project view (label, assignee)
2024-12-10 11:38:22 +08:00
"QueryBuild" : QueryBuild ,
2024-06-19 06:32:45 +08:00
"JSEscape" : jsEscapeSafe ,
2024-03-01 18:16:19 +08:00
"SanitizeHTML" : SanitizeHTML ,
"URLJoin" : util . URLJoin ,
2024-06-19 06:32:45 +08:00
"DotEscape" : dotEscape ,
2023-04-08 21:15:22 +08:00
"PathEscape" : url . PathEscape ,
"PathEscapeSegments" : util . PathEscapeSegments ,
2023-04-23 02:16:22 +08:00
// utils
"StringUtils" : NewStringUtils ,
"SliceUtils" : NewSliceUtils ,
2023-04-29 20:02:29 +08:00
"JsonUtils" : NewJsonUtils ,
2024-11-03 05:04:53 +08:00
"DateUtils" : NewDateUtils ,
2023-04-08 21:15:22 +08:00
// -----------------------------------------------------------------
2024-04-07 18:19:25 +02:00
// svg / avatar / icon / color
2023-08-10 11:19:39 +08:00
"svg" : svg . RenderHTML ,
"EntryIcon" : base . EntryIcon ,
2024-06-19 06:32:45 +08:00
"MigrationIcon" : migrationIcon ,
"ActionIcon" : actionIcon ,
"SortArrow" : sortArrow ,
2024-04-07 18:19:25 +02:00
"ContrastColor" : util . ContrastColor ,
2023-04-08 21:15:22 +08:00
// -----------------------------------------------------------------
// time / number / format
2024-11-04 19:30:00 +08:00
"FileSize" : base . FileSize ,
"CountFmt" : base . FormatNumberSI ,
"Sec2Time" : util . SecToTime ,
2024-12-05 15:07:53 +02:00
"TimeEstimateString" : timeEstimateString ,
2023-04-08 21:15:22 +08:00
"LoadTimes" : func ( startTime time . Time ) string {
return fmt . Sprint ( time . Since ( startTime ) . Nanoseconds ( ) / 1e6 ) + "ms"
} ,
// -----------------------------------------------------------------
// setting
2016-03-06 16:40:04 -05:00
"AppName" : func ( ) string {
return setting . AppName
} ,
"AppSubUrl" : func ( ) string {
2016-11-27 18:14:25 +08:00
return setting . AppSubURL
2016-03-06 16:40:04 -05:00
} ,
2021-05-08 16:27:25 +02:00
"AssetUrlPrefix" : func ( ) string {
2021-04-28 20:35:06 +08:00
return setting . StaticURLPrefix + "/assets"
2019-10-22 14:11:01 +02:00
} ,
2016-03-06 16:40:04 -05:00
"AppUrl" : func ( ) string {
2023-02-10 00:31:30 +08:00
// The usage of AppUrl should be avoided as much as possible,
// because the AppURL(ROOT_URL) may not match user's visiting site and the ROOT_URL in app.ini may be incorrect.
// And it's difficult for Gitea to guess absolute URL correctly with zero configuration,
// because Gitea doesn't know whether the scheme is HTTP or HTTPS unless the reverse proxy could tell Gitea.
2016-11-27 18:14:25 +08:00
return setting . AppURL
2016-03-06 16:40:04 -05:00
} ,
"AppVer" : func ( ) string {
return setting . AppVer
} ,
2023-04-07 15:31:41 +08:00
"AppDomain" : func ( ) string { // documented in mail-templates.md
2016-03-06 16:40:04 -05:00
return setting . Domain
} ,
2022-08-23 14:58:04 +02:00
"AssetVersion" : func ( ) string {
return setting . AssetVersion
} ,
2019-05-08 10:41:35 +02:00
"DefaultShowFullName" : func ( ) bool {
return setting . UI . DefaultShowFullName
} ,
2016-09-01 07:01:32 +02:00
"ShowFooterTemplateLoadTime" : func ( ) bool {
2023-04-23 07:38:25 +08:00
return setting . Other . ShowFooterTemplateLoadTime
2016-09-01 07:01:32 +02:00
} ,
2024-04-03 09:01:50 -07:00
"ShowFooterPoweredBy" : func ( ) bool {
return setting . Other . ShowFooterPoweredBy
} ,
2019-12-28 00:43:56 +01:00
"AllowedReactions" : func ( ) [ ] string {
return setting . UI . Reactions
} ,
2021-06-29 16:28:38 +02:00
"CustomEmojis" : func ( ) map [ string ] string {
return setting . UI . CustomEmojisMap
} ,
2017-04-01 03:03:01 +02:00
"MetaAuthor" : func ( ) string {
return setting . UI . Meta . Author
} ,
"MetaDescription" : func ( ) string {
return setting . UI . Meta . Description
} ,
"MetaKeywords" : func ( ) string {
return setting . UI . Meta . Keywords
} ,
2021-02-19 23:06:56 +00:00
"EnableTimetracking" : func ( ) bool {
return setting . Service . EnableTimetracking
} ,
2017-09-12 05:25:42 -04:00
"DisableGitHooks" : func ( ) bool {
return setting . DisableGitHooks
} ,
2021-02-11 18:34:34 +01:00
"DisableWebhooks" : func ( ) bool {
return setting . DisableWebhooks
} ,
2018-08-24 07:00:22 +02:00
"DisableImportLocal" : func ( ) bool {
return ! setting . ImportLocalPaths
} ,
2024-06-19 06:32:45 +08:00
"UserThemeName" : userThemeName ,
2023-07-04 20:36:08 +02:00
"NotificationSettings" : func ( ) map [ string ] any {
return map [ string ] any {
2020-05-07 22:49:00 +01:00
"MinTimeout" : int ( setting . UI . Notification . MinTimeout / time . Millisecond ) ,
"TimeoutStep" : int ( setting . UI . Notification . TimeoutStep / time . Millisecond ) ,
"MaxTimeout" : int ( setting . UI . Notification . MaxTimeout / time . Millisecond ) ,
"EventSourceUpdateTime" : int ( setting . UI . Notification . EventSourceUpdateTime / time . Millisecond ) ,
2020-04-24 04:57:38 +01:00
}
} ,
2023-04-08 21:15:22 +08:00
"MermaidMaxSourceCharacters" : func ( ) int {
return setting . MermaidMaxSourceCharacters
} ,
2020-11-08 17:21:54 +00:00
2023-04-08 21:15:22 +08:00
// -----------------------------------------------------------------
// render
2024-11-05 14:04:26 +08:00
"RenderCodeBlock" : renderCodeBlock ,
"ReactionToEmoji" : reactionToEmoji ,
2023-04-08 21:15:22 +08:00
// -----------------------------------------------------------------
// misc
"ShortSha" : base . ShortSha ,
"ActionContent2Commits" : ActionContent2Commits ,
2024-06-19 06:32:45 +08:00
"IsMultilineCommitMessage" : isMultilineCommitMessage ,
2023-04-08 21:15:22 +08:00
"CommentMustAsDiff" : gitdiff . CommentMustAsDiff ,
"MirrorRemoteAddress" : mirrorRemoteAddress ,
2024-06-19 06:32:45 +08:00
"FilenameIsImage" : filenameIsImage ,
"TabSizeClass" : tabSizeClass ,
2024-11-05 14:04:26 +08:00
// for backward compatibility only, do not use them anymore
"TimeSince" : timeSinceLegacy ,
"TimeSinceUnix" : timeSinceLegacy ,
"DateTime" : dateTimeLegacy ,
"RenderEmoji" : renderEmojiLegacy ,
"RenderLabel" : renderLabelLegacy ,
"RenderLabels" : renderLabelsLegacy ,
"RenderIssueTitle" : renderIssueTitleLegacy ,
"RenderMarkdownToHtml" : renderMarkdownToHtmlLegacy ,
"RenderCommitMessage" : renderCommitMessageLegacy ,
"RenderCommitMessageLinkSubject" : renderCommitMessageLinkSubjectLegacy ,
"RenderCommitBody" : renderCommitBodyLegacy ,
2023-04-30 20:22:23 +08:00
}
2019-11-07 10:34:28 -03:00
}
2024-06-19 06:32:45 +08:00
// safeHTML render raw as HTML
func safeHTML ( s any ) template . HTML {
2024-02-15 05:48:45 +08:00
switch v := s . ( type ) {
case string :
return template . HTML ( v )
case template . HTML :
return v
}
panic ( fmt . Sprintf ( "unexpected type %T" , s ) )
}
2024-03-01 18:16:19 +08:00
// SanitizeHTML sanitizes the input by pre-defined markdown rules
2024-03-04 20:02:45 +08:00
func SanitizeHTML ( s string ) template . HTML {
return template . HTML ( markup . Sanitize ( s ) )
2015-08-08 17:10:34 +08:00
}
2024-06-19 06:32:45 +08:00
func htmlEscape ( s any ) template . HTML {
2024-02-15 05:48:45 +08:00
switch v := s . ( type ) {
case string :
return template . HTML ( html . EscapeString ( v ) )
case template . HTML :
return v
}
panic ( fmt . Sprintf ( "unexpected type %T" , s ) )
}
2024-06-19 06:32:45 +08:00
func jsEscapeSafe ( s string ) template . HTML {
2024-02-18 17:52:02 +08:00
return template . HTML ( template . JSEscapeString ( s ) )
}
2024-06-19 06:32:45 +08:00
func queryEscape ( s string ) template . URL {
2024-03-13 21:32:30 +08:00
return template . URL ( url . QueryEscape ( s ) )
}
2024-06-19 06:32:45 +08:00
// 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 {
2022-03-23 12:34:20 +00:00
return strings . ReplaceAll ( raw , "." , "\u200d.\u200d" )
}
2024-06-19 06:32:45 +08:00
// iif is an "inline-if", similar util.Iif[T] but templates need the non-generic version,
// and it could be simply used as "{{iif expr trueVal}}" (omit the falseVal).
func iif ( condition any , vals ... any ) any {
2024-06-11 22:52:12 +08:00
if isTemplateTruthy ( condition ) {
2024-04-17 23:58:37 +08:00
return vals [ 0 ]
} else if len ( vals ) > 1 {
return vals [ 1 ]
}
return nil
}
2024-06-11 22:52:12 +08:00
func isTemplateTruthy ( v any ) bool {
2024-06-11 21:07:10 +08:00
if v == nil {
return false
}
rv := reflect . ValueOf ( v )
switch rv . Kind ( ) {
case reflect . Bool :
return rv . Bool ( )
case reflect . Int , reflect . Int8 , reflect . Int16 , reflect . Int32 , reflect . Int64 :
return rv . Int ( ) != 0
case reflect . Uint , reflect . Uint8 , reflect . Uint16 , reflect . Uint32 , reflect . Uint64 :
return rv . Uint ( ) != 0
case reflect . Float32 , reflect . Float64 :
return rv . Float ( ) != 0
2024-06-11 22:52:12 +08:00
case reflect . Complex64 , reflect . Complex128 :
return rv . Complex ( ) != 0
case reflect . String , reflect . Slice , reflect . Array , reflect . Map :
2024-06-11 21:07:10 +08:00
return rv . Len ( ) > 0
2024-06-11 22:52:12 +08:00
case reflect . Struct :
return true
2024-06-11 21:07:10 +08:00
default :
2024-06-11 22:52:12 +08:00
return ! rv . IsNil ( )
2024-06-11 21:07:10 +08:00
}
}
2024-06-19 06:32:45 +08:00
// evalTokens evaluates the expression by tokens and returns the result, see the comment of eval.Expr for details.
Use a general Eval function for expressions in templates. (#23927)
One of the proposals in #23328
This PR introduces a simple expression calculator
(templates/eval/eval.go), it can do basic expression calculations.
Many untested template helper functions like `Mul` `Add` can be replaced
by this new approach.
Then these `Add` / `Mul` / `percentage` / `Subtract` / `DiffStatsWidth`
could all use this `Eval`.
And it provides enhancements for Golang templates, and improves
readability.
Some examples:
----
* Before: `{{Add (Mul $glyph.Row 12) 12}}`
* After: `{{Eval $glyph.Row "*" 12 "+" 12}}`
----
* Before: `{{if lt (Add $i 1) (len $.Topics)}}`
* After: `{{if Eval $i "+" 1 "<" (len $.Topics)}}`
## FAQ
### Why not use an existing expression package?
We need a highly customized expression engine:
* do the calculation on the fly, without pre-compiling
* deal with int/int64/float64 types, to make the result could be used in
Golang template.
* make the syntax could be used in the Golang template directly
* do not introduce too much complex or strange syntax, we just need a
simple calculator.
* it needs to strictly follow Golang template's behavior, for example,
Golang template treats all non-zero values as truth, but many 3rd
packages don't do so.
### What's the benefit?
* Developers don't need to add more `Add`/`Mul`/`Sub`-like functions,
they were getting more and more.
Now, only one `Eval` is enough for all cases.
* The new code reads better than old `{{Add (Mul $glyph.Row 12) 12}}`,
the old one isn't familiar to most procedural programming developers
(eg, the Golang expression syntax).
* The `Eval` is fully covered by tests, many old `Add`/`Mul`-like
functions were never tested.
### The performance?
It doesn't use `reflect`, it doesn't need to parse or compile when used
in Golang template, the performance is as fast as native Go template.
### Is it too complex? Could it be unstable?
The expression calculator program is a common homework for computer
science students, and it's widely used as a teaching and practicing
purpose for developers. The algorithm is pretty well-known.
The behavior can be clearly defined, it is stable.
2023-04-07 21:25:49 +08:00
// To use this helper function in templates, pass each token as a separate parameter.
//
// {{ $int64 := Eval $var "+" 1 }}
// {{ $float64 := Eval $var "+" 1.0 }}
//
// Golang's template supports comparable int types, so the int64 result can be used in later statements like {{if lt $int64 10}}
2024-06-19 06:32:45 +08:00
func evalTokens ( tokens ... any ) ( any , error ) {
Use a general Eval function for expressions in templates. (#23927)
One of the proposals in #23328
This PR introduces a simple expression calculator
(templates/eval/eval.go), it can do basic expression calculations.
Many untested template helper functions like `Mul` `Add` can be replaced
by this new approach.
Then these `Add` / `Mul` / `percentage` / `Subtract` / `DiffStatsWidth`
could all use this `Eval`.
And it provides enhancements for Golang templates, and improves
readability.
Some examples:
----
* Before: `{{Add (Mul $glyph.Row 12) 12}}`
* After: `{{Eval $glyph.Row "*" 12 "+" 12}}`
----
* Before: `{{if lt (Add $i 1) (len $.Topics)}}`
* After: `{{if Eval $i "+" 1 "<" (len $.Topics)}}`
## FAQ
### Why not use an existing expression package?
We need a highly customized expression engine:
* do the calculation on the fly, without pre-compiling
* deal with int/int64/float64 types, to make the result could be used in
Golang template.
* make the syntax could be used in the Golang template directly
* do not introduce too much complex or strange syntax, we just need a
simple calculator.
* it needs to strictly follow Golang template's behavior, for example,
Golang template treats all non-zero values as truth, but many 3rd
packages don't do so.
### What's the benefit?
* Developers don't need to add more `Add`/`Mul`/`Sub`-like functions,
they were getting more and more.
Now, only one `Eval` is enough for all cases.
* The new code reads better than old `{{Add (Mul $glyph.Row 12) 12}}`,
the old one isn't familiar to most procedural programming developers
(eg, the Golang expression syntax).
* The `Eval` is fully covered by tests, many old `Add`/`Mul`-like
functions were never tested.
### The performance?
It doesn't use `reflect`, it doesn't need to parse or compile when used
in Golang template, the performance is as fast as native Go template.
### Is it too complex? Could it be unstable?
The expression calculator program is a common homework for computer
science students, and it's widely used as a teaching and practicing
purpose for developers. The algorithm is pretty well-known.
The behavior can be clearly defined, it is stable.
2023-04-07 21:25:49 +08:00
n , err := eval . Expr ( tokens ... )
return n . Value , err
}
2024-04-24 00:18:41 +08:00
2024-06-19 06:32:45 +08:00
func userThemeName ( user * user_model . User ) string {
2024-04-24 00:18:41 +08:00
if user == nil || user . Theme == "" {
return setting . UI . DefaultTheme
}
if webtheme . IsThemeAvailable ( user . Theme ) {
return user . Theme
}
return setting . UI . DefaultTheme
}
2024-11-05 14:04:26 +08:00
2024-12-05 15:07:53 +02:00
func timeEstimateString ( timeSec any ) string {
v , _ := util . ToInt64 ( timeSec )
if v == 0 {
return ""
}
return util . TimeEstimateString ( v )
}
Refactor issue filter (labels, poster, assignee) (#32771)
Rewrite a lot of legacy strange code, remove duplicate code, remove
jquery, and make these filters reusable.
Let's forget the old code, new code affects:
* issue list open/close switch
* issue list filter (label, author, assignee)
* milestone list open/close switch
* milestone issue list filter (label, author, assignee)
* project view (label, assignee)
2024-12-10 11:38:22 +08:00
// QueryBuild builds a query string from a list of key-value pairs.
// It omits the nil and empty strings, but it doesn't omit other zero values,
// because the zero value of number types may have a meaning.
func QueryBuild ( a ... any ) template . URL {
2024-12-08 20:44:17 +08:00
var s string
if len ( a ) % 2 == 1 {
if v , ok := a [ 0 ] . ( string ) ; ok {
if v == "" || ( v [ 0 ] != '?' && v [ 0 ] != '&' ) {
Refactor issue filter (labels, poster, assignee) (#32771)
Rewrite a lot of legacy strange code, remove duplicate code, remove
jquery, and make these filters reusable.
Let's forget the old code, new code affects:
* issue list open/close switch
* issue list filter (label, author, assignee)
* milestone list open/close switch
* milestone issue list filter (label, author, assignee)
* project view (label, assignee)
2024-12-10 11:38:22 +08:00
panic ( "QueryBuild: invalid argument" )
2024-12-08 20:44:17 +08:00
}
s = v
2024-12-09 15:54:59 +08:00
} else if v , ok := a [ 0 ] . ( template . URL ) ; ok {
2024-12-08 20:44:17 +08:00
s = string ( v )
} else {
Refactor issue filter (labels, poster, assignee) (#32771)
Rewrite a lot of legacy strange code, remove duplicate code, remove
jquery, and make these filters reusable.
Let's forget the old code, new code affects:
* issue list open/close switch
* issue list filter (label, author, assignee)
* milestone list open/close switch
* milestone issue list filter (label, author, assignee)
* project view (label, assignee)
2024-12-10 11:38:22 +08:00
panic ( "QueryBuild: invalid argument" )
2024-12-08 20:44:17 +08:00
}
}
for i := len ( a ) % 2 ; i < len ( a ) ; i += 2 {
k , ok := a [ i ] . ( string )
if ! ok {
Refactor issue filter (labels, poster, assignee) (#32771)
Rewrite a lot of legacy strange code, remove duplicate code, remove
jquery, and make these filters reusable.
Let's forget the old code, new code affects:
* issue list open/close switch
* issue list filter (label, author, assignee)
* milestone list open/close switch
* milestone issue list filter (label, author, assignee)
* project view (label, assignee)
2024-12-10 11:38:22 +08:00
panic ( "QueryBuild: invalid argument" )
2024-12-08 20:44:17 +08:00
}
var v string
if va , ok := a [ i + 1 ] . ( string ) ; ok {
v = va
} else if a [ i + 1 ] != nil {
v = fmt . Sprint ( a [ i + 1 ] )
}
// pos1 to pos2 is the "k=v&" part, "&" is optional
pos1 := strings . Index ( s , "&" + k + "=" )
if pos1 != - 1 {
pos1 ++
} else {
pos1 = strings . Index ( s , "?" + k + "=" )
if pos1 != - 1 {
pos1 ++
} else if strings . HasPrefix ( s , k + "=" ) {
pos1 = 0
}
}
pos2 := len ( s )
if pos1 == - 1 {
pos1 = len ( s )
} else {
pos2 = pos1 + 1
for pos2 < len ( s ) && s [ pos2 - 1 ] != '&' {
pos2 ++
}
}
if v != "" {
sep := ""
hasPrefixSep := pos1 == 0 || ( pos1 <= len ( s ) && ( s [ pos1 - 1 ] == '?' || s [ pos1 - 1 ] == '&' ) )
if ! hasPrefixSep {
sep = "&"
}
s = s [ : pos1 ] + sep + k + "=" + url . QueryEscape ( v ) + "&" + s [ pos2 : ]
} else {
s = s [ : pos1 ] + s [ pos2 : ]
}
}
if s != "" && s != "&" && s [ len ( s ) - 1 ] == '&' {
s = s [ : len ( s ) - 1 ]
}
2024-12-09 15:54:59 +08:00
return template . URL ( s )
2024-12-08 20:44:17 +08:00
}
2024-11-05 14:04:26 +08:00
func panicIfDevOrTesting ( ) {
if ! setting . IsProd || setting . IsInTesting {
panic ( "legacy template functions are for backward compatibility only, do not use them in new code" )
}
}