diff --git a/Core/API/Traits/Captcha.trait.php b/Core/API/Traits/Captcha.trait.php
new file mode 100644
index 0000000..23eb4c3
--- /dev/null
+++ b/Core/API/Traits/Captcha.trait.php
@@ -0,0 +1,30 @@
+context->getSettings();
+ if ($settings->isCaptchaEnabled()) {
+ $parameters["captcha"] = new StringType("captcha");
+ }
+ }
+
+ function checkCaptcha(string $action): bool {
+ $settings = $this->context->getSettings();
+ if ($settings->isCaptchaEnabled()) {
+ $captcha = $this->getParam("captcha");
+ $req = new VerifyCaptcha($this->context);
+ if (!$req->execute(array("captcha" => $captcha, "action" => $action))) {
+ return $this->createError($req->getLastError());
+ }
+ }
+
+ return true;
+ }
+}
\ No newline at end of file
diff --git a/Core/API/UserAPI.class.php b/Core/API/UserAPI.class.php
index 17def1d..c0b3d11 100644
--- a/Core/API/UserAPI.class.php
+++ b/Core/API/UserAPI.class.php
@@ -124,9 +124,9 @@ namespace Core\API\User {
use Core\API\Parameter\Parameter;
use Core\API\Parameter\StringType;
use Core\API\Template\Render;
+ use Core\API\Traits\Captcha;
use Core\API\Traits\Pagination;
use Core\API\UserAPI;
- use Core\API\VerifyCaptcha;
use Core\Driver\SQL\Condition\CondBool;
use Core\Driver\SQL\Condition\CondLike;
use Core\Driver\SQL\Condition\CondOr;
@@ -716,6 +716,8 @@ namespace Core\API\User {
class Register extends UserAPI {
+ use Captcha;
+
public function __construct(Context $context, bool $externalCall = false) {
$parameters = array(
"username" => new StringType("username", 32),
@@ -724,10 +726,7 @@ namespace Core\API\User {
"confirmPassword" => new StringType("confirmPassword"),
);
- $settings = $context->getSettings();
- if ($settings->isCaptchaEnabled()) {
- $parameters["captcha"] = new StringType("captcha");
- }
+ $this->addCaptchaParameters($parameters);
parent::__construct($context, $externalCall, $parameters);
$this->csrfTokenRequired = false;
@@ -745,12 +744,8 @@ namespace Core\API\User {
return $this->createError("User Registration is not enabled.");
}
- if ($settings->isCaptchaEnabled()) {
- $captcha = $this->getParam("captcha");
- $req = new VerifyCaptcha($this->context);
- if (!$req->execute(array("captcha" => $captcha, "action" => "register"))) {
- return $this->createError($req->getLastError());
- }
+ if (!$this->checkCaptcha("register")) {
+ return false;
}
$username = $this->getParam("username");
@@ -840,7 +835,8 @@ namespace Core\API\User {
'email' => new Parameter('email', Parameter::TYPE_EMAIL, true, NULL),
'password' => new StringType('password', -1, true, NULL),
'groups' => new ArrayType('groups', Parameter::TYPE_INT, true, true, NULL),
- 'confirmed' => new Parameter('confirmed', Parameter::TYPE_BOOLEAN, true, NULL)
+ 'confirmed' => new Parameter('confirmed', Parameter::TYPE_BOOLEAN, true, NULL),
+ 'active' => new Parameter('active', Parameter::TYPE_BOOLEAN, true, NULL)
));
$this->loginRequired = true;
@@ -865,6 +861,7 @@ namespace Core\API\User {
$password = $this->getParam("password");
$groups = $this->getParam("groups");
$confirmed = $this->getParam("confirmed");
+ $active = $this->getParam("active");
$email = (!is_null($email) && empty($email)) ? null : $email;
@@ -918,13 +915,22 @@ namespace Core\API\User {
if (!is_null($confirmed)) {
if ($id === $currentUser->getId() && $confirmed === false) {
- return $this->createError("Cannot make own account unconfirmed.");
+ return $this->createError("Cannot change confirmed flag on own account.");
} else {
$user->confirmed = $confirmed;
$columnsToUpdate[] = "confirmed";
}
}
+ if (!is_null($active)) {
+ if ($id === $currentUser->getId() && $active === false) {
+ return $this->createError("Cannot change active flag on own account.");
+ } else {
+ $user->active = $active;
+ $columnsToUpdate[] = "active";
+ }
+ }
+
if (empty($columnsToUpdate) || $user->save($sql, $columnsToUpdate)) {
$deleteQuery = $sql->delete("UserGroup")->whereEq("user_id", $id);
@@ -995,16 +1001,15 @@ namespace Core\API\User {
}
class RequestPasswordReset extends UserAPI {
+
+ use Captcha;
+
public function __construct(Context $context, $externalCall = false) {
- $parameters = array(
+ $parameters = [
'email' => new Parameter('email', Parameter::TYPE_EMAIL),
- );
-
- $settings = $context->getSettings();
- if ($settings->isCaptchaEnabled()) {
- $parameters["captcha"] = new StringType("captcha");
- }
+ ];
+ $this->addCaptchaParameters($parameters);
parent::__construct($context, $externalCall, $parameters);
}
@@ -1019,12 +1024,8 @@ namespace Core\API\User {
return $this->createError("The mail service is not enabled, please contact the server administration.");
}
- if ($settings->isCaptchaEnabled()) {
- $captcha = $this->getParam("captcha");
- $req = new VerifyCaptcha($this->context);
- if (!$req->execute(array("captcha" => $captcha, "action" => "resetPassword"))) {
- return $this->createError($req->getLastError());
- }
+ if (!$this->checkCaptcha("resetPassword")) {
+ return false;
}
$sql = $this->context->getSQL();
@@ -1088,16 +1089,15 @@ namespace Core\API\User {
}
class ResendConfirmEmail extends UserAPI {
+
+ use Captcha;
+
public function __construct(Context $context, $externalCall = false) {
$parameters = array(
'email' => new Parameter('email', Parameter::TYPE_EMAIL),
);
- $settings = $context->getSettings();
- if ($settings->isCaptchaEnabled()) {
- $parameters["captcha"] = new StringType("captcha");
- }
-
+ $this->addCaptchaParameters($parameters);
parent::__construct($context, $externalCall, $parameters);
}
@@ -1108,12 +1108,8 @@ namespace Core\API\User {
}
$settings = $this->context->getSettings();
- if ($settings->isCaptchaEnabled()) {
- $captcha = $this->getParam("captcha");
- $req = new VerifyCaptcha($this->context);
- if (!$req->execute(array("captcha" => $captcha, "action" => "resendConfirmation"))) {
- return $this->createError($req->getLastError());
- }
+ if (!$this->checkCaptcha("resendConfirmation")) {
+ return false;
}
$email = $this->getParam("email");
diff --git a/Core/Localization/de_DE/logs.php b/Core/Localization/de_DE/logs.php
index d6df6c5..fd3eddc 100644
--- a/Core/Localization/de_DE/logs.php
+++ b/Core/Localization/de_DE/logs.php
@@ -9,7 +9,7 @@ return [
"search" => "Suche",
"search_query" => "Suchanfrage",
"no_entries_placeholder" => "Keine Log-Einträge zum Anzeigen",
- "timestamp_placeholder" => "Datum und Zeitpunk Auswählen zum Filtern",
+ "timestamp_placeholder" => "Datum und Zeitpunkt auswählen zum Filtern",
"hide_details" => "Details verstecken",
"show_details" => "Details zeigen",
diff --git a/react/admin-panel/src/views/user/user-edit.js b/react/admin-panel/src/views/user/user-edit.js
index 08f46b9..0ab6b86 100644
--- a/react/admin-panel/src/views/user/user-edit.js
+++ b/react/admin-panel/src/views/user/user-edit.js
@@ -1,15 +1,32 @@
import {Link, useNavigate, useParams} from "react-router-dom";
import {useCallback, useContext, useEffect, useState} from "react";
-import {CircularProgress} from "@mui/material";
+import {
+ Box,
+ Button,
+ Checkbox,
+ CircularProgress,
+ FormControl,
+ FormControlLabel,
+ FormLabel,
+ TextField
+} from "@mui/material";
import {LocaleContext} from "shared/locale";
import * as React from "react";
import ViewContent from "../../elements/view-content";
+import FormGroup from "../../elements/form-group";
+import ButtonBar from "../../elements/button-bar";
+import {RestartAlt, Save} from "@mui/icons-material";
+import {parseBool} from "shared/util";
+import SpacedFormGroup from "../../elements/form-group";
export default function UserEditView(props) {
+ // meta
const { api, showDialog } = props;
const { userId } = useParams();
const navigate = useNavigate();
+
+ // data
const isNewUser = userId === "new";
const {translate: L, requestModules, currentLocale} = useContext(LocaleContext);
const [fetchUser, setFetchUser] = useState(!isNewUser);
@@ -20,8 +37,13 @@ export default function UserEditView(props) {
password: "",
groups: [],
confirmed: false,
+ active: true,
} : null);
+ // ui
+ const [hasChanged, setChanged] = useState(isNewUser);
+ const [isSaving, setSaving] = useState(false);
+
useEffect(() => {
requestModules(props.api, ["general", "account"], currentLocale).then(data => {
if (!data.success) {
@@ -30,6 +52,14 @@ export default function UserEditView(props) {
});
}, [currentLocale]);
+ const onReset = useCallback(() => {
+
+ }, []);
+
+ const onSaveUser = useCallback(() => {
+
+ }, []);
+
const onFetchUser = useCallback((force = false) => {
if (!isNewUser && (force || fetchUser)) {
setFetchUser(false);
@@ -46,6 +76,10 @@ export default function UserEditView(props) {
}
}, [api, showDialog, fetchUser, isNewUser, userId, user]);
+ const onChangeValue = useCallback((name, value) => {
+
+ }, []);
+
useEffect(() => {
if (!isNewUser) {
onFetchUser(true);
@@ -61,6 +95,69 @@ export default function UserEditView(props) {
User,
{isNewUser ? "New" : "Edit"}
]}>
+
+
+ {L("account.name")}
+
+ setUser({...user, name: e.target.value})} />
+
+
+
+ {L("account.full_name")}
+
+ setUser({...user, fullName: e.target.value})} />
+
+
+
+ {L("account.email")}
+
+ setUser({...user, email: e.target.value})} />
+
+
+ { !isNewUser ?
+ <>
+
+ onChangeValue("active", v)} />}
+ label={L("account.active")} />
+
+
+ onChangeValue("confirmed", v)} />}
+ label={L("account.confirmed")} />
+
+ > : <>
+
+ >
+ }
+
+
+ : }
+ variant={"outlined"} title={L(hasChanged ? "general.unsaved_changes" : "general.save")}>
+ {isSaving ? L("general.saving") + "…" : (L("general.save") + (hasChanged ? " *" : ""))}
+
+ }
+ variant={"outlined"} title={L("general.reset")}>
+ {L("general.reset")}
+
+
}
\ No newline at end of file