diff --git a/changelog/60_3.0.0_2020-12-22.md b/changelog/60_3.0.0_2020-12-22.md index e74d3993..286847e6 100644 --- a/changelog/60_3.0.0_2020-12-22.md +++ b/changelog/60_3.0.0_2020-12-22.md @@ -156,6 +156,7 @@ - Table states (visible columns, sorting, column order and so on) are now saved server side (in user settings) means that this stays the same when using different browsers - Dialogs are now used everywhere where appropriate instead of jumping between pages (for example when adding/editing shopping list items) - Added a "Clear filter"-button on all pages (with filters) to quickly reset applied filters +- Users can now have a picture (will then be shown next to the current user name instead of the generic user icon) - Prefilled number inputs now use sensible decimal places (max. the configured decimals while hiding trailing zeros where appropriate, means if you never use partial amounts for a product, you'll never see decimals for it) - Improved / more precise validation messages for number inputs - Ordering now happens case-insensitive diff --git a/controllers/UsersApiController.php b/controllers/UsersApiController.php index d8341c31..b14a84f4 100644 --- a/controllers/UsersApiController.php +++ b/controllers/UsersApiController.php @@ -41,7 +41,7 @@ class UsersApiController extends BaseApiController throw new \Exception('Request body could not be parsed (probably invalid JSON format or missing/wrong Content-Type header)'); } - $this->getUsersService()->CreateUser($requestBody['username'], $requestBody['first_name'], $requestBody['last_name'], $requestBody['password']); + $this->getUsersService()->CreateUser($requestBody['username'], $requestBody['first_name'], $requestBody['last_name'], $requestBody['password'], $requestBody['picture_file_name']); return $this->EmptyApiResponse($response); } catch (\Exception $ex) @@ -79,7 +79,7 @@ class UsersApiController extends BaseApiController try { - $this->getUsersService()->EditUser($args['userId'], $requestBody['username'], $requestBody['first_name'], $requestBody['last_name'], $requestBody['password']); + $this->getUsersService()->EditUser($args['userId'], $requestBody['username'], $requestBody['first_name'], $requestBody['last_name'], $requestBody['password'], $requestBody['picture_file_name']); return $this->EmptyApiResponse($response); } catch (\Exception $ex) diff --git a/controllers/UsersController.php b/controllers/UsersController.php index 5fccd270..a27f64c3 100644 --- a/controllers/UsersController.php +++ b/controllers/UsersController.php @@ -22,7 +22,8 @@ class UsersController extends BaseController { User::checkPermission($request, User::PERMISSION_USERS_CREATE); return $this->renderPage($response, 'userform', [ - 'mode' => 'create' + 'mode' => 'create', + 'userfields' => $this->getUserfieldsService()->GetFields('users') ]); } else @@ -38,7 +39,9 @@ class UsersController extends BaseController return $this->renderPage($response, 'userform', [ 'user' => $this->getDatabase()->users($args['userId']), - 'mode' => 'edit' + 'mode' => 'edit', + 'userfields' => $this->getUserfieldsService()->GetFields('users'), + 'userfieldValues' => $this->getUserfieldsService()->GetAllValues('users') ]); } } diff --git a/grocy.openapi.json b/grocy.openapi.json index bdaf3a7f..f818dc0d 100644 --- a/grocy.openapi.json +++ b/grocy.openapi.json @@ -4606,6 +4606,9 @@ "password": { "type": "string" }, + "picture_file_name": { + "type": "string" + }, "row_created_timestamp": { "type": "string", "format": "date-time" @@ -4631,6 +4634,9 @@ "display_name": { "type": "string" }, + "picture_file_name": { + "type": "string" + }, "row_created_timestamp": { "type": "string", "format": "date-time" diff --git a/localization/strings.pot b/localization/strings.pot index c8910f18..be5f8074 100644 --- a/localization/strings.pot +++ b/localization/strings.pot @@ -742,9 +742,6 @@ msgstr "" msgid "Delete" msgstr "" -msgid "The current picture will be deleted when you save the product" -msgstr "" - msgid "Select file" msgstr "" @@ -958,7 +955,7 @@ msgstr "" msgid "Gallery" msgstr "" -msgid "The current picture will be deleted when you save the recipe" +msgid "The current picture will be deleted on save" msgstr "" msgid "Journal for this battery" diff --git a/middleware/AuthMiddleware.php b/middleware/AuthMiddleware.php index 922a629b..e0d1f2f1 100644 --- a/middleware/AuthMiddleware.php +++ b/middleware/AuthMiddleware.php @@ -43,6 +43,7 @@ abstract class AuthMiddleware extends BaseMiddleware define('GROCY_AUTHENTICATED', true); define('GROCY_USER_USERNAME', $user->username); + define('GROCY_USER_PICTURE_FILE_NAME', $user->picture_file_name); return $handler->handle($request); } @@ -70,6 +71,7 @@ abstract class AuthMiddleware extends BaseMiddleware define('GROCY_AUTHENTICATED', true); define('GROCY_USER_ID', $user->id); define('GROCY_USER_USERNAME', $user->username); + define('GROCY_USER_PICTURE_FILE_NAME', $user->picture_file_name); return $response = $handler->handle($request); } diff --git a/migrations/0125.sql b/migrations/0125.sql new file mode 100644 index 00000000..2d9e5ff2 --- /dev/null +++ b/migrations/0125.sql @@ -0,0 +1,21 @@ +ALTER TABLE users +ADD picture_file_name TEXT; + +DROP VIEW users_dto; +CREATE VIEW users_dto +AS +SELECT + id, + username, + first_name, + last_name, + row_created_timestamp, + (CASE + WHEN IFNULL(first_name, '') = '' AND IFNULL(last_name, '') != '' THEN last_name + WHEN IFNULL(last_name, '') = '' AND IFNULL(first_name, '') != '' THEN first_name + WHEN IFNULL(last_name, '') != '' AND IFNULL(first_name, '') != '' THEN first_name || ' ' || last_name + ELSE username + END + ) AS display_name, + picture_file_name +FROM users; diff --git a/public/viewjs/recipeform.js b/public/viewjs/recipeform.js index 4833dac5..f87145f7 100644 --- a/public/viewjs/recipeform.js +++ b/public/viewjs/recipeform.js @@ -379,36 +379,3 @@ $(window).on("message", function(e) ); } }); - -// Grocy.Components.RecipePicker.GetPicker().on('change', function (e) -// { -// var value = Grocy.Components.RecipePicker.GetValue(); -// if (value.toString().isEmpty()) -// { -// return; -// } - -// Grocy.Api.Get('objects/recipes/' + value, -// function(recipe) -// { -// $("#includes_servings").val(recipe.servings); -// }, -// function(xhr) -// { -// console.error(xhr); -// } -// ); -// }); - -// Grocy.Components.ProductPicker.GetPicker().on('change', function(e) -// { -// // Just save the current recipe on every change of the product picker as a workflow could be started which leaves the page... -// Grocy.Api.Put('objects/recipes/' + Grocy.EditObjectId, $('#recipe-form').serializeJSON(), function () { }, function () { }); -// }); - -// As the /recipe/new route immediately creates a new recipe on load, -// always replace the current location by the created recipes edit page location -// if (window.location.pathname.toLowerCase() === "/recipe/new") -// { -// window.history.replaceState(null, null, U("/recipe/" + Grocy.EditObjectId)); -// } diff --git a/public/viewjs/userform.js b/public/viewjs/userform.js index 65818d01..1b2f857f 100644 --- a/public/viewjs/userform.js +++ b/public/viewjs/userform.js @@ -1,17 +1,46 @@ -$('#save-user-button').on('click', function(e) +function SaveUserPicture(result, jsonData) +{ + var userId = Grocy.EditObjectId || result.created_object_id; + Grocy.Components.UserfieldsForm.Save(() => + { + if (jsonData.hasOwnProperty("picture_file_name") && !Grocy.DeleteUserPictureOnSave) + { + Grocy.Api.UploadFile($("#user-picture")[0].files[0], 'userpictures', jsonData.picture_file_name, + (result) => + { + window.location.href = U('/users'); + }, + (xhr) => + { + Grocy.FrontendHelpers.EndUiBusy("user-form"); + Grocy.FrontendHelpers.ShowGenericError('Error while saving, probably this item already exists', xhr.response) + } + ); + } + else + { + window.location.href = U('/users'); + } + }); +} + +$('#save-user-button').on('click', function(e) { e.preventDefault(); var jsonData = $('#user-form').serializeJSON(); Grocy.FrontendHelpers.BeginUiBusy("user-form"); + if ($("#user-picture")[0].files.length > 0) + { + var someRandomStuff = Math.random().toString(36).substring(2, 100) + Math.random().toString(36).substring(2, 100); + jsonData.picture_file_name = someRandomStuff + $("#user-picture")[0].files[0].name; + } + if (Grocy.EditMode === 'create') { Grocy.Api.Post('users', jsonData, - function(result) - { - window.location.href = U('/users'); - }, + (result) => SaveUserPicture(result, jsonData), function(xhr) { Grocy.FrontendHelpers.EndUiBusy("user-form"); @@ -21,11 +50,25 @@ } else { + if (Grocy.DeleteUserPictureOnSave) + { + jsonData.picture_file_name = null; + + Grocy.Api.DeleteFile(Grocy.UserPictureFileName, 'userpictures', {}, + function(result) + { + // Nothing to do + }, + function(xhr) + { + Grocy.FrontendHelpers.EndUiBusy("user-form"); + Grocy.FrontendHelpers.ShowGenericError('Error while saving, probably this item already exists', xhr.response) + } + ); + } + Grocy.Api.Put('users/' + Grocy.EditObjectId, jsonData, - function(result) - { - window.location.href = U('/users'); - }, + (result) => SaveUserPicture(result, jsonData), function(xhr) { Grocy.FrontendHelpers.EndUiBusy("user-form"); @@ -76,4 +119,23 @@ else $('#username').focus(); } +$("#user-picture").on("change", function(e) +{ + $("#user-picture-label").removeClass("d-none"); + $("#user-picture-label-none").addClass("d-none"); + $("#delete-current-user-picture-on-save-hint").addClass("d-none"); + $("#current-user-picture").addClass("d-none"); + Grocy.DeleteUserePictureOnSave = false; +}); + +Grocy.DeleteUserPictureOnSave = false; +$("#delete-current-user-picture-button").on("click", function(e) +{ + Grocy.DeleteUserPictureOnSave = true; + $("#current-user-picture").addClass("d-none"); + $("#delete-current-user-picture-on-save-hint").removeClass("d-none"); + $("#user-picture-label").addClass("d-none"); + $("#user-picture-label-none").removeClass("d-none"); +}); + Grocy.FrontendHelpers.ValidateForm('user-form'); diff --git a/services/UserfieldsService.php b/services/UserfieldsService.php index 1cdce8d9..acdfee45 100644 --- a/services/UserfieldsService.php +++ b/services/UserfieldsService.php @@ -37,15 +37,15 @@ class UserfieldsService extends BaseService public function GetEntities() { $exposedDefaultEntities = $this->getOpenApiSpec()->components->internalSchemas->ExposedEntity->enum; - - $userentities = []; + $userEntities = []; + $specialEntities = ['users']; foreach ($this->getDatabase()->userentities()->orderBy('name', 'COLLATE NOCASE') as $userentity) { - $userentities[] = 'userentity-' . $userentity->name; + $userEntities[] = 'userentity-' . $userentity->name; } - return array_merge($exposedDefaultEntities, $userentities); + return array_merge($exposedDefaultEntities, $userEntities, $specialEntities); } public function GetField($fieldId) diff --git a/services/UsersService.php b/services/UsersService.php index 3c5e822d..6738ee9b 100644 --- a/services/UsersService.php +++ b/services/UsersService.php @@ -4,13 +4,14 @@ namespace Grocy\Services; class UsersService extends BaseService { - public function CreateUser(string $username, string $firstName, string $lastName, string $password) + public function CreateUser(string $username, string $firstName, string $lastName, string $password, string $pictureFileName = null) { $newUserRow = $this->getDatabase()->users()->createRow([ 'username' => $username, 'first_name' => $firstName, 'last_name' => $lastName, - 'password' => password_hash($password, PASSWORD_DEFAULT) + 'password' => password_hash($password, PASSWORD_DEFAULT), + 'picture_file_name' => $pictureFileName ]); $newUserRow = $newUserRow->save(); $permList = []; @@ -34,7 +35,7 @@ class UsersService extends BaseService $row->delete(); } - public function EditUser(int $userId, string $username, string $firstName, string $lastName, string $password) + public function EditUser(int $userId, string $username, string $firstName, string $lastName, string $password, string $pictureFileName = null) { if (!$this->UserExists($userId)) { @@ -46,7 +47,8 @@ class UsersService extends BaseService 'username' => $username, 'first_name' => $firstName, 'last_name' => $lastName, - 'password' => password_hash($password, PASSWORD_DEFAULT) + 'password' => password_hash($password, PASSWORD_DEFAULT), + 'picture_file_name' => $pictureFileName ]); } diff --git a/views/layout/default.blade.php b/views/layout/default.blade.php index d03c72eb..0382efbd 100644 --- a/views/layout/default.blade.php +++ b/views/layout/default.blade.php @@ -478,7 +478,15 @@