Reorganize into monorepo layout, move mitarbeiter-app to legacy reference

Consolidates the previously separate omsorgapp and omsorgCore repos
(each had their own nested .git with GitHub history) plus the old
root-level website/mitarbeiter-app into a single monorepo, matching
the structure already documented in the root CLAUDE.md. Also moves
the PHP employee app aside as omsorgWeb/mitarbeiter-app-legacy/ to
serve as a template for a ground-up rewrite.

Fixes .gitignore in the same pass: the config-secrets/uploads/data
patterns were unanchored (relative to repo root, not depth-agnostic),
so they silently stopped matching once the app moved under omsorgWeb/.
Patterns are now **/-prefixed and cover both mitarbeiter-app and
mitarbeiter-app-legacy, keeping DB/SMTP credentials and uploaded
employee documents out of version control.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
Felix Kemmler
2026-08-07 14:21:37 +02:00
co-authored by Claude Sonnet 5
parent 8beb0fcf52
commit b6c1389c55
355 changed files with 24348 additions and 361 deletions
@@ -0,0 +1,171 @@
<?php
require_once __DIR__ . '/../lib/auth.php';
require_admin();
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
header('Location: ../pages/admin.php');
exit;
}
/* ── Login attempts ────────────────────────────────────────────────── */
if (($_POST['_login_action'] ?? '') === 'clear') {
db()->exec('DELETE FROM login_attempts');
header('Location: ../pages/admin.php?tab=login&cleared=1');
exit;
}
/* ── User actions ──────────────────────────────────────────────────── */
if (!empty($_POST['_user_action'])) {
$action = $_POST['_user_action'];
$user_id = (int)($_POST['user_id'] ?? 0);
$back = '../pages/admin.php?tab=nutzer';
switch ($action) {
case 'add_user': {
$username = trim($_POST['username'] ?? '');
$uname = trim($_POST['name'] ?? '');
$email = trim($_POST['email'] ?? '');
$telefon = trim($_POST['telefon'] ?? '');
$role = in_array($_POST['role'] ?? '', ['user', 'admin'], true) ? $_POST['role'] : 'user';
$password = $_POST['password'] ?? '';
if ($username === '' || $uname === '' || strlen($password) < 6) {
header('Location: ' . $back . '&user_msg=' . urlencode('Benutzername, Name und Passwort (mind. 6 Zeichen) sind Pflichtfelder.'));
exit;
}
try {
$hash = password_hash($password, PASSWORD_BCRYPT, ['cost' => 12]);
$stmt = db()->prepare(
'INSERT INTO users (username, name, role, password_hash, active, created_at, email, telefon)
VALUES (?, ?, ?, ?, 1, ?, ?, ?)'
);
$stmt->execute([$username, $uname, $role, $hash, date('c'), $email, $telefon]);
} catch (\PDOException $ex) {
$msg = str_contains($ex->getMessage(), 'UNIQUE') ? 'Benutzername bereits vergeben.' : 'Datenbankfehler.';
header('Location: ' . $back . '&user_msg=' . urlencode($msg));
exit;
}
header('Location: ' . $back . '&user_ok=1&user_msg=' . urlencode('Nutzer erstellt.'));
exit;
}
case 'edit_user': {
$uname = trim($_POST['name'] ?? '');
$email = trim($_POST['email'] ?? '');
$telefon = trim($_POST['telefon'] ?? '');
$role = in_array($_POST['role'] ?? '', ['user', 'admin'], true) ? $_POST['role'] : 'user';
$active = (int)(bool)($_POST['active'] ?? 0);
if ($user_id < 1 || $uname === '') {
header('Location: ' . $back . '&user_msg=' . urlencode('Ungültige Eingabe.'));
exit;
}
$stmt = db()->prepare(
'UPDATE users SET name=?, email=?, telefon=?, role=?, active=? WHERE id=?'
);
$stmt->execute([$uname, $email, $telefon, $role, $active, $user_id]);
header('Location: ' . $back . '&user_ok=1&user_msg=' . urlencode('Nutzer gespeichert.'));
exit;
}
case 'change_password': {
$password = $_POST['new_password'] ?? '';
if ($user_id < 1 || strlen($password) < 6) {
header('Location: ' . $back . '&user_msg=' . urlencode('Passwort muss mindestens 6 Zeichen lang sein.'));
exit;
}
$hash = password_hash($password, PASSWORD_BCRYPT, ['cost' => 12]);
$stmt = db()->prepare('UPDATE users SET password_hash=? WHERE id=?');
$stmt->execute([$hash, $user_id]);
header('Location: ' . $back . '&user_ok=1&user_msg=' . urlencode('Passwort geändert.'));
exit;
}
case 'delete_user': {
if ($user_id < 1) {
header('Location: ' . $back . '&user_msg=' . urlencode('Ungültige Nutzer-ID.'));
exit;
}
$stmt = db()->prepare('SELECT username FROM users WHERE id=?');
$stmt->execute([$user_id]);
$row = $stmt->fetch();
if (!$row) {
header('Location: ' . $back . '&user_msg=' . urlencode('Nutzer nicht gefunden.'));
exit;
}
if ($row['username'] === current_username()) {
header('Location: ' . $back . '&user_msg=' . urlencode('Den eigenen Account kann man nicht löschen.'));
exit;
}
$stmt = db()->prepare('DELETE FROM users WHERE id=?');
$stmt->execute([$user_id]);
header('Location: ' . $back . '&user_ok=1&user_msg=' . urlencode('Nutzer gelöscht.'));
exit;
}
default:
header('Location: ' . $back . '&user_msg=' . urlencode('Unbekannte Aktion.'));
exit;
}
}
/* ── Request actions ───────────────────────────────────────────────── */
$request_id = (int)($_POST['request_id'] ?? 0);
$action = $_POST['action'] ?? '';
$admin_note = trim($_POST['admin_note'] ?? '');
$type = $_POST['type'] ?? '';
$allowed_types = ['urlaubsantrag', 'abwesenheitsantrag', 'benefitsantrag', 'werben', 'fortbildungsantrag', 'stundennachweis'];
if (!in_array($type, $allowed_types, true)) {
header('Location: ../pages/admin.php?error=' . urlencode('Ungültiger Antragstyp.'));
exit;
}
/* ── Delete request ────────────────────────────────────────────────── */
if ($action === 'delete') {
if ($request_id < 1) {
header('Location: ../pages/admin.php?error=' . urlencode('Ungültige Antrags-ID.'));
exit;
}
$table = 'requests_' . $type;
$stmt = db()->prepare("SELECT * FROM $table WHERE id = ?");
$stmt->execute([$request_id]);
$row = $stmt->fetch();
if (!$row) {
header('Location: ../pages/admin.php?error=' . urlencode('Antrag nicht gefunden.'));
exit;
}
// Delete associated file for types that have uploads
if (in_array($type, ['stundennachweis', 'fortbildungsantrag'], true) && !empty($row['filename'])) {
$file_path = __DIR__ . '/../uploads/' . $row['filename'];
if (is_file($file_path)) {
unlink($file_path);
}
}
$stmt = db()->prepare("DELETE FROM $table WHERE id = ?");
$stmt->execute([$request_id]);
header('Location: ../pages/admin.php?deleted=1');
exit;
}
/* ── Status update ─────────────────────────────────────────────────── */
if (!in_array($action, ['accepted', 'rejected'], true) || $request_id < 1) {
header('Location: ../pages/admin.php?error=' . urlencode('Ungültige Aktion.'));
exit;
}
$table = 'requests_' . $type;
$stmt = db()->prepare("SELECT id FROM $table WHERE id = ?");
$stmt->execute([$request_id]);
if (!$stmt->fetch()) {
header('Location: ../pages/admin.php?error=' . urlencode('Antrag nicht gefunden.'));
exit;
}
$stmt = db()->prepare("UPDATE $table SET status = ?, admin_note = ?, updated_at = ? WHERE id = ?");
$stmt->execute([$action, $admin_note, date('c'), $request_id]);
header('Location: ../pages/admin.php?type=' . urlencode($type) . '&id=' . $request_id . '&updated=1');
exit;
@@ -0,0 +1,42 @@
<?php
require_once __DIR__ . '/../lib/auth.php';
require_login();
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
header('Location: ../pages/dokumentenarchiv.php');
exit;
}
$id = (int) ($_POST['id'] ?? 0);
if ($id <= 0) {
http_response_code(400);
echo 'Ungültige Anfrage.';
exit;
}
$stmt = db()->prepare('SELECT * FROM dokumente WHERE id = ?');
$stmt->execute([$id]);
$doc = $stmt->fetch();
if (!$doc) {
http_response_code(404);
echo 'Dokument nicht gefunden.';
exit;
}
$user = current_user();
if ((int) $doc['user_id'] !== (int) $user['id'] && !is_admin()) {
http_response_code(403);
echo 'Keine Berechtigung.';
exit;
}
$file_path = dirname(__DIR__) . '/uploads/' . $doc['filename'];
if (is_file($file_path)) {
unlink($file_path);
}
db()->prepare('DELETE FROM dokumente WHERE id = ?')->execute([$id]);
header('Location: ../pages/dokumentenarchiv.php?deleted=1');
exit;
@@ -0,0 +1,77 @@
<?php
require_once __DIR__ . '/../lib/auth.php';
require_once __DIR__ . '/../lib/upload.php';
require_admin();
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
header('Location: ../pages/admin.php?tab=downloads');
exit;
}
$action = $_POST['_action'] ?? '';
/* ── Add ──────────────────────────────────────────────────────────────── */
if ($action === 'add') {
$title = trim($_POST['title'] ?? '');
$description = trim($_POST['description'] ?? '');
if ($title === '') {
header('Location: ../pages/admin.php?tab=downloads&err=notitle');
exit;
}
$up = handle_upload($_FILES['file'] ?? [], [
'allowed' => UPLOAD_TYPES_OFFICE,
'dir' => dirname(__DIR__) . '/downloads/',
'naming' => 'hash',
'max_bytes' => 20 * 1024 * 1024,
'messages' => [
'missing' => 'nofile', 'upload' => 'nofile',
'ext' => 'badext', 'mime' => 'badmime',
'size' => 'toobig', 'move' => 'upload',
],
]);
if (!$up['ok']) {
header('Location: ../pages/admin.php?tab=downloads&err=' . $up['error']);
exit;
}
$safe_name = $up['filename'];
$original_name = $up['original_name'];
$sort_order = (int)db()->query('SELECT COALESCE(MAX(sort_order), 0) + 10 FROM downloads')->fetchColumn();
$now = date('c');
$stmt = db()->prepare(
'INSERT INTO downloads (title, description, filename, original_name, sort_order, created_at, updated_at)
VALUES (?, ?, ?, ?, ?, ?, ?)'
);
$stmt->execute([$title, $description, $safe_name, $original_name, $sort_order, $now, $now]);
header('Location: ../pages/admin.php?tab=downloads&ok=added');
exit;
}
/* ── Delete ───────────────────────────────────────────────────────────── */
if ($action === 'delete') {
$id = (int)($_POST['download_id'] ?? 0);
if ($id <= 0) {
header('Location: ../pages/admin.php?tab=downloads&err=invalid');
exit;
}
$stmt = db()->prepare('SELECT filename FROM downloads WHERE id = ?');
$stmt->execute([$id]);
$row = $stmt->fetch();
if ($row) {
db()->prepare('DELETE FROM downloads WHERE id = ?')->execute([$id]);
@unlink(dirname(__DIR__) . '/downloads/' . $row['filename']);
}
header('Location: ../pages/admin.php?tab=downloads&ok=deleted');
exit;
}
header('Location: ../pages/admin.php?tab=downloads');
exit;
@@ -0,0 +1,45 @@
<?php
require_once __DIR__ . '/../lib/auth.php';
require_admin();
header('Content-Type: application/json');
if (($_SERVER['HTTP_X_REQUESTED_WITH'] ?? '') !== 'XMLHttpRequest') {
echo json_encode(['ok' => false, 'error' => 'forbidden']);
exit;
}
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
echo json_encode(['ok' => false, 'error' => 'method']);
exit;
}
$body = file_get_contents('php://input');
$data = json_decode($body, true);
$order = $data['order'] ?? null;
if (!is_array($order)) {
echo json_encode(['ok' => false, 'error' => 'invalid']);
exit;
}
$ids = array_values(array_filter(array_map('intval', $order), fn($v) => $v > 0));
if (empty($ids)) {
echo json_encode(['ok' => true]);
exit;
}
$now = date('c');
$pos = 10;
$stmt = db()->prepare('UPDATE downloads SET sort_order = ?, updated_at = ? WHERE id = ?');
db()->beginTransaction();
foreach ($ids as $id) {
$stmt->execute([$pos, $now, $id]);
$pos += 10;
}
db()->commit();
echo json_encode(['ok' => true]);
exit;
@@ -0,0 +1,92 @@
<?php
require_once __DIR__ . '/../lib/auth.php';
require_once __DIR__ . '/../lib/upload.php';
require_admin();
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
header('Location: ../pages/admin.php?tab=einsatzanweisung');
exit;
}
verify_csrf();
$back = '../pages/admin.php?tab=einsatzanweisung';
$action = $_POST['_action'] ?? '';
$user_id = (int)($_POST['target_user_id'] ?? 0);
if ($user_id <= 0) {
header('Location: ' . $back . '&err=' . urlencode('Ungültiger Nutzer.'));
exit;
}
$stmt = db()->prepare('SELECT id FROM users WHERE id = ?');
$stmt->execute([$user_id]);
if (!$stmt->fetch()) {
header('Location: ' . $back . '&err=' . urlencode('Nutzer nicht gefunden.'));
exit;
}
if ($action === 'update_ort') {
$ort = trim($_POST['ort'] ?? '');
db()->prepare(
'INSERT INTO einsatzanweisung (user_id, ort)
VALUES (?, ?)
ON CONFLICT(user_id) DO UPDATE SET ort = excluded.ort'
)->execute([$user_id, $ort]);
header('Location: ' . $back . '&ok=' . urlencode('Einsatzort gespeichert.'));
exit;
}
if ($action === 'upload_pdf') {
$stmt = db()->prepare('SELECT u.username, e.filename AS old_filename FROM users u LEFT JOIN einsatzanweisung e ON e.user_id = u.id WHERE u.id = ?');
$stmt->execute([$user_id]);
$row = $stmt->fetch();
$dir = dirname(__DIR__) . '/uploads/';
$up = handle_upload($_FILES['pdf'] ?? [], [
'allowed' => UPLOAD_TYPES_PDF,
'dir' => $dir,
'prefix' => 'einsatzanweisung',
'username' => $row['username'],
'messages' => [
'missing' => 'Bitte eine PDF-Datei auswählen.',
'upload' => 'Fehler beim Hochladen.',
'ext' => 'Nur PDF-Dateien erlaubt.',
'mime' => 'Nur PDF-Dateien erlaubt.',
'size' => 'Datei ist zu groß (max. 12 MB).',
'move' => 'Fehler beim Speichern der Datei.',
],
]);
if (!$up['ok']) {
header('Location: ' . $back . '&err=' . urlencode($up['error']));
exit;
}
$original_name = $up['original_name'];
$safe_name = $up['filename'];
if (!empty($row['old_filename'])) {
$old_path = $dir . basename($row['old_filename']);
if (is_file($old_path)) {
unlink($old_path);
}
}
$now = date('c');
db()->prepare(
'INSERT INTO einsatzanweisung (user_id, filename, original_name, uploaded_at)
VALUES (?, ?, ?, ?)
ON CONFLICT(user_id) DO UPDATE SET
filename = excluded.filename,
original_name = excluded.original_name,
uploaded_at = excluded.uploaded_at'
)->execute([$user_id, $safe_name, $original_name, $now]);
header('Location: ' . $back . '&ok=' . urlencode('PDF erfolgreich hochgeladen.'));
exit;
}
header('Location: ' . $back);
exit;
@@ -0,0 +1,77 @@
<?php
require_once __DIR__ . '/../lib/auth.php';
require_once __DIR__ . '/../lib/upload.php';
require_admin();
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
header('Location: ../pages/admin.php?tab=fortbildung');
exit;
}
$action = $_POST['_action'] ?? '';
/* ── Add ──────────────────────────────────────────────────────────────── */
if ($action === 'add') {
$title = trim($_POST['title'] ?? '');
$description = trim($_POST['description'] ?? '');
if ($title === '') {
header('Location: ../pages/admin.php?tab=fortbildung&err=notitle');
exit;
}
$up = handle_upload($_FILES['file'] ?? [], [
'allowed' => UPLOAD_TYPES_OFFICE,
'dir' => dirname(__DIR__) . '/fortbildung-materials/',
'naming' => 'hash',
'max_bytes' => 20 * 1024 * 1024,
'messages' => [
'missing' => 'nofile', 'upload' => 'nofile',
'ext' => 'badext', 'mime' => 'badmime',
'size' => 'toobig', 'move' => 'upload',
],
]);
if (!$up['ok']) {
header('Location: ../pages/admin.php?tab=fortbildung&err=' . $up['error']);
exit;
}
$safe_name = $up['filename'];
$original_name = $up['original_name'];
$sort_order = (int)db()->query('SELECT COALESCE(MAX(sort_order), 0) + 10 FROM fortbildung_materials')->fetchColumn();
$now = date('c');
$stmt = db()->prepare(
'INSERT INTO fortbildung_materials (title, description, filename, original_name, sort_order, created_at, updated_at)
VALUES (?, ?, ?, ?, ?, ?, ?)'
);
$stmt->execute([$title, $description, $safe_name, $original_name, $sort_order, $now, $now]);
header('Location: ../pages/admin.php?tab=fortbildung&ok=added');
exit;
}
/* ── Delete ───────────────────────────────────────────────────────────── */
if ($action === 'delete') {
$id = (int)($_POST['material_id'] ?? 0);
if ($id <= 0) {
header('Location: ../pages/admin.php?tab=fortbildung&err=invalid');
exit;
}
$stmt = db()->prepare('SELECT filename FROM fortbildung_materials WHERE id = ?');
$stmt->execute([$id]);
$row = $stmt->fetch();
if ($row) {
db()->prepare('DELETE FROM fortbildung_materials WHERE id = ?')->execute([$id]);
@unlink(dirname(__DIR__) . '/fortbildung-materials/' . $row['filename']);
}
header('Location: ../pages/admin.php?tab=fortbildung&ok=deleted');
exit;
}
header('Location: ../pages/admin.php?tab=fortbildung');
exit;
@@ -0,0 +1,45 @@
<?php
require_once __DIR__ . '/../lib/auth.php';
require_admin();
header('Content-Type: application/json');
if (($_SERVER['HTTP_X_REQUESTED_WITH'] ?? '') !== 'XMLHttpRequest') {
echo json_encode(['ok' => false, 'error' => 'forbidden']);
exit;
}
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
echo json_encode(['ok' => false, 'error' => 'method']);
exit;
}
$body = file_get_contents('php://input');
$data = json_decode($body, true);
$order = $data['order'] ?? null;
if (!is_array($order)) {
echo json_encode(['ok' => false, 'error' => 'invalid']);
exit;
}
$ids = array_values(array_filter(array_map('intval', $order), fn($v) => $v > 0));
if (empty($ids)) {
echo json_encode(['ok' => true]);
exit;
}
$now = date('c');
$pos = 10;
$stmt = db()->prepare('UPDATE fortbildung_materials SET sort_order = ?, updated_at = ? WHERE id = ?');
db()->beginTransaction();
foreach ($ids as $id) {
$stmt->execute([$pos, $now, $id]);
$pos += 10;
}
db()->commit();
echo json_encode(['ok' => true]);
exit;
@@ -0,0 +1,11 @@
<?php
require_once __DIR__ . '/../lib/auth.php';
$_SESSION = [];
if (ini_get('session.use_cookies')) {
$p = session_get_cookie_params();
setcookie(session_name(), '', time() - 42000,
$p['path'], $p['domain'], $p['secure'], $p['httponly']);
}
session_destroy();
header('Location: ../index.php');
exit;
@@ -0,0 +1,34 @@
<?php
require_once __DIR__ . '/../lib/auth.php';
require_once __DIR__ . '/../lib/db.php';
require_admin();
$action = $_POST['_action'] ?? '';
if ($action === 'add') {
$title = trim((string)($_POST['title'] ?? ''));
$text = trim((string)($_POST['text'] ?? ''));
$date = trim((string)($_POST['date'] ?? '')) ?: date('Y-m-d');
if ($title === '' || $text === '') {
header('Location: ../pages/admin.php?tab=news&err=empty');
exit;
}
$stmt = db()->prepare(
'INSERT INTO news (title, text, date, created_at) VALUES (?, ?, ?, ?)'
);
$stmt->execute([$title, $text, $date, date('c')]);
header('Location: ../pages/admin.php?tab=news&ok=added');
exit;
}
if ($action === 'delete') {
$id = (int)($_POST['news_id'] ?? 0);
if ($id > 0) {
db()->prepare('DELETE FROM news WHERE id = ?')->execute([$id]);
}
header('Location: ../pages/admin.php?tab=news&ok=deleted');
exit;
}
header('Location: ../pages/admin.php?tab=news');
exit;
@@ -0,0 +1,74 @@
<?php
require_once __DIR__ . '/../lib/auth.php';
require_once __DIR__ . '/../lib/db.php';
require_login();
$ajax = !empty($_POST['_ajax']);
function ajax_error(string $msg): never {
header('Content-Type: application/json');
echo json_encode(['ok' => false, 'error' => $msg]);
exit;
}
function ajax_ok(): never {
header('Content-Type: application/json');
echo json_encode(['ok' => true]);
exit;
}
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
if ($ajax) ajax_error('Ungültige Anfrage.');
header('Location: ../pages/dienstplan.php'); exit;
}
$user_id = current_user()['id'];
$year = (int)($_POST['year'] ?? 0);
$month = (int)($_POST['month'] ?? 0);
if ($year < 2020 || $year > 2099 || $month < 1 || $month > 12) {
if ($ajax) ajax_error('Ungültiger Monat.');
header('Location: ../pages/dienstplan.php'); exit;
}
$back = '../pages/dienstplan.php?year=' . $year . '&month=' . $month;
$month_str = sprintf('%04d-%02d', $year, $month);
$schicht_ok = ['frueh', 'spaet', 'nacht'];
$submitted = $_POST['schicht'] ?? [];
if (!is_array($submitted)) {
header('Location: ' . $back . '&saved=1'); exit;
}
$now = date('c');
db()->beginTransaction();
try {
foreach ($submitted as $date => $schicht) {
if (!is_string($date) || !preg_match('/^\d{4}-\d{2}-\d{2}$/', $date)) continue;
if (substr($date, 0, 7) !== $month_str) continue;
[$y, $m, $d] = array_map('intval', explode('-', $date));
if (!checkdate($m, $d, $y)) continue;
if (in_array($schicht, $schicht_ok, true)) {
db()->prepare(
'INSERT INTO dienstplan (user_id, date, schicht, created_at, updated_at)
VALUES (?, ?, ?, ?, ?)
ON DUPLICATE KEY UPDATE schicht=VALUES(schicht), updated_at=VALUES(updated_at)'
)->execute([$user_id, $date, $schicht, $now, $now]);
} else {
db()->prepare(
'DELETE FROM dienstplan WHERE user_id = ? AND date = ?'
)->execute([$user_id, $date]);
}
}
db()->commit();
} catch (\PDOException $ex) {
if (db()->inTransaction()) db()->rollBack();
$msg = 'Datenbankfehler: ' . $ex->getMessage();
if ($ajax) ajax_error($msg);
header('Location: ' . $back . '&error=' . urlencode($msg));
exit;
}
if ($ajax) ajax_ok();
header('Location: ' . $back . '&saved=1');
exit;
@@ -0,0 +1,74 @@
<?php
require_once __DIR__ . '/../lib/auth.php';
require_login();
$config = _omsorgcore_config();
$token = $_SESSION['omsorgcore_access_token'];
$action = $_POST['action'] ?? '';
$employeeId = $_SESSION['omsorgcore_profile']['employeeId'] ?? null;
if ($action === 'profile') {
$email = trim($_POST['email'] ?? '');
$telefon = trim($_POST['telefon'] ?? '');
if ($email !== '' && !filter_var($email, FILTER_VALIDATE_EMAIL)) {
header('Location: ../pages/settings.php?error=' . urlencode('Ungültige E-Mail-Adresse.'));
exit;
}
if (!$employeeId) {
header('Location: ../pages/settings.php?error=' . urlencode('Kein verknüpfter Mitarbeiter-Datensatz gefunden.'));
exit;
}
$current = omsorgcore_employees_get($config, $token, $employeeId);
if (!$current['ok']) {
header('Location: ../pages/settings.php?error=' . urlencode('Profil konnte nicht geladen werden.'));
exit;
}
$payload = $current['data'];
$payload['email'] = $email;
$payload['phoneNumber'] = $telefon;
unset($payload['id']);
$result = omsorgcore_employees_update($config, $token, $employeeId, $payload);
if (!$result['ok']) {
header('Location: ../pages/settings.php?error=' . urlencode('Profil konnte nicht gespeichert werden.'));
exit;
}
_refresh_profile();
header('Location: ../pages/settings.php?saved=1');
exit;
}
if ($action === 'password') {
$old = $_POST['old_password'] ?? '';
$new = $_POST['new_password'] ?? '';
$repeat = $_POST['new_password_repeat'] ?? '';
if (mb_strlen($new) < 8) {
header('Location: ../pages/settings.php?error=' . urlencode('Neues Passwort muss mindestens 8 Zeichen haben.'));
exit;
}
if ($new !== $repeat) {
header('Location: ../pages/settings.php?error=' . urlencode('Die neuen Passwörter stimmen nicht überein.'));
exit;
}
$result = omsorgcore_change_password($config, $token, $old, $new);
if (!$result['ok']) {
$message = $result['status'] === 401 ? 'Aktuelles Passwort ist falsch.' : 'Passwort konnte nicht geändert werden.';
header('Location: ../pages/settings.php?error=' . urlencode($message));
exit;
}
// Serverseitig wurden alle Sessions widerrufen - neuer Login mit dem neuen Passwort ist nötig.
_clear_omsorgcore_session();
header('Location: ../index.php?pwchanged=1');
exit;
}
header('Location: ../pages/settings.php');
exit;
@@ -0,0 +1,73 @@
<?php
require_once __DIR__ . '/../lib/auth.php';
require_once __DIR__ . '/../lib/mail.php';
require_login();
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
header('Location: ../pages/abwesenheitsantrag.php');
exit;
}
$back = '../pages/abwesenheitsantrag.php';
$von = trim($_POST['von'] ?? '');
$bis = trim($_POST['bis'] ?? '');
$grund = trim($_POST['grund'] ?? '');
$vertretung = trim($_POST['vertretung'] ?? '');
$nachricht = trim($_POST['nachricht'] ?? '');
if ($von === '' || $bis === '' || $grund === '') {
header('Location: ' . $back . '?error=' . urlencode('Von, Bis und Grund sind Pflichtfelder.'));
exit;
}
$allowed_grund = ['krankheit', 'arztbesuch', 'behoerdengang', 'sonderurlaub', 'elternzeit_pflegezeit', 'sonstiges'];
if (!in_array($grund, $allowed_grund, true)) {
header('Location: ' . $back . '?error=' . urlencode('Ungültiger Grund.'));
exit;
}
if (!strtotime($von) || !strtotime($bis)) {
header('Location: ' . $back . '?error=' . urlencode('Ungültiges Datum.'));
exit;
}
if (strtotime($von) > strtotime($bis)) {
header('Location: ' . $back . '?error=' . urlencode('Das Startdatum muss vor dem Enddatum liegen.'));
exit;
}
$user_id = current_user()['id'];
$now = date('c');
db()->prepare(
'INSERT INTO requests_abwesenheitsantrag (user_id, status, admin_note, created_at, updated_at, von, bis, grund, vertretung, nachricht)
VALUES (?, \'pending\', \'\', ?, ?, ?, ?, ?, ?, ?)'
)->execute([$user_id, $now, $now, $von, $bis, $grund, $vertretung, $nachricht]);
$grund_labels = [
'krankheit' => 'Krankheit',
'arztbesuch' => 'Arztbesuch',
'behoerdengang' => 'Behördengang',
'sonderurlaub' => 'Sonderurlaub',
'elternzeit_pflegezeit' => 'Elternzeit / Pflegezeit',
'sonstiges' => 'Sonstiges',
];
$config = require __DIR__ . '/../lib/config.php';
smtp_send(
$config,
$config['mail_sabrina'],
'Neuer Abwesenheitsantrag: ' . current_name(),
"Ein Abwesenheitsantrag wurde eingereicht.\n\n"
. 'Mitarbeiter: ' . current_name() . "\n"
. 'Von: ' . $von . "\n"
. 'Bis: ' . $bis . "\n"
. 'Grund: ' . ($grund_labels[$grund] ?? $grund) . "\n"
. 'Vertretung: ' . ($vertretung ?: '—') . "\n"
. 'Nachricht: ' . ($nachricht ?: '—') . "\n\n"
. "---\nGesendet über OMSORG Connect"
);
header('Location: ' . $back . '?success=1');
exit;
@@ -0,0 +1,63 @@
<?php
require_once __DIR__ . '/../lib/auth.php';
require_once __DIR__ . '/../lib/mail.php';
require_login();
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
header('Location: ../pages/benefitsantrag.php');
exit;
}
$back = '../pages/benefitsantrag.php';
$benefit = trim($_POST['benefit'] ?? '');
$nachricht = trim($_POST['nachricht'] ?? '');
$allowed_benefits = ['yoga', 'autogenes_training', 'massage', 'tankgutschein', 'online_gutscheine'];
if (!in_array($benefit, $allowed_benefits, true)) {
if (!empty($_SERVER['HTTP_X_REQUESTED_WITH']) && $_SERVER['HTTP_X_REQUESTED_WITH'] === 'XMLHttpRequest') {
header('Content-Type: application/json');
http_response_code(400);
echo json_encode(['ok' => false, 'error' => 'Bitte einen Benefit auswählen.']);
exit;
}
header('Location: ' . $back . '?error=' . urlencode('Bitte einen Benefit auswählen.'));
exit;
}
$user_id = current_user()['id'];
$now = date('c');
db()->prepare(
'INSERT INTO requests_benefitsantrag (user_id, status, admin_note, created_at, updated_at, benefit, nachricht)
VALUES (?, \'pending\', \'\', ?, ?, ?, ?)'
)->execute([$user_id, $now, $now, $benefit, $nachricht]);
$benefit_labels = [
'yoga' => 'Yoga',
'autogenes_training' => 'Autogenes Training',
'massage' => 'Massage',
'tankgutschein' => 'Tankgutschein',
'online_gutscheine' => 'Online-Gutscheine',
];
$config = require __DIR__ . '/../lib/config.php';
smtp_send(
$config,
$config['mail_info'],
'Neuer Benefitsantrag: ' . current_name(),
"Ein Benefitsantrag wurde eingereicht.\n\n"
. 'Mitarbeiter: ' . current_name() . "\n"
. 'Benefit: ' . ($benefit_labels[$benefit] ?? $benefit) . "\n"
. 'Nachricht: ' . ($nachricht ?: '—') . "\n\n"
. "---\nGesendet über OMSORG Connect"
);
if (!empty($_SERVER['HTTP_X_REQUESTED_WITH']) && $_SERVER['HTTP_X_REQUESTED_WITH'] === 'XMLHttpRequest') {
header('Content-Type: application/json');
echo json_encode(['ok' => true, 'benefit' => $benefit]);
exit;
}
header('Location: ' . $back . '?success=1');
exit;
@@ -0,0 +1,54 @@
<?php
require_once __DIR__ . '/../lib/auth.php';
require_once __DIR__ . '/../lib/db.php';
require_login();
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
header('Location: ../pages/einsatzbewertung.php');
exit;
}
$back = '../pages/einsatzbewertung.php';
$einsatzort = trim($_POST['einsatzort'] ?? '');
$von = trim($_POST['von'] ?? '');
$bis = trim($_POST['bis'] ?? '');
$bewertung = (int)($_POST['bewertung'] ?? 0);
$wieder = isset($_POST['wieder']) ? 1 : 0;
$feedback = trim($_POST['feedback'] ?? '');
if ($einsatzort === '') {
header('Location: ' . $back . '?error=' . urlencode('Bitte den Einsatzort angeben.'));
exit;
}
if ($von === '' || $bis === '') {
header('Location: ' . $back . '?error=' . urlencode('Von und Bis sind Pflichtfelder.'));
exit;
}
if (!strtotime($von) || !strtotime($bis)) {
header('Location: ' . $back . '?error=' . urlencode('Ungültiges Datum.'));
exit;
}
if (strtotime($von) > strtotime($bis)) {
header('Location: ' . $back . '?error=' . urlencode('Das Startdatum muss vor dem Enddatum liegen.'));
exit;
}
if ($bewertung < 1 || $bewertung > 5) {
header('Location: ' . $back . '?error=' . urlencode('Bitte eine Bewertung von 1 bis 5 Sternen auswählen.'));
exit;
}
$user_id = current_user()['id'];
$now = date('c');
db()->prepare(
'INSERT INTO einsatzbewertungen (user_id, einsatzort, von, bis, bewertung, wieder, feedback, created_at)
VALUES (?, ?, ?, ?, ?, ?, ?, ?)'
)->execute([$user_id, $einsatzort, $von, $bis, $bewertung, $wieder, $feedback, $now]);
header('Location: ' . $back . '?success=1');
exit;
@@ -0,0 +1,73 @@
<?php
require_once __DIR__ . '/../lib/auth.php';
require_once __DIR__ . '/../lib/upload.php';
require_once __DIR__ . '/../lib/mail.php';
require_login();
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
header('Location: ../pages/fortbildungsantrag.php');
exit;
}
$back = '../pages/fortbildungsantrag.php';
$anliegen = trim($_POST['anliegen'] ?? '');
$thema = trim($_POST['thema'] ?? '');
$nachricht = trim($_POST['nachricht'] ?? '');
$allowed_anliegen = ['wbl_pdl', 'fortbildung', 'pflichtschulung'];
if (!in_array($anliegen, $allowed_anliegen, true)) {
redirect_error($back, 'Bitte ein Anliegen auswählen.');
}
if ($thema === '') {
redirect_error($back, 'Bitte ein Thema angeben.');
}
$up = handle_upload($_FILES['datei'] ?? [], [
'allowed' => UPLOAD_TYPES_DOCS,
'dir' => dirname(__DIR__) . '/uploads/',
'prefix' => 'fortbildung',
'required' => false,
]);
if (!$up['ok']) {
redirect_error($back, $up['error']);
}
$filename = $up['filename'] ?? '';
$original_name = $up['original_name'] ?? '';
$mime = $up['mime'] ?? null;
$user_id = current_user()['id'];
$now = date('c');
db()->prepare(
'INSERT INTO requests_fortbildungsantrag
(user_id, status, admin_note, anliegen, thema, nachricht, filename, original_name, created_at, updated_at)
VALUES (?, \'pending\', \'\', ?, ?, ?, ?, ?, ?, ?)'
)->execute([$user_id, $anliegen, $thema, $nachricht, $filename, $original_name, $now, $now]);
$anliegen_labels = [
'wbl_pdl' => 'WBL / PDL',
'fortbildung' => 'Fortbildung',
'pflichtschulung' => 'Pflichtschulung',
];
$config = require __DIR__ . '/../lib/config.php';
$attach = ($filename !== '')
? ['path' => dirname(__DIR__) . '/uploads/' . $filename, 'name' => $original_name, 'mime' => $mime]
: [];
smtp_send(
$config,
$config['mail_info'],
'Neuer Fortbildungsantrag: ' . current_name(),
"Ein Fortbildungsantrag wurde eingereicht.\n\n"
. 'Mitarbeiter: ' . current_name() . "\n"
. 'Anliegen: ' . ($anliegen_labels[$anliegen] ?? $anliegen) . "\n"
. 'Thema: ' . $thema . "\n"
. 'Nachricht: ' . ($nachricht ?: '—') . "\n\n"
. "---\nGesendet über OMSORG Connect",
$attach
);
redirect_ok($back);
@@ -0,0 +1,63 @@
<?php
require_once __DIR__ . '/../lib/auth.php';
require_once __DIR__ . '/../lib/upload.php';
require_once __DIR__ . '/../lib/mail.php';
require_login();
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
header('Location: ../pages/stundennachweis.php');
exit;
}
$back = '../pages/stundennachweis.php';
$monat_m = trim($_POST['monat_m'] ?? '');
$monat_j = trim($_POST['monat_j'] ?? '');
if (!preg_match('/^\d{2}$/', $monat_m) || !preg_match('/^\d{4}$/', $monat_j)
|| (int)$monat_m < 1 || (int)$monat_m > 12
|| (int)$monat_j < 2020 || (int)$monat_j > 2099) {
redirect_error($back, 'Bitte einen gültigen Monat und ein gültiges Jahr auswählen.');
}
$monat = $monat_j . '-' . $monat_m;
$dir = dirname(__DIR__) . '/uploads/';
$up = handle_upload($_FILES['datei'] ?? [], [
'allowed' => UPLOAD_TYPES_PDF_IMG,
'dir' => $dir,
'prefix' => 'stundennachweis',
'messages' => [
'missing' => 'Bitte eine Datei hochladen.',
'ext' => 'Dateityp nicht erlaubt. Erlaubt: PDF, JPG, PNG.',
],
]);
if (!$up['ok']) {
redirect_error($back, $up['error']);
}
$safe_name = $up['filename'];
$original_name = $up['original_name'];
$mime = $up['mime'];
$user_id = current_user()['id'];
$now = date('c');
db()->prepare(
'INSERT INTO requests_stundennachweis
(user_id, status, admin_note, monat, filename, original_name, created_at, updated_at)
VALUES (?, \'pending\', \'\', ?, ?, ?, ?, ?)'
)->execute([$user_id, $monat, $safe_name, $original_name, $now, $now]);
$config = require __DIR__ . '/../lib/config.php';
smtp_send(
$config,
$config['mail_sabrina'],
'Neuer Stundennachweis: ' . current_name(),
"Ein Stundennachweis wurde hochgeladen.\n\n"
. 'Mitarbeiter: ' . current_name() . "\n"
. 'Monat: ' . $monat . "\n"
. 'Datei: ' . $original_name . "\n\n"
. "---\nGesendet über OMSORG Connect",
['path' => $dir . $safe_name, 'name' => $original_name, 'mime' => $mime]
);
redirect_ok($back);
@@ -0,0 +1,73 @@
<?php
require_once __DIR__ . '/../lib/auth.php';
require_once __DIR__ . '/../lib/upload.php';
require_once __DIR__ . '/../lib/mail.php';
require_login();
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
header('Location: ../pages/urlaubsantrag.php');
exit;
}
$back = '../pages/urlaubsantrag.php';
verify_csrf();
$von = trim($_POST['von'] ?? '');
$bis = trim($_POST['bis'] ?? '');
$vertretung = trim($_POST['vertretung'] ?? '');
$nachricht = trim($_POST['nachricht'] ?? '');
if ($von === '' || $bis === '') {
redirect_error($back, 'Von und Bis sind Pflichtfelder.');
}
if (!strtotime($von) || !strtotime($bis)) {
redirect_error($back, 'Ungültiges Datum.');
}
if (strtotime($von) > strtotime($bis)) {
redirect_error($back, 'Das Startdatum muss vor dem Enddatum liegen.');
}
$up = handle_upload($_FILES['datei'] ?? [], [
'allowed' => UPLOAD_TYPES_DOCS,
'dir' => dirname(__DIR__) . '/uploads/',
'prefix' => 'urlaubsantrag',
'required' => false,
]);
if (!$up['ok']) {
redirect_error($back, $up['error']);
}
$filename = $up['filename'] ?? '';
$original_name = $up['original_name'] ?? '';
$mime = $up['mime'] ?? null;
$user_id = current_user()['id'];
$now = date('c');
db()->prepare(
'INSERT INTO requests_urlaubsantrag (user_id, status, admin_note, created_at, updated_at, von, bis, vertretung, nachricht, filename, original_name)
VALUES (?, \'pending\', \'\', ?, ?, ?, ?, ?, ?, ?, ?)'
)->execute([$user_id, $now, $now, $von, $bis, $vertretung, $nachricht, $filename, $original_name]);
$config = require __DIR__ . '/../lib/config.php';
$attach = ($filename !== '')
? ['path' => dirname(__DIR__) . '/uploads/' . $filename, 'name' => $original_name, 'mime' => $mime]
: [];
smtp_send(
$config,
$config['mail_sabrina'],
'Neuer Urlaubsantrag: ' . current_name(),
"Ein Urlaubsantrag wurde eingereicht.\n\n"
. 'Mitarbeiter: ' . current_name() . "\n"
. 'Von: ' . $von . "\n"
. 'Bis: ' . $bis . "\n"
. 'Vertretung: ' . ($vertretung ?: '—') . "\n"
. 'Nachricht: ' . ($nachricht ?: '—') . "\n\n"
. "---\nGesendet über OMSORG Connect",
$attach
);
redirect_ok($back);
@@ -0,0 +1,62 @@
<?php
require_once __DIR__ . '/../lib/auth.php';
require_once __DIR__ . '/../lib/mail.php';
require_login();
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
header('Location: ../pages/werben.php');
exit;
}
$back = '../pages/werben.php';
$name = trim($_POST['name'] ?? '');
$email = trim($_POST['email'] ?? '');
$qualifikation = trim($_POST['qualifikation'] ?? '');
$nachricht = trim($_POST['nachricht'] ?? '');
if ($name === '' || $email === '' || $qualifikation === '') {
header('Location: ' . $back . '?error=' . urlencode('Name, E-Mail und Qualifikation sind Pflichtfelder.'));
exit;
}
if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
header('Location: ' . $back . '?error=' . urlencode('Bitte eine gültige E-Mail-Adresse eingeben.'));
exit;
}
$allowed_qual = ['1jahr', '3jahr'];
if (!in_array($qualifikation, $allowed_qual, true)) {
header('Location: ' . $back . '?error=' . urlencode('Ungültige Qualifikation.'));
exit;
}
$user_id = current_user()['id'];
$now = date('c');
db()->prepare(
'INSERT INTO requests_werben (user_id, status, admin_note, created_at, updated_at, name, email, qualifikation, nachricht)
VALUES (?, \'pending\', \'\', ?, ?, ?, ?, ?, ?)'
)->execute([$user_id, $now, $now, $name, $email, $qualifikation, $nachricht]);
$qual_labels = [
'1jahr' => '1 Jährige Examinierte Pflegekraft',
'3jahr' => '3 Jährige Examinierte Pflegekraft',
];
$config = require __DIR__ . '/../lib/config.php';
smtp_send(
$config,
$config['mail_info'],
'Mitarbeiter werben: ' . current_name(),
"Ein Mitarbeiter wurde empfohlen.\n\n"
. 'Empfohlen von: ' . current_name() . "\n"
. 'Name der empfohlenen Person: ' . $name . "\n"
. 'E-Mail: ' . $email . "\n"
. 'Art der Stelle: ' . ($qual_labels[$qualifikation] ?? $qualifikation) . "\n"
. 'Nachricht: ' . ($nachricht ?: '—') . "\n\n"
. "---\nGesendet über OMSORG Connect"
);
header('Location: ' . $back . '?success=1');
exit;
@@ -0,0 +1,80 @@
<?php
require_once __DIR__ . '/../lib/auth.php';
require_once __DIR__ . '/../lib/upload.php';
require_login();
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
header('Location: ../pages/settings.php');
exit;
}
$back = '../pages/settings.php';
$user = current_user();
$dir = dirname(__DIR__) . '/assets/avatars/';
$config = _omsorgcore_config();
$token = $_SESSION['omsorgcore_access_token'];
$employeeId = $_SESSION['omsorgcore_profile']['employeeId'] ?? null;
function _set_avatar_file_name(array $config, string $token, string $employeeId, ?string $filename): bool {
$current = omsorgcore_employees_get($config, $token, $employeeId);
if (!$current['ok']) {
return false;
}
$payload = $current['data'];
$payload['avatarFileName'] = $filename;
unset($payload['id']);
$result = omsorgcore_employees_update($config, $token, $employeeId, $payload);
if ($result['ok']) {
_refresh_profile();
}
return $result['ok'];
}
if (!$employeeId) {
redirect_error($back, 'Kein verknüpfter Mitarbeiter-Datensatz gefunden.');
}
// Remove avatar
if (!empty($_POST['remove_avatar'])) {
if (!empty($user['avatar'])) {
$old = $dir . basename($user['avatar']);
if (is_file($old)) {
unlink($old);
}
}
_set_avatar_file_name($config, $token, $employeeId, null);
redirect_ok($back, 'avatar_saved');
}
$up = handle_upload($_FILES['avatar'] ?? [], [
'allowed' => UPLOAD_TYPES_IMAGES,
'dir' => $dir,
'naming' => 'plain',
'max_bytes' => 3 * 1024 * 1024,
'messages' => [
'missing' => 'Bitte ein Bild auswählen.',
'upload' => 'Fehler beim Hochladen des Bildes.',
'ext' => 'Nur Bilder erlaubt (JPG, PNG, GIF, WebP).',
'mime' => 'Nur Bilder erlaubt (JPG, PNG, GIF, WebP).',
'size' => 'Bild ist zu groß (max. 3 MB).',
'move' => 'Fehler beim Speichern des Bildes.',
],
]);
if (!$up['ok']) {
redirect_error($back, $up['error']);
}
$safe_name = $up['filename'];
// Delete old avatar after successful upload
if (!empty($user['avatar'])) {
$old = $dir . basename($user['avatar']);
if (is_file($old)) {
unlink($old);
}
}
_set_avatar_file_name($config, $token, $employeeId, $safe_name);
redirect_ok($back, 'avatar_saved');
@@ -0,0 +1,42 @@
<?php
require_once __DIR__ . '/../lib/auth.php';
require_once __DIR__ . '/../lib/upload.php';
require_login();
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
header('Location: ../pages/dokumentenarchiv.php');
exit;
}
$back = '../pages/dokumentenarchiv.php';
$allowed_kategorien = ['Fortbildungsnachweis', 'Zeugnis', 'Bescheinigung', 'Sonstiges'];
$kategorie = trim($_POST['kategorie'] ?? '');
$beschreibung = trim($_POST['beschreibung'] ?? '');
if (!in_array($kategorie, $allowed_kategorien, true)) {
redirect_error($back, 'Bitte eine Kategorie auswählen.');
}
$filesize = $_FILES['datei']['size'] ?? 0;
$up = handle_upload($_FILES['datei'] ?? [], [
'allowed' => UPLOAD_TYPES_DOCS,
'dir' => dirname(__DIR__) . '/uploads/',
'prefix' => 'dokument',
]);
if (!$up['ok']) {
redirect_error($back, $up['error']);
}
$safe_name = $up['filename'];
$original_name = $up['original_name'];
$user_id = current_user()['id'];
$now = date('c');
db()->prepare(
'INSERT INTO dokumente (user_id, kategorie, beschreibung, filename, original_name, filesize, created_at)
VALUES (?, ?, ?, ?, ?, ?, ?)'
)->execute([$user_id, $kategorie, $beschreibung, $safe_name, $original_name, $filesize, $now]);
redirect_ok($back, 'ok');