diff --git a/app/Users/Controllers/UserAccountController.php b/app/Users/Controllers/UserAccountController.php index 3dd13b851..83e942b04 100644 --- a/app/Users/Controllers/UserAccountController.php +++ b/app/Users/Controllers/UserAccountController.php @@ -7,6 +7,7 @@ use BookStack\Http\Controller; use BookStack\Permissions\PermissionApplicator; use BookStack\Settings\UserNotificationPreferences; use BookStack\Settings\UserShortcutMap; +use BookStack\Uploads\ImageRepo; use BookStack\Users\UserRepo; use Closure; use Illuminate\Http\Request; @@ -19,6 +20,7 @@ class UserAccountController extends Controller ) { $this->middleware(function (Request $request, Closure $next) { $this->preventGuestAccess(); + $this->preventAccessInDemoMode(); return $next($request); }); } @@ -35,6 +37,51 @@ class UserAccountController extends Controller ]); } + /** + * Show the profile form interface. + */ + public function showProfile() + { + return view('users.account.profile', [ + 'model' => user(), + 'category' => 'profile', + ]); + } + + /** + * Handle the submission of the user profile form. + */ + public function updateProfile(Request $request, ImageRepo $imageRepo) + { + $user = user(); + $validated = $this->validate($request, [ + 'name' => ['min:2', 'max:100'], + 'email' => ['min:2', 'email', 'unique:users,email,' . $user->id], + 'language' => ['string', 'max:15', 'alpha_dash'], + 'profile_image' => array_merge(['nullable'], $this->getImageValidationRules()), + ]); + + $this->userRepo->update($user, $validated, userCan('users-manage')); + + // Save profile image if in request + if ($request->hasFile('profile_image')) { + $imageUpload = $request->file('profile_image'); + $imageRepo->destroyImage($user->avatar); + $image = $imageRepo->saveNew($imageUpload, 'user', $user->id); + $user->image_id = $image->id; + $user->save(); + } + + // Delete the profile image if reset option is in request + if ($request->has('profile_image_reset')) { + $imageRepo->destroyImage($user->avatar); + $user->image_id = 0; + $user->save(); + } + + return redirect('/my-account/profile'); + } + /** * Show the user-specific interface shortcuts. */ diff --git a/lang/en/preferences.php b/lang/en/preferences.php index d112b9ebb..7774db570 100644 --- a/lang/en/preferences.php +++ b/lang/en/preferences.php @@ -35,5 +35,5 @@ return [ 'auth_change_password_success' => 'Password has been updated!', 'profile' => 'Profile Details', - 'profile_overview_desc' => ' Manage your user profile details including preferred language and authentication options.', + 'profile_overview_desc' => 'Manage your user profile details including preferred language and authentication options.', ]; diff --git a/resources/views/users/account/profile.blade.php b/resources/views/users/account/profile.blade.php new file mode 100644 index 000000000..4256df109 --- /dev/null +++ b/resources/views/users/account/profile.blade.php @@ -0,0 +1,107 @@ +@extends('users.account.layout') + +@section('main') + + <section class="card content-wrap auto-height"> + <form action="{{ url('/my-account/profile') }}" method="post" enctype="multipart/form-data"> + {{ method_field('put') }} + {{ csrf_field() }} + + <div class="flex-container-row gap-l items-center wrap justify-space-between"> + <h1 class="list-heading">{{ trans('preferences.profile') }}</h1> + <div> + <a href="{{ user()->getProfileUrl() }}" class="button outline">View Public Profile</a> + </div> + </div> + + <p class="text-muted text-small mb-none"> + Manage the details of your account that represent you to other users, in addition to + details that are used for communication and system personalisation. + </p> + + <div class="setting-list"> + + <div class="flex-container-row gap-l items-center wrap"> + <div class="flex"> + <label class="setting-list-label" for="name">{{ trans('auth.name') }}</label> + <p class="text-small mb-none"> + Configure your display name which will be visible to other users in the system + within the activity you perform, and content you own. + </p> + </div> + <div class="flex stretch-inputs"> + @include('form.text', ['name' => 'name']) + </div> + </div> + + <div> + <div class="flex-container-row gap-l items-center wrap"> + <div class="flex"> + <label class="setting-list-label" for="email">{{ trans('auth.email') }}</label> + <p class="text-small mb-none"> + This email will be used for notifications and, depending on active system authentication, system access. + </p> + </div> + <div class="flex stretch-inputs"> + @include('form.text', ['name' => 'email', 'disabled' => !userCan('users-manage')]) + </div> + </div> + @if(!userCan('users-manage')) + <p class="text-small text-muted"> + Unfortunately you don't have permission to change your email address. + If you want to change this, you'd need to ask an administrator to change this for you. + </p> + @endif + </div> + + <div class="grid half gap-xl"> + <div> + <label for="user-avatar" + class="setting-list-label">{{ trans('settings.users_avatar') }}</label> + <p class="text-small"> + Select an image which will be used to represent yourself to others + in the system. Ideally this image should be square and about 256px in width and height. + </p> + </div> + <div> + @include('form.image-picker', [ + 'resizeHeight' => '512', + 'resizeWidth' => '512', + 'showRemove' => false, + 'defaultImage' => url('/user_avatar.png'), + 'currentImage' => user()->getAvatar(80), + 'currentId' => user()->image_id, + 'name' => 'profile_image', + 'imageClass' => 'avatar large' + ]) + </div> + </div> + + @include('users.parts.language-option-row', ['value' => old('language') ?? user()->getLocale()->appLocale()]) + + </div> + + <div class="form-group text-right"> + <button class="button">{{ trans('common.save') }}</button> + </div> + + </form> + </section> + + @if(userCan('users-manage')) + <section class="card content-wrap auto-height"> + <div class="flex-container-row gap-l items-center wrap"> + <div class="flex"> + <h2 class="list-heading">Administrator Options</h2> + <p class="text-small"> + Additional administrator-level options, like role options, can be found for your user account in the + <nobr>"Settings > Users"</nobr> area of the application. + </p> + </div> + <div class="text-m-right"> + <a class="button outline" href="{{ user()->getEditUrl() }}">Open</a> + </div> + </div> + </section> + @endif +@stop diff --git a/routes/web.php b/routes/web.php index a7d3534bd..3f1bcc07e 100644 --- a/routes/web.php +++ b/routes/web.php @@ -234,6 +234,8 @@ Route::middleware('auth')->group(function () { // User Account Route::get('/my-account', [UserControllers\UserAccountController::class, 'index']); + Route::get('/my-account/profile', [UserControllers\UserAccountController::class, 'showProfile']); + Route::put('/my-account/profile', [UserControllers\UserAccountController::class, 'updateProfile']); Route::get('/my-account/shortcuts', [UserControllers\UserAccountController::class, 'showShortcuts']); Route::put('/my-account/shortcuts', [UserControllers\UserAccountController::class, 'updateShortcuts']); Route::get('/my-account/notifications', [UserControllers\UserAccountController::class, 'showNotifications']);