User Edit
This commit is contained in:
parent
b6c726bad5
commit
9cbd129d4e
@ -286,8 +286,7 @@ namespace Api\User {
|
|||||||
);
|
);
|
||||||
|
|
||||||
foreach($user as $row) {
|
foreach($user as $row) {
|
||||||
$this->result["user"]["groups"][] = array(
|
$this->result["user"]["groups"][$row["groupId"]] = array(
|
||||||
"uid" => $row["groupId"],
|
|
||||||
"name" => $row["groupName"],
|
"name" => $row["groupName"],
|
||||||
"color" => $row["groupColor"],
|
"color" => $row["groupColor"],
|
||||||
);
|
);
|
||||||
@ -482,7 +481,7 @@ If the invitation was not intended, you can simply ignore this email.<br><br><a
|
|||||||
$validUntil = (new DateTime())->modify("+48 hour");
|
$validUntil = (new DateTime())->modify("+48 hour");
|
||||||
$sql = $this->user->getSQL();
|
$sql = $this->user->getSQL();
|
||||||
$res = $sql->insert("UserToken", array("user_id", "token", "token_type", "valid_until"))
|
$res = $sql->insert("UserToken", array("user_id", "token", "token_type", "valid_until"))
|
||||||
->addRow(array($this->userId, $this->token, "confirmation", $validUntil))
|
->addRow($this->userId, $this->token, "confirmation", $validUntil)
|
||||||
->execute();
|
->execute();
|
||||||
|
|
||||||
$this->success = ($res !== FALSE);
|
$this->success = ($res !== FALSE);
|
||||||
@ -606,8 +605,20 @@ If the registration was not intended, you can simply ignore this email.<br><br><
|
|||||||
$password = $this->getParam("password");
|
$password = $this->getParam("password");
|
||||||
$groups = $this->getParam("groups");
|
$groups = $this->getParam("groups");
|
||||||
|
|
||||||
|
$groupIds = array();
|
||||||
if (!is_null($groups)) {
|
if (!is_null($groups)) {
|
||||||
if ($id === $this->user->getId() && !in_array(USER_GROUP_ADMIN, $groups)) {
|
$param = new Parameter('groupId', Parameter::TYPE_INT);
|
||||||
|
|
||||||
|
foreach($groups as $groupId) {
|
||||||
|
if (!$param->parseParam($groupId)) {
|
||||||
|
$value = print_r($groupId, true);
|
||||||
|
return $this->createError("Invalid Type for groupId in parameter groups: '$value' (Required: " . $param->getTypeName() . ")");
|
||||||
|
}
|
||||||
|
|
||||||
|
$groupIds[] = $param->value;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($id === $this->user->getId() && !in_array(USER_GROUP_ADMIN, $groupIds)) {
|
||||||
return $this->createError("Cannot remove Administrator group from own user.");
|
return $this->createError("Cannot remove Administrator group from own user.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -628,18 +639,20 @@ If the registration was not intended, you can simply ignore this email.<br><br><
|
|||||||
if ($emailChanged) $query->set("email", $email);
|
if ($emailChanged) $query->set("email", $email);
|
||||||
if (!is_null($password)) $query->set("password", $this->hashPassword($password));
|
if (!is_null($password)) $query->set("password", $this->hashPassword($password));
|
||||||
|
|
||||||
|
if (!empty($query->getValues())) {
|
||||||
$query->where(new Compare("User.uid", $id));
|
$query->where(new Compare("User.uid", $id));
|
||||||
$res = $query->execute();
|
$res = $query->execute();
|
||||||
$this->lastError = $sql->getLastError();
|
$this->lastError = $sql->getLastError();
|
||||||
$this->success = ($res !== FALSE);
|
$this->success = ($res !== FALSE);
|
||||||
|
}
|
||||||
|
|
||||||
if ($this->success && !is_null($groups)) {
|
if ($this->success && !empty($groupIds)) {
|
||||||
|
|
||||||
$deleteQuery = $sql->delete("UserGroup")->where(new Compare("user_id", $id));
|
$deleteQuery = $sql->delete("UserGroup")->where(new Compare("user_id", $id));
|
||||||
$insertQuery = $sql->insert("UserGroup", array("user_id", "group_id"));
|
$insertQuery = $sql->insert("UserGroup", array("user_id", "group_id"));
|
||||||
|
|
||||||
foreach($groups as $groupId) {
|
foreach($groupIds as $groupId) {
|
||||||
$insertQuery->addRow(array($id, $groupId));
|
$insertQuery->addRow($id, $groupId);
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->success = ($deleteQuery->execute() !== FALSE) && ($insertQuery->execute() !== FALSE);
|
$this->success = ($deleteQuery->execute() !== FALSE) && ($insertQuery->execute() !== FALSE);
|
||||||
|
@ -203,12 +203,13 @@ abstract class SQL {
|
|||||||
|
|
||||||
public function executeDelete(Delete $delete) {
|
public function executeDelete(Delete $delete) {
|
||||||
|
|
||||||
|
$params = array();
|
||||||
$table = $this->tableName($delete->getTable());
|
$table = $this->tableName($delete->getTable());
|
||||||
$where = $this->getWhereClause($delete->getConditions(), $params);
|
$where = $this->getWhereClause($delete->getConditions(), $params);
|
||||||
|
|
||||||
$query = "DELETE FROM $table$where";
|
$query = "DELETE FROM $table$where";
|
||||||
if($delete->dump) { var_dump($query); }
|
if($delete->dump) { var_dump($query); }
|
||||||
return $this->execute($query);
|
return $this->execute($query, $params);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function executeTruncate(Truncate $truncate) {
|
public function executeTruncate(Truncate $truncate) {
|
||||||
@ -222,7 +223,7 @@ abstract class SQL {
|
|||||||
|
|
||||||
$valueStr = array();
|
$valueStr = array();
|
||||||
foreach($update->getValues() as $key => $val) {
|
foreach($update->getValues() as $key => $val) {
|
||||||
$valueStr[] = "$key=" . $this->addValue($val, $params);
|
$valueStr[] = $this->columnName($key) . "=" . $this->addValue($val, $params);
|
||||||
}
|
}
|
||||||
$valueStr = implode(",", $valueStr);
|
$valueStr = implode(",", $valueStr);
|
||||||
|
|
||||||
|
26
js/admin.min.js
vendored
26
js/admin.min.js
vendored
File diff suppressed because one or more lines are too long
@ -35,6 +35,10 @@ export default class API {
|
|||||||
return data && data.success && data.loggedIn;
|
return data && data.success && data.loggedIn;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async editUser(id, username, email, password, groups) {
|
||||||
|
return this.apiCall("user/edit", { "id": id, "username": username, "email": email, "password": password, "groups": groups });
|
||||||
|
}
|
||||||
|
|
||||||
async logout() {
|
async logout() {
|
||||||
return this.apiCall("user/logout");
|
return this.apiCall("user/logout");
|
||||||
}
|
}
|
||||||
|
1
src/src/include/select2.min.css
vendored
Normal file
1
src/src/include/select2.min.css
vendored
Normal file
File diff suppressed because one or more lines are too long
@ -2,6 +2,7 @@ import * as React from "react";
|
|||||||
import Icon from "../elements/icon";
|
import Icon from "../elements/icon";
|
||||||
import Alert from "../elements/alert";
|
import Alert from "../elements/alert";
|
||||||
import {Link} from "react-router-dom";
|
import {Link} from "react-router-dom";
|
||||||
|
import "../include/select2.min.css";
|
||||||
|
|
||||||
export default class EditUser extends React.Component {
|
export default class EditUser extends React.Component {
|
||||||
|
|
||||||
@ -13,33 +14,102 @@ export default class EditUser extends React.Component {
|
|||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
user: {},
|
user: {},
|
||||||
errors: [],
|
alerts: [],
|
||||||
fetchError: null,
|
fetchError: null,
|
||||||
loaded: false,
|
loaded: false,
|
||||||
isSaving: false
|
isSaving: false,
|
||||||
}
|
groups: { },
|
||||||
|
searchString: "",
|
||||||
|
searchActive: false
|
||||||
|
};
|
||||||
|
|
||||||
|
this.searchBox = React.createRef();
|
||||||
}
|
}
|
||||||
|
|
||||||
removeError(i) {
|
removeAlert(i) {
|
||||||
if (i >= 0 && i < this.state.errors.length) {
|
if (i >= 0 && i < this.state.alerts.length) {
|
||||||
let errors = this.state.errors.slice();
|
let alerts = this.state.alerts.slice();
|
||||||
errors.splice(i, 1);
|
alerts.splice(i, 1);
|
||||||
this.setState({...this.state, errors: errors});
|
this.setState({...this.state, alerts: alerts});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
this.parent.api.getUser(this.props.match.params["userId"]).then((res) => {
|
this.parent.api.getUser(this.props.match.params["userId"]).then((res) => {
|
||||||
if (res.success) {
|
if (res.success) {
|
||||||
this.setState({ ...this.state, user: res.user, loaded: true });
|
this.setState({ ...this.state, user: {... res.user, password: ""} });
|
||||||
|
this.parent.api.fetchGroups(1, 50).then((res) => {
|
||||||
|
if (res.success) {
|
||||||
|
this.setState({ ...this.state, groups: res.groups, loaded: true });
|
||||||
|
} else {
|
||||||
|
this.setState({ ...this.state, fetchError: res.msg, loaded: true });
|
||||||
|
}
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
this.setState({ ...this.state, fetchError: res.msg, loaded: true });
|
this.setState({ ...this.state, fetchError: res.msg, loaded: true });
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
onChangeInput(event) {
|
||||||
|
const target = event.target;
|
||||||
|
const value = target.value;
|
||||||
|
const name = target.name;
|
||||||
|
|
||||||
|
if (name === "search") {
|
||||||
|
this.setState({ ...this.state, searchString: value });
|
||||||
|
} else {
|
||||||
|
this.setState({ ...this.state, user: { ...this.state.user, [name]: value } });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onToggleSearch(e) {
|
||||||
|
e.stopPropagation();
|
||||||
|
this.setState({ ...this.state, searchActive: !this.state.searchActive });
|
||||||
|
this.searchBox.current.focus();
|
||||||
|
}
|
||||||
|
|
||||||
|
onSubmitForm(event) {
|
||||||
|
event.preventDefault();
|
||||||
|
event.stopPropagation();
|
||||||
|
const id = this.props.match.params["userId"];
|
||||||
|
const username = this.state.user["name"];
|
||||||
|
const email = this.state.user["email"];
|
||||||
|
let password = this.state.user["password"].length > 0 ? this.state.user["password"] : null;
|
||||||
|
let groups = Object.keys(this.state.user.groups);
|
||||||
|
|
||||||
|
this.setState({ ...this.state, isSaving: true});
|
||||||
|
this.parent.api.editUser(id, username, email, password, groups).then((res) => {
|
||||||
|
let alerts = this.state.alerts.slice();
|
||||||
|
|
||||||
|
if (res.success) {
|
||||||
|
alerts.push({ title: "Success", message: "User was successfully updated.", type: "success" });
|
||||||
|
this.setState({ ...this.state, isSaving: false, alerts: alerts, user: { ...this.state.user, password: "" } });
|
||||||
|
} else {
|
||||||
|
alerts.push({ title: "Error updating user", message: res.msg, type: "danger" });
|
||||||
|
this.setState({ ...this.state, isSaving: false, alerts: alerts, user: { ...this.state.user, password: "" } });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
onRemoveGroup(event, groupId) {
|
||||||
|
event.stopPropagation();
|
||||||
|
if (this.state.user.groups.hasOwnProperty(groupId)) {
|
||||||
|
let groups = { ...this.state.user.groups };
|
||||||
|
delete groups[groupId];
|
||||||
|
this.setState({ ...this.state, user: { ...this.state.user, groups: groups }});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onAddGroup(event, groupId) {
|
||||||
|
event.stopPropagation();
|
||||||
|
if (!this.state.user.groups.hasOwnProperty(groupId)) {
|
||||||
|
let groups = { ...this.state.user.groups, [groupId]: { ...this.state.groups[groupId] } };
|
||||||
|
this.setState({ ...this.state, user: { ...this.state.user, groups: groups }, searchActive: false, searchString: "" });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
if (!this.state.loaded) {
|
if (!this.state.loaded) {
|
||||||
return <h2 className={"text-center"}>
|
return <h2 className={"text-center"}>
|
||||||
Loading…<br/>
|
Loading…<br/>
|
||||||
@ -47,31 +117,112 @@ export default class EditUser extends React.Component {
|
|||||||
</h2>
|
</h2>
|
||||||
}
|
}
|
||||||
|
|
||||||
let errors = [];
|
let alerts = [];
|
||||||
let form = null;
|
let form = null;
|
||||||
if(this.state.fetchError) {
|
if(this.state.fetchError) {
|
||||||
errors.push(
|
alerts.push(
|
||||||
<Alert key={"error-fetch"} title={"Error fetching user details"} type={"danger"} message={
|
<Alert key={"error-fetch"} title={"Error fetching data"} type={"danger"} message={
|
||||||
<div>{this.state.fetchError}<br/>You can meanwhile return to the
|
<div>{this.state.fetchError}<br/>You can meanwhile return to the
|
||||||
<Link to={"/admin/users"}>user overview</Link>
|
<Link to={"/admin/users"}>user overview</Link>
|
||||||
</div>
|
</div>
|
||||||
}/>
|
}/>
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
for (let i = 0; i < this.state.errors.length; i++) {
|
|
||||||
errors.push(<Alert key={"error-" + i} onClose={() => this.removeError(i)} {...this.state.errors[i]}/>)
|
for (let i = 0; i < this.state.alerts.length; i++) {
|
||||||
|
alerts.push(<Alert key={"error-" + i} onClose={() => this.removeAlert(i)} {...this.state.alerts[i]}/>)
|
||||||
}
|
}
|
||||||
|
|
||||||
form = <form role={"form"} onSubmit={(e) => e.preventDefault()}>
|
let possibleOptions = [];
|
||||||
|
let renderedOptions = [];
|
||||||
|
for (let groupId in this.state.groups) {
|
||||||
|
if (this.state.groups.hasOwnProperty(groupId)) {
|
||||||
|
let groupName = this.state.groups[groupId].name;
|
||||||
|
let groupColor = this.state.groups[groupId].color;
|
||||||
|
if (this.state.user.groups.hasOwnProperty(groupId)) {
|
||||||
|
renderedOptions.push(
|
||||||
|
<li className={"select2-selection__choice"} key={"group-" + groupId} title={groupName} style={{backgroundColor: groupColor}}>
|
||||||
|
<span className="select2-selection__choice__remove" role="presentation"
|
||||||
|
onClick={(e) => this.onRemoveGroup(e, groupId)}>
|
||||||
|
×
|
||||||
|
</span>
|
||||||
|
{groupName}
|
||||||
|
</li>
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
if (this.state.searchString.length === 0 || groupName.toLowerCase().includes(this.state.searchString.toLowerCase())) {
|
||||||
|
possibleOptions.push(
|
||||||
|
<li className={"select2-results__option"} role={"option"} key={"group-" + groupId} aria-selected={false}
|
||||||
|
onClick={(e) => this.onAddGroup(e, groupId)}>
|
||||||
|
{groupName}
|
||||||
|
</li>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let searchWidth = "100%";
|
||||||
|
let placeholder = "Select Groups";
|
||||||
|
let searchVisible = (this.state.searchString.length > 0 || this.state.searchActive) ? "block" : "none";
|
||||||
|
if (renderedOptions.length > 0) {
|
||||||
|
searchWidth = (0.75 + this.state.searchString.length * 0.75) + "em";
|
||||||
|
placeholder = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
form = <form role={"form"} onSubmit={this.onSubmitForm.bind(this)}>
|
||||||
<div className={"form-group"}>
|
<div className={"form-group"}>
|
||||||
<label htmlFor={"username"}>Username</label>
|
<label htmlFor={"username"}>Username</label>
|
||||||
<input type={"text"} className={"form-control"} placeholder={"Enter username"}
|
<input type={"text"} className={"form-control"} placeholder={"Enter username"}
|
||||||
name={"username"} id={"username"} maxLength={32} value={this.state.user.name}/>
|
name={"username"} id={"username"} maxLength={32} value={this.state.user.name}
|
||||||
|
onChange={this.onChangeInput.bind(this)}/>
|
||||||
</div>
|
</div>
|
||||||
<div className={"form-group"}>
|
<div className={"form-group"}>
|
||||||
<label htmlFor={"email"}>E-Mail</label>
|
<label htmlFor={"email"}>E-Mail</label>
|
||||||
<input type={"email"} className={"form-control"} placeholder={"E-Mail address"}
|
<input type={"email"} className={"form-control"} placeholder={"E-Mail address"}
|
||||||
id={"email"} name={"email"} maxLength={64} value={this.state.user.email} />
|
id={"email"} name={"email"} maxLength={64} value={this.state.user.email}
|
||||||
|
onChange={this.onChangeInput.bind(this)}/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className={"form-group"}>
|
||||||
|
<label htmlFor={"password"}>Password</label>
|
||||||
|
<input type={"password"} className={"form-control"} placeholder={"(unchanged)"}
|
||||||
|
id={"password"} name={"password"} value={this.state.user.password}
|
||||||
|
onChange={this.onChangeInput.bind(this)}/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className={"form-group position-relative"}>
|
||||||
|
<label>Groups</label>
|
||||||
|
<span className={"select2 select2-container select2-container--default select2-container--below"}
|
||||||
|
dir={"ltr"} style={{width: "100%"}} >
|
||||||
|
<span className="selection">
|
||||||
|
<span className={"select2-selection select2-selection--multiple"} role={"combobox"} aria-haspopup={"true"}
|
||||||
|
aria-expanded={false} aria-disabled={false} onClick={this.onToggleSearch.bind(this)}>
|
||||||
|
<ul className={"select2-selection__rendered"}>
|
||||||
|
{renderedOptions}
|
||||||
|
<li className={"select2-search select2-search--inline"}>
|
||||||
|
<input className={"select2-search__field"} type={"search"} tabIndex={0}
|
||||||
|
autoComplete={"off"} autoCorrect={"off"} autoCapitalize={"none"} spellCheck={false}
|
||||||
|
role={"searchbox"} aria-autocomplete={"list"} placeholder={placeholder}
|
||||||
|
name={"search"} style={{width: searchWidth}} value={this.state.searchString}
|
||||||
|
onChange={this.onChangeInput.bind(this)} ref={this.searchBox} />
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
<span className="dropdown-wrapper" aria-hidden="true"/>
|
||||||
|
</span>
|
||||||
|
<span className={"select2-container select2-container--default select2-container--open"}
|
||||||
|
style={{position: "absolute", bottom: 0, left: 0, width: "100%", display: searchVisible}}>
|
||||||
|
<span className={"select2-dropdown select2-dropdown--below"} dir={"ltr"}>
|
||||||
|
<span className={"select2-results"}>
|
||||||
|
<ul className={"select2-results__options"} role={"listbox"}
|
||||||
|
aria-multiselectable={true} aria-expanded={true} aria-hidden={false}>
|
||||||
|
{possibleOptions}
|
||||||
|
</ul>
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Link to={"/admin/users"} className={"btn btn-info mt-2 mr-2"}>
|
<Link to={"/admin/users"} className={"btn btn-info mt-2 mr-2"}>
|
||||||
@ -105,7 +256,7 @@ export default class EditUser extends React.Component {
|
|||||||
<div className={"content"}>
|
<div className={"content"}>
|
||||||
<div className={"row"}>
|
<div className={"row"}>
|
||||||
<div className={"col-lg-6 pl-5 pr-5"}>
|
<div className={"col-lg-6 pl-5 pr-5"}>
|
||||||
{errors}
|
{alerts}
|
||||||
{form}
|
{form}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
Loading…
Reference in New Issue
Block a user