Add some basic user management logic.
This commit is contained in:
parent
fca5b423a4
commit
d579d2c8e0
6 changed files with 321 additions and 86 deletions
|
@ -16,8 +16,8 @@
|
|||
box-shadow: 0 1px 5px 0 fade(#555, 80%);
|
||||
}
|
||||
|
||||
&--active,
|
||||
&:active {
|
||||
&:not(&--disabled)&--active,
|
||||
&:not(&--disabled)&:active {
|
||||
background-color: #444;
|
||||
}
|
||||
|
||||
|
@ -48,4 +48,8 @@
|
|||
font-size: 2em;
|
||||
}
|
||||
}
|
||||
|
||||
&__option--disabled &__link {
|
||||
cursor: default;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -47,7 +47,6 @@ switch ($_GET['v'] ?? null) {
|
|||
echo 'no';
|
||||
break;
|
||||
}
|
||||
|
||||
$getUser = $db->prepare('
|
||||
SELECT
|
||||
u.*,
|
||||
|
@ -68,7 +67,75 @@ switch ($_GET['v'] ?? null) {
|
|||
break;
|
||||
}
|
||||
|
||||
$templating->var('view_user', $manageUser);
|
||||
$getHasRoles = $db->prepare('
|
||||
SELECT `role_id`, `role_name`
|
||||
FROM `msz_roles`
|
||||
WHERE `role_id` IN (
|
||||
SELECT `role_id`
|
||||
FROM `msz_user_roles`
|
||||
WHERE `user_id` = :user_id
|
||||
)
|
||||
');
|
||||
$getHasRoles->bindValue('user_id', $manageUser['user_id']);
|
||||
$hasRoles = $getHasRoles->execute() ? $getHasRoles->fetchAll() : [];
|
||||
|
||||
$getAvailableRoles = $db->prepare('
|
||||
SELECT `role_id`, `role_name`
|
||||
FROM `msz_roles`
|
||||
WHERE `role_id` NOT IN (
|
||||
SELECT `role_id`
|
||||
FROM `msz_user_roles`
|
||||
WHERE `user_id` = :user_id
|
||||
)
|
||||
');
|
||||
$getAvailableRoles->bindValue('user_id', $manageUser['user_id']);
|
||||
$availableRoles = $getAvailableRoles->execute() ? $getAvailableRoles->fetchAll() : [];
|
||||
|
||||
if ($isPostRequest) {
|
||||
if (!tmp_csrf_verify($_POST['csrf'] ?? '')) {
|
||||
echo 'csrf err';
|
||||
break;
|
||||
}
|
||||
|
||||
if (isset($_POST['avatar'])) {
|
||||
switch ($_POST['avatar']['mode'] ?? '') {
|
||||
case 'delete':
|
||||
user_avatar_delete($manageUser['user_id']);
|
||||
break;
|
||||
|
||||
case 'upload':
|
||||
user_avatar_set_from_path($manageUser['user_id'], $_FILES['avatar']['tmp_name']['file']);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (isset($_POST['add_role'])) {
|
||||
user_role_add($manageUser['user_id'], $_POST['add_role']['role']);
|
||||
}
|
||||
|
||||
if (isset($_POST['manage_roles'])) {
|
||||
switch ($_POST['manage_roles']['mode'] ?? '') {
|
||||
case 'display':
|
||||
user_role_set_display($manageUser['user_id'], $_POST['manage_roles']['role']);
|
||||
break;
|
||||
|
||||
case 'remove':
|
||||
if ((int)$_POST['manage_roles']['role'] !== MSZ_ROLE_MAIN) {
|
||||
user_role_remove($manageUser['user_id'], $_POST['manage_roles']['role']);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
header("Location: ?v=view&u={$manageUser['user_id']}");
|
||||
break;
|
||||
}
|
||||
|
||||
$templating->vars([
|
||||
'available_roles' => $availableRoles,
|
||||
'has_roles' => $hasRoles,
|
||||
'view_user' => $manageUser,
|
||||
]);
|
||||
echo $templating->render('@manage.users.view');
|
||||
break;
|
||||
|
||||
|
|
|
@ -17,6 +17,30 @@ if (!$app->hasActiveSession()) {
|
|||
|
||||
$csrfErrorString = "Couldn't verify you, please refresh the page and retry.";
|
||||
|
||||
$avatarErrorStrings = [
|
||||
'upload' => [
|
||||
'default' => 'Something happened? (UP:%1$d)',
|
||||
UPLOAD_ERR_OK => '',
|
||||
UPLOAD_ERR_NO_FILE => 'Select a file before hitting upload!',
|
||||
UPLOAD_ERR_PARTIAL => 'The upload was interrupted, please try again!',
|
||||
UPLOAD_ERR_INI_SIZE => 'Your avatar is not allowed to be larger in file size than %2$s!',
|
||||
UPLOAD_ERR_FORM_SIZE => 'Your avatar is not allowed to be larger in file size than %2$s!',
|
||||
UPLOAD_ERR_NO_TMP_DIR => 'Unable to save your avatar, contact an administator!',
|
||||
UPLOAD_ERR_CANT_WRITE => 'Unable to save your avatar, contact an administator!',
|
||||
],
|
||||
'set' => [
|
||||
'default' => 'Something happened? (SET:%1$d)',
|
||||
MSZ_USER_AVATAR_NO_ERRORS => '',
|
||||
MSZ_USER_AVATAR_ERROR_INVALID_IMAGE => 'The file you uploaded was not an image!',
|
||||
MSZ_USER_AVATAR_ERROR_PROHIBITED_TYPE => 'This type of image is not supported, keep to PNG, JPG or GIF!',
|
||||
MSZ_USER_AVATAR_ERROR_DIMENSIONS_TOO_LARGE => 'Your avatar can\'t be larger than %3$dx%4$d!',
|
||||
MSZ_USER_AVATAR_ERROR_DATA_TOO_LARGE => 'Your avatar is not allowed to be larger in file size than %2$s!',
|
||||
MSZ_USER_AVATAR_ERROR_TMP_FAILED => 'Unable to save your avatar, contact an administator!',
|
||||
MSZ_USER_AVATAR_ERROR_STORE_FAILED => 'Unable to save your avatar, contact an administator!',
|
||||
MSZ_USER_AVATAR_ERROR_FILE_NOT_FOUND => 'Unable to save your avatar, contact an administator!',
|
||||
],
|
||||
];
|
||||
|
||||
$settingsModes = [
|
||||
'account' => 'Account',
|
||||
'avatar' => 'Avatar',
|
||||
|
@ -193,16 +217,7 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
|||
break;
|
||||
}
|
||||
|
||||
$delete_this = [
|
||||
$app->getStore('avatars/original')->filename($avatarFileName),
|
||||
$app->getStore('avatars/200x200')->filename($avatarFileName),
|
||||
];
|
||||
|
||||
foreach ($delete_this as $delete_avatar) {
|
||||
if (File::exists($delete_avatar)) {
|
||||
File::delete($delete_avatar);
|
||||
}
|
||||
}
|
||||
user_avatar_delete($app->getUserId());
|
||||
break;
|
||||
}
|
||||
|
||||
|
@ -212,78 +227,29 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
|||
break;
|
||||
}
|
||||
|
||||
switch ($_FILES['avatar']['error']) {
|
||||
case UPLOAD_ERR_OK:
|
||||
break;
|
||||
|
||||
case UPLOAD_ERR_NO_FILE:
|
||||
$settingsErrors[] = 'Select a file before hitting upload!';
|
||||
break;
|
||||
|
||||
case UPLOAD_ERR_PARTIAL:
|
||||
$settingsErrors[] = 'The upload was interrupted, please try again!';
|
||||
break;
|
||||
|
||||
case UPLOAD_ERR_INI_SIZE:
|
||||
case UPLOAD_ERR_FORM_SIZE:
|
||||
$settingsErrors[] = sprintf(
|
||||
'Your avatar is not allowed to be larger in filesize than %s',
|
||||
byte_symbol($avatarFileSizeMax, true)
|
||||
);
|
||||
break;
|
||||
|
||||
case UPLOAD_ERR_NO_TMP_DIR:
|
||||
case UPLOAD_ERR_CANT_WRITE:
|
||||
$settingsErrors[] = 'Unable to save your avatar, contact an administator!';
|
||||
break;
|
||||
|
||||
case UPLOAD_ERR_EXTENSION:
|
||||
default:
|
||||
$settingsErrors[] = 'Something happened?';
|
||||
break;
|
||||
}
|
||||
|
||||
if (count($settingsErrors) > 0) {
|
||||
break;
|
||||
}
|
||||
|
||||
$upload_path = $_FILES['avatar']['tmp_name'];
|
||||
$upload_meta = getimagesize($upload_path);
|
||||
|
||||
if (!$upload_meta
|
||||
|| !in_array($upload_meta[2], [IMAGETYPE_GIF, IMAGETYPE_JPEG, IMAGETYPE_PNG], true)
|
||||
|| $upload_meta[0] < 1
|
||||
|| $upload_meta[1] < 1) {
|
||||
$settingsErrors[] = 'Please provide a valid image.';
|
||||
break;
|
||||
}
|
||||
|
||||
if ($upload_meta[0] > $avatarWidthMax || $upload_meta[1] > $avatarHeightMax) {
|
||||
if ($_FILES['avatar']['error'] !== UPLOAD_ERR_OK) {
|
||||
$settingsErrors[] = sprintf(
|
||||
"Your avatar can't be larger than %dx%d, yours was %dx%d",
|
||||
$avatarErrorStrings['upload'][$_FILES['avatar']['error']]
|
||||
?? $avatarErrorStrings['upload']['default'],
|
||||
$_FILES['avatar']['error'],
|
||||
byte_symbol($avatarFileSizeMax, true),
|
||||
$avatarWidthMax,
|
||||
$avatarHeightMax,
|
||||
$upload_meta[0],
|
||||
$upload_meta[1]
|
||||
$avatarHeightMax
|
||||
);
|
||||
break;
|
||||
}
|
||||
|
||||
if (filesize($upload_path) > $avatarFileSizeMax) {
|
||||
$setAvatar = user_avatar_set_from_path($app->getUserId(), $_FILES['avatar']['tmp_name']);
|
||||
|
||||
if ($setAvatar !== MSZ_USER_AVATAR_NO_ERRORS) {
|
||||
$settingsErrors[] = sprintf(
|
||||
'Your avatar is not allowed to be larger in filesize than %s!',
|
||||
byte_symbol($avatarFileSizeMax, true)
|
||||
$avatarErrorStrings['set'][$setAvatar]
|
||||
?? $avatarErrorStrings['set']['default'],
|
||||
$setAvatar,
|
||||
byte_symbol($avatarFileSizeMax, true),
|
||||
$avatarWidthMax,
|
||||
$avatarHeightMax
|
||||
);
|
||||
break;
|
||||
}
|
||||
|
||||
$avatar_path = $app->getStore('avatars/original')->filename($avatarFileName);
|
||||
move_uploaded_file($upload_path, $avatar_path);
|
||||
|
||||
$crop_path = $app->getStore('avatars/200x200')->filename($avatarFileName);
|
||||
|
||||
if (File::exists($crop_path)) {
|
||||
File::delete($crop_path);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
|
|
@ -15,3 +15,45 @@ function user_role_add(int $userId, int $roleId): bool
|
|||
$addRole->bindValue('role_id', $roleId);
|
||||
return $addRole->execute();
|
||||
}
|
||||
|
||||
function user_role_remove(int $userId, int $roleId): bool
|
||||
{
|
||||
$removeRole = Database::connection()->prepare('
|
||||
DELETE FROM `msz_user_roles`
|
||||
WHERE `user_id` = :user_id
|
||||
AND `role_id` = :role_id
|
||||
');
|
||||
$removeRole->bindValue('user_id', $userId);
|
||||
$removeRole->bindValue('role_id', $roleId);
|
||||
return $removeRole->execute();
|
||||
}
|
||||
|
||||
function user_role_has(int $userId, int $roleId): bool
|
||||
{
|
||||
$hasRole = Database::connection()->prepare('
|
||||
SELECT COUNT(`role_id`) > 0
|
||||
FROM `msz_user_roles`
|
||||
WHERE `user_id` = :user_id
|
||||
AND `role_id` = :role_id
|
||||
');
|
||||
$hasRole->bindValue('user_id', $userId);
|
||||
$hasRole->bindValue('role_id', $roleId);
|
||||
return $hasRole->execute() ? (bool)$hasRole->fetchColumn() : false;
|
||||
}
|
||||
|
||||
function user_role_set_display(int $userId, int $roleId): bool
|
||||
{
|
||||
if (!user_role_has($userId, $roleId)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$setDisplay = Database::connection()->prepare('
|
||||
UPDATE `msz_users`
|
||||
SET `display_role` = :role_id
|
||||
WHERE `user_id` = :user_id
|
||||
');
|
||||
$setDisplay->bindValue('user_id', $userId);
|
||||
$setDisplay->bindValue('role_id', $roleId);
|
||||
|
||||
return $setDisplay->execute();
|
||||
}
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
<?php
|
||||
use Misuzu\Application;
|
||||
use Misuzu\Database;
|
||||
use Misuzu\IO\File;
|
||||
|
||||
define('MSZ_USERS_PASSWORD_HASH_ALGO', PASSWORD_ARGON2I);
|
||||
|
||||
|
@ -53,3 +55,110 @@ function user_generate_chat_key(int $userId): string
|
|||
|
||||
return $setChatKey->execute() ? $chatKey : '';
|
||||
}
|
||||
|
||||
define('MSZ_USER_AVATAR_FORMAT', '%d.msz');
|
||||
|
||||
function user_avatar_delete(int $userId): void
|
||||
{
|
||||
$app = Application::getInstance();
|
||||
$avatarFileName = sprintf(MSZ_USER_AVATAR_FORMAT, $userId);
|
||||
|
||||
$deleteThis = [
|
||||
$app->getStore('avatars/original')->filename($avatarFileName),
|
||||
$app->getStore('avatars/200x200')->filename($avatarFileName),
|
||||
];
|
||||
|
||||
foreach ($deleteThis as $deleteAvatar) {
|
||||
if (File::exists($deleteAvatar)) {
|
||||
File::delete($deleteAvatar);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
define('MSZ_USER_AVATAR_TYPE_PNG', IMAGETYPE_PNG);
|
||||
define('MSZ_USER_AVATAR_TYPE_JPG', IMAGETYPE_JPEG);
|
||||
define('MSZ_USER_AVATAR_TYPE_GIF', IMAGETYPE_GIF);
|
||||
define('MSZ_USER_AVATAR_TYPES', [
|
||||
MSZ_USER_AVATAR_TYPE_PNG,
|
||||
MSZ_USER_AVATAR_TYPE_JPG,
|
||||
MSZ_USER_AVATAR_TYPE_GIF,
|
||||
]);
|
||||
|
||||
function user_avatar_is_allowed_type(int $type): bool
|
||||
{
|
||||
return in_array($type, MSZ_USER_AVATAR_TYPES, true);
|
||||
}
|
||||
|
||||
define('MSZ_USER_AVATAR_OPTIONS', [
|
||||
'max_width' => 4000,
|
||||
'max_height' => 4000,
|
||||
'max_size' => 1000000,
|
||||
]);
|
||||
|
||||
define('MSZ_USER_AVATAR_NO_ERRORS', 0);
|
||||
define('MSZ_USER_AVATAR_ERROR_INVALID_IMAGE', 1);
|
||||
define('MSZ_USER_AVATAR_ERROR_PROHIBITED_TYPE', 2);
|
||||
define('MSZ_USER_AVATAR_ERROR_DIMENSIONS_TOO_LARGE', 3);
|
||||
define('MSZ_USER_AVATAR_ERROR_DATA_TOO_LARGE', 4);
|
||||
define('MSZ_USER_AVATAR_ERROR_TMP_FAILED', 5);
|
||||
define('MSZ_USER_AVATAR_ERROR_STORE_FAILED', 6);
|
||||
define('MSZ_USER_AVATAR_ERROR_FILE_NOT_FOUND', 7);
|
||||
|
||||
function user_avatar_set_from_path(int $userId, string $path, array $options = []): int
|
||||
{
|
||||
if (!file_exists($path)) {
|
||||
return MSZ_USER_AVATAR_ERROR_FILE_NOT_FOUND;
|
||||
}
|
||||
|
||||
$options = array_merge(MSZ_USER_AVATAR_OPTIONS, $options);
|
||||
|
||||
// 0 => width, 1 => height, 2 => type
|
||||
$imageInfo = getimagesize($path);
|
||||
|
||||
if ($imageInfo === false
|
||||
|| count($imageInfo) < 3
|
||||
|| $imageInfo[0] < 1
|
||||
|| $imageInfo[1] < 1) {
|
||||
return MSZ_USER_AVATAR_ERROR_INVALID_IMAGE;
|
||||
}
|
||||
|
||||
if (!user_avatar_is_allowed_type($imageInfo[2])) {
|
||||
return MSZ_USER_AVATAR_ERROR_PROHIBITED_TYPE;
|
||||
}
|
||||
|
||||
if ($imageInfo[0] > $options['max_width']
|
||||
|| $imageInfo[1] > $options['max_height']) {
|
||||
return MSZ_USER_AVATAR_ERROR_DIMENSIONS_TOO_LARGE;
|
||||
}
|
||||
|
||||
if (filesize($path) > $options['max_size']) {
|
||||
return MSZ_USER_AVATAR_ERROR_DATA_TOO_LARGE;
|
||||
}
|
||||
|
||||
user_avatar_delete($userId);
|
||||
|
||||
$fileName = sprintf(MSZ_USER_AVATAR_FORMAT, $userId);
|
||||
$avatarPath = Application::getInstance()->getStore('avatars/original')->filename($fileName);
|
||||
|
||||
if (!copy($path, $avatarPath)) {
|
||||
return MSZ_USER_AVATAR_ERROR_STORE_FAILED;
|
||||
}
|
||||
|
||||
return MSZ_USER_AVATAR_NO_ERRORS;
|
||||
}
|
||||
|
||||
function user_avatar_set_from_data(int $userId, string $data, array $options = []): int
|
||||
{
|
||||
$tmp = tempnam(sys_get_temp_dir(), 'msz');
|
||||
|
||||
if ($tmp === false || !file_exists($tmp)) {
|
||||
return MSZ_USER_AVATAR_ERROR_TMP_FAILED;
|
||||
}
|
||||
|
||||
chmod($tmp, 644);
|
||||
file_put_contents($tmp, $data);
|
||||
$result = user_avatar_set_from_path($userId, $tmp, $options);
|
||||
unlink($tmp);
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
|
|
@ -46,23 +46,25 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<div class="container">
|
||||
<form class="container" method="post" enctype="multipart/form-data" action="">
|
||||
<h1 class="container__title">
|
||||
Avatar
|
||||
</h1>
|
||||
|
||||
<input type="hidden" name="csrf" value="{{ csrf_token() }}">
|
||||
|
||||
<label class="form__label">
|
||||
<div class="form__label__text">New Avatar</div>
|
||||
<div class="form__label__input">
|
||||
<input class="input input--text" type="file" name="user[avatar]">
|
||||
<input class="input input--text" type="file" name="avatar[file]">
|
||||
</div>
|
||||
</label>
|
||||
|
||||
<div>
|
||||
<button class="button" name="csrf" value="{{ csrf_token() }}">Upload</button>
|
||||
<button class="button" name="csrf" value="{{ csrf_token() }}">Delete</button>
|
||||
<button class="button" name="avatar[mode]" value="upload">Upload</button>
|
||||
<button class="button" name="avatar[mode]" value="delete">Delete</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<div class="container">
|
||||
<h1 class="container__title">
|
||||
|
@ -168,9 +170,54 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<div class="container">
|
||||
<h1 class="container__title">Roles</h1>
|
||||
</div>
|
||||
{% if available_roles|length > 0 %}
|
||||
<form class="container" method="post" action="">
|
||||
<h1 class="container__title">Add Role</h1>
|
||||
|
||||
<label class="form__label">
|
||||
<div class="form__label__text">Available Roles</div>
|
||||
<div class="form__label__input">
|
||||
<select name="add_role[role]" class="input input--select">
|
||||
{% for role in available_roles %}
|
||||
<option value="{{ role.role_id }}">
|
||||
{{ role.role_name }}
|
||||
</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
</label>
|
||||
|
||||
<div>
|
||||
<button class="button" name="csrf" value="{{ csrf_token() }}">Add</button>
|
||||
</div>
|
||||
</form>
|
||||
{% endif %}
|
||||
|
||||
{% if has_roles|length > 0 %}
|
||||
<form class="container" method="post" action="">
|
||||
<h1 class="container__title">Manage Roles</h1>
|
||||
|
||||
<input type="hidden" name="csrf" value="{{ csrf_token() }}">
|
||||
|
||||
<label class="form__label">
|
||||
<div class="form__label__text">Has Roles</div>
|
||||
<div class="form__label__input">
|
||||
<select name="manage_roles[role]" class="input input--select">
|
||||
{% for role in has_roles %}
|
||||
<option value="{{ role.role_id }}"{% if role.role_id == view_user.display_role %} selected{% endif %}>
|
||||
{{ role.role_name }}
|
||||
</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
</label>
|
||||
|
||||
<div>
|
||||
<button class="button" name="manage_roles[mode]" value="display">Set Display</button>
|
||||
<button class="button" name="manage_roles[mode]" value="remove">Remove</button>
|
||||
</div>
|
||||
</form>
|
||||
{% endif %}
|
||||
|
||||
<div class="container">
|
||||
<h1 class="container__title">Permissions</h1>
|
||||
|
|
Loading…
Add table
Reference in a new issue