From 5ccab235ef2a32c2795c6dab60eeed3b7edcc716 Mon Sep 17 00:00:00 2001
From: Jan <96944229+modelrailroader@users.noreply.github.com>
Date: Wed, 1 May 2024 18:50:56 +0200
Subject: [PATCH] refactor: move user management to twig (#2791)
---
phpmyfaq/admin/assets/src/api/user.js | 21 +
phpmyfaq/admin/assets/src/user/user-list.js | 67 +-
phpmyfaq/admin/assets/src/user/users.js | 187 +++-
phpmyfaq/admin/user.php | 928 +++---------------
.../assets/templates/admin/user/users.twig | 511 ++++++++++
phpmyfaq/src/admin-routes.php | 8 +
.../Administration/UserController.php | 124 ++-
phpmyfaq/translations/language_de.php | 3 +
phpmyfaq/translations/language_en.php | 3 +
9 files changed, 959 insertions(+), 893 deletions(-)
create mode 100644 phpmyfaq/assets/templates/admin/user/users.twig
diff --git a/phpmyfaq/admin/assets/src/api/user.js b/phpmyfaq/admin/assets/src/api/user.js
index eec555d01d..9c3e7b62d6 100644
--- a/phpmyfaq/admin/assets/src/api/user.js
+++ b/phpmyfaq/admin/assets/src/api/user.js
@@ -126,3 +126,24 @@ export const postUserData = async (url = '', data = {}) => {
throw error;
}
};
+
+export const deleteUser = async (userId, csrfToken) => {
+ try {
+ return await fetch('./api/user/delete', {
+ method: 'DELETE',
+ cache: 'no-cache',
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ redirect: 'follow',
+ referrerPolicy: 'no-referrer',
+ body: JSON.stringify({
+ csrfToken: csrfToken,
+ userId: userId
+ }),
+ });
+ } catch (error) {
+ console.error('Error posting user data:', error);
+ throw error;
+ }
+};
diff --git a/phpmyfaq/admin/assets/src/user/user-list.js b/phpmyfaq/admin/assets/src/user/user-list.js
index 6dfca51550..9bc7e94c80 100644
--- a/phpmyfaq/admin/assets/src/user/user-list.js
+++ b/phpmyfaq/admin/assets/src/user/user-list.js
@@ -16,7 +16,9 @@
*/
import { addElement } from '../../../../assets/src/utils';
-import { pushNotification } from '../utils';
+import { pushErrorNotification, pushNotification } from '../utils';
+import { deleteUser } from '../api';
+import { Modal } from 'bootstrap';
const activateUser = async (userId, csrfToken) => {
try {
@@ -51,40 +53,6 @@ const activateUser = async (userId, csrfToken) => {
}
};
-const deleteUser = async (userId, csrfToken) => {
- const message = document.getElementById('pmf-user-message');
-
- try {
- const response = await fetch('./api/user/delete', {
- method: 'DELETE',
- headers: {
- Accept: 'application/json, text/plain, */*',
- 'Content-Type': 'application/json',
- },
- body: JSON.stringify({
- csrfToken: csrfToken,
- userId: userId,
- }),
- });
-
- if (response.ok) {
- const responseData = await response.json();
- const row = document.querySelector(`.row_user_id_${userId}`);
- row.addEventListener('click', () => (row.style.opacity = '0'));
- row.addEventListener('transitionend', () => row.remove());
- pushNotification(responseData);
- } else {
- throw new Error('Network response was not ok: ', { cause: { response } });
- }
- } catch (error) {
- const errorMessage = await error.cause.response.json();
- message.insertAdjacentElement(
- 'afterend',
- addElement('div', { classList: 'alert alert-danger', innerText: errorMessage })
- );
- }
-};
-
export const handleUserList = () => {
const activateButtons = document.querySelectorAll('.btn-activate-user');
const deleteButtons = document.querySelectorAll('.btn-delete-user');
@@ -107,11 +75,32 @@ export const handleUserList = () => {
button.addEventListener('click', (event) => {
event.preventDefault();
- const csrfToken = event.target.getAttribute('data-csrf-token');
- const userId = event.target.getAttribute('data-user-id');
-
- deleteUser(userId, csrfToken);
+ const deleteModal = new Modal(document.getElementById('pmf-modal-user-confirm-delete'));
+ deleteModal.show();
+ document.getElementById('pmf-username-delete').innerText = button.getAttribute('data-username');
+ document.getElementById('pmf-user-id-delete').value = button.getAttribute('data-user-id');
+ document.getElementById('source_page').value = 'user-list';
});
});
+
+ const deleteUserConfirm = document.getElementById('pmf-delete-user-yes');
+ deleteUserConfirm.addEventListener('click', async (event) => {
+ event.preventDefault();
+ const source = document.getElementById('source_page');
+ if (source.value === 'user-list') {
+ const userId = document.getElementById('pmf-user-id-delete').value;
+ const csrfToken = document.getElementById('csrf-token-delete-user').value;
+ const response = await deleteUser(userId, csrfToken);
+ const json = await response.json();
+ if (json.success) {
+ pushNotification(json.success);
+ const row = document.getElementById('row_user_id_' + userId);
+ row.remove();
+ }
+ if (json.error) {
+ pushErrorNotification(json.error);
+ }
+ }
+ });
}
};
diff --git a/phpmyfaq/admin/assets/src/user/users.js b/phpmyfaq/admin/assets/src/user/users.js
index 6cc75a177f..5c13d2a368 100644
--- a/phpmyfaq/admin/assets/src/user/users.js
+++ b/phpmyfaq/admin/assets/src/user/users.js
@@ -16,9 +16,9 @@
*/
import { Modal } from 'bootstrap';
-import { fetchAllUsers, fetchUserData, fetchUserRights, postUserData } from '../api';
+import { fetchAllUsers, fetchUserData, fetchUserRights, deleteUser, postUserData } from '../api';
import { addElement, capitalize } from '../../../../assets/src/utils';
-import { pushNotification } from '../utils';
+import { pushErrorNotification, pushNotification } from '../utils';
/**
* Updates the current loaded user
@@ -57,6 +57,13 @@ const setUserData = async (userId) => {
twoFactorEnabled.setAttribute('checked', 'checked');
twoFactorEnabled.removeAttribute('disabled');
}
+
+ if (userData.status !== 'protected') {
+ const deleteUser = document.getElementById('pmf-delete-user');
+ deleteUser.classList.remove('disabled');
+ }
+ const saveUser = document.getElementById('pmf-user-save');
+ saveUser.classList.remove('disabled');
};
const setUserRights = async (userId) => {
@@ -69,10 +76,34 @@ const setUserRights = async (userId) => {
document.getElementById('rights_user_id').value = userId;
};
+const clearUserForm = async () => {
+ updateInput('current_user_id', '');
+ updateInput('pmf-user-list-autocomplete', '');
+ updateInput('last_modified', '');
+ updateInput('update_user_id', '');
+ updateInput('modal_user_id', '');
+ updateInput('auth_source', '');
+ updateInput('user_status', '');
+ updateInput('display_name', '');
+ updateInput('email', '');
+ updateInput('overwrite_twofactor', '');
+
+ document.querySelectorAll('.permission').forEach((item) => {
+ if (item.checked) {
+ item.removeAttribute('checked');
+ }
+ });
+
+ document.getElementById('pmf-user-save').classList.add('disabled');
+ document.getElementById('pmf-delete-user').classList.add('disabled');
+};
+
const updateInput = (id, value) => {
const input = document.getElementById(id);
- input.value = value;
- input.removeAttribute('disabled');
+ if (input) {
+ input.value = value;
+ input.removeAttribute('disabled');
+ }
};
export const handleUsers = async () => {
@@ -144,51 +175,26 @@ export const handleUsers = async () => {
if (response.ok) {
return response.json();
}
+ if (response.status === 400) {
+ const json = await response.json();
+ json.forEach((item) => {
+ pushErrorNotification(item);
+ });
+ }
throw new Error('Network response was not ok: ', { cause: { response } });
})
.then((response) => {
modal.style.display = 'none';
modal.classList.remove('show');
modalBackdrop[0].parentNode.removeChild(modalBackdrop[0]);
-
- const tableBody = document.querySelector('#pmf-admin-user-table tbody');
- const row = addElement('tr', { id: `row_user_id_${response.id}` }, [
- addElement('td', { innerText: response.realName }),
- addElement('td', {}, [addElement('a', { href: 'mailto:' + response.email, innerText: response.email })]),
- addElement('td', { innerText: response.userName }),
- addElement('td', { className: 'text-center' }, [
- addElement('i', {
- className: response.status ? 'bi bi-check-circle-o text-success' : 'bi bi-ban text-danger',
- }),
- ]),
- addElement('td', { className: 'text-center' }, [
- addElement('i', { className: response.isSuperAdmin ? 'bi bi-user-secret' : 'bi bi-user-times' }),
- ]),
- addElement('td', { className: 'text-center' }, [
- addElement('i', { className: response.isVisible ? 'bi bi-user' : 'bi bi-user-o' }),
- ]),
- addElement('td', {}, [
- addElement('a', { className: 'btn', href: `?action=user&user_id=${response.id}` }, [
- addElement('i', { className: 'bi bi-pencil text-info' }),
- addElement('span', { innerText: ' ' + response.editTranslationString }),
- ]),
- ]),
- addElement('td', {}),
- addElement('td', {}),
- ]);
- tableBody.appendChild(row);
pushNotification(response.success);
+ setTimeout(() => {
+ location.reload();
+ }, 1500);
})
.catch(async (error) => {
- const errors = await error.cause.response.json();
- let errorMessage = '';
-
- errors.forEach((error) => {
- errorMessage += `${error}
`;
- });
-
- addUserError.classList.remove('d-none');
- addUserError.innerHTML = errorMessage;
+ console.error('Error adding user: ' + error);
+ throw error;
});
});
}
@@ -204,7 +210,7 @@ export const handleUsers = async () => {
const replacer = (key, value) => (value === null ? '' : value);
const header = Object.keys(userData[0]);
let csv = userData.map((row) =>
- header.map((fieldName) => JSON.stringify(row[fieldName], replacer)).join(',')
+ header.map((fieldName) => JSON.stringify(row[fieldName], replacer)).join(','),
);
csv.unshift(header.join(','));
csv = csv.join('\r\n');
@@ -216,7 +222,7 @@ export const handleUsers = async () => {
hiddenElement.setAttribute('target', '_blank');
hiddenElement.setAttribute(
'download',
- 'phpmyfaq-users-' + new Date().toISOString().substring(0, 10) + '.csv'
+ 'phpmyfaq-users-' + new Date().toISOString().substring(0, 10) + '.csv',
);
hiddenElement.click();
})
@@ -263,7 +269,7 @@ export const handleUsers = async () => {
.then((response) => {
message.insertAdjacentElement(
'afterend',
- addElement('div', { classList: 'alert alert-success', innerText: response.success })
+ addElement('div', { classList: 'alert alert-success', innerText: response.success }),
);
modal.hide();
})
@@ -272,9 +278,102 @@ export const handleUsers = async () => {
console.error(errorMessage.error);
message.insertAdjacentElement(
'afterend',
- addElement('div', { classList: 'alert alert-danger', innerText: errorMessage.error })
+ addElement('div', { classList: 'alert alert-danger', innerText: errorMessage.error }),
);
});
});
}
+
+ // Delete user
+ const deleteUserButton = document.getElementById('pmf-delete-user');
+ const deleteUser_yes = document.getElementById('pmf-delete-user-yes');
+
+ if (deleteUserButton) {
+ deleteUserButton.addEventListener('click', (event) => {
+ event.preventDefault();
+ const modalDeleteConfirmation = new Modal(document.getElementById('pmf-modal-user-confirm-delete'));
+ modalDeleteConfirmation.show();
+ const username = document.getElementById('pmf-username-delete');
+ const userid = document.getElementById('pmf-user-id-delete');
+ username.innerText = document.getElementById('display_name').value;
+ userid.value = document.getElementById('current_user_id').value;
+ document.getElementById('source_page').value = 'users';
+ });
+ deleteUser_yes.addEventListener('click', async (event) => {
+ event.preventDefault();
+ const source = document.getElementById('source_page');
+ if (source.value === 'users') {
+ const userId = document.getElementById('pmf-user-id-delete').value;
+ const csrfToken = document.getElementById('csrf-token-delete-user').value;
+ const response = await deleteUser(userId, csrfToken);
+ const json = await response.json();
+ if (json.success) {
+ pushNotification(json.success);
+ await clearUserForm();
+ }
+ if (json.error) {
+ pushErrorNotification(json.error);
+ }
+ }
+ });
+ }
+
+ // Edit user
+ const editUserButton = document.getElementById('pmf-user-save');
+ if (editUserButton) {
+ editUserButton.addEventListener('click', async (event) => {
+ event.preventDefault();
+ const userId = document.getElementById('update_user_id').value;
+ let userData = {
+ csrfToken: document.getElementById('pmf-csrf-token').value,
+ display_name: document.getElementById('display_name').value,
+ email: document.getElementById('email').value,
+ last_modified: document.getElementById('last_modified').value,
+ user_status: document.getElementById('user_status').value,
+ is_superadmin: document.getElementById('is_superadmin').checked,
+ overwrite_twofactor: document.getElementById('overwrite_twofactor').checked,
+ userId: userId
+ };
+
+ console.log(userData);
+
+ const response = await postUserData('./api/user/edit', userData);
+ const json = await response.json();
+ if (json.success) {
+ pushNotification(json.success);
+ }
+ if (json.error) {
+ pushErrorNotification(json.error);
+ }
+ await updateUser(userId);
+ });
+ }
+
+ // Update user rights
+ document.querySelectorAll('#pmf-user-rights-save').forEach((item) => {
+ item.addEventListener('click', async (event) => {
+ event.preventDefault();
+ let rightData = [];
+ document.querySelectorAll('.permission').forEach(async (checkbox) => {
+ if (checkbox.checked) {
+ rightData.push(checkbox.value);
+ }
+ });
+ const userId = document.getElementById('rights_user_id').value
+ let data = {
+ csrfToken: document.getElementById('pmf-csrf-token-rights').value,
+ userId: userId,
+ userRights: rightData
+ }
+ const response = await postUserData('./api/user/update-rights', data);
+ const json = await response.json();
+ if (json.success) {
+ pushNotification(json.success);
+ }
+ if (json.error) {
+ pushErrorNotification(json.error);
+ }
+ await updateUser(userId);
+ });
+ });
};
diff --git a/phpmyfaq/admin/user.php b/phpmyfaq/admin/user.php
index 79f23da2ab..ebb4847d3d 100755
--- a/phpmyfaq/admin/user.php
+++ b/phpmyfaq/admin/user.php
@@ -26,9 +26,12 @@
use phpMyFAQ\Permission;
use phpMyFAQ\Session\Token;
use phpMyFAQ\Strings;
+use phpMyFAQ\Template\PermissionTranslationTwigExtension;
+use phpMyFAQ\Template\TwigWrapper;
use phpMyFAQ\Translation;
use phpMyFAQ\User;
use phpMyFAQ\User\CurrentUser;
+use Twig\Extension\DebugExtension;
if (!defined('IS_VALID_PHPMYFAQ')) {
http_response_code(400);
@@ -36,807 +39,160 @@
}
if (
- $user->perm->hasPermission($user->getUserId(), PermissionType::USER_EDIT->value) ||
- $user->perm->hasPermission($user->getUserId(), PermissionType::USER_DELETE->value) ||
- $user->perm->hasPermission($user->getUserId(), PermissionType::USER_ADD->value)
+ !$user->perm->hasPermission($user->getUserId(), PermissionType::USER_EDIT->value) ||
+ !$user->perm->hasPermission($user->getUserId(), PermissionType::USER_DELETE->value) ||
+ !$user->perm->hasPermission($user->getUserId(), PermissionType::USER_ADD->value)
) {
- $userId = Filter::filterInput(INPUT_GET, 'user_id', FILTER_VALIDATE_INT);
-
- // set some parameters
- $selectSize = 10;
- $defaultUserAction = 'list';
- $defaultUserStatus = 'active';
- $userActionList = [
- 'update_rights',
- 'update_data',
- 'delete_confirm',
- 'delete',
- 'addsave',
- 'list',
- 'listallusers'
- ];
-
- // what shall we do?
- // actions defined by url: user_action=
- $userAction = Filter::filterInput(INPUT_GET, 'user_action', FILTER_SANITIZE_SPECIAL_CHARS, $defaultUserAction);
-
- $currentUser = new CurrentUser($faqConfig);
-
- // actions defined by submit button
- if (isset($_POST['user_action_deleteConfirm'])) {
- $userAction = 'delete_confirm';
- }
- if (isset($_POST['cancel'])) {
- $userAction = $defaultUserAction;
- }
-
- // update user rights
- if (
- $userAction == 'update_rights' &&
- $user->perm->hasPermission($user->getUserId(), PermissionType::USER_EDIT->value)
- ) {
- $message = '';
- $userAction = $defaultUserAction;
- $userId = Filter::filterInput(INPUT_POST, 'user_id', FILTER_VALIDATE_INT, 0);
- $csrfOkay = true;
- $csrfToken = Filter::filterInput(INPUT_POST, 'pmf-csrf-token', FILTER_SANITIZE_SPECIAL_CHARS);
- if (!Token::getInstance()->verifyToken('update-user-rights', $csrfToken)) {
- $csrfOkay = false;
- }
-
- if (0 === (int)$userId || !$csrfOkay) {
- $message .= Alert::danger('ad_user_error_noId');
- } else {
- $user = new User($faqConfig);
- $perm = $user->perm;
- // @todo: Add Filter::filterInput[]
- $userRights = $_POST['user_rights'] ?? [];
- if (!$perm->refuseAllUserRights($userId)) {
- $message .= Alert::danger('ad_msg_mysqlerr');
- }
- foreach ($userRights as $rightId) {
- $perm->grantUserRight($userId, $rightId);
- }
-
- $idUser = $user->getUserById($userId, true);
- // Terminate session in case of different permissions after the update
- $user->terminateSessionId();
- $message .= sprintf(
- '
%s %s %s
', - Translation::get('ad_msg_savedsuc_1'), - Strings::htmlentities($user->getLogin(), ENT_QUOTES), - Translation::get('ad_msg_savedsuc_2') - ); - $user = new CurrentUser($faqConfig); - } - } - - // update user data - if ( - $userAction == 'update_data' && - $user->perm->hasPermission($user->getUserId(), PermissionType::USER_EDIT->value) - ) { - $message = ''; - $userAction = $defaultUserAction; - $userId = Filter::filterInput(INPUT_POST, 'user_id', FILTER_VALIDATE_INT, 0); - if ($userId === 0) { - $message .= Alert::danger('ad_user_error_noId'); - } else { - $userData = []; - $userData['display_name'] = Filter::filterInput(INPUT_POST, 'display_name', FILTER_SANITIZE_SPECIAL_CHARS); - $userData['email'] = Filter::filterInput(INPUT_POST, 'email', FILTER_VALIDATE_EMAIL); - $userData['last_modified'] = Filter::filterInput(INPUT_POST, 'last_modified', FILTER_SANITIZE_SPECIAL_CHARS); - $userStatus = Filter::filterInput(INPUT_POST, 'user_status', FILTER_SANITIZE_SPECIAL_CHARS, $defaultUserStatus); - $isSuperAdmin = Filter::filterInput(INPUT_POST, 'is_superadmin', FILTER_SANITIZE_SPECIAL_CHARS); - $isSuperAdmin = $isSuperAdmin === 'on'; - $deleteTwofactor = Filter::filterInput(INPUT_POST, 'overwrite_twofactor', FILTER_SANITIZE_SPECIAL_CHARS); - $deleteTwofactor = $deleteTwofactor === 'on'; - - $user = new User($faqConfig); - $user->getUserById($userId, true); - - $stats = $user->getStatus(); - - // reset two-factor authentication if required - if ($deleteTwofactor) { - $user->setUserData(['secret' => '', 'twofactor_enabled' => 0]); - } - - // set new password and sent email if a user is switched to active - if ($stats == 'blocked' && $userStatus == 'active') { - if (!$user->activateUser()) { - $userStatus = 'invalid_status'; - } - } - - // Set super-admin flag - $user->setSuperAdmin($isSuperAdmin); + require __DIR__ . '/no-permission.php'; + exit(); +} - if ( - !$user->userdata->set(array_keys($userData), array_values($userData)) || !$user->setStatus( - $userStatus - ) - ) { - $message .= Alert::danger('ad_msg_mysqlerr'); - } else { - $message .= sprintf( - '%s %s %s
', - Translation::get('ad_msg_savedsuc_1'), - Strings::htmlentities($user->getLogin(), ENT_QUOTES), - Translation::get('ad_msg_savedsuc_2') - ); - } - } - } +$templateVars = []; - // delete user confirmation - if ( - $userAction == 'delete_confirm' && - $user->perm->hasPermission($user->getUserId(), PermissionType::USER_DELETE->value) - ) { - $message = ''; - $user = new CurrentUser($faqConfig); +$userId = Filter::filterInput(INPUT_GET, 'user_id', FILTER_VALIDATE_INT); - $userId = Filter::filterInput(INPUT_GET, 'user_delete_id', FILTER_VALIDATE_INT, 0); - if ($userId == 0) { - $message .= Alert::danger('ad_user_error_noId'); - $userAction = $defaultUserAction; - } else { - $user->getUserById($userId, true); - // account is protected - if ($user->getStatus() == 'protected' || $userId == 1) { - $message .= Alert::danger('ad_user_error_protectedAccount'); - $userAction = $defaultUserAction; - } else { - ?> +// set some parameters +$selectSize = 10; +$defaultUserAction = 'list'; +$userActionList = [ + 'list', + 'listallusers' +]; -- = Translation::get('ad_user_del_3') . ' ' . Translation::get( - 'ad_user_del_1' - ) . ' ' . Translation::get('ad_user_del_2') ?> -
- - perm->hasPermission($user->getUserId(), PermissionType::USER_EDIT->value)) { + require __DIR__ . '/no-permission.php'; + exit(); } + $allUsers = $user->getAllUsers(false); + $numUsers = is_countable($allUsers) ? count($allUsers) : 0; + $page = Filter::filterInput(INPUT_GET, 'page', FILTER_VALIDATE_INT, 0); + $perPage = 10; + $numPages = ceil($numUsers / $perPage); + $lastPage = $page * $perPage; + $firstPage = $lastPage - $perPage; + + $baseUrl = sprintf( + '%sadmin/?action=user&user_action=listallusers&page=%d', + $faqConfig->getDefaultUrl(), + $page + ); + + // Pagination options + $options = [ + 'baseUrl' => $baseUrl, + 'total' => $numUsers, + 'perPage' => $perPage, + 'pageParamName' => 'page', + ]; + $pagination = new Pagination($options); - // delete user - if ($userAction == 'delete' && $user->perm->hasPermission($user->getUserId(), PermissionType::USER_DELETE->value)) { - $message = ''; - $user = new User($faqConfig); - $userId = Filter::filterInput(INPUT_POST, 'user_id', FILTER_VALIDATE_INT, 0); - $csrfOkay = true; - $csrfToken = Filter::filterInput(INPUT_POST, 'pmf-csrf-token', FILTER_SANITIZE_SPECIAL_CHARS); - $userAction = $defaultUserAction; + $counter = $displayedCounter = 0; + $users = []; + foreach ($allUsers as $listedUserId) { + $user->getUserById($listedUserId, true); + $tempUser = []; - if (!Token::getInstance()->verifyToken('delete-user', $csrfToken)) { - $csrfOkay = false; + if ($displayedCounter >= $perPage) { + continue; } - - if (0 === (int)$userId || !$csrfOkay) { - $message .= Alert::danger('ad_user_error_noId'); - } else { - if (!$user->getUserById($userId, true)) { - $message .= Alert::danger('ad_user_error_noId'); - } - if (!$user->deleteUser()) { - $message .= Alert::danger('ad_user_error_delete'); - } else { - // Move the category ownership to admin (id == 1) - $oCat = new Category($faqConfig, [], false); - $oCat->setUser($currentAdminUser); - $oCat->setGroups($currentAdminGroups); - $oCat->moveOwnership((int)$userId, 1); - - // Remove the user from groups - if ('basic' !== $faqConfig->get('security.permLevel')) { - $oPerm = Permission::selectPerm('medium', $faqConfig); - $oPerm->removeFromAllGroups($userId); - } - - $message .= Alert::success('ad_user_deleted'); - } - $userError = $user->error(); - if ($userError != '') { - $message .= sprintf('%s
', $userError); - } + ++$counter; + if ($counter <= $firstPage) { + continue; } - } - - if (!isset($message)) { - $message = ''; - } - - // show a list of users - if ($userAction === 'list') { ?> -= Translation::get('msgNewContentName') ?> | -= Translation::get('msgNewContentMail') ?> | -= Translation::get('ad_auth_user') ?> | -= Translation::get('ad_user_status') ?> | -= Translation::get('ad_user_is_superadmin') ?> | -= Translation::get('ad_user_is_visible') ?> | -Actions | -|
---|---|---|---|---|---|---|---|
= $pagination->render() ?> | -|||||||
= Strings::htmlentities($user->getUserData('display_name')) ?> | -- - = Strings::htmlentities($user->getUserData('email')) ?> - - | -= Strings::htmlentities($user->getLogin()) ?> | -- | - - | -- - | -- - = Strings::htmlentities($user->getUserData('email')) ?> - - | - -- - = Translation::get('ad_user_edit') ?> - - getStatus() === 'blocked') : ?> - - - getStatus() !== 'protected' && - $currentUser->perm->hasPermission( - $currentUser->getUserId(), - PermissionType::USER_DELETE->value - ) - ) { - $csrfToken = Token::getInstance()->getTokenString('delete-user'); - ?> - - - | -
{{ msgNewContentName }} | +{{ msgNewContentMail }} | +{{ ad_auth_user }} | +{{ ad_user_status }} | +{{ ad_user_is_superadmin }} | +{{ ad_user_is_visible }} | +Actions | +|
---|---|---|---|---|---|---|---|
{{ pagination|raw }} | +|||||||
{{ user.display_name|raw }} | ++ + {{ user.email|raw }} + + | +{{ user.login|raw }} | + ++ | + + | ++ + | + ++ + {{ ad_user_edit }} + + {% if user.status == 'blocked' %} + + {% endif %} + {% if user.status != 'protected' and permissionDeleteUser == true %} + + {% endif %} + | +
' . Translation::get('ad_user_error_protectedAccount') . '
'; + $json = $this->json( + ['error' => Translation::get('ad_user_error_protectedAccount')], + Response::HTTP_BAD_REQUEST + ); } elseif (!$user->deleteUser()) { - $message = Translation::get('ad_user_error_delete'); + $json = $this->json(['error' => Translation::get('ad_user_error_delete')], Response::HTTP_BAD_REQUEST); } else { $category = new Category($configuration, [], false); $category->moveOwnership((int) $userId, 1); @@ -246,13 +249,10 @@ public function deleteUser(Request $request): JsonResponse $permissions->removeFromAllGroups($userId); } - $message = Translation::get('ad_user_deleted'); + $json = $this->json(['success' => Translation::get('ad_user_deleted')], Response::HTTP_OK); } - $jsonResponse->setStatusCode(Response::HTTP_OK); - $jsonResponse->setData($message); - - return $jsonResponse; + return $json; } /** @@ -325,27 +325,103 @@ public function addUser(Request $request): JsonResponse // @todo catch exception } - $successMessage = [ - 'success' => Translation::get('ad_adus_suc'), - 'id' => $newUser->getUserId(), - 'status' => $newUser->getStatus(), - 'isSuperAdmin' => (bool)$userIsSuperAdmin, - 'isVisible' => (bool) $newUser->userdata->get('is_visible'), - 'realName' => $userRealName, - 'userName' => $userName, - 'email' => $userEmail, - 'editTranslationString' => Translation::get('ad_user_edit') - ]; + return $this->json(['success' => Translation::get('ad_adus_suc')], Response::HTTP_OK); } + } - $jsonResponse->setStatusCode(Response::HTTP_OK); - $jsonResponse->setData($successMessage); - return $jsonResponse; + return $this->json($errorMessage, Response::HTTP_BAD_REQUEST); + } + + #[Route('admin/api/user/edit')] + public function editUser(Request $request): JsonResponse + { + $this->userHasPermission(PermissionType::USER_EDIT); + + $data = json_decode($request->getContent()); + + if (!Token::getInstance()->verifyToken('update-user-data', $data->csrfToken)) { + return $this->json(['error' => Translation::get('err_NotAuth')], Response::HTTP_UNAUTHORIZED); } - $jsonResponse->setStatusCode(Response::HTTP_BAD_REQUEST); - $jsonResponse->setData($errorMessage); + $userId = Filter::filterVar($data->userId, FILTER_VALIDATE_INT, 0); + if ($userId === 0) { + return $this->json(['error' => Translation::get('ad_user_error_noId')], Response::HTTP_BAD_REQUEST); + } else { + $userData = []; + $userData['display_name'] = Filter::filterVar($data->display_name, FILTER_SANITIZE_SPECIAL_CHARS); + $userData['email'] = Filter::filterVar($data->email, FILTER_VALIDATE_EMAIL); + $userData['last_modified'] = Filter::filterVar($data->last_modified, FILTER_SANITIZE_SPECIAL_CHARS); + $userStatus = Filter::filterVar($data->user_status, FILTER_SANITIZE_SPECIAL_CHARS, 'active'); + $isSuperAdmin = Filter::filterVar($data->is_superadmin, FILTER_SANITIZE_SPECIAL_CHARS); + $isSuperAdmin = $isSuperAdmin === 'on'; + $deleteTwofactor = Filter::filterVar($data->overwrite_twofactor, FILTER_SANITIZE_SPECIAL_CHARS); + $deleteTwofactor = $deleteTwofactor === 'on'; + + $user = new User($this->configuration); + $user->getUserById($userId, true); + + $stats = $user->getStatus(); + + // reset two-factor authentication if required + if ($deleteTwofactor) { + $user->setUserData(['secret' => '', 'twofactor_enabled' => 0]); + } - return $jsonResponse; + // set new password and sent email if a user is switched to active + if ($stats == 'blocked' && $userStatus == 'active') { + if (!$user->activateUser()) { + $userStatus = 'invalid_status'; + } + } + + // Set super-admin flag + $user->setSuperAdmin($isSuperAdmin); + + if ( + !$user->userdata->set(array_keys($userData), array_values($userData)) || !$user->setStatus( + $userStatus + ) + ) { + return $this->json(['error' => 'ad_msg_mysqlerr'], Response::HTTP_BAD_REQUEST); + } else { + $success = Translation::get('ad_msg_savedsuc_1') . ' ' . + Strings::htmlentities($user->getLogin(), ENT_QUOTES) . ' ' . + Translation::get('ad_msg_savedsuc_2'); + return $this->json(['success' => $success], Response::HTTP_OK); + } + } + } + + #[Route('admin/api/user/update-rights')] + public function updateUserRights(Request $request): JsonResponse + { + $this->userHasPermission(PermissionType::USER_EDIT); + + $data = json_decode($request->getContent()); + + if (!Token::getInstance()->verifyToken('update-user-rights', $data->csrfToken)) { + return $this->json(['error' => Translation::get('err_NotAuth')], Response::HTTP_UNAUTHORIZED); + } + + $userId = Filter::filterVar($data->userId, FILTER_VALIDATE_INT, 0); + + if (0 === (int)$userId) { + return $this->json(['error' => Translation::get('ad_user_error_noId')], Response::HTTP_BAD_REQUEST); + } else { + $user = new User($this->configuration); + $userRights = Filter::filterVar($data->userRights, FILTER_SANITIZE_SPECIAL_CHARS, []); + if (!$user->perm->refuseAllUserRights($userId)) { + return $this->json(['error' => Translation::get('ad_msg_mysqlerr')], Response::HTTP_BAD_REQUEST); + } + foreach ($userRights as $rightId) { + $user->perm->grantUserRight($userId, $rightId); + } + + $user->terminateSessionId(); + $success = Translation::get('ad_msg_savedsuc_1') . + ' ' . Strings::htmlentities($user->getLogin(), ENT_QUOTES) . ' ' . + Translation::get('ad_msg_savedsuc_2'); + return $this->json(['success' => $success], Response::HTTP_OK); + } } } diff --git a/phpmyfaq/translations/language_de.php b/phpmyfaq/translations/language_de.php index 1e6e16ab0f..5107889740 100755 --- a/phpmyfaq/translations/language_de.php +++ b/phpmyfaq/translations/language_de.php @@ -1419,6 +1419,9 @@ // added v4.0.0-alpha.2 - 2024-04-21 by Jan $PMF_LANG['msgDeleteNews'] = 'News löschen'; +$PMF_LANG['msgExportUsersAsCSV'] = 'Benutzer als CSV exportieren'; +$PMF_LANG['msgWarning'] = 'Warnung'; +$PMF_LANG['msgUserList'] = 'Benutzerliste'; // added v4.0.0-alpha.2 - 2024-04-30 by Thorsten $PMF_LANG['msgNoQuestionAndAnswer'] = 'Keine Frage und Antwort gefunden.'; diff --git a/phpmyfaq/translations/language_en.php b/phpmyfaq/translations/language_en.php index e9ef8f0b91..2656adc953 100644 --- a/phpmyfaq/translations/language_en.php +++ b/phpmyfaq/translations/language_en.php @@ -1439,6 +1439,9 @@ // added v4.0.0-alpha.2 - 2024-04-21 by Jan $PMF_LANG['msgDeleteNews'] = 'Delete news'; +$PMF_LANG['msgExportUsersAsCSV'] = 'Export users as csv'; +$PMF_LANG['msgWarning'] = 'Warning'; +$PMF_LANG['msgUserList'] = 'List of users'; // added v4.0.0-alpha.2 - 2024-04-30 by Thorsten $PMF_LANG['msgNoQuestionAndAnswer'] = 'No question and answer found.';