summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--cmd/web.go5
-rw-r--r--conf/app.ini3
-rw-r--r--conf/locale/locale_en-US.ini7
-rw-r--r--models/user.go2
-rw-r--r--modules/auth/user_form.go27
-rw-r--r--modules/base/tool.go22
-rw-r--r--modules/setting/setting.go25
-rw-r--r--routers/admin/admin.go1
-rw-r--r--routers/install.go2
-rw-r--r--routers/org/setting.go4
-rw-r--r--routers/user/setting.go27
-rw-r--r--templates/admin/config.tmpl6
-rw-r--r--templates/install.tmpl6
-rw-r--r--templates/user/settings/avatar.tmpl50
-rw-r--r--templates/user/settings/navbar.tmpl3
-rw-r--r--templates/user/settings/profile.tmpl26
16 files changed, 157 insertions, 59 deletions
diff --git a/cmd/web.go b/cmd/web.go
index 4c4dbe94e9..1ce5acc783 100644
--- a/cmd/web.go
+++ b/cmd/web.go
@@ -226,7 +226,8 @@ func runWeb(ctx *cli.Context) error {
m.Group("/user/settings", func() {
m.Get("", user.Settings)
m.Post("", bindIgnErr(auth.UpdateProfileForm{}), user.SettingsPost)
- m.Post("/avatar", binding.MultipartForm(auth.UploadAvatarForm{}), user.SettingsAvatar)
+ m.Combo("/avatar").Get(user.SettingsAvatar).
+ Post(binding.MultipartForm(auth.AvatarForm{}), user.SettingsAvatarPost)
m.Post("/avatar/delete", user.SettingsDeleteAvatar)
m.Combo("/email").Get(user.SettingsEmails).
Post(bindIgnErr(auth.AddEmailForm{}), user.SettingsEmailPost)
@@ -375,7 +376,7 @@ func runWeb(ctx *cli.Context) error {
m.Group("/settings", func() {
m.Combo("").Get(org.Settings).
Post(bindIgnErr(auth.UpdateOrgSettingForm{}), org.SettingsPost)
- m.Post("/avatar", binding.MultipartForm(auth.UploadAvatarForm{}), org.SettingsAvatar)
+ m.Post("/avatar", binding.MultipartForm(auth.AvatarForm{}), org.SettingsAvatar)
m.Post("/avatar/delete", org.SettingsDeleteAvatar)
m.Group("/hooks", func() {
diff --git a/conf/app.ini b/conf/app.ini
index 3c36195ff6..e46436e65d 100644
--- a/conf/app.ini
+++ b/conf/app.ini
@@ -230,6 +230,9 @@ AVATAR_UPLOAD_PATH = data/avatars
; or a custom avatar source, like: http://cn.gravatar.com/avatar/
GRAVATAR_SOURCE = gravatar
DISABLE_GRAVATAR = false
+; Federated avatar lookup uses DNS to discover avatar associated
+; with emails, see http://www.libravatar.org
+ENABLE_FEDERATED_AVATAR = false
[attachment]
; Whether attachments are enabled. Defaults to `true`
diff --git a/conf/locale/locale_en-US.ini b/conf/locale/locale_en-US.ini
index dd3d54d6f8..99b5d17363 100644
--- a/conf/locale/locale_en-US.ini
+++ b/conf/locale/locale_en-US.ini
@@ -96,6 +96,7 @@ offline_mode = Enable Offline Mode
offline_mode_popup = Disable CDN even in production mode, all resource files will be served locally.
disable_gravatar = Disable Gravatar Service
disable_gravatar_popup = Disable Gravatar and custom sources, all avatars are uploaded by users or default.
+federated_avatar_lookup = Enable Federated Avatars Lookup
disable_registration = Disable Self-registration
disable_registration_popup = Disable user self-registration, only admin can create accounts.
enable_captcha = Enable Captcha
@@ -239,6 +240,7 @@ form.name_pattern_not_allowed = Username pattern '%s' is not allowed.
[settings]
profile = Profile
password = Password
+avatar = Avatar
ssh_keys = SSH Keys
social = Social Accounts
applications = Applications
@@ -259,7 +261,9 @@ change_username_prompt = This change will affect the way how links relate to you
continue = Continue
cancel = Cancel
-enable_custom_avatar = Enable Custom Avatar
+lookup_avatar_by_mail = Lookup Avatar by mail
+federated_avatar_lookup = Federated Avatar Lookup
+enable_custom_avatar = Use Custom Avatar
choose_new_avatar = Choose new avatar
update_avatar = Update Avatar Setting
delete_current_avatar = Delete Current Avatar
@@ -1044,6 +1048,7 @@ config.cookie_life_time = Cookie Life Time
config.picture_config = Picture Configuration
config.picture_service = Picture Service
config.disable_gravatar = Disable Gravatar
+config.enable_federated_avatar = Enable Federated Avatars
config.log_config = Log Configuration
config.log_mode = Log Mode
diff --git a/models/user.go b/models/user.go
index 941dc054aa..d08b1cc8fa 100644
--- a/models/user.go
+++ b/models/user.go
@@ -254,7 +254,7 @@ func (u *User) RelAvatarLink() string {
return setting.AppSubUrl + "/avatars/" + com.ToStr(u.ID)
}
- return setting.GravatarSource + u.Avatar
+ return base.AvatarLink(u.AvatarEmail)
}
// AvatarLink returns user avatar absolute link.
diff --git a/modules/auth/user_form.go b/modules/auth/user_form.go
index 57451d9eb6..7bd6c7b9bc 100644
--- a/modules/auth/user_form.go
+++ b/modules/auth/user_form.go
@@ -36,11 +36,12 @@ type InstallForm struct {
RegisterConfirm bool
MailNotify bool
- OfflineMode bool
- DisableGravatar bool
- DisableRegistration bool
- EnableCaptcha bool
- RequireSignInView bool
+ OfflineMode bool
+ DisableGravatar bool
+ EnableFederatedAvatar bool
+ DisableRegistration bool
+ EnableCaptcha bool
+ RequireSignInView bool
AdminName string `binding:"OmitEmpty;AlphaDashDot;MaxSize(30)" locale:"install.admin_name"`
AdminPasswd string `binding:"OmitEmpty;MaxSize(255)" locale:"install.admin_password"`
@@ -93,19 +94,25 @@ type UpdateProfileForm struct {
Email string `binding:"Required;Email;MaxSize(254)"`
Website string `binding:"Url;MaxSize(100)"`
Location string `binding:"MaxSize(50)"`
- Gravatar string `binding:"OmitEmpty;Email;MaxSize(254)"`
}
func (f *UpdateProfileForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors {
return validate(errs, ctx.Data, f, ctx.Locale)
}
-type UploadAvatarForm struct {
- Enable bool
- Avatar *multipart.FileHeader
+const (
+ AVATAR_LOCAL string = "local"
+ AVATAR_BYMAIL string = "bymail"
+)
+
+type AvatarForm struct {
+ Source string
+ Avatar *multipart.FileHeader
+ Gravatar string `binding:"OmitEmpty;Email;MaxSize(254)"`
+ Federavatar bool
}
-func (f *UploadAvatarForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors {
+func (f *AvatarForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors {
return validate(errs, ctx.Data, f, ctx.Locale)
}
diff --git a/modules/base/tool.go b/modules/base/tool.go
index f045cb2270..b5125cab8a 100644
--- a/modules/base/tool.go
+++ b/modules/base/tool.go
@@ -205,12 +205,26 @@ func HashEmail(email string) string {
}
// AvatarLink returns avatar link by given email.
-func AvatarLink(email string) string {
- if setting.DisableGravatar || setting.OfflineMode {
- return setting.AppSubUrl + "/img/avatar_default.png"
+func AvatarLink(email string) (url string) {
+
+ if !setting.OfflineMode {
+ if setting.EnableFederatedAvatar && setting.LibravatarService != nil {
+ var err error
+ url, err = setting.LibravatarService.FromEmail(email)
+ if err != nil {
+ log.Error(1, "LibravatarService.FromEmail:: %v", err)
+ }
+ }
+ if len(url) == 0 && !setting.DisableGravatar {
+ url = setting.GravatarSource + HashEmail(email)
+ }
+ }
+
+ if len(url) == 0 {
+ url = setting.AppSubUrl + "/img/avatar_default.png"
}
- return setting.GravatarSource + HashEmail(email)
+ return url
}
// Seconds-based time units
diff --git a/modules/setting/setting.go b/modules/setting/setting.go
index f59aa0884d..b8e8f65ef1 100644
--- a/modules/setting/setting.go
+++ b/modules/setting/setting.go
@@ -21,6 +21,7 @@ import (
"github.com/go-macaron/session"
_ "github.com/go-macaron/session/redis"
"gopkg.in/ini.v1"
+ "github.com/strk/go-libravatar"
"github.com/gogits/gogs/modules/bindata"
"github.com/gogits/gogs/modules/log"
@@ -140,9 +141,11 @@ var (
}
// Picture settings
- AvatarUploadPath string
- GravatarSource string
- DisableGravatar bool
+ AvatarUploadPath string
+ GravatarSource string
+ DisableGravatar bool
+ EnableFederatedAvatar bool
+ LibravatarService *libravatar.Libravatar
// Log settings
LogRootPath string
@@ -462,8 +465,24 @@ func NewContext() {
GravatarSource = source
}
DisableGravatar = sec.Key("DISABLE_GRAVATAR").MustBool()
+ EnableFederatedAvatar = sec.Key("ENABLE_FEDERATED_AVATAR").MustBool()
if OfflineMode {
DisableGravatar = true
+ EnableFederatedAvatar = false
+ }
+
+ if !DisableGravatar && EnableFederatedAvatar {
+ LibravatarService = libravatar.New()
+ parts := strings.Split(GravatarSource, "/")
+ if len(parts) >= 3 {
+ if parts[0] == "https:" {
+ LibravatarService.SetUseHTTPS(true)
+ LibravatarService.SetSecureFallbackHost(parts[2])
+ } else {
+ LibravatarService.SetUseHTTPS(false)
+ LibravatarService.SetFallbackHost(parts[2])
+ }
+ }
}
if err = Cfg.Section("ui").MapTo(&UI); err != nil {
diff --git a/routers/admin/admin.go b/routers/admin/admin.go
index bc850b638e..b8eb6743d7 100644
--- a/routers/admin/admin.go
+++ b/routers/admin/admin.go
@@ -222,6 +222,7 @@ func Config(ctx *context.Context) {
ctx.Data["SessionConfig"] = setting.SessionConfig
ctx.Data["DisableGravatar"] = setting.DisableGravatar
+ ctx.Data["EnableFederatedAvatar"] = setting.EnableFederatedAvatar
type logger struct {
Mode, Config string
diff --git a/routers/install.go b/routers/install.go
index 0af1f446c0..8b96ff8254 100644
--- a/routers/install.go
+++ b/routers/install.go
@@ -169,6 +169,7 @@ func Install(ctx *context.Context) {
// Server and other services settings
form.OfflineMode = setting.OfflineMode
form.DisableGravatar = setting.DisableGravatar
+ form.EnableFederatedAvatar = setting.EnableFederatedAvatar
form.DisableRegistration = setting.Service.DisableRegistration
form.EnableCaptcha = setting.Service.EnableCaptcha
form.RequireSignInView = setting.Service.RequireSignInView
@@ -329,6 +330,7 @@ func InstallPost(ctx *context.Context, form auth.InstallForm) {
cfg.Section("server").Key("OFFLINE_MODE").SetValue(com.ToStr(form.OfflineMode))
cfg.Section("picture").Key("DISABLE_GRAVATAR").SetValue(com.ToStr(form.DisableGravatar))
+ cfg.Section("picture").Key("ENABLE_FEDERATED_AVATAR").SetValue(com.ToStr(form.EnableFederatedAvatar))
cfg.Section("service").Key("DISABLE_REGISTRATION").SetValue(com.ToStr(form.DisableRegistration))
cfg.Section("service").Key("ENABLE_CAPTCHA").SetValue(com.ToStr(form.EnableCaptcha))
cfg.Section("service").Key("REQUIRE_SIGNIN_VIEW").SetValue(com.ToStr(form.RequireSignInView))
diff --git a/routers/org/setting.go b/routers/org/setting.go
index 128fba36a8..14681e4c59 100644
--- a/routers/org/setting.go
+++ b/routers/org/setting.go
@@ -83,8 +83,8 @@ func SettingsPost(ctx *context.Context, form auth.UpdateOrgSettingForm) {
ctx.Redirect(ctx.Org.OrgLink + "/settings")
}
-func SettingsAvatar(ctx *context.Context, form auth.UploadAvatarForm) {
- form.Enable = true
+func SettingsAvatar(ctx *context.Context, form auth.AvatarForm) {
+ form.Source = auth.AVATAR_LOCAL
if err := user.UpdateAvatarSetting(ctx, form, ctx.Org.Organization); err != nil {
ctx.Flash.Error(err.Error())
} else {
diff --git a/routers/user/setting.go b/routers/user/setting.go
index 7412ce34d1..c5441f3543 100644
--- a/routers/user/setting.go
+++ b/routers/user/setting.go
@@ -22,6 +22,7 @@ import (
const (
SETTINGS_PROFILE base.TplName = "user/settings/profile"
+ SETTINGS_AVATAR base.TplName = "user/settings/avatar"
SETTINGS_PASSWORD base.TplName = "user/settings/password"
SETTINGS_EMAILS base.TplName = "user/settings/email"
SETTINGS_SSH_KEYS base.TplName = "user/settings/sshkeys"
@@ -91,10 +92,6 @@ func SettingsPost(ctx *context.Context, form auth.UpdateProfileForm) {
ctx.User.Email = form.Email
ctx.User.Website = form.Website
ctx.User.Location = form.Location
- if len(form.Gravatar) > 0 {
- ctx.User.Avatar = base.EncodeMD5(form.Gravatar)
- ctx.User.AvatarEmail = form.Gravatar
- }
if err := models.UpdateUser(ctx.User); err != nil {
ctx.Handle(500, "UpdateUser", err)
return
@@ -106,8 +103,12 @@ func SettingsPost(ctx *context.Context, form auth.UpdateProfileForm) {
}
// FIXME: limit size.
-func UpdateAvatarSetting(ctx *context.Context, form auth.UploadAvatarForm, ctxUser *models.User) error {
- ctxUser.UseCustomAvatar = form.Enable
+func UpdateAvatarSetting(ctx *context.Context, form auth.AvatarForm, ctxUser *models.User) error {
+ ctxUser.UseCustomAvatar = form.Source == auth.AVATAR_LOCAL
+ if len(form.Gravatar) > 0 {
+ ctxUser.Avatar = base.EncodeMD5(form.Gravatar)
+ ctxUser.AvatarEmail = form.Gravatar
+ }
if form.Avatar != nil {
fr, err := form.Avatar.Open()
@@ -129,7 +130,7 @@ func UpdateAvatarSetting(ctx *context.Context, form auth.UploadAvatarForm, ctxUs
} else {
// No avatar is uploaded but setting has been changed to enable,
// generate a random one when needed.
- if form.Enable && !com.IsFile(ctxUser.CustomAvatarPath()) {
+ if ctxUser.UseCustomAvatar && !com.IsFile(ctxUser.CustomAvatarPath()) {
if err := ctxUser.GenerateRandomAvatar(); err != nil {
log.Error(4, "GenerateRandomAvatar[%d]: %v", ctxUser.ID, err)
}
@@ -143,14 +144,20 @@ func UpdateAvatarSetting(ctx *context.Context, form auth.UploadAvatarForm, ctxUs
return nil
}
-func SettingsAvatar(ctx *context.Context, form auth.UploadAvatarForm) {
+func SettingsAvatar(ctx *context.Context) {
+ ctx.Data["Title"] = ctx.Tr("settings")
+ ctx.Data["PageIsSettingsAvatar"] = true
+ ctx.HTML(200, SETTINGS_AVATAR)
+}
+
+func SettingsAvatarPost(ctx *context.Context, form auth.AvatarForm) {
if err := UpdateAvatarSetting(ctx, form, ctx.User); err != nil {
ctx.Flash.Error(err.Error())
} else {
ctx.Flash.Success(ctx.Tr("settings.update_avatar_success"))
}
- ctx.Redirect(setting.AppSubUrl + "/user/settings")
+ ctx.Redirect(setting.AppSubUrl + "/user/settings/avatar")
}
func SettingsDeleteAvatar(ctx *context.Context) {
@@ -158,7 +165,7 @@ func SettingsDeleteAvatar(ctx *context.Context) {
ctx.Flash.Error(err.Error())
}
- ctx.Redirect(setting.AppSubUrl + "/user/settings")
+ ctx.Redirect(setting.AppSubUrl + "/user/settings/avatar")
}
func SettingsPassword(ctx *context.Context) {
diff --git a/templates/admin/config.tmpl b/templates/admin/config.tmpl
index 880fca713b..93fc404ba1 100644
--- a/templates/admin/config.tmpl
+++ b/templates/admin/config.tmpl
@@ -215,6 +215,12 @@
<dd><i class="fa fa{{if .DisableGravatar}}-check{{end}}-square-o"></i></dd>
</dl>
</div>
+ <div class="ui attached table segment">
+ <dl class="dl-horizontal admin-dl-horizontal">
+ <dt>{{.i18n.Tr "admin.config.enable_federated_avatar"}}</dt>
+ <dd><i class="fa fa{{if .EnableFederatedAvatar}}-check{{end}}-square-o"></i></dd>
+ </dl>
+ </div>
<h4 class="ui top attached header">
{{.i18n.Tr "admin.config.log_config"}}
diff --git a/templates/install.tmpl b/templates/install.tmpl
index b3d4d7339d..496c425654 100644
--- a/templates/install.tmpl
+++ b/templates/install.tmpl
@@ -178,6 +178,12 @@
</div>
</div>
<div class="inline field">
+ <div class="ui checkbox" id="federated-avatar-lookup">
+ <label class="poping up" data-content="{{.i18n.Tr "install.federated_avatar_lookup"}}"><strong>{{.i18n.Tr "install.federated_avatar_lookup"}}</strong></label>
+ <input name="enable_federated_avatar" type="checkbox" {{if .enable_federated_avatar}}checked{{end}}>
+ </div>
+ </div>
+ <div class="inline field">
<div class="ui checkbox" id="disable-registration">
<label class="poping up" data-content="{{.i18n.Tr "install.disable_registration_popup"}}"><strong>{{.i18n.Tr "install.disable_registration"}}</strong></label>
<input name="disable_registration" type="checkbox" {{if .disable_registration}}checked{{end}}>
diff --git a/templates/user/settings/avatar.tmpl b/templates/user/settings/avatar.tmpl
new file mode 100644
index 0000000000..926c9d8508
--- /dev/null
+++ b/templates/user/settings/avatar.tmpl
@@ -0,0 +1,50 @@
+{{template "base/head" .}}
+<div class="user settings avatar">
+ <div class="ui container">
+ <div class="ui grid">
+ {{template "user/settings/navbar" .}}
+ <div class="twelve wide column content">
+ {{template "base/alert" .}}
+ <h4 class="ui top attached header">
+ {{.i18n.Tr "settings.avatar"}}
+ </h4>
+ <div class="ui attached segment">
+
+ <form class="ui form" action="{{.Link}}" method="post" enctype="multipart/form-data">
+ {{.CsrfTokenHtml}}
+ {{if not DisableGravatar}}
+ <div class="inline field">
+ <div class="ui radio">
+ <input name="source" value="lookup" type="radio" {{if not .SignedUser.UseCustomAvatar}}checked{{end}}>
+ <label>{{.i18n.Tr "settings.lookup_avatar_by_mail"}}</label>
+ </div>
+ </div>
+ <div class="field {{if .Err_Gravatar}}error{{end}}">
+ <label for="gravatar">Avatar {{.i18n.Tr "email"}}</label>
+ <input id="gravatar" name="gravatar" value="{{.SignedUser.AvatarEmail}}" />
+ </div>
+ {{end}}
+
+ <div class="inline field">
+ <div class="ui radio">
+ <input name="source" value="local" type="radio" {{if .SignedUser.UseCustomAvatar}}checked{{end}}>
+ <label>{{.i18n.Tr "settings.enable_custom_avatar"}}</label>
+ </div>
+ </div>
+
+ <div class="inline field">
+ <label for="avatar">{{.i18n.Tr "settings.choose_new_avatar"}}</label>
+ <input name="avatar" type="file" >
+ </div>
+
+ <div class="field">
+ <button class="ui green button">{{$.i18n.Tr "settings.update_avatar"}}</button>
+ <a class="ui red button delete-post" data-request-url="{{.Link}}/delete" data-done-url="{{.Link}}">{{$.i18n.Tr "settings.delete_current_avatar"}}</a>
+ </div>
+ </form>
+ </div>
+ </div>
+ </div>
+ </div>
+</div>
+{{template "base/footer" .}}
diff --git a/templates/user/settings/navbar.tmpl b/templates/user/settings/navbar.tmpl
index 87e24522b4..5166bafaba 100644
--- a/templates/user/settings/navbar.tmpl
+++ b/templates/user/settings/navbar.tmpl
@@ -4,6 +4,9 @@
<a class="{{if .PageIsSettingsProfile}}active{{end}} item" href="{{AppSubUrl}}/user/settings">
{{.i18n.Tr "settings.profile"}}
</a>
+ <a class="{{if .PageIsSettingsAvatar}}active{{end}} item" href="{{AppSubUrl}}/user/settings/avatar">
+ {{.i18n.Tr "settings.avatar"}}
+ </a>
<a class="{{if .PageIsSettingsPassword}}active{{end}} item" href="{{AppSubUrl}}/user/settings/password">
{{.i18n.Tr "settings.password"}}
</a>
diff --git a/templates/user/settings/profile.tmpl b/templates/user/settings/profile.tmpl
index a1328a2210..a42d9c49c3 100644
--- a/templates/user/settings/profile.tmpl
+++ b/templates/user/settings/profile.tmpl
@@ -35,38 +35,12 @@
<label for="location">{{.i18n.Tr "settings.location"}}</label>
<input id="location" name="location" value="{{.SignedUser.Location}}">
</div>
- {{if not DisableGravatar}}
- <div class="field {{if .Err_Gravatar}}error{{end}}">
- <label for="gravatar">Gravatar {{.i18n.Tr "email"}}</label>
- <input id="gravatar" name="gravatar" value="{{.SignedUser.AvatarEmail}}" />
- </div>
- {{end}}
<div class="field">
<button class="ui green button">{{$.i18n.Tr "settings.update_profile"}}</button>
</div>
</form>
- <div class="ui divider"></div>
-
- <form class="ui form" action="{{.Link}}/avatar" method="post" enctype="multipart/form-data">
- {{.CsrfTokenHtml}}
- <div class="inline field">
- <div class="ui checkbox">
- <input name="enable" type="checkbox" {{if .SignedUser.UseCustomAvatar}}checked{{end}}>
- <label>{{.i18n.Tr "settings.enable_custom_avatar"}}</label>
- </div>
- </div>
- <div class="inline field">
- <label for="avatar">{{.i18n.Tr "settings.choose_new_avatar"}}</label>
- <input name="avatar" type="file" >
- </div>
-
- <div class="field">
- <button class="ui green button">{{$.i18n.Tr "settings.update_avatar"}}</button>
- <a class="ui red button delete-post" data-request-url="{{.Link}}/avatar/delete" data-done-url="{{.Link}}">{{$.i18n.Tr "settings.delete_current_avatar"}}</a>
- </div>
- </form>
</div>
</div>
</div>