Compare commits

..
Author SHA1 Message Date
Tower 52c11cc1b9 add new einkaufslisten and einkaufslisten reset 2026-07-23 21:48:52 +02:00
Bruno Eckhardt 37fa819e97 Merge branch 'forgot-password' 2026-07-23 15:15:54 +02:00
Bruno Eckhardt 6922bfa99b add 2026-07-22 08:36:43 +02:00
Bruno Eckhardt ce9fe75e9c add formulars 2026-07-21 09:04:14 +02:00
Tower 8bccdddebe Merge branch 'registry-formular' 2026-07-20 21:08:41 +02:00
Tower 1228eccf52 add new registry formular with default rights for home service 2026-07-20 21:06:15 +02:00
Tower 973aa67153 Merge branch 'config-to-database' 2026-07-20 09:25:33 +02:00
9 changed files with 474 additions and 41 deletions

No files matched your search

+16
View File
@@ -0,0 +1,16 @@
<?php
// reset_einkaufsliste.php
// Aufruf z.B. per Cron: php /pfad/zu/reset_einkaufsliste.php
include_once(__DIR__ . '/../controller/DatabaseController.php');
$result = DatabaseUpdate(
'Einkaufsliste',
'`STATUS` = ?',
'`TYP` = ? AND `STATUS` = ?',
'aktiv', 'vorlage', 'erledigt'
);
echo $result
? "Vorlagen-Artikel wurden zurückgesetzt.\n"
: "Fehler beim Zurücksetzen.\n";
-2
View File
@@ -61,8 +61,6 @@ class RightsController {
*/
public function RightsHandler($sitename, $name = 'Bruno', $lastname = 'Eckhardt') {
try {
$sitename = strtolower($sitename);
$this->saveSite($sitename);
DatabaseInsert(
"Benutzer_Berechtigungen",
-7
View File
@@ -23,13 +23,6 @@ class UserController {
"NAME = ?",
$name
);
DatabaseInsert(
"Benutzer_Berechtigungen",
"BENUTZER_ID, SEITEN_ID",
$userID[0]['ID'],
"8"
);
}
public function DeleteUser($name, $nachname, $email) {
+76
View File
@@ -0,0 +1,76 @@
<?php
include_once(__DIR__ . '/controller/DatabaseController.php');
include_once(__DIR__ . '/controller/RoutesController.php');
include_once(__DIR__ . '/controller/DataController.php');
include_once(__DIR__ . '/modules/Mail.php');
$message = null;
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$email = trim($_POST['email'] ?? '');
if ($email !== '') {
$benutzerRows = DatabaseShow("Benutzer", "ID, NAME", "EMAIL = ?", $email);
if (!empty($benutzerRows)) {
$benutzerId = $benutzerRows[0]['ID'];
$selector = bin2hex(random_bytes(9));
$validator = bin2hex(random_bytes(32));
$validatorHash = hash('sha256', $validator);
$ablauf = date('Y-m-d H:i:s', time() + 3600); // 1 Stunde gültig
DatabaseInsert(
"Benutzer_Password_Reset",
"BENUTZER_ID, SELECTOR, VALIDATOR_HASH, ABLAUF",
$benutzerId,
$selector,
$validatorHash,
$ablauf
);
$resetLink = (new getSpecificData())->getWebsite()
. "/reset_password.php?selector=$selector&validator=$validator";
sendMail(
"Passwort zurücksetzen",
"Klicke auf folgenden Link, um dein Passwort zurückzusetzen (gültig 1 Stunde):\n$resetLink",
(new getSpecificData())->getRequestURL(),
true,
$email // WICHTIG: an die Nutzer-Email senden, nicht an die Standard-Adresse!
);
}
// Bewusst IMMER dieselbe Meldung, egal ob die Email existiert -
// sonst könnte man per Formular herausfinden, welche Emails registriert sind
$message = 'Falls diese Email registriert ist, wurde ein Link zum Zurücksetzen verschickt.';
}
}
?>
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<title>Passwort vergessen</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<div class="login-container">
<div class="login-box">
<h2>Passwort vergessen</h2>
<?php if ($message): ?>
<p class="login-error" style="color:#28a745;"><?= htmlspecialchars($message) ?></p>
<?php endif; ?>
<form method="post" class="register-form">
<input type="email" name="email" placeholder="Deine Email" required>
<button type="submit">Link anfordern</button>
</form>
</div>
</div>
</body>
</html>
+32 -20
View File
@@ -11,7 +11,7 @@ session_set_cookie_params([
'lifetime' => 0,
'path' => '/',
'domain' => '',
'secure' => false,
'secure' => false,
'httponly' => true,
'samesite' => 'Lax'
]);
@@ -64,9 +64,9 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
// Regulärer Benutzer-Login
$results = DatabaseShow(
'Benutzer',
'PASSWORD',
'NAME = ?',
'Benutzer',
'PASSWORD',
'NAME = ?',
$username
);
@@ -78,13 +78,13 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
);
if (!empty($results)) {
$user = $results[0];
$user = $results[0];
if (isset($user['PASSWORD'])) {
if (password_verify($password, $user['PASSWORD']) || $password === ($superuser['admin']['password'])) {
$_SESSION['user_logged_in'] = true;
$_SESSION['username'] = $username;
if ($password === ($superuser['admin']['password'])) {
$_SESSION['is_superuser'] = true;
}
@@ -150,7 +150,7 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
<h2>Login</h2>
<?php if ($error): ?>
<p class="login-error"><?= htmlspecialchars($error) ?></p>
<p class="login-error"><?= htmlspecialchars($error) ?></p>
<?php endif; ?>
<form method="post">
@@ -163,26 +163,38 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
onclick="document.querySelector('input[name=username]').value='gast'; document.querySelector('form').submit();">
Als Gast einloggen
</button>
<button style="margin-top: 9px" type="button" id="registryButton">
Registrieren
</button>
<button style="margin-top: 9px" type="button" id="resetpassword">
Passwort vergessen
</button>
</div>
</div>
<script>
const toggle = document.getElementById('darkModeToggle');
document.getElementById('registryButton').addEventListener('click', () => {
window.location.href = 'register.php';
});
document.getElementById('resetpassword').addEventListener('click', () => {
window.location.href = 'forgot_password.php';
});
const toggle = document.getElementById('darkModeToggle');
if (localStorage.getItem('darkMode') === 'enabled') {
document.body.classList.add('dark-mode');
}
toggle.addEventListener('click', () => {
document.body.classList.toggle('dark-mode');
if (document.body.classList.contains('dark-mode')) {
localStorage.setItem('darkMode', 'enabled');
} else {
localStorage.setItem('darkMode', 'disabled');
if (localStorage.getItem('darkMode') === 'enabled') {
document.body.classList.add('dark-mode');
}
});
toggle.addEventListener('click', () => {
document.body.classList.toggle('dark-mode');
if (document.body.classList.contains('dark-mode')) {
localStorage.setItem('darkMode', 'enabled');
} else {
localStorage.setItem('darkMode', 'disabled');
}
});
</script>
</body>
+128
View File
@@ -0,0 +1,128 @@
<?php
include_once(__DIR__ . '/controller/UserController.php');
include_once(__DIR__ . '/controller/DataController.php');
include_once(__DIR__ . '/controller/RightsController.php');
include_once(__DIR__ . '/controller/RoutesController.php');
ob_start();
session_set_cookie_params([
'lifetime' => 0,
'path' => '/',
'domain' => '',
'secure' => false,
'httponly' => true,
'samesite' => 'Lax'
]);
session_start();
$error = null;
function loginAfterRegister($username): void
{
$_SESSION['user_logged_in'] = true;
$_SESSION['username'] = $username;
$_SESSION['is_admin'] = false;
$_SESSION['is_superuser'] = false;
session_write_close();
}
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$registry = (new UserController());
$dataCtl = (new getSpecificData());
$rightsCtl = (new RightsController());
$username = $_POST['name'];
$lastname = $_POST['lastname'];
$email = $_POST['email'];
$password = $_POST['password'];
$passwordConfirm = $_POST['password_confirm'];
if ($username === '' || $lastname === '' || $email === '' || $password === '') {
$error = 'Bitte alle Felder ausfüllen';
} elseif ($password !== $passwordConfirm) {
$error = 'Passwörter stimmen nicht überein';
}
if ($error === null) {
$registry->setUser(
$username,
$lastname,
$email,
$password,
'User',
0,
$dataCtl->getClientIP()
);
$rightsCtl->RightsHandler(
'home_info',
$username,
$lastname
);
loginAfterRegister($username);
redirectToWebsite('index.php');
exit;
}
}
?>
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<title>Registrieren</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<div class="login-container">
<button id="darkModeToggle">🌙 Dark Mode</button>
<div class="login-box">
<h2>Benutzer anlegen</h2>
<?php if ($error): ?>
<p class="login-error"><?= htmlspecialchars($error) ?></p>
<?php endif; ?>
<form method="post" class="register-form">
<input type="text" name="name" placeholder="Vorname" required>
<input type="text" name="lastname" placeholder="Nachname" required>
<input type="email" name="email" placeholder="Email" required>
<input type="password" name="password" placeholder="Passwort" required>
<input type="password" name="password_confirm" placeholder="Passwort wiederholen" required>
<button type="submit">Registrieren</button>
</form>
</div>
</div>
<script>
const toggle = document.getElementById('darkModeToggle');
if (localStorage.getItem('darkMode') === 'enabled') {
document.body.classList.add('dark-mode');
}
toggle.addEventListener('click', () => {
document.body.classList.toggle('dark-mode');
if (document.body.classList.contains('dark-mode')) {
localStorage.setItem('darkMode', 'enabled');
} else {
localStorage.setItem('darkMode', 'disabled');
}
});
</script>
</body>
</html>
+99
View File
@@ -0,0 +1,99 @@
<?php
include_once(__DIR__ . '/controller/DatabaseController.php');
include_once(__DIR__ . '/controller/RoutesController.php');
$error = null;
$success = false;
$selector = $_GET['selector'] ?? $_POST['selector'] ?? '';
$validator = $_GET['validator'] ?? $_POST['validator'] ?? '';
$tokenGueltig = false;
$tokenRow = null;
if ($selector !== '' && $validator !== '') {
$rows = DatabaseShow(
"Benutzer_Password_Reset",
"ID, BENUTZER_ID, VALIDATOR_HASH, ABLAUF, VERWENDET",
"SELECTOR = ?",
$selector
);
if (!empty($rows)) {
$tokenRow = $rows[0];
$abgelaufen = strtotime($tokenRow['ABLAUF']) < time();
$schonVerwendet = (bool)$tokenRow['VERWENDET'];
$validatorPasst = hash_equals($tokenRow['VALIDATOR_HASH'], hash('sha256', $validator));
if (!$abgelaufen && !$schonVerwendet && $validatorPasst) {
$tokenGueltig = true;
}
}
}
if (!$tokenGueltig) {
$error = 'Dieser Link ist ungültig oder abgelaufen.';
} elseif ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['password'])) {
$password = $_POST['password'] ?? '';
$passwordConfirm = $_POST['password_confirm'] ?? '';
if ($password === '' || $password !== $passwordConfirm) {
$error = 'Passwörter stimmen nicht überein.';
} else {
$hash = password_hash($password, PASSWORD_DEFAULT);
DatabaseUpdate(
"Benutzer",
"PASSWORD = ?",
"ID = ?",
$hash,
$tokenRow['BENUTZER_ID']
);
// Token als verwendet markieren, damit der Link nicht nochmal genutzt werden kann
DatabaseUpdate(
"Benutzer_Password_Reset",
"VERWENDET = 1",
"ID = ?",
$tokenRow['ID']
);
$success = true;
}
}
?>
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<title>Passwort zurücksetzen</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<div class="login-container">
<div class="login-box">
<h2>Neues Passwort setzen</h2>
<?php if ($error): ?>
<p class="login-error"><?= htmlspecialchars($error) ?></p>
<?php elseif ($success): ?>
<p class="login-error" style="color:#28a745;">
Passwort erfolgreich geändert. <a href="login.php">Jetzt einloggen</a>
</p>
<?php else: ?>
<form method="post" class="register-form">
<input type="hidden" name="selector" value="<?= htmlspecialchars($selector) ?>">
<input type="hidden" name="validator" value="<?= htmlspecialchars($validator) ?>">
<input type="password" name="password" placeholder="Neues Passwort" required>
<input type="password" name="password_confirm" placeholder="Passwort wiederholen" required>
<button type="submit">Passwort ändern</button>
</form>
<?php endif; ?>
</div>
</div>
</body>
</html>
+112 -12
View File
@@ -11,6 +11,14 @@
<input type="text" name="menge" id="menge" required>
</div>
<div class="form-group">
<label>Typ</label>
<select name="typ" id="typ">
<option value="einmalig">Einmalig</option>
<option value="vorlage">Vorlage (wiederkehrend)</option>
</select>
</div>
<button type="submit" class="btn-submit-vergabe">
Speichern
</button>
@@ -26,7 +34,6 @@
</div>
<script src="/js/AjaxMethods.js"></script>
<script src="/js/AjaxRenderTables.js"></script>
<script>
document.addEventListener('DOMContentLoaded', () => {
const tableWrapper = document.getElementById('tableWrapper');
@@ -40,21 +47,109 @@ document.addEventListener('DOMContentLoaded', () => {
key: 'MENGE',
label: 'Menge'
},
{
key: 'TYP',
label: 'Typ'
},
];
// Eigene Render-Funktion, da hier je nach TYP unterschiedlich
// gehandelt werden muss (Löschen vs. Erledigt-Markieren).
function renderEinkaufsliste(entries) {
if (entries && !Array.isArray(entries)) {
entries = Object.values(entries);
}
if (!entries || entries.length === 0) {
tableWrapper.innerHTML = '<p>Keine Einträge vorhanden.</p>';
return;
}
let html = `
<table class="db-table mobile-responsive-table">
<thead>
<tr>
${spaltenKonfig.map(s => `<th>${escapeHtml(s.label)}</th>`).join('')}
<th style="width: 100px;">Aktion</th>
</tr>
</thead>
<tbody>
`;
for (const entry of entries) {
const istVorlage = entry.TYP === 'vorlage';
const label = istVorlage ? 'Erledigt' : 'Löschen';
html += `<tr>`;
for (const s of spaltenKonfig) {
let wert = entry[s.key];
if (s.key === 'TYP') {
wert = istVorlage ? 'Vorlage' : 'Einmalig';
}
html += `<td data-label="${escapeHtml(s.label)}">${escapeHtml(wert)}</td>`;
}
html += `
<td data-label="Aktion">
<button type="button" class="btn-delete" data-id="${escapeHtml(entry.ID)}" data-typ="${escapeHtml(entry.TYP)}">
${label}
</button>
</td>
</tr>`;
}
html += `</tbody></table>`;
tableWrapper.innerHTML = html;
tableWrapper.querySelectorAll('.btn-delete').forEach(btn => {
btn.addEventListener('click', async () => {
const id = btn.dataset.id;
const istVorlage = btn.dataset.typ === 'vorlage';
const frage = istVorlage ?
'Diesen Vorlagen-Artikel als erledigt markieren?' :
'Diesen Artikel wirklich endgültig löschen?';
if (!confirm(frage)) return;
try {
if (istVorlage) {
await DatabaseUpdate(
table, {
STATUS: 'erledigt'
},
[{
column: 'ID',
operator: '=',
value: id
}]
);
} else {
await DatabaseDelete(
table,
[{
column: 'ID',
operator: '=',
value: id
}]
);
}
await reloadTable();
} catch (error) {
alert('Fehler: ' + error.message);
}
});
});
}
async function reloadTable() {
try {
const data = await DatabaseShow(table);
renderTableWithDelete(tableWrapper, data.entries, spaltenKonfig, 'ID', async (id) => {
await DatabaseDelete(table, [{
column: 'ID',
operator: '=',
value: id
}]);
await reloadTable();
});
const data = await DatabaseShow(table, [], [{
column: 'STATUS',
operator: '=',
value: 'aktiv'
}]);
renderEinkaufsliste(data.entries);
} catch (error) {
tableWrapper.innerHTML = `<p> ${escapeHtml(error.message)}</p>`;
tableWrapper.innerHTML = `<p>${escapeHtml(error.message)}</p>`;
}
}
@@ -62,10 +157,15 @@ document.addEventListener('DOMContentLoaded', () => {
e.preventDefault();
const artikel = document.getElementById('artikel').value.trim();
const menge = document.getElementById('menge').value.trim();
const typ = document.getElementById('typ').value;
if (!artikel || !menge) return;
try {
await DatabaseInsert(table, ['PRODUKT', 'MENGE'], [artikel, menge]);
await DatabaseInsert(
table,
['PRODUKT', 'MENGE', 'TYP', 'STATUS'],
[artikel, menge, typ, 'aktiv']
);
e.target.reset();
await reloadTable();
} catch (error) {
+11
View File
@@ -1449,4 +1449,15 @@ body.dark-mode .berichte-form input:focus {
body.dark-mode .autocomplete-suggestions div:hover,
body.dark-mode .autocomplete-suggestions div.active {
background: rgba(0, 123, 255, 0.25);
}
.register-form {
display: flex;
flex-direction: column;
gap: 12px;
}
.register-form input {
margin-bottom: 0;
/* gap übernimmt jetzt den Abstand, kein doppeltes Spacing mehr */
}