From 6eadf3efb3c460cc450b0c8b67220a5d00125ff7 Mon Sep 17 00:00:00 2001
From: Dan Brown <ssddanbrown@googlemail.com>
Date: Mon, 31 Jan 2022 22:15:21 +0000
Subject: [PATCH] Added language select to the user create form

- Updated user invite to take language from user.
- Added tests to cover.
- Added page/tab title to user create view.

For #2576 and #2408
---
 app/Http/Controllers/UserController.php       | 11 ++++++-
 app/Notifications/UserInvite.php              | 22 ++++++-------
 resources/views/users/create.blade.php        |  1 +
 resources/views/users/edit.blade.php          | 17 +---------
 .../users/parts/language-option-row.blade.php | 18 +++++++++++
 tests/Auth/UserInviteTest.php                 | 32 +++++++++++++++++--
 tests/User/UserManagementTest.php             | 10 ++++++
 7 files changed, 79 insertions(+), 32 deletions(-)
 create mode 100644 resources/views/users/parts/language-option-row.blade.php

diff --git a/app/Http/Controllers/UserController.php b/app/Http/Controllers/UserController.php
index a76105fa6..eebd4cd4f 100644
--- a/app/Http/Controllers/UserController.php
+++ b/app/Http/Controllers/UserController.php
@@ -62,6 +62,7 @@ class UserController extends Controller
         $this->checkPermission('users-manage');
         $authMethod = config('auth.method');
         $roles = $this->userRepo->getAllRoles();
+        $this->setPageTitle(trans('settings.users_add_new'));
 
         return view('users.create', ['authMethod' => $authMethod, 'roles' => $roles]);
     }
@@ -78,6 +79,7 @@ class UserController extends Controller
         $validationRules = [
             'name'  => ['required'],
             'email' => ['required', 'email', 'unique:users,email'],
+            'setting' => ['array'],
         ];
 
         $authMethod = config('auth.method');
@@ -104,6 +106,13 @@ class UserController extends Controller
         DB::transaction(function () use ($user, $sendInvite, $request) {
             $user->save();
 
+            // Save user-specific settings
+            if ($request->filled('setting')) {
+                foreach ($request->get('setting') as $key => $value) {
+                    setting()->putUser($user, $key, $value);
+                }
+            }
+
             if ($sendInvite) {
                 $this->inviteService->sendInvitation($user);
             }
@@ -198,7 +207,7 @@ class UserController extends Controller
             $user->external_auth_id = $request->get('external_auth_id');
         }
 
-        // Save an user-specific settings
+        // Save user-specific settings
         if ($request->filled('setting')) {
             foreach ($request->get('setting') as $key => $value) {
                 setting()->putUser($user, $key, $value);
diff --git a/app/Notifications/UserInvite.php b/app/Notifications/UserInvite.php
index b0dc9afac..3bae32721 100644
--- a/app/Notifications/UserInvite.php
+++ b/app/Notifications/UserInvite.php
@@ -2,35 +2,33 @@
 
 namespace BookStack\Notifications;
 
+use BookStack\Auth\User;
+use Illuminate\Notifications\Messages\MailMessage;
+
 class UserInvite extends MailNotification
 {
     public $token;
 
     /**
      * Create a new notification instance.
-     *
-     * @param string $token
      */
-    public function __construct($token)
+    public function __construct(string $token)
     {
         $this->token = $token;
     }
 
     /**
      * Get the mail representation of the notification.
-     *
-     * @param mixed $notifiable
-     *
-     * @return \Illuminate\Notifications\Messages\MailMessage
      */
-    public function toMail($notifiable)
+    public function toMail(User $notifiable): MailMessage
     {
         $appName = ['appName' => setting('app-name')];
+        $language = setting()->getUser($notifiable, 'language');
 
         return $this->newMailMessage()
-                ->subject(trans('auth.user_invite_email_subject', $appName))
-                ->greeting(trans('auth.user_invite_email_greeting', $appName))
-                ->line(trans('auth.user_invite_email_text'))
-                ->action(trans('auth.user_invite_email_action'), url('/register/invite/' . $this->token));
+                ->subject(trans('auth.user_invite_email_subject', $appName, $language))
+                ->greeting(trans('auth.user_invite_email_greeting', $appName, $language))
+                ->line(trans('auth.user_invite_email_text', [], $language))
+                ->action(trans('auth.user_invite_email_action', [], $language), url('/register/invite/' . $this->token));
     }
 }
diff --git a/resources/views/users/create.blade.php b/resources/views/users/create.blade.php
index 960e38f7c..7015b162a 100644
--- a/resources/views/users/create.blade.php
+++ b/resources/views/users/create.blade.php
@@ -16,6 +16,7 @@
 
                 <div class="setting-list">
                     @include('users.parts.form')
+                    @include('users.parts.language-option-row', ['value' => old('setting.language') ?? config('app.default_locale')])
                 </div>
 
                 <div class="form-group text-right">
diff --git a/resources/views/users/edit.blade.php b/resources/views/users/edit.blade.php
index 997fd1bf0..41e64dbb9 100644
--- a/resources/views/users/edit.blade.php
+++ b/resources/views/users/edit.blade.php
@@ -35,22 +35,7 @@
                         </div>
                     </div>
 
-                    <div class="grid half gap-xl v-center">
-                        <div>
-                            <label for="user-language" class="setting-list-label">{{ trans('settings.users_preferred_language') }}</label>
-                            <p class="small">
-                                {{ trans('settings.users_preferred_language_desc') }}
-                            </p>
-                        </div>
-                        <div>
-                            <select name="setting[language]" id="user-language">
-                                @foreach(trans('settings.language_select') as $lang => $label)
-                                    <option @if(setting()->getUser($user, 'language', config('app.default_locale')) === $lang) selected @endif value="{{ $lang }}">{{ $label }}</option>
-                                @endforeach
-                            </select>
-                        </div>
-                    </div>
-
+                    @include('users.parts.language-option-row', ['value' => setting()->getUser($user, 'language', config('app.default_locale'))])
                 </div>
 
                 <div class="text-right">
diff --git a/resources/views/users/parts/language-option-row.blade.php b/resources/views/users/parts/language-option-row.blade.php
new file mode 100644
index 000000000..82907b53d
--- /dev/null
+++ b/resources/views/users/parts/language-option-row.blade.php
@@ -0,0 +1,18 @@
+{{--
+$value - Currently selected lanuage value
+--}}
+<div class="grid half gap-xl v-center">
+    <div>
+        <label for="user-language" class="setting-list-label">{{ trans('settings.users_preferred_language') }}</label>
+        <p class="small">
+            {{ trans('settings.users_preferred_language_desc') }}
+        </p>
+    </div>
+    <div>
+        <select name="setting[language]" id="user-language">
+            @foreach(trans('settings.language_select') as $lang => $label)
+                <option @if($value === $lang) selected @endif value="{{ $lang }}">{{ $label }}</option>
+            @endforeach
+        </select>
+    </div>
+</div>
\ No newline at end of file
diff --git a/tests/Auth/UserInviteTest.php b/tests/Auth/UserInviteTest.php
index 1e1235f33..5b9b14e60 100644
--- a/tests/Auth/UserInviteTest.php
+++ b/tests/Auth/UserInviteTest.php
@@ -6,6 +6,7 @@ use BookStack\Auth\Access\UserInviteService;
 use BookStack\Auth\User;
 use BookStack\Notifications\UserInvite;
 use Carbon\Carbon;
+use Illuminate\Notifications\Messages\MailMessage;
 use Illuminate\Support\Facades\DB;
 use Illuminate\Support\Facades\Notification;
 use Illuminate\Support\Str;
@@ -20,8 +21,8 @@ class UserInviteTest extends TestCase
 
         $email = Str::random(16) . '@example.com';
         $resp = $this->actingAs($admin)->post('/settings/users/create', [
-            'name'        => 'Barry',
-            'email'       => $email,
+            'name' => 'Barry',
+            'email' => $email,
             'send_invite' => 'true',
         ]);
         $resp->assertRedirect('/settings/users');
@@ -34,6 +35,31 @@ class UserInviteTest extends TestCase
         ]);
     }
 
+    public function test_user_invite_sent_in_selected_language()
+    {
+        Notification::fake();
+        $admin = $this->getAdmin();
+
+        $email = Str::random(16) . '@example.com';
+        $resp = $this->actingAs($admin)->post('/settings/users/create', [
+            'name' => 'Barry',
+            'email' => $email,
+            'send_invite' => 'true',
+            'setting' => [
+                'language' => 'de',
+            ]
+        ]);
+        $resp->assertRedirect('/settings/users');
+
+        $newUser = User::query()->where('email', '=', $email)->orderBy('id', 'desc')->first();
+        Notification::assertSentTo($newUser, UserInvite::class, function ($notification, $channels, $notifiable) {
+            /** @var MailMessage $mail */
+            $mail = $notification->toMail($notifiable);
+            return 'Du wurdest eingeladen BookStack beizutreten!' === $mail->subject &&
+                'Ein Konto wurde für Sie auf BookStack erstellt.' === $mail->greeting;
+        });
+    }
+
     public function test_invite_set_password()
     {
         Notification::fake();
@@ -54,7 +80,7 @@ class UserInviteTest extends TestCase
         ]);
         $setPasswordResp->assertSee('Password set, you should now be able to login using your set password to access BookStack!');
         $newPasswordValid = auth()->validate([
-            'email'    => $user->email,
+            'email' => $user->email,
             'password' => 'my test password',
         ]);
         $this->assertTrue($newPasswordValid);
diff --git a/tests/User/UserManagementTest.php b/tests/User/UserManagementTest.php
index 806e35ad4..2fbbee7e2 100644
--- a/tests/User/UserManagementTest.php
+++ b/tests/User/UserManagementTest.php
@@ -183,6 +183,16 @@ class UserManagementTest extends TestCase
         $resp->assertSee('cannot delete the guest user');
     }
 
+    public function test_user_create_language_reflects_default_system_locale()
+    {
+        $langs = ['en', 'fr', 'hr'];
+        foreach ($langs as $lang) {
+            config()->set('app.locale', $lang);
+            $resp = $this->asAdmin()->get('/settings/users/create');
+            $resp->assertElementExists('select[name="setting[language]"] option[value="' . $lang . '"][selected]');
+        }
+    }
+
     public function test_user_creation_is_not_performed_if_the_invitation_sending_fails()
     {
         /** @var User $user */