Clean up
All checks were successful
Deploy / deploy (push) Successful in 54s

This commit is contained in:
2026-08-21 02:20:46 +02:00
parent fae2dcc830
commit c8ed8fab3b
12 changed files with 130 additions and 80 deletions

View File

@@ -16,8 +16,10 @@ Papa-Kind-Treff ist eine PHP-basierte Plattform für Väter. Kernbereiche sind l
- Seiten-Admins erhalten eine eigene System-Sektion für globale Betriebs- und Diensteschalter.
## Konto / Mitgliederbereich
- Profil-Menü aktuell: `Profil`, `Kinder`, `Events`, `Orte & Veranstaltungen`, `Community`, `Einstellungen`, `Abmelden`
- Für `SiteOwner` kommt zusätzlich der Bereich `Profil-Levels` dazu.
- Das Profilmenü oben rechts enthält nur `Profil`, für berechtigte Nutzer zusätzlich `Admin-Einstellungen`, sowie `Abmelden`.
- Der Profilbereich enthält persönliche Angaben, Kinder, eigene Events, eigene Orte und Veranstaltungen, Cookie-/Kontoeinstellungen sowie Community-Informationen.
- Der Admin-Bereich ist getrennt vom Profilbereich und enthält abhängig von der Berechtigung Profil-Levels, Kategorien, System-Einstellungen sowie Community-Moderation und Listing-Freigaben.
- Bestehende Direktlinks zu `Kategorien`, `Profil-Levels` und `System` werden automatisch in den Admin-Bereich eingeordnet.
- Kinder können angelegt, bearbeitet und gelöscht werden.
- Wenn ein Geburtsdatum gesetzt ist, wird das Alter automatisch berechnet und später bei Bedarf aktualisiert.
- Die Konto-E-Mail wird app-seitig verschlüsselt gespeichert und über einen separaten HMAC-Lookup-Hash adressiert.

View File

@@ -16,7 +16,7 @@ Stand: 2026-08-10
## Aktuelle Strukturhinweise
- Avatar-Verwaltung: `src/App/Avatar/AvatarManager.php`
- aktive Styles: `src/App/Avatar/Lorelei.php`, `src/App/Avatar/Croodles.php`
- Profil-/Kinder-/Event-/Ausflugsziel-Logik im Mitgliederbereich: `partials/landing/account/dashboard.php`
- Profil- und Admin-Bereiche mit getrennten Seitennavigationen: `partials/landing/account/dashboard.php`, Zugriffskontext in `src/App/AccountPages.php`, Einstieg über `partials/structure/nav.php`
- Event-Quellen aus Datenbank plus Speicherung der Event-Location-Herkunft: `partials/landing/account/dashboard.php`, `src/App/AccountPages.php`, `Internal/db/schema.sql`
- Consent- und rechtliche Texte: `public/page/datenschutz.php`
- System-Einstellungen: `src/App/SystemSettings.php`

View File

@@ -16,8 +16,9 @@ Papa-Kind-Treff ist eine PHP-basierte Plattform für Väter mit Fokus auf lokale
- Community-Admin-Bereich für Bewerbungen, Meldungen, Rollen und Migration
- standortbasierte Sortierung für die neuesten Events
- Mitgliederbereich mit linker Bereichsnavigation
- Profil-Menü mit `Profil`, `Kinder`, `Events`, `Orte & Veranstaltungen`, `Community`, `Einstellungen`
- Für `SiteOwner` gibt es zusätzlich den Punkt `Profil-Levels`
- Profilmenü oben rechts mit `Profil` sowie für Berechtigte `Admin-Einstellungen`
- Profilbereich für persönliche Angaben, Kinder, eigene Events, eigene Orte und Veranstaltungen, Einstellungen und Community-Informationen
- Separater Admin-Bereich für Profil-Levels, Kategorien, System-Einstellungen sowie Moderation und Freigaben von Orten und Veranstaltungen
- Konto-E-Mails sowie sensible Profilfelder werden app-seitig verschlüsselt gespeichert
- Kinder können angelegt, bearbeitet und gelöscht werden
- Profiladresse mit verschlüsselter Straße/Hausnummer, Adresssuche, Browser-Übernahme und Validierung

View File

@@ -16,8 +16,10 @@ Papa-Kind-Treff is a PHP-based platform for fathers. Core areas are local events
- Site admins get a dedicated system section for global operating and service flags.
## Account / Member Area
- Profile menu currently contains `Profile`, `Children`, `Events`, `Places & Events`, `Community`, `Settings`, `Logout`.
- `SiteOwner` additionally gets the area `Profile Levels`.
- The top-right profile menu only contains `Profile`, `Admin Settings` for authorized users, and `Logout`.
- The profile area contains personal information, children, own events, own places and event-style entries, cookie/account settings, and community information.
- The admin area is separated from the profile area and contains, depending on permissions, profile levels, categories, system settings, community moderation, and listing approvals.
- Existing direct links to `Categories`, `Profile Levels`, and `System` are automatically assigned to the admin area.
- Children can be created, edited, and deleted.
- If a birth date is set, age is calculated automatically and updated later when needed.
- Account email is encrypted application-side and addressed through a separate HMAC lookup hash.

View File

@@ -16,7 +16,7 @@ Updated: 2026-08-10
## Current Structure Notes
- avatar management: `src/App/Avatar/AvatarManager.php`
- active avatar styles: `src/App/Avatar/Lorelei.php`, `src/App/Avatar/Croodles.php`
- profile/children/event/outing logic inside member area: `partials/landing/account/dashboard.php`
- separated profile and admin navigation inside the member area: `partials/landing/account/dashboard.php`, access context in `src/App/AccountPages.php`, entry through `partials/structure/nav.php`
- database-backed event location sources plus persisted event location origin: `partials/landing/account/dashboard.php`, `src/App/AccountPages.php`, `Internal/db/schema.sql`
- consent and legal texts: `public/page/datenschutz.php`
- system settings: `src/App/SystemSettings.php`

View File

@@ -16,8 +16,9 @@ Papa-Kind-Treff is a PHP-based platform for fathers focused on local events, app
- community admin area for applications, reports, roles, and migrations
- location-based ordering for the newest events
- member area with left-side section navigation
- profile menu with `Profile`, `Children`, `Events`, `Places & Events`, `Community`, `Settings`
- `SiteOwner` additionally gets the member-area item `Profile Levels`
- top-right profile menu with `Profile` and, for authorized users, `Admin Settings`
- profile area for personal information, children, own events, own places and event-style entries, settings, and community information
- separate admin area for profile levels, categories, system settings, and moderation and approvals for places and event-style entries
- account emails and sensitive profile fields are stored encrypted application-side
- children can be created, edited, and deleted
- profile address supports encrypted street/house number, address search, browser import, and validation

View File

@@ -36,8 +36,9 @@ Papa-Kind-Treff ist eine PHP-basierte Plattform für Väter mit Fokus auf:
- Primärbegriff im Produkt: `Events`
- `Termine` und `Treffen` ergänzend in SEO- und Erklärungstexten
- Hauptnavigation aktuell: `Home`, `Event Suche`, `Community`
- Profil-Menü aktuell: `Profil`, `Kinder`, `Events`, `Orte & Veranstaltungen`, `Community`, `Einstellungen`, `Abmelden`
- Für `SiteOwner` gibt es zusätzlich den Mitgliederbereichspunkt `Profil-Levels`
- Das Profilmenü oben rechts trennt klar zwischen `Profil` und dem nur für Berechtigte sichtbaren Punkt `Admin-Einstellungen`.
- Der Profilbereich bündelt persönliche Angaben, Kinder, eigene Events, eigene Orte und Veranstaltungen, Cookie-/Kontoeinstellungen sowie Community-Informationen.
- Der Admin-Bereich bündelt getrennt Profil-Levels, Kategorien, System-Einstellungen sowie die Moderation und Freigabe von Orten und Veranstaltungen.
- In `Profil-Levels` kann der `SiteOwner` Community-Level pflegen, Benutzer suchen und Community-Punkte manuell mit Begründung erhöhen
- In `Profil-Levels` werden zusätzlich Nutzer angezeigt, die in den letzten 30 Tagen mindestens den Rang `Säule der Väter-Community` erreicht haben
- Im Bereich `Community-Admin` läuft die Rollenvergabe jetzt über eine Benutzersuche statt über rohe Benutzer-IDs; `Site-Admins` bleiben dabei auf die vorhandene Rollenlogik begrenzt

View File

@@ -77,7 +77,7 @@ $migrationStatus = ($migration && $canManageApplications) ? $migration->status()
<div class="forum-breadcrumbs">
<a href="/">Home</a>
<span>/</span>
<a href="/dashboard">Mitgliederbereich</a>
<a href="/dashboard?area=admin&amp;section=admin-home">Admin-Einstellungen</a>
<span>/</span>
<span>Community-Admin</span>
</div>
@@ -88,7 +88,7 @@ $migrationStatus = ($migration && $canManageApplications) ? $migration->status()
<p class="forum-hero__copy">Moderation, Bewerbungen und Rollen an einem Ort.</p>
</div>
<div class="forum-hero__cta">
<a class="btn ghost" href="/dashboard">Zurück zum Mitgliederbereich</a>
<a class="btn ghost" href="/dashboard?area=admin&amp;section=admin-home">Zurück zu den Admin-Einstellungen</a>
</div>
</div>
@@ -101,7 +101,7 @@ $migrationStatus = ($migration && $canManageApplications) ? $migration->status()
<div class="forum-admin-grid">
<?php if ($canReviewListings): ?>
<section class="forum-board">
<section class="forum-board" id="listing-requests">
<div class="forum-board__head">
<div>
<h2>Offene Orts- und Veranstaltungsanfragen</h2>

View File

@@ -135,23 +135,26 @@ if ($priceRows === []) {
$editingChild = isset($editChild) && $editChild !== null;
$childAction = $editingChild ? 'child_update' : 'child_add';
$childModalTitle = $editingChild ? 'Kind bearbeiten' : 'Kind hinzufügen';
$sectionLinks = [
$isAdminArea = ($area ?? 'profile') === 'admin';
$sectionLinks = $isAdminArea
? ['admin-home' => 'Übersicht']
: [
'profile' => 'Profil',
'children' => 'Kinder',
'events' => 'Events',
'places' => 'Orte & Veranstaltungen',
'community' => 'Community',
'settings' => 'Einstellungen',
];
if (!empty($canManageProfileLevels)) {
$sectionLinks['profile-levels'] = 'Profil-Levels';
}
];
$showEventTypeChoice = !$editing && (($section ?? 'events') === 'places') && $requestedCreateListingKind === null;
$modalSectionTarget = ($editingListing || ($section ?? '') === 'places') ? 'places' : 'events';
if (!empty($canManageCategories)) {
if ($isAdminArea && !empty($canManageCategories)) {
$sectionLinks['categories'] = 'Kategorien';
}
if (!empty($canManageSystemSettings)) {
if ($isAdminArea && !empty($canManageProfileLevels)) {
$sectionLinks['profile-levels'] = 'Profil-Levels';
}
if ($isAdminArea && !empty($canManageSystemSettings)) {
$sectionLinks['system'] = 'System';
}
?>
@@ -159,7 +162,7 @@ if (!empty($canManageSystemSettings)) {
<div class="container" style="display:flex; align-items:center; justify-content:space-between; flex-wrap:wrap; gap:12px;">
<div>
<h1>Hallo, <span style="color: var(--color-primary);"><?= htmlspecialchars($profile['display_name'] ?: 'Papa', ENT_QUOTES) ?></span>!</h1>
<p class="muted">Verwalte dein Profil, Kinder, Events und Teilnahmen.</p>
<p class="muted"><?= $isAdminArea ? 'Verwalte Moderation, Freigaben und Systemeinstellungen.' : 'Verwalte dein Profil, Kinder, eigene Orte und Veranstaltungen sowie Einstellungen.' ?></p>
</div>
</div>
@@ -181,15 +184,67 @@ if (!empty($canManageSystemSettings)) {
<div class="account-sidebar__inner">
<nav class="account-sidebar__nav">
<?php foreach ($sectionLinks as $sectionKey => $sectionLabel): ?>
<a href="/dashboard?section=<?= rawurlencode($sectionKey) ?>" class="<?= $section === $sectionKey ? 'is-active' : '' ?>">
<a href="/dashboard?<?= $isAdminArea ? 'area=admin&amp;' : '' ?>section=<?= rawurlencode($sectionKey) ?>" class="<?= $section === $sectionKey ? 'is-active' : '' ?>">
<span><?= htmlspecialchars($sectionLabel, ENT_QUOTES) ?></span>
</a>
<?php endforeach; ?>
<?php if ($isAdminArea && !empty($canAccessCommunityAdmin)): ?>
<a href="/community-admin#listing-requests"><span>Orte &amp; Veranstaltungen freigeben</span></a>
<a href="/community-admin"><span>Community-Moderation</span></a>
<?php endif; ?>
<?php if ($isAdminArea): ?>
<a class="account-sidebar__back" href="/dashboard?section=profile"><span>Zum Profil</span></a>
<?php endif; ?>
</nav>
</div>
</aside>
<div class="account-main">
<?php if ($section === 'admin-home' && $isAdminArea): ?>
<section class="account-panel">
<div class="account-panel__head">
<h2>Admin-Einstellungen</h2>
<p class="muted">Hier liegen ausschließlich Funktionen zur Moderation und Verwaltung der Plattform.</p>
</div>
<div class="account-panel__body">
<div class="profile-summary-grid">
<?php if (!empty($canAccessCommunityAdmin)): ?>
<a class="profile-summary-card profile-summary-card--link" href="/community-admin#listing-requests">
<span class="profile-summary-card__eyebrow">Freigaben</span>
<strong>Orte &amp; Veranstaltungen</strong>
<p class="muted small">Neue Einträge sowie Änderungs- und Löschanfragen prüfen.</p>
</a>
<a class="profile-summary-card profile-summary-card--link" href="/community-admin">
<span class="profile-summary-card__eyebrow">Moderation</span>
<strong>Community</strong>
<p class="muted small">Meldungen, Bewerbungen und Rollen verwalten.</p>
</a>
<?php endif; ?>
<?php if (!empty($canManageCategories)): ?>
<a class="profile-summary-card profile-summary-card--link" href="/dashboard?area=admin&amp;section=categories">
<span class="profile-summary-card__eyebrow">Inhalte</span>
<strong>Kategorien</strong>
<p class="muted small">Neue Kategorien bestätigen und zusammenführen.</p>
</a>
<?php endif; ?>
<?php if (!empty($canManageProfileLevels)): ?>
<a class="profile-summary-card profile-summary-card--link" href="/dashboard?area=admin&amp;section=profile-levels">
<span class="profile-summary-card__eyebrow">Community</span>
<strong>Profil-Levels</strong>
<p class="muted small">Level, Punkte und Systemrollen verwalten.</p>
</a>
<?php endif; ?>
<?php if (!empty($canManageSystemSettings)): ?>
<a class="profile-summary-card profile-summary-card--link" href="/dashboard?area=admin&amp;section=system">
<span class="profile-summary-card__eyebrow">Betrieb</span>
<strong>System</strong>
<p class="muted small">Wartungs- und Diensteschalter konfigurieren.</p>
</a>
<?php endif; ?>
</div>
</div>
</section>
<?php endif; ?>
<?php if ($section === 'profile'): ?>
<section class="account-panel">
<div class="account-panel__head">

View File

@@ -3,10 +3,7 @@ $app = app();
$isLoggedIn = isset($_SESSION['user_id']);
$displayName = 'Profil';
$profileInitial = 'P';
$showAdminLink = false;
$showSystemLink = false;
$showProfileLevelsLink = false;
$showCategoriesLink = false;
$showAdminSettingsLink = false;
if ($isLoggedIn) {
try {
@@ -29,19 +26,14 @@ if ($isLoggedIn) {
$community = new \App\Community($pdo, $communityConfig);
$communityAccess = new \App\CommunityAccess($pdo, $communityConfig);
$navCommunityPoints = $community->computePoints((int)$_SESSION['user_id']);
$showAdminLink = $communityAccess->canAccessCommunityAdmin((int)$_SESSION['user_id'], $navCommunityPoints);
$showSystemLink = $communityAccess->canManageApplications((int)$_SESSION['user_id']);
$showProfileLevelsLink = $communityAccess->canManageRoles((int)$_SESSION['user_id']);
$showCategoriesLink = $communityAccess->canManageCategories((int)$_SESSION['user_id'], $navCommunityPoints);
$showAdminSettingsLink = $communityAccess->canAccessCommunityAdmin((int)$_SESSION['user_id'], $navCommunityPoints)
|| $communityAccess->canManageCategories((int)$_SESSION['user_id'], $navCommunityPoints);
}
}
} catch (\Throwable) {
$displayName = 'Profil';
$profileInitial = 'P';
$showAdminLink = false;
$showSystemLink = false;
$showProfileLevelsLink = false;
$showCategoriesLink = false;
$showAdminSettingsLink = false;
}
}
?>
@@ -68,22 +60,8 @@ if ($isLoggedIn) {
</button>
<div class="profile-menu__dropdown" role="menu">
<a href="/dashboard?section=profile" role="menuitem">Profil</a>
<a href="/dashboard?section=children" role="menuitem">Kinder</a>
<a href="/dashboard?section=events" role="menuitem">Events</a>
<a href="/dashboard?section=places" role="menuitem">Orte &amp; Veranstaltungen</a>
<a href="/dashboard?section=community" role="menuitem">Community</a>
<a href="/dashboard?section=settings" role="menuitem">Einstellungen</a>
<?php if ($showCategoriesLink): ?>
<a href="/dashboard?section=categories" role="menuitem">Kategorien</a>
<?php endif; ?>
<?php if ($showProfileLevelsLink): ?>
<a href="/dashboard?section=profile-levels" role="menuitem">Profil-Levels</a>
<?php endif; ?>
<?php if ($showSystemLink): ?>
<a href="/dashboard?section=system" role="menuitem">System</a>
<?php endif; ?>
<?php if ($showAdminLink): ?>
<a href="/community-admin" role="menuitem">Admin</a>
<?php if ($showAdminSettingsLink): ?>
<a href="/dashboard?area=admin&section=admin-home" role="menuitem">Admin-Einstellungen</a>
<?php endif; ?>
<a href="/logout" role="menuitem">Abmelden</a>
</div>
@@ -101,22 +79,8 @@ if ($isLoggedIn) {
<a href="/community">Community</a>
<?php if ($isLoggedIn): ?>
<a class="btn ghost" href="/dashboard?section=profile">Profil</a>
<a class="btn ghost" href="/dashboard?section=children">Kinder</a>
<a class="btn ghost" href="/dashboard?section=events">Events</a>
<a class="btn ghost" href="/dashboard?section=places">Orte &amp; Veranstaltungen</a>
<a class="btn ghost" href="/dashboard?section=community">Community</a>
<a class="btn ghost" href="/dashboard?section=settings">Einstellungen</a>
<?php if ($showCategoriesLink): ?>
<a class="btn ghost" href="/dashboard?section=categories">Kategorien</a>
<?php endif; ?>
<?php if ($showProfileLevelsLink): ?>
<a class="btn ghost" href="/dashboard?section=profile-levels">Profil-Levels</a>
<?php endif; ?>
<?php if ($showSystemLink): ?>
<a class="btn ghost" href="/dashboard?section=system">System</a>
<?php endif; ?>
<?php if ($showAdminLink): ?>
<a class="btn ghost" href="/community-admin">Admin</a>
<?php if ($showAdminSettingsLink): ?>
<a class="btn ghost" href="/dashboard?area=admin&section=admin-home">Admin-Einstellungen</a>
<?php endif; ?>
<a class="btn block" href="/logout">Abmelden</a>
<?php else: ?>

View File

@@ -235,7 +235,10 @@ body {
.account-sidebar__nav a { display: flex; align-items: center; justify-content: space-between; gap: 12px; padding: 10px 12px; border-radius: 12px; color: var(--color-text); font-weight: 600; }
.account-sidebar__nav a:hover { background: #f8f4ec; color: var(--color-primary); }
.account-sidebar__nav a.is-active { background: var(--color-primary); color: var(--color-primary-contrast); }
.account-sidebar__nav .account-sidebar__back { margin-top: 10px; padding-top: 18px; border-top: 1px solid var(--color-border); color: var(--color-muted); }
.account-main { min-width: 0; display: grid; gap: 20px; }
.profile-summary-card--link { display: block; color: var(--color-text); transition: transform .18s ease, box-shadow .18s ease; }
.profile-summary-card--link:hover { color: var(--color-text); transform: translateY(-2px); box-shadow: var(--shadow-card); }
.account-panel { background: #fff; border: 1px solid var(--color-border); border-radius: var(--radius-lg); box-shadow: var(--shadow-card); overflow: hidden; }
.account-panel__head { padding: 22px 24px; border-bottom: 1px solid var(--color-border); background: #fffdfa; }
.account-panel__head h2 { margin: 0 0 6px; }

View File

@@ -178,19 +178,37 @@ final class AccountPages
$listingCatalog = $pdo ? new ListingCatalog($pdo) : null;
$calendarSync = $pdo ? new CalendarSync($app) : null;
$section = (string)($_GET['section'] ?? 'profile');
$area = (string)($_GET['area'] ?? '') === 'admin' ? 'admin' : 'profile';
$canManageSystemSettings = $communityAccess ? $communityAccess->canManageApplications($userId) : false;
$canManageProfileLevels = $communityAccess ? $communityAccess->canManageRoles($userId) : false;
$communityPointsForAccess = $community ? $community->computePoints($userId) : 0.0;
$canManageCategories = $communityAccess ? $communityAccess->canManageCategories($userId, $communityPointsForAccess) : false;
$canReviewListings = $communityAccess ? $communityAccess->canReviewListings($userId, $communityPointsForAccess) : false;
$allowedSections = ['profile', 'children', 'events', 'places', 'community', 'settings'];
if ($canManageSystemSettings) {
$canAccessCommunityAdmin = $communityAccess ? $communityAccess->canAccessCommunityAdmin($userId, $communityPointsForAccess) : false;
$canAccessAdminArea = $canAccessCommunityAdmin || $canManageCategories || $canManageSystemSettings || $canManageProfileLevels;
// Alte Deep Links zu Verwaltungssektionen bleiben gültig, landen aber im getrennten Admin-Bereich.
if ($area === 'profile' && in_array($section, ['categories', 'profile-levels', 'system'], true)) {
$area = 'admin';
}
if ($area === 'admin' && !isset($_GET['section'])) {
$section = 'admin-home';
}
if ($area === 'admin' && !$canAccessAdminArea) {
redirect('/dashboard?section=profile');
}
$allowedSections = $area === 'admin'
? ['admin-home']
: ['profile', 'children', 'events', 'places', 'community', 'settings'];
if ($area === 'admin' && $canManageSystemSettings) {
$allowedSections[] = 'system';
}
if ($canManageCategories) {
if ($area === 'admin' && $canManageCategories) {
$allowedSections[] = 'categories';
}
if ($canManageProfileLevels) {
if ($area === 'admin' && $canManageProfileLevels) {
$allowedSections[] = 'profile-levels';
}
@@ -353,14 +371,14 @@ final class AccountPages
$targetCategorySlug
);
$app->flash()->set('success', 'Kategorie zusammengeführt.');
redirect('/dashboard?section=categories');
redirect('/dashboard?area=admin&section=categories');
} elseif ($action === 'category_confirm') {
if (!$canManageCategories || !$listingCatalog) {
throw new \RuntimeException('Keine Berechtigung für die Kategorien-Verwaltung.');
}
$listingCatalog->confirmCategory((string)($_POST['source_category_slug'] ?? ''));
$app->flash()->set('success', 'Kategorie bestätigt.');
redirect('/dashboard?section=categories');
redirect('/dashboard?area=admin&section=categories');
} elseif ($action === 'child_add' || $action === 'child_update') {
$crypto = self::requireCrypto($crypto, 'Kinder');
$childId = (int)($_POST['child_id'] ?? 0);
@@ -1104,6 +1122,8 @@ final class AccountPages
'communityLevelRightDefinitions',
'communityRoles',
'canReviewListings',
'canAccessCommunityAdmin',
'canAccessAdminArea',
'systemRoleAssignments',
'recentHighLevelUsers',
'recentHighLevelThresholdLabel',
@@ -1122,6 +1142,7 @@ final class AccountPages
'calendarExportUrl',
'calendarFeedUrl',
'section',
'area',
'allowedSections'
);
}