Skip to content

Commit 0b27b93

Browse files
6543zeripath
andauthored
Make allowed Visiblity modes configurable for Users (#16271)
Now that #16069 is merged, some sites may wish to enforce that users are all public, limited or private, and/or disallow users from becoming private. This PR adds functionality and settings to constrain a user's ability to change their visibility. Co-authored-by: zeripath <[email protected]>
1 parent 2a98ec1 commit 0b27b93

File tree

11 files changed

+146
-63
lines changed

11 files changed

+146
-63
lines changed

custom/conf/app.example.ini

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -656,6 +656,9 @@ PATH =
656656
;; Public is for users visible for everyone
657657
;DEFAULT_USER_VISIBILITY = public
658658
;;
659+
;; Set whitch visibibilty modes a user can have
660+
;ALLOWED_USER_VISIBILITY_MODES = public,limited,private
661+
;;
659662
;; Either "public", "limited" or "private", default is "public"
660663
;; Limited is for organizations visible only to signed users
661664
;; Private is for organizations visible only to members of the organization

docs/content/doc/advanced/config-cheat-sheet.en-us.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -513,6 +513,7 @@ relation to port exhaustion.
513513
- `AUTO_WATCH_NEW_REPOS`: **true**: Enable this to let all organisation users watch new repos when they are created
514514
- `AUTO_WATCH_ON_CHANGES`: **false**: Enable this to make users watch a repository after their first commit to it
515515
- `DEFAULT_USER_VISIBILITY`: **public**: Set default visibility mode for users, either "public", "limited" or "private".
516+
- `ALLOWED_USER_VISIBILITY_MODES`: **public,limited,private**: Set whitch visibibilty modes a user can have
516517
- `DEFAULT_ORG_VISIBILITY`: **public**: Set default visibility mode for organisations, either "public", "limited" or "private".
517518
- `DEFAULT_ORG_MEMBER_VISIBLE`: **false** True will make the membership of the users visible when added to the organisation.
518519
- `ALLOW_ONLY_INTERNAL_REGISTRATION`: **false** Set to true to force registration only via gitea.

models/user.go

Lines changed: 42 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -863,26 +863,36 @@ func CreateUser(u *User, overwriteDefault ...*CreateUserOverwriteOptions) (err e
863863
return err
864864
}
865865

866+
// set system defaults
867+
u.KeepEmailPrivate = setting.Service.DefaultKeepEmailPrivate
868+
u.Visibility = setting.Service.DefaultUserVisibilityMode
869+
u.AllowCreateOrganization = setting.Service.DefaultAllowCreateOrganization && !setting.Admin.DisableRegularOrgCreation
870+
u.EmailNotificationsPreference = setting.Admin.DefaultEmailNotification
871+
u.MaxRepoCreation = -1
872+
u.Theme = setting.UI.DefaultTheme
873+
874+
// overwrite defaults if set
875+
if len(overwriteDefault) != 0 && overwriteDefault[0] != nil {
876+
u.Visibility = overwriteDefault[0].Visibility
877+
}
878+
866879
sess := x.NewSession()
867880
defer sess.Close()
868881
if err = sess.Begin(); err != nil {
869882
return err
870883
}
871884

872-
isExist, err := isUserExist(sess, 0, u.Name)
873-
if err != nil {
874-
return err
875-
} else if isExist {
876-
return ErrUserAlreadyExist{u.Name}
877-
}
885+
// validate data
878886

879-
if err = deleteUserRedirect(sess, u.Name); err != nil {
887+
if err := validateUser(u); err != nil {
880888
return err
881889
}
882890

883-
u.Email = strings.ToLower(u.Email)
884-
if err = ValidateEmail(u.Email); err != nil {
891+
isExist, err := isUserExist(sess, 0, u.Name)
892+
if err != nil {
885893
return err
894+
} else if isExist {
895+
return ErrUserAlreadyExist{u.Name}
886896
}
887897

888898
isExist, err = isEmailUsed(sess, u.Email)
@@ -892,6 +902,8 @@ func CreateUser(u *User, overwriteDefault ...*CreateUserOverwriteOptions) (err e
892902
return ErrEmailAlreadyUsed{u.Email}
893903
}
894904

905+
// prepare for database
906+
895907
u.LowerName = strings.ToLower(u.Name)
896908
u.AvatarEmail = u.Email
897909
if u.Rands, err = GetUserSalt(); err != nil {
@@ -901,16 +913,10 @@ func CreateUser(u *User, overwriteDefault ...*CreateUserOverwriteOptions) (err e
901913
return err
902914
}
903915

904-
// set system defaults
905-
u.KeepEmailPrivate = setting.Service.DefaultKeepEmailPrivate
906-
u.Visibility = setting.Service.DefaultUserVisibilityMode
907-
u.AllowCreateOrganization = setting.Service.DefaultAllowCreateOrganization && !setting.Admin.DisableRegularOrgCreation
908-
u.EmailNotificationsPreference = setting.Admin.DefaultEmailNotification
909-
u.MaxRepoCreation = -1
910-
u.Theme = setting.UI.DefaultTheme
911-
// overwrite defaults if set
912-
if len(overwriteDefault) != 0 && overwriteDefault[0] != nil {
913-
u.Visibility = overwriteDefault[0].Visibility
916+
// save changes to database
917+
918+
if err = deleteUserRedirect(sess, u.Name); err != nil {
919+
return err
914920
}
915921

916922
if _, err = sess.Insert(u); err != nil {
@@ -1056,12 +1062,22 @@ func checkDupEmail(e Engine, u *User) error {
10561062
return nil
10571063
}
10581064

1059-
func updateUser(e Engine, u *User) (err error) {
1065+
// validateUser check if user is valide to insert / update into database
1066+
func validateUser(u *User) error {
1067+
if !setting.Service.AllowedUserVisibilityModesSlice.IsAllowedVisibility(u.Visibility) {
1068+
return fmt.Errorf("visibility Mode not allowed: %s", u.Visibility.String())
1069+
}
1070+
10601071
u.Email = strings.ToLower(u.Email)
1061-
if err = ValidateEmail(u.Email); err != nil {
1072+
return ValidateEmail(u.Email)
1073+
}
1074+
1075+
func updateUser(e Engine, u *User) error {
1076+
if err := validateUser(u); err != nil {
10621077
return err
10631078
}
1064-
_, err = e.ID(u.ID).AllCols().Update(u)
1079+
1080+
_, err := e.ID(u.ID).AllCols().Update(u)
10651081
return err
10661082
}
10671083

@@ -1076,6 +1092,10 @@ func UpdateUserCols(u *User, cols ...string) error {
10761092
}
10771093

10781094
func updateUserCols(e Engine, u *User, cols ...string) error {
1095+
if err := validateUser(u); err != nil {
1096+
return err
1097+
}
1098+
10791099
_, err := e.ID(u.ID).Cols(cols...).Update(u)
10801100
return err
10811101
}

models/user_test.go

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import (
1111
"testing"
1212

1313
"code.gitea.io/gitea/modules/setting"
14+
"code.gitea.io/gitea/modules/structs"
1415
"code.gitea.io/gitea/modules/util"
1516

1617
"github.com/stretchr/testify/assert"
@@ -189,6 +190,7 @@ func TestDeleteUser(t *testing.T) {
189190

190191
func TestEmailNotificationPreferences(t *testing.T) {
191192
assert.NoError(t, PrepareTestDatabase())
193+
192194
for _, test := range []struct {
193195
expected string
194196
userID int64
@@ -467,3 +469,23 @@ ssh-dss AAAAB3NzaC1kc3MAAACBAOChCC7lf6Uo9n7BmZ6M8St19PZf4Tn59NriyboW2x/DZuYAz3ib
467469
}
468470
}
469471
}
472+
473+
func TestUpdateUser(t *testing.T) {
474+
assert.NoError(t, PrepareTestDatabase())
475+
user := AssertExistsAndLoadBean(t, &User{ID: 2}).(*User)
476+
477+
user.KeepActivityPrivate = true
478+
assert.NoError(t, UpdateUser(user))
479+
user = AssertExistsAndLoadBean(t, &User{ID: 2}).(*User)
480+
assert.True(t, user.KeepActivityPrivate)
481+
482+
setting.Service.AllowedUserVisibilityModesSlice = []bool{true, false, false}
483+
user.KeepActivityPrivate = false
484+
user.Visibility = structs.VisibleTypePrivate
485+
assert.Error(t, UpdateUser(user))
486+
user = AssertExistsAndLoadBean(t, &User{ID: 2}).(*User)
487+
assert.True(t, user.KeepActivityPrivate)
488+
489+
user.Email = "no [email protected]"
490+
assert.Error(t, UpdateUser(user))
491+
}

modules/setting/service.go

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,11 @@ import (
1414
)
1515

1616
// Service settings
17-
var Service struct {
17+
var Service = struct {
1818
DefaultUserVisibility string
1919
DefaultUserVisibilityMode structs.VisibleType
20+
AllowedUserVisibilityModes []string
21+
AllowedUserVisibilityModesSlice AllowedVisibility `ini:"-"`
2022
DefaultOrgVisibility string
2123
DefaultOrgVisibilityMode structs.VisibleType
2224
ActiveCodeLives int
@@ -71,6 +73,29 @@ var Service struct {
7173
RequireSigninView bool `ini:"REQUIRE_SIGNIN_VIEW"`
7274
DisableUsersPage bool `ini:"DISABLE_USERS_PAGE"`
7375
} `ini:"service.explore"`
76+
}{
77+
AllowedUserVisibilityModesSlice: []bool{true, true, true},
78+
}
79+
80+
// AllowedVisibility store in a 3 item bool array what is allowed
81+
type AllowedVisibility []bool
82+
83+
// IsAllowedVisibility check if a AllowedVisibility allow a specific VisibleType
84+
func (a AllowedVisibility) IsAllowedVisibility(t structs.VisibleType) bool {
85+
if int(t) >= len(a) {
86+
return false
87+
}
88+
return a[t]
89+
}
90+
91+
// ToVisibleTypeSlice convert a AllowedVisibility into a VisibleType slice
92+
func (a AllowedVisibility) ToVisibleTypeSlice() (result []structs.VisibleType) {
93+
for i, v := range a {
94+
if v {
95+
result = append(result, structs.VisibleType(i))
96+
}
97+
}
98+
return
7499
}
75100

76101
func newService() {
@@ -122,6 +147,13 @@ func newService() {
122147
Service.AutoWatchOnChanges = sec.Key("AUTO_WATCH_ON_CHANGES").MustBool(false)
123148
Service.DefaultUserVisibility = sec.Key("DEFAULT_USER_VISIBILITY").In("public", structs.ExtractKeysFromMapString(structs.VisibilityModes))
124149
Service.DefaultUserVisibilityMode = structs.VisibilityModes[Service.DefaultUserVisibility]
150+
Service.AllowedUserVisibilityModes = sec.Key("ALLOWED_USER_VISIBILITY_MODES").Strings(",")
151+
if len(Service.AllowedUserVisibilityModes) != 0 {
152+
Service.AllowedUserVisibilityModesSlice = []bool{false, false, false}
153+
for _, sMode := range Service.AllowedUserVisibilityModes {
154+
Service.AllowedUserVisibilityModesSlice[structs.VisibilityModes[sMode]] = true
155+
}
156+
}
125157
Service.DefaultOrgVisibility = sec.Key("DEFAULT_ORG_VISIBILITY").In("public", structs.ExtractKeysFromMapString(structs.VisibilityModes))
126158
Service.DefaultOrgVisibilityMode = structs.VisibilityModes[Service.DefaultOrgVisibility]
127159
Service.DefaultOrgMemberVisible = sec.Key("DEFAULT_ORG_MEMBER_VISIBLE").MustBool()

routers/web/admin/users.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ func NewUser(ctx *context.Context) {
5252
ctx.Data["PageIsAdmin"] = true
5353
ctx.Data["PageIsAdminUsers"] = true
5454
ctx.Data["DefaultUserVisibilityMode"] = setting.Service.DefaultUserVisibilityMode
55+
ctx.Data["AllowedUserVisibilityModes"] = setting.Service.AllowedUserVisibilityModesSlice.ToVisibleTypeSlice()
5556

5657
ctx.Data["login_type"] = "0-0"
5758

@@ -211,6 +212,7 @@ func EditUser(ctx *context.Context) {
211212
ctx.Data["PageIsAdminUsers"] = true
212213
ctx.Data["DisableRegularOrgCreation"] = setting.Admin.DisableRegularOrgCreation
213214
ctx.Data["DisableMigrations"] = setting.Repository.DisableMigrations
215+
ctx.Data["AllowedUserVisibilityModes"] = setting.Service.AllowedUserVisibilityModesSlice.ToVisibleTypeSlice()
214216

215217
prepareUserInfo(ctx)
216218
if ctx.Written() {

routers/web/admin/users_test.go

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,6 @@ func TestNewUserPost_MustChangePassword(t *testing.T) {
5656
}
5757

5858
func TestNewUserPost_MustChangePasswordFalse(t *testing.T) {
59-
6059
models.PrepareTestEnv(t)
6160
ctx := test.MockContext(t, "admin/users/new")
6261

@@ -94,7 +93,6 @@ func TestNewUserPost_MustChangePasswordFalse(t *testing.T) {
9493
}
9594

9695
func TestNewUserPost_InvalidEmail(t *testing.T) {
97-
9896
models.PrepareTestEnv(t)
9997
ctx := test.MockContext(t, "admin/users/new")
10098

@@ -125,7 +123,6 @@ func TestNewUserPost_InvalidEmail(t *testing.T) {
125123
}
126124

127125
func TestNewUserPost_VisiblityDefaultPublic(t *testing.T) {
128-
129126
models.PrepareTestEnv(t)
130127
ctx := test.MockContext(t, "admin/users/new")
131128

@@ -164,7 +161,6 @@ func TestNewUserPost_VisiblityDefaultPublic(t *testing.T) {
164161
}
165162

166163
func TestNewUserPost_VisibilityPrivate(t *testing.T) {
167-
168164
models.PrepareTestEnv(t)
169165
ctx := test.MockContext(t, "admin/users/new")
170166

routers/web/user/setting/profile.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ const (
3838
func Profile(ctx *context.Context) {
3939
ctx.Data["Title"] = ctx.Tr("settings")
4040
ctx.Data["PageIsSettingsProfile"] = true
41+
ctx.Data["AllowedUserVisibilityModes"] = setting.Service.AllowedUserVisibilityModesSlice.ToVisibleTypeSlice()
4142

4243
ctx.HTML(http.StatusOK, tplSettingsProfile)
4344
}

templates/admin/user/edit.tmpl

Lines changed: 15 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -32,25 +32,25 @@
3232
<div class="inline field {{if .Err_Visibility}}error{{end}}">
3333
<span class="inline required field"><label for="visibility">{{.i18n.Tr "settings.visibility"}}</label></span>
3434
<div class="ui selection type dropdown">
35-
{{if .User.Visibility.IsPublic}}
36-
<input type="hidden" id="visibility" name="visibility" value="0">
37-
{{end}}
38-
{{if .User.Visibility.IsLimited}}
39-
<input type="hidden" id="visibility" name="visibility" value="1">
40-
{{end}}
41-
{{if .User.Visibility.IsPrivate}}
42-
<input type="hidden" id="visibility" name="visibility" value="2">
43-
{{end}}
35+
{{if .User.Visibility.IsPublic}}<input type="hidden" id="visibility" name="visibility" value="0">{{end}}
36+
{{if .User.Visibility.IsLimited}}<input type="hidden" id="visibility" name="visibility" value="1">{{end}}
37+
{{if .User.Visibility.IsPrivate}}<input type="hidden" id="visibility" name="visibility" value="2">{{end}}
4438
<div class="text">
45-
{{if .User.Visibility.IsPublic}}{{.i18n.Tr "settings.visibility.public"}}{{end}}
46-
{{if .User.Visibility.IsLimited}}{{.i18n.Tr "settings.visibility.limited"}}{{end}}
47-
{{if .User.Visibility.IsPrivate}}{{.i18n.Tr "settings.visibility.private"}}{{end}}
39+
{{if .User.Visibility.IsPublic}}{{.i18n.Tr "settings.visibility.public"}}{{end}}
40+
{{if .User.Visibility.IsLimited}}{{.i18n.Tr "settings.visibility.limited"}}{{end}}
41+
{{if .User.Visibility.IsPrivate}}{{.i18n.Tr "settings.visibility.private"}}{{end}}
4842
</div>
4943
{{svg "octicon-triangle-down" 14 "dropdown icon"}}
5044
<div class="menu">
51-
<div class="item poping up" data-content="{{.i18n.Tr "settings.visibility.public_tooltip"}}" data-value="0">{{.i18n.Tr "settings.visibility.public"}}</div>
52-
<div class="item poping up" data-content="{{.i18n.Tr "settings.visibility.limited_tooltip"}}" data-value="1">{{.i18n.Tr "settings.visibility.limited"}}</div>
53-
<div class="item poping up" data-content="{{.i18n.Tr "settings.visibility.private_tooltip"}}" data-value="2">{{.i18n.Tr "settings.visibility.private"}}</div>
45+
{{range $mode := .AllowedUserVisibilityModes}}
46+
{{if $mode.IsPublic}}
47+
<div class="item poping up" data-content="{{$.i18n.Tr "settings.visibility.public_tooltip"}}" data-value="0">{{$.i18n.Tr "settings.visibility.public"}}</div>
48+
{{else if $mode.IsLimited}}
49+
<div class="item poping up" data-content="{{$.i18n.Tr "settings.visibility.limited_tooltip"}}" data-value="1">{{$.i18n.Tr "settings.visibility.limited"}}</div>
50+
{{else if $mode.IsPrivate}}
51+
<div class="item poping up" data-content="{{$.i18n.Tr "settings.visibility.private_tooltip"}}" data-value="2">{{$.i18n.Tr "settings.visibility.private"}}</div>
52+
{{end}}
53+
{{end}}
5454
</div>
5555
</div>
5656
</div>

templates/admin/user/new.tmpl

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -30,15 +30,21 @@
3030
<div class="ui selection type dropdown">
3131
<input type="hidden" id="visibility" name="visibility" value="{{.visibility}}">
3232
<div class="text">
33-
{{if .DefaultUserVisibilityMode.IsPublic}}{{.i18n.Tr "settings.visibility.public"}}{{end}}
34-
{{if .DefaultUserVisibilityMode.IsLimited}}{{.i18n.Tr "settings.visibility.limited"}}{{end}}
35-
{{if .DefaultUserVisibilityMode.IsPrivate}}{{.i18n.Tr "settings.visibility.private"}}{{end}}
33+
{{if .DefaultUserVisibilityMode.IsPublic}}{{.i18n.Tr "settings.visibility.public"}}{{end}}
34+
{{if .DefaultUserVisibilityMode.IsLimited}}{{.i18n.Tr "settings.visibility.limited"}}{{end}}
35+
{{if .DefaultUserVisibilityMode.IsPrivate}}{{.i18n.Tr "settings.visibility.private"}}{{end}}
3636
</div>
3737
{{svg "octicon-triangle-down" 14 "dropdown icon"}}
3838
<div class="menu">
39-
<div class="item poping up" data-content="{{.i18n.Tr "settings.visibility.public_tooltip"}}" data-value="0">{{.i18n.Tr "settings.visibility.public"}}</div>
40-
<div class="item poping up" data-content="{{.i18n.Tr "settings.visibility.limited_tooltip"}}" data-value="1">{{.i18n.Tr "settings.visibility.limited"}}</div>
41-
<div class="item poping up" data-content="{{.i18n.Tr "settings.visibility.private_tooltip"}}" data-value="2">{{.i18n.Tr "settings.visibility.private"}}</div>
39+
{{range $mode := .AllowedUserVisibilityModes}}
40+
{{if $mode.IsPublic}}
41+
<div class="item poping up" data-content="{{$.i18n.Tr "settings.visibility.public_tooltip"}}" data-value="0">{{$.i18n.Tr "settings.visibility.public"}}</div>
42+
{{else if $mode.IsLimited}}
43+
<div class="item poping up" data-content="{{$.i18n.Tr "settings.visibility.limited_tooltip"}}" data-value="1">{{$.i18n.Tr "settings.visibility.limited"}}</div>
44+
{{else if $mode.IsPrivate}}
45+
<div class="item poping up" data-content="{{$.i18n.Tr "settings.visibility.private_tooltip"}}" data-value="2">{{$.i18n.Tr "settings.visibility.private"}}</div>
46+
{{end}}
47+
{{end}}
4248
</div>
4349
</div>
4450
</div>

templates/user/settings/profile.tmpl

Lines changed: 15 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -71,25 +71,25 @@
7171
<div class="inline field {{if .Err_Visibility}}error{{end}}">
7272
<span class="inline required field"><label for="visibility">{{.i18n.Tr "settings.visibility"}}</label></span>
7373
<div class="ui selection type dropdown">
74-
{{if .SignedUser.Visibility.IsPublic}}
75-
<input type="hidden" id="visibility" name="visibility" value="0">
76-
{{end}}
77-
{{if .SignedUser.Visibility.IsLimited}}
78-
<input type="hidden" id="visibility" name="visibility" value="1">
79-
{{end}}
80-
{{if .SignedUser.Visibility.IsPrivate}}
81-
<input type="hidden" id="visibility" name="visibility" value="2">
82-
{{end}}
74+
{{if .SignedUser.Visibility.IsPublic}}<input type="hidden" id="visibility" name="visibility" value="0">{{end}}
75+
{{if .SignedUser.Visibility.IsLimited}}<input type="hidden" id="visibility" name="visibility" value="1">{{end}}
76+
{{if .SignedUser.Visibility.IsPrivate}}<input type="hidden" id="visibility" name="visibility" value="2">{{end}}
8377
<div class="text">
84-
{{if .SignedUser.Visibility.IsPublic}}{{.i18n.Tr "settings.visibility.public"}}{{end}}
85-
{{if .SignedUser.Visibility.IsLimited}}{{.i18n.Tr "settings.visibility.limited"}}{{end}}
86-
{{if .SignedUser.Visibility.IsPrivate}}{{.i18n.Tr "settings.visibility.private"}}{{end}}
78+
{{if .SignedUser.Visibility.IsPublic}}{{.i18n.Tr "settings.visibility.public"}}{{end}}
79+
{{if .SignedUser.Visibility.IsLimited}}{{.i18n.Tr "settings.visibility.limited"}}{{end}}
80+
{{if .SignedUser.Visibility.IsPrivate}}{{.i18n.Tr "settings.visibility.private"}}{{end}}
8781
</div>
8882
{{svg "octicon-triangle-down" 14 "dropdown icon"}}
8983
<div class="menu">
90-
<div class="item poping up" data-content="{{.i18n.Tr "settings.visibility.public_tooltip"}}" data-value="0">{{.i18n.Tr "settings.visibility.public"}}</div>
91-
<div class="item poping up" data-content="{{.i18n.Tr "settings.visibility.limited_tooltip"}}" data-value="1">{{.i18n.Tr "settings.visibility.limited"}}</div>
92-
<div class="item poping up" data-content="{{.i18n.Tr "settings.visibility.private_tooltip"}}" data-value="2">{{.i18n.Tr "settings.visibility.private"}}</div>
84+
{{range $mode := .AllowedUserVisibilityModes}}
85+
{{if $mode.IsPublic}}
86+
<div class="item poping up" data-content="{{$.i18n.Tr "settings.visibility.public_tooltip"}}" data-value="0">{{$.i18n.Tr "settings.visibility.public"}}</div>
87+
{{else if $mode.IsLimited}}
88+
<div class="item poping up" data-content="{{$.i18n.Tr "settings.visibility.limited_tooltip"}}" data-value="1">{{$.i18n.Tr "settings.visibility.limited"}}</div>
89+
{{else if $mode.IsPrivate}}
90+
<div class="item poping up" data-content="{{$.i18n.Tr "settings.visibility.private_tooltip"}}" data-value="2">{{$.i18n.Tr "settings.visibility.private"}}</div>
91+
{{end}}
92+
{{end}}
9393
</div>
9494
</div>
9595
</div>

0 commit comments

Comments
 (0)