mirror of
https://github.com/grocy/grocy.git
synced 2025-04-29 01:32:38 +00:00
Implemented user pictures (closes #1158)
This commit is contained in:
parent
3f718eab60
commit
8f1ce607f7
@ -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
|
||||
|
@ -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)
|
||||
|
@ -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')
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
@ -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"
|
||||
|
@ -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"
|
||||
|
@ -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);
|
||||
}
|
||||
|
21
migrations/0125.sql
Normal file
21
migrations/0125.sql
Normal file
@ -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;
|
@ -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));
|
||||
// }
|
||||
|
@ -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,12 +50,26 @@
|
||||
}
|
||||
else
|
||||
{
|
||||
Grocy.Api.Put('users/' + Grocy.EditObjectId, jsonData,
|
||||
if (Grocy.DeleteUserPictureOnSave)
|
||||
{
|
||||
jsonData.picture_file_name = null;
|
||||
|
||||
Grocy.Api.DeleteFile(Grocy.UserPictureFileName, 'userpictures', {},
|
||||
function(result)
|
||||
{
|
||||
window.location.href = U('/users');
|
||||
// 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,
|
||||
(result) => SaveUserPicture(result, jsonData),
|
||||
function(xhr)
|
||||
{
|
||||
Grocy.FrontendHelpers.EndUiBusy("user-form");
|
||||
console.error(xhr);
|
||||
@ -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');
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
]);
|
||||
}
|
||||
|
||||
|
@ -478,7 +478,15 @@
|
||||
<li class="nav-item dropdown">
|
||||
<a class="nav-link dropdown-toggle discrete-link"
|
||||
href="#"
|
||||
data-toggle="dropdown"><i class="fas fa-user"></i> {{ GROCY_USER_USERNAME }}</a>
|
||||
data-toggle="dropdown">
|
||||
@if(empty(GROCY_USER_PICTURE_FILE_NAME))
|
||||
<i class="fas fa-user"></i>
|
||||
@else
|
||||
<img class="lazy rounded-circle mt-n1"
|
||||
src="{{ $U('/files/userpictures/' . base64_encode(GROCY_USER_PICTURE_FILE_NAME) . '_' . base64_encode(GROCY_USER_PICTURE_FILE_NAME) . '?force_serve_as=picture&best_fit_width=16&best_fit_height=16') }}">
|
||||
@endif
|
||||
{{ GROCY_USER_USERNAME }}
|
||||
</a>
|
||||
|
||||
<div class="dropdown-menu dropdown-menu-right">
|
||||
<a class="dropdown-item logout-button discrete-link"
|
||||
|
@ -661,7 +661,7 @@
|
||||
data-src="{{ $U('/api/files/productpictures/' . base64_encode($product->picture_file_name) . '?force_serve_as=picture&best_fit_width=400') }}"
|
||||
class="img-fluid img-thumbnail mt-2 lazy mb-5">
|
||||
<p id="delete-current-product-picture-on-save-hint"
|
||||
class="form-text text-muted font-italic d-none mb-5">{{ $__t('The current picture will be deleted when you save the product') }}</p>
|
||||
class="form-text text-muted font-italic d-none mb-5">{{ $__t('The current picture will be deleted on save') }}</p>
|
||||
@else
|
||||
<p id="no-current-product-picture-hint"
|
||||
class="form-text text-muted font-italic mb-5">{{ $__t('No picture available') }}</p>
|
||||
|
@ -319,7 +319,7 @@
|
||||
data-src="{{ $U('/api/files/recipepictures/' . base64_encode($recipe->picture_file_name) . '?force_serve_as=picture&best_fit_width=400') }}"
|
||||
class="img-fluid img-thumbnail mt-2 lazy mb-5">
|
||||
<p id="delete-current-recipe-picture-on-save-hint"
|
||||
class="form-text text-muted font-italic d-none mb-5">{{ $__t('The current picture will be deleted when you save the recipe') }}</p>
|
||||
class="form-text text-muted font-italic d-none mb-5">{{ $__t('The current picture will be deleted on save') }}</p>
|
||||
@else
|
||||
<p id="no-current-recipe-picture-hint"
|
||||
class="form-text text-muted font-italic mb-5">{{ $__t('No picture available') }}</p>
|
||||
|
@ -26,6 +26,10 @@
|
||||
@if($mode == 'edit')
|
||||
<script>
|
||||
Grocy.EditObjectId = {{ $user->id }};
|
||||
|
||||
@if(!empty($user->picture_file_name))
|
||||
Grocy.UserPictureFileName = '{{ $user->picture_file_name }}';
|
||||
@endif
|
||||
</script>
|
||||
@endif
|
||||
|
||||
@ -80,10 +84,57 @@
|
||||
<div class="invalid-feedback">{{ $__t('Passwords do not match') }}</div>
|
||||
</div>
|
||||
|
||||
@include('components.userfieldsform', array(
|
||||
'userfields' => $userfields,
|
||||
'entity' => 'users'
|
||||
))
|
||||
|
||||
<button id="save-user-button"
|
||||
class="btn btn-success">{{ $__t('Save') }}</button>
|
||||
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div class="col-lg-6 col-xs-12">
|
||||
<div class="title-related-links">
|
||||
<h4>
|
||||
{{ $__t('Picture') }}
|
||||
</h4>
|
||||
<div class="form-group w-75 m-0">
|
||||
<div class="input-group">
|
||||
<div class="custom-file">
|
||||
<input type="file"
|
||||
class="custom-file-input"
|
||||
id="user-picture"
|
||||
accept="image/*">
|
||||
<label id="user-picture-label"
|
||||
class="custom-file-label @if(empty($user->picture_file_name)) d-none @endif"
|
||||
for="user-picture">
|
||||
{{ $user->picture_file_name }}
|
||||
</label>
|
||||
<label id="user-picture-label-none"
|
||||
class="custom-file-label @if(!empty($user->picture_file_name)) d-none @endif"
|
||||
for="user-picture">
|
||||
{{ $__t('No file selected') }}
|
||||
</label>
|
||||
</div>
|
||||
<div class="input-group-append">
|
||||
<span class="input-group-text"><i class="fas fa-trash"
|
||||
id="delete-current-user-picture-button"></i></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@if(!empty($user->picture_file_name))
|
||||
<img id="current-user-picture"
|
||||
data-src="{{ $U('/api/files/userpictures/' . base64_encode($user->picture_file_name) . '?force_serve_as=picture&best_fit_width=400') }}"
|
||||
class="img-fluid img-thumbnail mt-2 lazy mb-5">
|
||||
<p id="delete-current-user-picture-on-save-hint"
|
||||
class="form-text text-muted font-italic d-none mb-5">{{ $__t('The current picture will be deleted on save') }}</p>
|
||||
@else
|
||||
<p id="no-current-user-picture-hint"
|
||||
class="form-text text-muted font-italic mb-5">{{ $__t('No picture available') }}</p>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
@stop
|
||||
|
Loading…
x
Reference in New Issue
Block a user