Merge branch 'feature/99-user-settings-page' of aheathershaw/blue-twilight into master

This commit is contained in:
Andy Heathershaw 2018-09-12 21:25:02 +01:00 committed by Gitea
commit 4b8cd6e5ab
15 changed files with 383 additions and 8 deletions

View File

@ -7,14 +7,115 @@ use App\Facade\Theme;
use App\Facade\UserConfig; use App\Facade\UserConfig;
use App\Helpers\DbHelper; use App\Helpers\DbHelper;
use App\Http\Controllers\Controller; use App\Http\Controllers\Controller;
use App\Http\Requests\SaveUserSettingsRequest;
use App\Mail\UserChangeEmailRequired;
use App\User; use App\User;
use Illuminate\Support\Collection; use Illuminate\Support\Collection;
use Illuminate\Support\Facades\App; use Illuminate\Support\Facades\App;
use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Mail;
use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Request;
class UserController extends Controller class UserController extends Controller
{ {
public function confirmEmailChangeState(Request $request)
{
$user = $this->getUser();
if (!$user->is_email_change_in_progress)
{
return redirect(route('userSettings'));
}
// Update the e-mail address
$user->email = $user->new_email_address;
// Reset the e-mail change state
$user->is_email_change_in_progress = false;
$user->new_email_address = null;
$user->save();
$request->session()->flash('success', trans('auth.change_email_success_message'));
return redirect(route('userSettings'));
}
public function resetEmailChangeState(Request $request)
{
$user = $this->getUser();
if (!$user->is_email_change_in_progress)
{
return redirect(route('userSettings'));
}
$data = $request->all();
if (isset($data['resend_email']))
{
$this->sendEmailChangeConfirmationEmail($user, $user->new_email_address);
$request->session()->flash('info', trans('auth.change_email_required_message'));
}
if (isset($data['cancel_change']))
{
$user->is_email_change_in_progress = false;
$user->new_email_address = null;
$user->save();
}
return redirect(route('userSettings'));
}
public function saveSettings(SaveUserSettingsRequest $request)
{
$data = $request->only(['name', 'email', 'profile_alias', 'enable_profile_page']);
$user = $this->getUser();
if (
UserConfig::get('require_email_verification') &&
isset($data['email']) &&
$data['email'] != $user->email &&
!$user->is_email_change_in_progress
)
{
// Can't update the e-mail directly until the new e-mail address has been verified.
// TODO - send e-mail and handle response, flag e-mail as being "change in-progress"
// Send activation e-mail
$this->sendEmailChangeConfirmationEmail($user, $data['email']);
$request->session()->flash('info', trans('auth.change_email_required_message'));
// Flag the user as a change e-mail in progress
$user->new_email_address = $data['email'];
$user->is_email_change_in_progress = true;
$user->save();
unset($data['email']);
$request->session()->flash('info', trans('auth.change_email_required_message'));
}
// Don't allow e-mail address to be changed if a change is in progress
if ($user->is_email_change_in_progress)
{
unset($data['email']);
}
$user->fill($data);
$user->enable_profile_page = (isset($data['enable_profile_page']) && strtolower($data['enable_profile_page']) == 'on');
$user->save();
$request->session()->flash('success', trans('gallery.user_settings.settings_saved'));
return redirect(route('userSettings'));
}
public function settings(Request $request)
{
return Theme::render('gallery.user_settings', [
'info' => $request->session()->get('info'),
'success' => $request->session()->get('success'),
'user' => $this->getUser()
]);
}
public function show(Request $request, $idOrAlias) public function show(Request $request, $idOrAlias)
{ {
// If a user has a profile alias set, their profile page cannot be accessed by the ID // If a user has a profile alias set, their profile page cannot be accessed by the ID
@ -198,4 +299,13 @@ class UserController extends Controller
return $results; return $results;
} }
private function sendEmailChangeConfirmationEmail(User $user, $newEmailAddress)
{
$oldEmailAddress = $user->email;
$user->email = $newEmailAddress;
Mail::to($user)->send(new UserChangeEmailRequired($user));
$user->email = $oldEmailAddress;
}
} }

View File

@ -0,0 +1,33 @@
<?php
namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Support\Facades\Auth;
class SaveUserSettingsRequest extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*
* @return bool
*/
public function authorize()
{
return true;
}
/**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules()
{
return [
'name' => 'required|max:255',
'email' => 'required|email|max:255|unique:users,email,' . Auth::user()->id,
'profile_alias' => 'sometimes|max:255|unique:users,profile_alias,' . Auth::user()->id
];
}
}

View File

@ -0,0 +1,46 @@
<?php
namespace App\Mail;
use App\Facade\Theme;
use App\Facade\UserConfig;
use App\User;
use Illuminate\Bus\Queueable;
use Illuminate\Mail\Mailable;
use Illuminate\Queue\SerializesModels;
use Illuminate\Contracts\Queue\ShouldQueue;
class UserChangeEmailRequired extends Mailable
{
use Queueable, SerializesModels;
private $user;
/**
* Create a new message instance.
*
* @return void
*/
public function __construct(User $user)
{
$this->user = $user;
}
/**
* Build the message.
*
* @return $this
*/
public function build()
{
$subject = trans('email.change_email_required_subject', ['app_name' => UserConfig::get('app_name')]);
return $this
->subject($subject)
->markdown(Theme::viewName('email.user_change_email_required'))
->with([
'subject' => $subject,
'user' => $this->user
]);
}
}

View File

@ -0,0 +1,36 @@
<?php
use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
class AddUserEmailChangeColumns extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::table('users', function (Blueprint $table)
{
$table->boolean('is_email_change_in_progress')->default(false);
$table->string('new_email_address')->nullable(true);
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::table('users', function (Blueprint $table)
{
$table->dropColumn('is_email_change_in_progress');
$table->dropColumn('new_email_address');
});
}
}

View File

@ -20,6 +20,9 @@ return [
'and password you provided when you registered.', 'and password you provided when you registered.',
'activation_required_message' => 'An e-mail containing an activation link has been sent to the e-mail address you provided. ' . 'activation_required_message' => 'An e-mail containing an activation link has been sent to the e-mail address you provided. ' .
'Please click the link in this e-mail to activate your account.', 'Please click the link in this e-mail to activate your account.',
'change_email_required_message' => 'An e-mail containing an activation link has been sent to the e-mail address you provided. ' .
'Please click the link in this e-mail to confirm your new e-mail address.',
'change_email_success_message' => 'Your e-mail address change has been confirmed. You will now need to login with your new e-mail address.',
'change_password_action' => 'Change password', 'change_password_action' => 'Change password',
'change_password_title' => 'Change your password', 'change_password_title' => 'Change your password',
'email_password_login' => 'Alternatively, login with your e-mail address and password:', 'email_password_login' => 'Alternatively, login with your e-mail address and password:',

View File

@ -4,6 +4,9 @@ return [
'activation_required_p2' => 'To confirm your e-mail address and activate your account, please click the link below. ' . 'activation_required_p2' => 'To confirm your e-mail address and activate your account, please click the link below. ' .
'You may also need to copy + paste this into your browser\'s address bar if your e-mail reader has split this line.', 'You may also need to copy + paste this into your browser\'s address bar if your e-mail reader has split this line.',
'activation_required_subject' => 'Activate your :app_name account', 'activation_required_subject' => 'Activate your :app_name account',
'change_email_required_p1' => 'A change was requested to your e-mail address on :app_name.',
'change_email_required_p2' => 'To confirm this e-mail address is valid and update the e-mail address on your account, please click the button below.',
'change_email_required_subject' => 'Confirm the e-mail change to your :app_name account',
'generic_intro' => 'Hi :user_name,', 'generic_intro' => 'Hi :user_name,',
'generic_regards' => 'Regards,', 'generic_regards' => 'Regards,',
'test_email_subject' => 'Test e-mail from :app_name' 'test_email_subject' => 'Test e-mail from :app_name'

View File

@ -14,6 +14,7 @@ return [
'bulk_edit_photos_placeholder' => 'Select an action', 'bulk_edit_photos_placeholder' => 'Select an action',
'cancel_action' => 'Cancel', 'cancel_action' => 'Cancel',
'close_action' => 'Close', 'close_action' => 'Close',
'confirm_email_action' => 'Confirm e-mail address',
'continue_action' => 'Continue', 'continue_action' => 'Continue',
'create_action' => 'Create', 'create_action' => 'Create',
'create_album_label' => 'Create a new album:', 'create_album_label' => 'Create a new album:',
@ -24,6 +25,7 @@ return [
'download_action' => 'Download', 'download_action' => 'Download',
'edit_action' => 'Edit', 'edit_action' => 'Edit',
'email_label' => 'E-mail address:', 'email_label' => 'E-mail address:',
'enable_profile_page_label' => 'Allow others to see my profile page',
'labels_label' => 'Labels:', 'labels_label' => 'Labels:',
'login_action' => 'Login', 'login_action' => 'Login',
'name_label' => 'Name:', 'name_label' => 'Name:',
@ -33,6 +35,8 @@ return [
'password_confirm_label' => 'Confirm password:', 'password_confirm_label' => 'Confirm password:',
'please_select' => '- Select an Option -', 'please_select' => '- Select an Option -',
'private_album_label' => 'Private album (only visible to me)', 'private_album_label' => 'Private album (only visible to me)',
'profile_alias_label' => 'Alias:',
'profile_alias_help' => 'The profile alias is used to create a unique URL direct to your profile page.',
'quick_upload_file_label' => 'Photo:', 'quick_upload_file_label' => 'Photo:',
'realname_label' => 'Your name:', 'realname_label' => 'Your name:',
'register_action' => 'Create account', 'register_action' => 'Create account',

View File

@ -77,5 +77,13 @@ return [
'cameras' => 'Cameras', 'cameras' => 'Cameras',
'no_albums_p1' => 'No Photo Albums', 'no_albums_p1' => 'No Photo Albums',
'no_albums_p2' => ':user_name has not created any albums yet.' 'no_albums_p2' => ':user_name has not created any albums yet.'
],
'user_settings' => [
'cancel_email_change' => 'Don\'t change e-mail address',
'change_email_in_progress' => 'To confirm your new e-mail address, please click on the &quot;confirm&quot; link in the e-mail that was sent to: <b>:new_email_address</b>.',
'change_email_resend' => 'Re-send confirmation e-mail.',
'change_password' => 'Change password',
'settings_saved' => 'Your settings were updated successfully.',
'title' => 'Change my settings'
] ]
]; ];

View File

@ -36,6 +36,7 @@ return [
'public_profile_page' => 'My public profile', 'public_profile_page' => 'My public profile',
'quick_post' => 'Quick Upload', 'quick_post' => 'Quick Upload',
'register' => 'Register', 'register' => 'Register',
'statistics' => 'Statistics' 'statistics' => 'Statistics',
'user_settings' => 'Change my settings'
] ]
]; ];

View File

@ -0,0 +1,17 @@
@component('mail::message')
@lang('email.generic_intro', ['user_name' => $user->name])
@lang('email.change_email_required_p1', ['app_name' => UserConfig::get('app_name')])
@lang('email.change_email_required_p2')
@component('mail::button', ['url' => route('userSettings.confirmEmailChangeState'), 'color' => 'blue'])
@lang('forms.confirm_email_action')
@endcomponent
@lang('email.generic_regards')<br/>
{{ UserConfig::get('app_name') }}<br/>
<a href="{{ route('home') }}">{{ route('home') }}</a>
@endcomponent

View File

@ -0,0 +1,103 @@
@extends(Theme::viewName('layout'))
@section('title', trans('gallery.user_settings.title'))
@section('content')
<div class="container">
<div class="row">
<div class="col-lg-8 mr-lg-auto ml-lg-auto">
<div class="card">
<div class="card-header">
<ul class="nav nav-tabs card-header-tabs">
<li class="nav-item">
<a class="nav-link active" href="{{ url('/password/change') }}">@yield('title')</a>
</li>
</ul>
</div>
<div class="card-body">
@if ($user->is_email_change_in_progress)
<div class="alert alert-warning mb-4" role="alert">
<p>@lang('gallery.user_settings.change_email_in_progress', ['new_email_address' => $user->new_email_address])</p>
<form action="{{ route('userSettings.resetEmailChangeState') }}" method="post">
{{ csrf_field() }}
<button type="submit" class="btn btn-outline-info" name="resend_email">@lang('gallery.user_settings.change_email_resend')</button>
<button type="submit" class="btn btn-outline-info" name="cancel_change">@lang('gallery.user_settings.cancel_email_change')</button>
</form>
</div>
@endif
<form action="{{ route('saveUserSettings') }}" method="post">
{{ csrf_field() }}
<div class="form-group row{{ $errors->has('name') ? ' has-danger' : '' }}">
<label class="col-md-4 col-form-label text-md-right" for="user-name">@lang('forms.name_label')</label>
<div class="col-md-6">
<input type="text" class="form-control{{ $errors->has('name') ? ' is-invalid' : '' }}" id="user-name" name="name" value="{{ old('name', $user->name) }}">
@if ($errors->has('name'))
<div class="invalid-feedback">
<strong>{{ $errors->first('name') }}</strong>
</div>
@endif
</div>
</div>
<div class="form-group row{{ $errors->has('email') ? ' has-danger' : '' }}">
<label class="col-md-4 col-form-label text-md-right" for="user-email">@lang('forms.email_label')</label>
<div class="col-md-6">
<input type="text" class="form-control{{ $errors->has('email') ? ' is-invalid' : '' }}" id="user-email" name="email" value="{{ old('email', $user->email) }}"{{ $user->is_email_change_in_progress ? ' readonly="readonly"' : '' }}>
@if ($errors->has('email'))
<div class="invalid-feedback">
<strong>{{ $errors->first('email') }}</strong>
</div>
@endif
</div>
</div>
<div class="form-group row">
<div class="col-md-6 offset-md-4">
<a href="{{ route('auth.changePassword') }}" class="btn btn-link"><i class="fa fa-lock"></i>&nbsp;&nbsp;@lang('gallery.user_settings.change_password')</a>
</div>
</div>
<fieldset class="mt-4">
<legend class="ml-md-5">Public profile</legend>
<div class="form-group row{{ $errors->has('profile_alias') ? ' has-danger' : '' }}">
<label class="col-md-4 col-form-label text-md-right" for="profile-alias">@lang('forms.profile_alias_label')</label>
<div class="col-md-6">
<input type="text" class="form-control{{ $errors->has('profile_alias') ? ' is-invalid' : '' }}" id="profile-alias" name="profile_alias" value="{{ old('profile_alias', $user->profile_alias) }}">
<small id="emailHelp" class="form-text text-muted">@lang('forms.profile_alias_help')</small>
@if ($errors->has('profile_alias'))
<div class="invalid-feedback">
<strong>{{ $errors->first('profile_alias') }}</strong>
</div>
@endif
</div>
</div>
<div class="form-group row">
<div class="col-md-6 offset-md-4">
<div class="form-check">
<input type="checkbox" class="form-check-input" id="enable-profile-page" name="enable_profile_page"@if (old('enable_profile_page', $user->enable_profile_page)) checked="checked"@endif>
<label class="form-check-label" for="enable-profile-page">@lang('forms.enable_profile_page_label')</label>
</div>
</div>
</div>
</fieldset>
<div class="text-right mt-5">
<a href="{{ route('home') }}" class="btn btn-link">@lang('forms.cancel_action')</a>
<button type="submit" class="btn btn-success"><i class="fa fa-fw fa-check"></i> @lang('forms.save_action')</button>
</div>
</form>
</div>
</div>
</div>
</div>
</div>
@endsection

View File

@ -2,9 +2,8 @@
@if (UserConfig::isSocialMediaLoginEnabled()) @if (UserConfig::isSocialMediaLoginEnabled())
<p>@lang('auth.social_login')</p> <p>@lang('auth.social_login')</p>
@include(Theme::viewName('partials.social_login_providers')) @include(Theme::viewName('partials.social_login_providers'))
@endif
<p class="mt-5 mb-4">@lang('auth.email_password_login')</p> <p class="mt-5 mb-4">@lang('auth.email_password_login')</p>
@endif
@else @else
<p>@lang('auth.email_password_login_sso')</p> <p>@lang('auth.email_password_login_sso')</p>
<p class="mb-5">@lang('auth.email_password_login_sso_2')</p> <p class="mb-5">@lang('auth.email_password_login_sso_2')</p>

View File

@ -78,8 +78,9 @@
<div class="dropdown-menu dropdown-menu-right"> <div class="dropdown-menu dropdown-menu-right">
@if (UserConfig::get('social_user_profiles') && Auth::user()->enable_profile_page) @if (UserConfig::get('social_user_profiles') && Auth::user()->enable_profile_page)
<a class="dropdown-item" href="{{ Auth::user()->profileUrl() }}">@lang('navigation.navbar.public_profile_page')</a> <a class="dropdown-item" href="{{ Auth::user()->profileUrl() }}">@lang('navigation.navbar.public_profile_page')</a>
<div class="dropdown-divider"></div>
@endif @endif
<a class="dropdown-item" href="{{ route('userSettings') }}">@lang('navigation.navbar.user_settings')</a>
<div class="dropdown-divider"></div>
<a class="dropdown-item" href="{{ route('auth.changePassword') }}">@lang('navigation.navbar.change_password')</a> <a class="dropdown-item" href="{{ route('auth.changePassword') }}">@lang('navigation.navbar.change_password')</a>
<a class="dropdown-item" href="{{ url('/logout') }}" onclick="event.preventDefault(); document.getElementById('logout-form').submit();">@lang('navigation.navbar.logout')</a> <a class="dropdown-item" href="{{ url('/logout') }}" onclick="event.preventDefault(); document.getElementById('logout-form').submit();">@lang('navigation.navbar.logout')</a>
<form id="logout-form" action="{{ url('/logout') }}" method="POST" style="display: none;"> <form id="logout-form" action="{{ url('/logout') }}" method="POST" style="display: none;">

View File

@ -2,9 +2,8 @@
@if (UserConfig::isSocialMediaLoginEnabled()) @if (UserConfig::isSocialMediaLoginEnabled())
<p>@lang('auth.social_register')</p> <p>@lang('auth.social_register')</p>
@include(Theme::viewName('partials.social_login_providers')) @include(Theme::viewName('partials.social_login_providers'))
@endif
<p class="mt-5 mb-4">@lang('auth.email_password_register')</p> <p class="mt-5 mb-4">@lang('auth.email_password_register')</p>
@endif
@else @else
<p class="mb-4">@lang('auth.email_password_register_sso')</p> <p class="mb-4">@lang('auth.email_password_register_sso')</p>
@endif @endif

View File

@ -114,6 +114,18 @@ Route::get('i/{albumUrlAlias}/{photoFilename}', 'Gallery\PhotoController@downloa
Route::get('label/{labelAlias}', 'Gallery\LabelController@show') Route::get('label/{labelAlias}', 'Gallery\LabelController@show')
->name('viewLabel') ->name('viewLabel')
->where('labelAlias', '.*'); ->where('labelAlias', '.*');
Route::get('user/{idOrAlias}', 'Gallery\UserController@show') Route::get('u/{idOrAlias}', 'Gallery\UserController@show')
->name('viewUser') ->name('viewUser')
->where('idOrAlias', '.*'); ->where('idOrAlias', '.*');
Route::get('me/confirm-email-change', 'Gallery\UserController@confirmEmailChangeState')
->name('userSettings.confirmEmailChangeState')
->middleware('auth');
Route::post('me/reset-email-change', 'Gallery\UserController@resetEmailChangeState')
->name('userSettings.resetEmailChangeState')
->middleware('auth');
Route::get('me/settings', 'Gallery\UserController@settings')
->name('userSettings')
->middleware('auth');
Route::post('me/save-settings', 'Gallery\UserController@saveSettings')
->name('saveUserSettings')
->middleware('auth');