diff options
-rw-r--r-- | cmd/web.go | 5 | ||||
-rw-r--r-- | conf/app.ini | 3 | ||||
-rw-r--r-- | conf/locale/locale_en-US.ini | 7 | ||||
-rw-r--r-- | models/user.go | 2 | ||||
-rw-r--r-- | modules/auth/user_form.go | 27 | ||||
-rw-r--r-- | modules/base/tool.go | 22 | ||||
-rw-r--r-- | modules/setting/setting.go | 25 | ||||
-rw-r--r-- | routers/admin/admin.go | 1 | ||||
-rw-r--r-- | routers/install.go | 2 | ||||
-rw-r--r-- | routers/org/setting.go | 4 | ||||
-rw-r--r-- | routers/user/setting.go | 27 | ||||
-rw-r--r-- | templates/admin/config.tmpl | 6 | ||||
-rw-r--r-- | templates/install.tmpl | 6 | ||||
-rw-r--r-- | templates/user/settings/avatar.tmpl | 50 | ||||
-rw-r--r-- | templates/user/settings/navbar.tmpl | 3 | ||||
-rw-r--r-- | templates/user/settings/profile.tmpl | 26 |
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> |