2018-05-17 07:05:00 +03:00
// Copyright 2014 The Gogs Authors. All rights reserved.
// Copyright 2018 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package setting
import (
"errors"
"fmt"
"io/ioutil"
2021-04-05 18:30:52 +03:00
"net/http"
2020-09-25 07:09:23 +03:00
"os"
"path/filepath"
2018-05-17 07:05:00 +03:00
"strings"
"code.gitea.io/gitea/models"
"code.gitea.io/gitea/modules/base"
"code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
2021-06-05 15:32:19 +03:00
"code.gitea.io/gitea/modules/typesniffer"
2020-12-04 09:20:30 +03:00
"code.gitea.io/gitea/modules/util"
2021-01-26 18:36:53 +03:00
"code.gitea.io/gitea/modules/web"
2021-03-07 11:12:43 +03:00
"code.gitea.io/gitea/modules/web/middleware"
2021-07-28 12:42:56 +03:00
"code.gitea.io/gitea/services/agit"
2021-04-06 22:44:05 +03:00
"code.gitea.io/gitea/services/forms"
2018-05-17 07:05:00 +03:00
2019-08-23 19:40:30 +03:00
"github.com/unknwon/i18n"
2018-05-17 07:05:00 +03:00
)
const (
tplSettingsProfile base . TplName = "user/settings/profile"
tplSettingsOrganization base . TplName = "user/settings/organization"
tplSettingsRepositories base . TplName = "user/settings/repos"
)
// Profile render user's profile page
func Profile ( ctx * context . Context ) {
ctx . Data [ "Title" ] = ctx . Tr ( "settings" )
ctx . Data [ "PageIsSettingsProfile" ] = true
2021-06-27 21:47:35 +03:00
ctx . Data [ "AllowedUserVisibilityModes" ] = setting . Service . AllowedUserVisibilityModesSlice . ToVisibleTypeSlice ( )
2018-06-18 21:24:45 +03:00
2021-04-05 18:30:52 +03:00
ctx . HTML ( http . StatusOK , tplSettingsProfile )
2018-05-17 07:05:00 +03:00
}
2021-01-10 15:14:02 +03:00
// HandleUsernameChange handle username changes from user settings and admin interface
func HandleUsernameChange ( ctx * context . Context , user * models . User , newName string ) error {
2018-05-17 07:05:00 +03:00
// Non-local users are not allowed to change their username.
2021-01-10 15:14:02 +03:00
if ! user . IsLocal ( ) {
ctx . Flash . Error ( ctx . Tr ( "form.username_change_not_local_user" ) )
return fmt . Errorf ( ctx . Tr ( "form.username_change_not_local_user" ) )
2018-05-17 07:05:00 +03:00
}
// Check if user name has been changed
2021-01-10 15:14:02 +03:00
if user . LowerName != strings . ToLower ( newName ) {
if err := models . ChangeUserName ( user , newName ) ; err != nil {
2018-05-17 07:05:00 +03:00
switch {
case models . IsErrUserAlreadyExist ( err ) :
ctx . Flash . Error ( ctx . Tr ( "form.username_been_taken" ) )
case models . IsErrEmailAlreadyUsed ( err ) :
ctx . Flash . Error ( ctx . Tr ( "form.email_been_used" ) )
case models . IsErrNameReserved ( err ) :
ctx . Flash . Error ( ctx . Tr ( "user.form.name_reserved" , newName ) )
case models . IsErrNamePatternNotAllowed ( err ) :
ctx . Flash . Error ( ctx . Tr ( "user.form.name_pattern_not_allowed" , newName ) )
2020-02-23 22:52:05 +03:00
case models . IsErrNameCharsNotAllowed ( err ) :
ctx . Flash . Error ( ctx . Tr ( "user.form.name_chars_not_allowed" , newName ) )
2018-05-17 07:05:00 +03:00
default :
ctx . ServerError ( "ChangeUserName" , err )
}
2021-01-10 15:14:02 +03:00
return err
2018-05-17 07:05:00 +03:00
}
2021-06-02 15:03:59 +03:00
} else {
if err := models . UpdateRepositoryOwnerNames ( user . ID , newName ) ; err != nil {
ctx . ServerError ( "UpdateRepository" , err )
return err
}
2018-05-17 07:05:00 +03:00
}
2021-07-28 12:42:56 +03:00
// update all agit flow pull request header
err := agit . UserNameChanged ( user , newName )
if err != nil {
ctx . ServerError ( "agit.UserNameChanged" , err )
return err
}
2021-06-02 15:03:59 +03:00
log . Trace ( "User name changed: %s -> %s" , user . Name , newName )
2021-01-10 15:14:02 +03:00
return nil
2018-05-17 07:05:00 +03:00
}
// ProfilePost response for change user's profile
2021-01-26 18:36:53 +03:00
func ProfilePost ( ctx * context . Context ) {
2021-04-06 22:44:05 +03:00
form := web . GetForm ( ctx ) . ( * forms . UpdateProfileForm )
2018-05-17 07:05:00 +03:00
ctx . Data [ "Title" ] = ctx . Tr ( "settings" )
ctx . Data [ "PageIsSettingsProfile" ] = true
if ctx . HasError ( ) {
2021-04-05 18:30:52 +03:00
ctx . HTML ( http . StatusOK , tplSettingsProfile )
2018-05-17 07:05:00 +03:00
return
}
2021-01-10 15:14:02 +03:00
if len ( form . Name ) != 0 && ctx . User . Name != form . Name {
2021-06-02 15:03:59 +03:00
log . Debug ( "Changing name for %s to %s" , ctx . User . Name , form . Name )
2021-01-10 15:14:02 +03:00
if err := HandleUsernameChange ( ctx , ctx . User , form . Name ) ; err != nil {
ctx . Redirect ( setting . AppSubURL + "/user/settings" )
return
}
ctx . User . Name = form . Name
ctx . User . LowerName = strings . ToLower ( form . Name )
2018-05-17 07:05:00 +03:00
}
ctx . User . FullName = form . FullName
ctx . User . KeepEmailPrivate = form . KeepEmailPrivate
ctx . User . Website = form . Website
ctx . User . Location = form . Location
2020-12-04 09:20:30 +03:00
if len ( form . Language ) != 0 {
if ! util . IsStringInSlice ( form . Language , setting . Langs ) {
ctx . Flash . Error ( ctx . Tr ( "settings.update_language_not_found" , form . Language ) )
ctx . Redirect ( setting . AppSubURL + "/user/settings" )
return
}
ctx . User . Language = form . Language
}
2019-03-19 05:28:10 +03:00
ctx . User . Description = form . Description
2020-06-05 23:01:53 +03:00
ctx . User . KeepActivityPrivate = form . KeepActivityPrivate
2021-06-26 22:53:14 +03:00
ctx . User . Visibility = form . Visibility
2018-05-17 07:05:00 +03:00
if err := models . UpdateUserSetting ( ctx . User ) ; err != nil {
if _ , ok := err . ( models . ErrEmailAlreadyUsed ) ; ok {
ctx . Flash . Error ( ctx . Tr ( "form.email_been_used" ) )
ctx . Redirect ( setting . AppSubURL + "/user/settings" )
return
}
ctx . ServerError ( "UpdateUser" , err )
return
}
// Update the language to the one we just set
2021-03-07 11:12:43 +03:00
middleware . SetLocaleCookie ( ctx . Resp , ctx . User . Language , 0 )
2018-05-17 07:05:00 +03:00
log . Trace ( "User settings updated: %s" , ctx . User . Name )
ctx . Flash . Success ( i18n . Tr ( ctx . User . Language , "settings.update_profile_success" ) )
ctx . Redirect ( setting . AppSubURL + "/user/settings" )
}
// UpdateAvatarSetting update user's avatar
// FIXME: limit size.
2021-04-06 22:44:05 +03:00
func UpdateAvatarSetting ( ctx * context . Context , form * forms . AvatarForm , ctxUser * models . User ) error {
ctxUser . UseCustomAvatar = form . Source == forms . AvatarLocal
2018-05-17 07:05:00 +03:00
if len ( form . Gravatar ) > 0 {
2020-10-23 20:55:10 +03:00
if form . Avatar != nil {
ctxUser . Avatar = base . EncodeMD5 ( form . Gravatar )
} else {
ctxUser . Avatar = ""
}
2018-05-17 07:05:00 +03:00
ctxUser . AvatarEmail = form . Gravatar
}
2018-08-01 12:38:56 +03:00
if form . Avatar != nil && form . Avatar . Filename != "" {
2018-05-17 07:05:00 +03:00
fr , err := form . Avatar . Open ( )
if err != nil {
return fmt . Errorf ( "Avatar.Open: %v" , err )
}
defer fr . Close ( )
2020-10-14 16:07:51 +03:00
if form . Avatar . Size > setting . Avatar . MaxFileSize {
2019-05-30 05:22:26 +03:00
return errors . New ( ctx . Tr ( "settings.uploaded_avatar_is_too_big" ) )
}
2018-05-17 07:05:00 +03:00
data , err := ioutil . ReadAll ( fr )
if err != nil {
return fmt . Errorf ( "ioutil.ReadAll: %v" , err )
}
2021-06-05 15:32:19 +03:00
st := typesniffer . DetectContentType ( data )
if ! ( st . IsImage ( ) && ! st . IsSvgImage ( ) ) {
2018-05-17 07:05:00 +03:00
return errors . New ( ctx . Tr ( "settings.uploaded_avatar_not_a_image" ) )
}
if err = ctxUser . UploadAvatar ( data ) ; err != nil {
return fmt . Errorf ( "UploadAvatar: %v" , err )
}
2020-10-14 16:07:51 +03:00
} else if ctxUser . UseCustomAvatar && ctxUser . Avatar == "" {
2018-05-17 07:05:00 +03:00
// No avatar is uploaded but setting has been changed to enable,
// generate a random one when needed.
2019-06-12 22:41:28 +03:00
if err := ctxUser . GenerateRandomAvatar ( ) ; err != nil {
log . Error ( "GenerateRandomAvatar[%d]: %v" , ctxUser . ID , err )
2018-05-17 07:05:00 +03:00
}
}
if err := models . UpdateUserCols ( ctxUser , "avatar" , "avatar_email" , "use_custom_avatar" ) ; err != nil {
return fmt . Errorf ( "UpdateUser: %v" , err )
}
return nil
}
// AvatarPost response for change user's avatar request
2021-01-26 18:36:53 +03:00
func AvatarPost ( ctx * context . Context ) {
2021-04-06 22:44:05 +03:00
form := web . GetForm ( ctx ) . ( * forms . AvatarForm )
2018-05-17 07:05:00 +03:00
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" )
}
// DeleteAvatar render delete avatar page
func DeleteAvatar ( ctx * context . Context ) {
if err := ctx . User . DeleteAvatar ( ) ; err != nil {
ctx . Flash . Error ( err . Error ( ) )
}
ctx . Redirect ( setting . AppSubURL + "/user/settings" )
}
// Organization render all the organization of the user
func Organization ( ctx * context . Context ) {
ctx . Data [ "Title" ] = ctx . Tr ( "settings" )
ctx . Data [ "PageIsSettingsOrganization" ] = true
orgs , err := models . GetOrgsByUserID ( ctx . User . ID , ctx . IsSigned )
if err != nil {
ctx . ServerError ( "GetOrgsByUserID" , err )
return
}
ctx . Data [ "Orgs" ] = orgs
2021-04-05 18:30:52 +03:00
ctx . HTML ( http . StatusOK , tplSettingsOrganization )
2018-05-17 07:05:00 +03:00
}
// Repos display a list of all repositories of the user
func Repos ( ctx * context . Context ) {
ctx . Data [ "Title" ] = ctx . Tr ( "settings" )
ctx . Data [ "PageIsSettingsRepos" ] = true
2020-09-25 07:09:23 +03:00
ctx . Data [ "allowAdopt" ] = ctx . IsUserSiteAdmin ( ) || setting . Repository . AllowAdoptionOfUnadoptedRepositories
ctx . Data [ "allowDelete" ] = ctx . IsUserSiteAdmin ( ) || setting . Repository . AllowDeleteOfUnadoptedRepositories
2018-05-17 07:05:00 +03:00
2020-09-25 07:09:23 +03:00
opts := models . ListOptions {
PageSize : setting . UI . Admin . UserPagingNum ,
2021-07-29 04:42:15 +03:00
Page : ctx . FormInt ( "page" ) ,
2020-09-25 07:09:23 +03:00
}
if opts . Page <= 0 {
opts . Page = 1
2018-05-17 07:05:00 +03:00
}
2020-09-25 07:09:23 +03:00
start := ( opts . Page - 1 ) * opts . PageSize
end := start + opts . PageSize
2018-05-17 07:05:00 +03:00
2020-09-25 07:09:23 +03:00
adoptOrDelete := ctx . IsUserSiteAdmin ( ) || ( setting . Repository . AllowAdoptionOfUnadoptedRepositories && setting . Repository . AllowDeleteOfUnadoptedRepositories )
ctxUser := ctx . User
count := 0
if adoptOrDelete {
repoNames := make ( [ ] string , 0 , setting . UI . Admin . UserPagingNum )
repos := map [ string ] * models . Repository { }
// We're going to iterate by pagesize.
root := filepath . Join ( models . UserPath ( ctxUser . Name ) )
if err := filepath . Walk ( root , func ( path string , info os . FileInfo , err error ) error {
2018-05-17 07:05:00 +03:00
if err != nil {
2020-12-31 10:45:54 +03:00
if os . IsNotExist ( err ) {
return nil
}
2020-09-25 07:09:23 +03:00
return err
2018-05-17 07:05:00 +03:00
}
2020-09-25 07:09:23 +03:00
if ! info . IsDir ( ) || path == root {
return nil
2018-05-17 07:05:00 +03:00
}
2020-09-25 07:09:23 +03:00
name := info . Name ( )
if ! strings . HasSuffix ( name , ".git" ) {
return filepath . SkipDir
}
name = name [ : len ( name ) - 4 ]
if models . IsUsableRepoName ( name ) != nil || strings . ToLower ( name ) != name {
return filepath . SkipDir
}
if count >= start && count < end {
repoNames = append ( repoNames , name )
}
count ++
return filepath . SkipDir
} ) ; err != nil {
ctx . ServerError ( "filepath.Walk" , err )
return
2018-05-17 07:05:00 +03:00
}
2020-09-25 07:09:23 +03:00
if err := ctxUser . GetRepositories ( models . ListOptions { Page : 1 , PageSize : setting . UI . Admin . UserPagingNum } , repoNames ... ) ; err != nil {
ctx . ServerError ( "GetRepositories" , err )
return
}
for _ , repo := range ctxUser . Repos {
if repo . IsFork {
if err := repo . GetBaseRepo ( ) ; err != nil {
ctx . ServerError ( "GetBaseRepo" , err )
return
}
}
repos [ repo . LowerName ] = repo
}
ctx . Data [ "Dirs" ] = repoNames
ctx . Data [ "ReposMap" ] = repos
} else {
var err error
var count64 int64
ctxUser . Repos , count64 , err = models . GetUserRepositories ( & models . SearchRepoOptions { Actor : ctxUser , Private : true , ListOptions : opts } )
2018-05-17 07:05:00 +03:00
2020-09-25 07:09:23 +03:00
if err != nil {
ctx . ServerError ( "GetRepositories" , err )
return
}
count = int ( count64 )
repos := ctxUser . Repos
for i := range repos {
if repos [ i ] . IsFork {
if err := repos [ i ] . GetBaseRepo ( ) ; err != nil {
ctx . ServerError ( "GetBaseRepo" , err )
return
}
}
}
ctx . Data [ "Repos" ] = repos
}
ctx . Data [ "Owner" ] = ctxUser
pager := context . NewPagination ( int ( count ) , opts . PageSize , opts . Page , 5 )
pager . SetDefaultParams ( ctx )
ctx . Data [ "Page" ] = pager
2021-04-05 18:30:52 +03:00
ctx . HTML ( http . StatusOK , tplSettingsRepositories )
2018-05-17 07:05:00 +03:00
}