web - refactor core modules, harden authorization, simplify endpoints
This commit is contained in:
@@ -33,7 +33,7 @@ class lib_Feiertage_Connector
|
||||
return $wert === $datum;
|
||||
});
|
||||
|
||||
if ($filter !== null) {
|
||||
if (!empty($filter)) {
|
||||
|
||||
$feiertagsname = implode(" ",array_keys($filter));
|
||||
}
|
||||
@@ -46,7 +46,7 @@ class lib_Feiertage_Connector
|
||||
if(isset(self::$cache['c0-'.$jahr]))
|
||||
return self::$cache['c0-'.$jahr];
|
||||
|
||||
$file = "data/feiertage_$jahr.json";
|
||||
$file = __DIR__ . "/../data/feiertage_$jahr.json";
|
||||
|
||||
if (!file_exists($file)) { //Ggf. Datei von externer API speichern
|
||||
|
||||
@@ -63,7 +63,7 @@ class lib_Feiertage_Connector
|
||||
if(isset(self::$cache['c0-'.$jahr.$land]))
|
||||
return self::$cache['c0-'.$jahr.$land];
|
||||
|
||||
$file = "data/feiertage_" . $jahr . "_" . $land . ".json";
|
||||
$file = __DIR__ . "/../data/feiertage_" . $jahr . "_" . $land . ".json";
|
||||
|
||||
if (!file_exists($file)) { //Ggf. Datei von externer API speichern
|
||||
|
||||
@@ -99,7 +99,7 @@ class lib_Feiertage_Connector
|
||||
if(isset(self::$cache['c1-'.$jahr]))
|
||||
return self::$cache['c1-'.$jahr];
|
||||
|
||||
$file = "data/ferientage_berlin_$jahr.json";
|
||||
$file = __DIR__ . "/../data/ferientage_berlin_$jahr.json";
|
||||
|
||||
if (!file_exists($file)) { //Ggf. Datei von externer API speichern
|
||||
|
||||
|
||||
@@ -0,0 +1,446 @@
|
||||
<?php
|
||||
/* Gemeinsame, seiteneffektarme Hilfsfunktionen des Seelsorgeplaners. */
|
||||
|
||||
if (!defined('SEELSORGE_INITIAL_VISIT_DATE')) {
|
||||
define('SEELSORGE_INITIAL_VISIT_DATE', '2024-07-11');
|
||||
}
|
||||
|
||||
function seelsorge_csrf_token(): string
|
||||
{
|
||||
if (empty($_SESSION['seelsorge_csrf'])) {
|
||||
$_SESSION['seelsorge_csrf'] = bin2hex(random_bytes(32));
|
||||
}
|
||||
return (string) $_SESSION['seelsorge_csrf'];
|
||||
}
|
||||
|
||||
function seelsorge_verify_csrf(array $input): void
|
||||
{
|
||||
$postedToken = $input['csrf_token'] ?? '';
|
||||
$sessionToken = $_SESSION['seelsorge_csrf'] ?? '';
|
||||
if (!is_string($postedToken) || !is_string($sessionToken) || $sessionToken === '' || !hash_equals($sessionToken, $postedToken)) {
|
||||
throw new InvalidArgumentException('Ungültige Anfrage. Bitte laden Sie die Seite neu.');
|
||||
}
|
||||
}
|
||||
|
||||
function seelsorge_profile_image_url(int $userId): string
|
||||
{
|
||||
$default = '/intranet_repo/general_files/profile_pictures/default.jpg';
|
||||
if ($userId <= 0 || empty($_SERVER['DOCUMENT_ROOT'])) {
|
||||
return $default;
|
||||
}
|
||||
|
||||
$images = glob($_SERVER['DOCUMENT_ROOT'] . "/intranet_repo/general_files/profile_pictures/{$userId}-*.jpg") ?: [];
|
||||
if (!$images) {
|
||||
return $default;
|
||||
}
|
||||
|
||||
usort($images, static fn($a, $b) => filemtime($b) <=> filemtime($a));
|
||||
return str_replace($_SERVER['DOCUMENT_ROOT'], '', $images[0]);
|
||||
}
|
||||
|
||||
function seelsorge_load_priests(PDO $pdo): array
|
||||
{
|
||||
$stmt = $pdo->query(
|
||||
'SELECT u.user_id, u.first_name, u.last_name, u.mail AS email
|
||||
FROM user_general_information u
|
||||
JOIN user_permissions p ON p.user_id = u.user_id
|
||||
WHERE p."PriesterIn" = 1
|
||||
ORDER BY u.last_name ASC, u.first_name ASC'
|
||||
);
|
||||
|
||||
return $stmt ? $stmt->fetchAll() : [];
|
||||
}
|
||||
|
||||
function seelsorge_priest_options(array $priests): string
|
||||
{
|
||||
$options = [];
|
||||
foreach ($priests as $priest) {
|
||||
$userId = (int) ($priest['user_id'] ?? 0);
|
||||
$firstName = trim((string) ($priest['first_name'] ?? ''));
|
||||
$lastName = trim((string) ($priest['last_name'] ?? ''));
|
||||
$email = trim((string) ($priest['email'] ?? ''));
|
||||
|
||||
if ($firstName !== '' && $lastName !== '') {
|
||||
$label = $lastName . ', ' . $firstName;
|
||||
} elseif ($lastName !== '' || $firstName !== '') {
|
||||
$label = $lastName !== '' ? $lastName : $firstName;
|
||||
} else {
|
||||
$label = $email !== '' ? $email : "User #{$userId}";
|
||||
}
|
||||
|
||||
$options[] = sprintf(
|
||||
'<option value="%d">%s</option>',
|
||||
$userId,
|
||||
htmlspecialchars($label, ENT_QUOTES, 'UTF-8')
|
||||
);
|
||||
}
|
||||
|
||||
return implode('', $options);
|
||||
}
|
||||
|
||||
function seelsorge_parse_visit_entry(array $input): array
|
||||
{
|
||||
$priestId = trim((string) ($input['pr_bezirk'] ?? ''));
|
||||
$entry = [
|
||||
'besuch_id' => max(0, (int) ($input['besuch_id'] ?? 0)),
|
||||
'last_name' => trim((string) ($input['last_name'] ?? '')),
|
||||
'first_name' => trim((string) ($input['first_name'] ?? '')),
|
||||
'birth_date' => trim((string) ($input['birth_date'] ?? '')),
|
||||
'address_street' => trim((string) ($input['address_street'] ?? '')),
|
||||
'address_plz' => trim((string) ($input['address_plz'] ?? '')),
|
||||
'address_link' => trim((string) ($input['address_link'] ?? '')),
|
||||
'phone' => preg_replace('/\D+/', '', trim((string) ($input['phone'] ?? ''))) ?: null,
|
||||
'comment' => trim((string) ($input['comment'] ?? '')),
|
||||
'letzter_besuch' => trim((string) ($input['letzter_besuch'] ?? '')) ?: SEELSORGE_INITIAL_VISIT_DATE,
|
||||
'priority' => (int) ($input['priority'] ?? 0),
|
||||
'pr_bezirk' => ctype_digit($priestId) ? (int) $priestId : 0,
|
||||
];
|
||||
|
||||
if ($entry['last_name'] === '' || $entry['first_name'] === '' || $entry['address_street'] === ''
|
||||
|| $entry['address_plz'] === '' || $entry['address_link'] === '' || $entry['pr_bezirk'] <= 0) {
|
||||
throw new InvalidArgumentException('Pflichtfelder fehlen.');
|
||||
}
|
||||
|
||||
foreach (['birth_date', 'letzter_besuch'] as $field) {
|
||||
$date = DateTime::createFromFormat('!Y-m-d', $entry[$field]);
|
||||
if (!$date || $date->format('Y-m-d') !== $entry[$field]) {
|
||||
throw new InvalidArgumentException('Ungültiges Datum.');
|
||||
}
|
||||
}
|
||||
|
||||
if ($entry['priority'] < 0 || $entry['priority'] > 2) {
|
||||
throw new InvalidArgumentException('Ungültige Priorität.');
|
||||
}
|
||||
|
||||
return $entry;
|
||||
}
|
||||
|
||||
function seelsorge_visit_entry_client_data(array $row): array
|
||||
{
|
||||
return [
|
||||
'besuch_id' => (int) ($row['besuch_id'] ?? 0),
|
||||
'last_name' => $row['last_name'] ?? '',
|
||||
'first_name' => $row['first_name'] ?? '',
|
||||
'birth_date' => $row['birth_date'] ?? '',
|
||||
'address_street' => $row['address_street'] ?? '',
|
||||
'address_plz' => $row['address_plz'] ?? '',
|
||||
'address_link' => $row['address_link'] ?? '',
|
||||
'phone' => $row['phone'] ?? '',
|
||||
'comment' => $row['comment'] ?? '',
|
||||
'letzter_besuch' => $row['letzter_besuch'] ?? '',
|
||||
'priority' => (int) ($row['priority'] ?? 0),
|
||||
'pr_bezirk' => $row['pr_bezirk'] === null ? '' : (int) $row['pr_bezirk'],
|
||||
'reservation' => $row['reservation'] ?? null,
|
||||
];
|
||||
}
|
||||
|
||||
function seelsorge_changed_visit_fields(array $oldEntry, array $newEntry): array
|
||||
{
|
||||
$labels = [
|
||||
'last_name' => 'Nachname',
|
||||
'first_name' => 'Vorname',
|
||||
'birth_date' => 'Geburtsdatum',
|
||||
'address_street' => 'Adresse',
|
||||
'address_plz' => 'PLZ/Ort',
|
||||
'address_link' => 'Karten-Link',
|
||||
'phone' => 'Telefon',
|
||||
'comment' => 'Kommentar',
|
||||
'letzter_besuch' => 'Letzter Besuch',
|
||||
'priority' => 'Priorität',
|
||||
'pr_bezirk' => 'Priester-Bezirk',
|
||||
];
|
||||
|
||||
$changed = [];
|
||||
foreach ($labels as $field => $label) {
|
||||
if ((string) ($oldEntry[$field] ?? '') !== (string) ($newEntry[$field] ?? '')) {
|
||||
$changed[] = $label;
|
||||
}
|
||||
}
|
||||
return $changed;
|
||||
}
|
||||
|
||||
function seelsorge_save_visit_entry(PDO $pdo, array $entry): void
|
||||
{
|
||||
$fields = [
|
||||
'last_name', 'first_name', 'birth_date', 'address_street', 'address_plz',
|
||||
'address_link', 'phone', 'comment', 'letzter_besuch', 'priority', 'pr_bezirk',
|
||||
];
|
||||
$values = array_map(static fn($field) => $entry[$field], $fields);
|
||||
$visitId = (int) $entry['besuch_id'];
|
||||
|
||||
$pdo->beginTransaction();
|
||||
try {
|
||||
if ($visitId === 0) {
|
||||
$stmt = $pdo->prepare(
|
||||
'INSERT INTO seelsorge_besuchsstellen
|
||||
(last_name, first_name, birth_date, address_street, address_plz, address_link, phone, comment, letzter_besuch, priority, pr_bezirk, reservation)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, NULL)'
|
||||
);
|
||||
$stmt->execute($values);
|
||||
$pdo->commit();
|
||||
return;
|
||||
}
|
||||
|
||||
$stmtOld = $pdo->prepare(
|
||||
'SELECT last_name, first_name, birth_date, address_street, address_plz, address_link, phone, comment, letzter_besuch, priority, pr_bezirk
|
||||
FROM seelsorge_besuchsstellen WHERE besuch_id = ?'
|
||||
);
|
||||
$stmtOld->execute([$visitId]);
|
||||
$oldEntry = $stmtOld->fetch();
|
||||
if (!$oldEntry) {
|
||||
throw new RuntimeException('Eintrag nicht gefunden.');
|
||||
}
|
||||
|
||||
$stmt = $pdo->prepare(
|
||||
'UPDATE seelsorge_besuchsstellen
|
||||
SET last_name = ?, first_name = ?, birth_date = ?, address_street = ?, address_plz = ?,
|
||||
address_link = ?, phone = ?, comment = ?, letzter_besuch = ?, priority = ?, pr_bezirk = ?
|
||||
WHERE besuch_id = ?'
|
||||
);
|
||||
$stmt->execute(array_merge($values, [$visitId]));
|
||||
|
||||
$changedFields = seelsorge_changed_visit_fields($oldEntry, $entry);
|
||||
if ($changedFields) {
|
||||
$personName = ($oldEntry['last_name'] ?? $entry['last_name']) . ', ' . ($oldEntry['first_name'] ?? $entry['first_name']);
|
||||
$stmtLog = $pdo->prepare(
|
||||
"INSERT INTO admin_messages
|
||||
(type, sender_name, sender_email, subject, message, status, processed_at, created_at)
|
||||
VALUES ('system', 'Seelsorgeplaner', '', ?, ?, 'done', NOW(), NOW())"
|
||||
);
|
||||
$stmtLog->execute([
|
||||
"Besuchsstelle geändert: {$personName}",
|
||||
"Besuchsstelle #{$visitId} ({$personName}) wurde geändert. Geänderte Felder: " . implode(', ', $changedFields) . '.',
|
||||
]);
|
||||
}
|
||||
|
||||
$pdo->commit();
|
||||
} catch (Throwable $e) {
|
||||
if ($pdo->inTransaction()) {
|
||||
$pdo->rollBack();
|
||||
}
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
|
||||
function seelsorge_visit_action(PDO $pdo, string $action, int $visitId, int $userId): void
|
||||
{
|
||||
if ($visitId <= 0) {
|
||||
throw new InvalidArgumentException('Ungültige Besuchs-ID.');
|
||||
}
|
||||
|
||||
if ($action === 'delete') {
|
||||
$stmt = $pdo->prepare('DELETE FROM seelsorge_besuchsstellen WHERE besuch_id = ?');
|
||||
$stmt->execute([$visitId]);
|
||||
if ($stmt->rowCount() === 0) {
|
||||
throw new RuntimeException('Eintrag nicht gefunden.');
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if ($action === 'prioritize') {
|
||||
$stmt = $pdo->prepare('UPDATE seelsorge_besuchsstellen SET priority = 2 WHERE besuch_id = ?');
|
||||
$stmt->execute([$visitId]);
|
||||
if ($stmt->rowCount() === 0) {
|
||||
throw new RuntimeException('Eintrag nicht gefunden.');
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
$stmt = $pdo->prepare('SELECT address_street, address_plz FROM seelsorge_besuchsstellen WHERE besuch_id = ?');
|
||||
$stmt->execute([$visitId]);
|
||||
$address = $stmt->fetch();
|
||||
if (!$address) {
|
||||
throw new RuntimeException('Eintrag nicht gefunden.');
|
||||
}
|
||||
|
||||
$params = [];
|
||||
switch ($action) {
|
||||
case 'reserve':
|
||||
$sql = 'UPDATE seelsorge_besuchsstellen SET reservation = ? WHERE address_street = ? AND address_plz = ?';
|
||||
$params[] = $userId;
|
||||
break;
|
||||
case 'unreserve':
|
||||
$sql = 'UPDATE seelsorge_besuchsstellen SET reservation = NULL WHERE address_street = ? AND address_plz = ?';
|
||||
break;
|
||||
case 'complete':
|
||||
$sql = 'UPDATE seelsorge_besuchsstellen SET letzter_besuch = CURRENT_DATE, priority = 0, reservation = NULL WHERE address_street = ? AND address_plz = ?';
|
||||
break;
|
||||
case 'update_date':
|
||||
$sql = 'UPDATE seelsorge_besuchsstellen SET letzter_besuch = CURRENT_DATE, priority = 0 WHERE address_street = ? AND address_plz = ?';
|
||||
break;
|
||||
default:
|
||||
throw new InvalidArgumentException('Unbekannte Aktion.');
|
||||
}
|
||||
|
||||
$stmt = $pdo->prepare($sql);
|
||||
$stmt->execute(array_merge($params, [$address['address_street'], $address['address_plz']]));
|
||||
}
|
||||
|
||||
function seelsorge_process_visit_command(PDO $pdo, array $input, int $userId): void
|
||||
{
|
||||
$command = trim((string) ($input['visit_command'] ?? ''));
|
||||
if ($command === 'save') {
|
||||
seelsorge_save_visit_entry($pdo, seelsorge_parse_visit_entry($input));
|
||||
return;
|
||||
}
|
||||
|
||||
seelsorge_visit_action($pdo, $command, (int) ($input['besuch_id'] ?? 0), $userId);
|
||||
}
|
||||
|
||||
function seelsorge_toggle_availability(PDO $pdo, string $date, int $userId): bool
|
||||
{
|
||||
$stmt = $pdo->prepare(
|
||||
'WITH deleted AS (
|
||||
DELETE FROM verfuegbarkeiten WHERE datum = ? AND user_id = ? RETURNING user_id
|
||||
)
|
||||
INSERT INTO verfuegbarkeiten (datum, user_id)
|
||||
SELECT ?, ? WHERE NOT EXISTS (SELECT 1 FROM deleted)
|
||||
RETURNING user_id'
|
||||
);
|
||||
$stmt->execute([$date, $userId, $date, $userId]);
|
||||
return $stmt->fetchColumn() !== false;
|
||||
}
|
||||
|
||||
function seelsorge_notify_match_by_mail(array $users, DateTimeInterface $date): void
|
||||
{
|
||||
if (count($users) < 2) {
|
||||
return;
|
||||
}
|
||||
|
||||
$names = array_values(array_filter(array_map(
|
||||
static fn($user) => trim(($user['first_name'] ?? '') . ' ' . ($user['last_name'] ?? '')),
|
||||
$users
|
||||
)));
|
||||
require_once $_SERVER['DOCUMENT_ROOT'] . '/intranet_repo/general_files/ini/send_mail.php';
|
||||
|
||||
$formattedDate = $date->format('d.m.Y');
|
||||
$subject = "Seelsorge-Match am {$formattedDate}";
|
||||
$message = "It's a match!\n\n" . implode(', ', $names) . " haben am {$formattedDate} für einen Seelsorgebesuch Zeit.\n\nBitte stimmt euch untereinander ab.";
|
||||
$host = $_SERVER['HTTP_HOST'] ?? intranet_env('INTRANET_FQDN');
|
||||
$plannerUrl = "https://{$host}/intranet_repo/projects/seelsorge/seelsorge.php";
|
||||
|
||||
foreach ($users as $user) {
|
||||
$email = trim((string) ($user['email'] ?? ''));
|
||||
if ($email === '' || !filter_var($email, FILTER_VALIDATE_EMAIL)) {
|
||||
continue;
|
||||
}
|
||||
if (!send_intranet_mail($email, $subject, $message, null, 'intranet@nak-reinickendorf.de', 'Zum Seelsorgeplaner', $plannerUrl)) {
|
||||
error_log("Seelsorge match mail failed for user " . (int) ($user['user_id'] ?? 0));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function seelsorge_calendar_availability_map(PDO $pdo, string $startDate, string $endDate, int $userId): array
|
||||
{
|
||||
$stmt = $pdo->prepare(
|
||||
'SELECT datum, COUNT(*) AS count,
|
||||
SUM(CASE WHEN user_id = ? THEN 1 ELSE 0 END) AS self_count
|
||||
FROM verfuegbarkeiten
|
||||
WHERE datum BETWEEN ? AND ?
|
||||
GROUP BY datum'
|
||||
);
|
||||
$stmt->execute([$userId, $startDate, $endDate]);
|
||||
|
||||
$map = [];
|
||||
foreach ($stmt->fetchAll() as $row) {
|
||||
$date = (string) $row['datum'];
|
||||
$map[$date] = [
|
||||
'count' => (int) $row['count'],
|
||||
'self' => (int) $row['self_count'] > 0,
|
||||
];
|
||||
}
|
||||
return $map;
|
||||
}
|
||||
|
||||
function seelsorge_group_households(array $entries): array
|
||||
{
|
||||
$households = [];
|
||||
foreach ($entries as $entry) {
|
||||
$key = ($entry['address_street'] ?? '') . '||' . ($entry['address_plz'] ?? '');
|
||||
if (!isset($households[$key])) {
|
||||
$households[$key] = [
|
||||
'members' => [],
|
||||
'address_street' => $entry['address_street'] ?? '',
|
||||
'address_plz' => $entry['address_plz'] ?? '',
|
||||
'address_link' => $entry['address_link'] ?? '',
|
||||
'phone' => $entry['phone'] ?? '',
|
||||
'reservation' => null,
|
||||
];
|
||||
}
|
||||
$households[$key]['members'][] = $entry;
|
||||
if (($entry['reservation'] ?? null) !== null) {
|
||||
$households[$key]['reservation'] = $entry['reservation'];
|
||||
}
|
||||
}
|
||||
return $households;
|
||||
}
|
||||
|
||||
function seelsorge_household_status(array $members, bool $isReserved, ?int $todayTimestamp = null): array
|
||||
{
|
||||
if ($isReserved) {
|
||||
return ['key' => 'blue', 'label' => 'Geplant'];
|
||||
}
|
||||
|
||||
$todayTimestamp = $todayTimestamp ?? strtotime(date('Y-m-d'));
|
||||
$status = 'green';
|
||||
foreach ($members as $member) {
|
||||
$lastVisit = strtotime((string) ($member['letzter_besuch'] ?? SEELSORGE_INITIAL_VISIT_DATE));
|
||||
$months = (int) floor(($todayTimestamp - $lastVisit) / (86400 * 30));
|
||||
$priority = (int) ($member['priority'] ?? 0);
|
||||
if ($priority === 2 || $months >= 6) {
|
||||
$status = 'red';
|
||||
break;
|
||||
}
|
||||
if ($priority === 1 || $months >= 2) {
|
||||
$status = 'orange';
|
||||
}
|
||||
}
|
||||
|
||||
$labels = ['red' => 'Dringend', 'orange' => 'Fällig', 'green' => 'Aktuell'];
|
||||
return ['key' => $status, 'label' => $labels[$status]];
|
||||
}
|
||||
|
||||
function seelsorge_relative_visit_text(string $lastVisit, ?int $todayTimestamp = null): string
|
||||
{
|
||||
if ($lastVisit === SEELSORGE_INITIAL_VISIT_DATE) {
|
||||
return 'Initialer Eintrag';
|
||||
}
|
||||
|
||||
$todayTimestamp = $todayTimestamp ?? strtotime(date('Y-m-d'));
|
||||
$days = max(0, (int) floor(($todayTimestamp - strtotime($lastVisit)) / 86400));
|
||||
if ($days >= 25) {
|
||||
$months = (int) floor($days / 30);
|
||||
return $months === 1 ? 'vor einem Monat' : "vor {$months} Monaten";
|
||||
}
|
||||
if ($days >= 7) {
|
||||
$weeks = (int) floor($days / 7);
|
||||
return $weeks === 1 ? 'vor einer Woche' : "vor {$weeks} Wochen";
|
||||
}
|
||||
return $days === 1 ? 'vor einem Tag' : "vor {$days} Tagen";
|
||||
}
|
||||
|
||||
function seelsorge_profile_records(PDO $pdo): array
|
||||
{
|
||||
static $cache = null;
|
||||
if ($cache !== null) {
|
||||
return $cache;
|
||||
}
|
||||
|
||||
$users = $pdo->query('SELECT * FROM user_general_information ORDER BY user_id')->fetchAll();
|
||||
$permissionRows = $pdo->query('SELECT * FROM user_permissions')->fetchAll();
|
||||
$permissionsByUser = [];
|
||||
foreach ($permissionRows as $permissions) {
|
||||
$permissionsByUser[(int) $permissions['user_id']] = $permissions;
|
||||
}
|
||||
|
||||
$cache = [];
|
||||
foreach ($users as $user) {
|
||||
$userId = (int) $user['user_id'];
|
||||
$cache[] = [
|
||||
'user' => $user,
|
||||
'permissions' => $permissionsByUser[$userId] ?? [],
|
||||
'image' => seelsorge_profile_image_url($userId),
|
||||
];
|
||||
}
|
||||
return $cache;
|
||||
}
|
||||
Reference in New Issue
Block a user