This commit is contained in:
@@ -51,6 +51,8 @@ Papa-Kind-Treff ist eine PHP-basierte Plattform für Väter. Kernbereiche sind l
|
|||||||
- System-Level bleiben getrennt als Rollen `Forum-Admin`, `Site-Admin` und `SiteOwner`.
|
- System-Level bleiben getrennt als Rollen `Forum-Admin`, `Site-Admin` und `SiteOwner`.
|
||||||
- Die Vergabe von System-Leveln läuft im Bereich `Community-Admin` jetzt über eine Benutzersuche statt über rohe Benutzer-IDs; `Site-Admins` bleiben auf die bestehende Rollenlogik begrenzt.
|
- Die Vergabe von System-Leveln läuft im Bereich `Community-Admin` jetzt über eine Benutzersuche statt über rohe Benutzer-IDs; `Site-Admins` bleiben auf die bestehende Rollenlogik begrenzt.
|
||||||
- Kategorien liegen nicht mehr unter `System`, sondern in einem eigenen Mitgliederbereichspunkt `Kategorien`; dort werden nur neue Kategorien angezeigt und per Vorschlagsfeld mit bestehenden Kategorien aus der Datenbank zusammengeführt.
|
- Kategorien liegen nicht mehr unter `System`, sondern in einem eigenen Mitgliederbereichspunkt `Kategorien`; dort werden nur neue Kategorien angezeigt und per Vorschlagsfeld mit bestehenden Kategorien aus der Datenbank zusammengeführt.
|
||||||
|
- Neue Kategorien bleiben dort sichtbar, bis sie von einem Berechtigten bestätigt oder mit einer bestehenden Kategorie zusammengeführt werden.
|
||||||
|
- Community-Level können zusätzlich die Rechte für Kategorien sowie für die Freigabe von Orten und Veranstaltungen tragen.
|
||||||
|
|
||||||
## Technischer Rahmen
|
## Technischer Rahmen
|
||||||
- eigener Front-Controller in `public/index.php`
|
- eigener Front-Controller in `public/index.php`
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ Stand: 2026-08-10
|
|||||||
- Kalender-Export und abonnierbarer Feed: `src/App/CalendarSync.php` plus `public/page/calendar/export.php` und `public/page/calendar/feed.php`
|
- Kalender-Export und abonnierbarer Feed: `src/App/CalendarSync.php` plus `public/page/calendar/export.php` und `public/page/calendar/feed.php`
|
||||||
- UI für getrennte Bereiche `Events` und `Orte & Veranstaltungen` im Mitgliederbereich: `partials/landing/account/dashboard.php`
|
- UI für getrennte Bereiche `Events` und `Orte & Veranstaltungen` im Mitgliederbereich: `partials/landing/account/dashboard.php`
|
||||||
- eigener Mitgliederbereichspunkt `Kategorien` für neue Kategorien und deren Zusammenführung per Vorschlagsfeld: `partials/landing/account/dashboard.php`, `src/App/AccountPages.php`, `src/App/ListingCatalog.php`
|
- eigener Mitgliederbereichspunkt `Kategorien` für neue Kategorien und deren Zusammenführung per Vorschlagsfeld: `partials/landing/account/dashboard.php`, `src/App/AccountPages.php`, `src/App/ListingCatalog.php`
|
||||||
|
- Community-Level-Rechte für Kategorien und Orts-/Veranstaltungsfreigaben: `config/community.php`, `src/App/Community.php`, `src/App/CommunityAccess.php`, `partials/landing/account/community-admin.php`
|
||||||
- Preis-, Bild- und Ortseingabe für Orte und Veranstaltungen sowie reduzierte Event-Maske: `partials/landing/account/dashboard.php` plus Speicherung in `src/App/ListingCatalog.php`
|
- Preis-, Bild- und Ortseingabe für Orte und Veranstaltungen sowie reduzierte Event-Maske: `partials/landing/account/dashboard.php` plus Speicherung in `src/App/ListingCatalog.php`
|
||||||
- Freigaben sowie Änderungs- und Löschanfragen für Orte und Veranstaltungen: `src/App/ListingCatalog.php`, `partials/landing/account/dashboard.php`, `partials/landing/account/community-admin.php`
|
- Freigaben sowie Änderungs- und Löschanfragen für Orte und Veranstaltungen: `src/App/ListingCatalog.php`, `partials/landing/account/dashboard.php`, `partials/landing/account/community-admin.php`
|
||||||
- Rollenvergabe per Benutzersuche im Community-Admin: `partials/landing/account/community-admin.php`, `src/App/CommunityAccess.php`
|
- Rollenvergabe per Benutzersuche im Community-Admin: `partials/landing/account/community-admin.php`, `src/App/CommunityAccess.php`
|
||||||
|
|||||||
@@ -51,6 +51,8 @@ Papa-Kind-Treff is a PHP-based platform for fathers. Core areas are local events
|
|||||||
- System levels remain separate as the roles `Forum Admin`, `Site Admin`, and `SiteOwner`.
|
- System levels remain separate as the roles `Forum Admin`, `Site Admin`, and `SiteOwner`.
|
||||||
- System-level assignment in `Community Admin` now runs through user search instead of raw user IDs; site admins remain limited by the existing role rules.
|
- System-level assignment in `Community Admin` now runs through user search instead of raw user IDs; site admins remain limited by the existing role rules.
|
||||||
- Categories no longer live under `System`, but in their own member-area section `Categories`; that area only shows newly created categories and merges them into existing database categories through a suggestion field.
|
- Categories no longer live under `System`, but in their own member-area section `Categories`; that area only shows newly created categories and merges them into existing database categories through a suggestion field.
|
||||||
|
- New categories remain visible there until an authorized user confirms them or merges them into an existing category.
|
||||||
|
- Community levels can now additionally carry the rights for category handling as well as place and event approvals.
|
||||||
|
|
||||||
## Technical Frame
|
## Technical Frame
|
||||||
- custom front controller in `public/index.php`
|
- custom front controller in `public/index.php`
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ Updated: 2026-08-10
|
|||||||
- calendar export and subscribable feed: `src/App/CalendarSync.php` plus `public/page/calendar/export.php` and `public/page/calendar/feed.php`
|
- calendar export and subscribable feed: `src/App/CalendarSync.php` plus `public/page/calendar/export.php` and `public/page/calendar/feed.php`
|
||||||
- separated `Events` and `Places & Events` UI in member area: `partials/landing/account/dashboard.php`
|
- separated `Events` and `Places & Events` UI in member area: `partials/landing/account/dashboard.php`
|
||||||
- dedicated member-area section `Categories` for new categories and merging through a suggestion field: `partials/landing/account/dashboard.php`, `src/App/AccountPages.php`, `src/App/ListingCatalog.php`
|
- dedicated member-area section `Categories` for new categories and merging through a suggestion field: `partials/landing/account/dashboard.php`, `src/App/AccountPages.php`, `src/App/ListingCatalog.php`
|
||||||
|
- community-level rights for categories and place/event approvals: `config/community.php`, `src/App/Community.php`, `src/App/CommunityAccess.php`, `partials/landing/account/community-admin.php`
|
||||||
- pricing, image upload, and place/address input for places and event-style entries plus a reduced own-event form: `partials/landing/account/dashboard.php` with persistence in `src/App/ListingCatalog.php`
|
- pricing, image upload, and place/address input for places and event-style entries plus a reduced own-event form: `partials/landing/account/dashboard.php` with persistence in `src/App/ListingCatalog.php`
|
||||||
- approvals as well as change and deletion requests for places and event-style entries: `src/App/ListingCatalog.php`, `partials/landing/account/dashboard.php`, `partials/landing/account/community-admin.php`
|
- approvals as well as change and deletion requests for places and event-style entries: `src/App/ListingCatalog.php`, `partials/landing/account/dashboard.php`, `partials/landing/account/community-admin.php`
|
||||||
- role assignment via user search in community admin: `partials/landing/account/community-admin.php`, `src/App/CommunityAccess.php`
|
- role assignment via user search in community admin: `partials/landing/account/community-admin.php`, `src/App/CommunityAccess.php`
|
||||||
|
|||||||
@@ -42,6 +42,8 @@ Papa-Kind-Treff ist eine PHP-basierte Plattform für Väter mit Fokus auf:
|
|||||||
- 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
|
- 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
|
- 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
|
||||||
- Kategorien wurden aus `System` in den eigenen Mitgliederbereichspunkt `Kategorien` verschoben; dort werden nur neue Kategorien angezeigt und per Vorschlagsfeld mit bestehenden Kategorien aus der Datenbank zusammengeführt
|
- Kategorien wurden aus `System` in den eigenen Mitgliederbereichspunkt `Kategorien` verschoben; dort werden nur neue Kategorien angezeigt und per Vorschlagsfeld mit bestehenden Kategorien aus der Datenbank zusammengeführt
|
||||||
|
- Neue Kategorien bleiben im Bereich `Kategorien` sichtbar, bis sie von einem Berechtigten bestätigt oder zusammengeführt werden
|
||||||
|
- Community-Level können jetzt zusätzlich die Rechte für Kategorien sowie für die Freigabe von Orten und Veranstaltungen tragen
|
||||||
- neue interne Grundstruktur: `listing_places`, `listings`, `listing_occurrences`, `listing_prices`, `listing_benefits`
|
- neue interne Grundstruktur: `listing_places`, `listings`, `listing_occurrences`, `listing_prices`, `listing_benefits`
|
||||||
- Seiten-Admins haben zusätzlich eine System-Sektion für globale Wartungs- und Diensteschalter
|
- Seiten-Admins haben zusätzlich eine System-Sektion für globale Wartungs- und Diensteschalter
|
||||||
- Im Mitgliederbereich ist `Events` jetzt wieder auf echte eigene Events und Event-Teilnahmen beschränkt
|
- Im Mitgliederbereich ist `Events` jetzt wieder auf echte eigene Events und Event-Teilnahmen beschränkt
|
||||||
|
|||||||
@@ -41,8 +41,29 @@ return [
|
|||||||
['min' => 150, 'label' => 'Unterstützender Vater'],
|
['min' => 150, 'label' => 'Unterstützender Vater'],
|
||||||
['min' => 300, 'label' => 'Erfahrener Vater'],
|
['min' => 300, 'label' => 'Erfahrener Vater'],
|
||||||
['min' => 500, 'label' => 'Mentor-Vater'],
|
['min' => 500, 'label' => 'Mentor-Vater'],
|
||||||
['min' => 750, 'label' => 'Community-Vater'],
|
[
|
||||||
['min' => 1000, 'label' => 'Säule der Väter-Community'],
|
'min' => 750,
|
||||||
['min' => 1500, 'label' => 'Vater der Gemeinschaft'],
|
'label' => 'Community-Vater',
|
||||||
|
'rights' => [
|
||||||
|
'can_manage_categories' => true,
|
||||||
|
'can_review_listings' => true,
|
||||||
|
],
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'min' => 1000,
|
||||||
|
'label' => 'Säule der Väter-Community',
|
||||||
|
'rights' => [
|
||||||
|
'can_manage_categories' => true,
|
||||||
|
'can_review_listings' => true,
|
||||||
|
],
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'min' => 1500,
|
||||||
|
'label' => 'Vater der Gemeinschaft',
|
||||||
|
'rights' => [
|
||||||
|
'can_manage_categories' => true,
|
||||||
|
'can_review_listings' => true,
|
||||||
|
],
|
||||||
|
],
|
||||||
],
|
],
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -11,10 +11,12 @@ if (!$userId) {
|
|||||||
|
|
||||||
$communityCfg = require __DIR__ . '/../../../config/community.php';
|
$communityCfg = require __DIR__ . '/../../../config/community.php';
|
||||||
$access = $pdo ? new \App\CommunityAccess($pdo, $communityCfg) : null;
|
$access = $pdo ? new \App\CommunityAccess($pdo, $communityCfg) : null;
|
||||||
|
$community = $pdo ? new \App\Community($pdo, $communityCfg) : null;
|
||||||
$migration = $pdo ? new \App\CommunityMigration($pdo) : null;
|
$migration = $pdo ? new \App\CommunityMigration($pdo) : null;
|
||||||
$listingCatalog = $pdo ? new \App\ListingCatalog($pdo) : null;
|
$listingCatalog = $pdo ? new \App\ListingCatalog($pdo) : null;
|
||||||
|
$communityPoints = $community ? $community->computePoints((int)$userId) : 0.0;
|
||||||
|
|
||||||
if (!$access || !$access->canModerateForum((int)$userId)) {
|
if (!$access || !$access->canAccessCommunityAdmin((int)$userId, $communityPoints)) {
|
||||||
http_response_code(403);
|
http_response_code(403);
|
||||||
echo '<main class="section"><div class="container"><div class="card dash-card"><h1>Kein Zugriff</h1><p class="muted">Dieser Bereich ist nur für Community-Admins freigegeben.</p></div></div></main>';
|
echo '<main class="section"><div class="container"><div class="card dash-card"><h1>Kein Zugriff</h1><p class="muted">Dieser Bereich ist nur für Community-Admins freigegeben.</p></div></div></main>';
|
||||||
return;
|
return;
|
||||||
@@ -22,9 +24,10 @@ if (!$access || !$access->canModerateForum((int)$userId)) {
|
|||||||
|
|
||||||
$error = '';
|
$error = '';
|
||||||
$info = '';
|
$info = '';
|
||||||
|
$canModerateForum = $access->canModerateForum((int)$userId);
|
||||||
$canManageApplications = $access->canManageApplications((int)$userId) && $access->supportsApplications();
|
$canManageApplications = $access->canManageApplications((int)$userId) && $access->supportsApplications();
|
||||||
$canManageRoles = $access->canManageRoles((int)$userId);
|
$canManageRoles = $access->canManageRoles((int)$userId);
|
||||||
$canReviewListings = $listingCatalog !== null && !$access->hasRole((int)$userId, 'owner');
|
$canReviewListings = $listingCatalog !== null && $access->canReviewListings((int)$userId, $communityPoints);
|
||||||
$roleUserSearchQuery = trim((string)($_GET['role_user_query'] ?? ''));
|
$roleUserSearchQuery = trim((string)($_GET['role_user_query'] ?? ''));
|
||||||
$roleUserSearchResults = ($canManageRoles || $canManageApplications) && $roleUserSearchQuery !== ''
|
$roleUserSearchResults = ($canManageRoles || $canManageApplications) && $roleUserSearchQuery !== ''
|
||||||
? $access->searchUsers($roleUserSearchQuery, 12)
|
? $access->searchUsers($roleUserSearchQuery, 12)
|
||||||
@@ -194,34 +197,36 @@ $migrationStatus = ($migration && $canManageApplications) ? $migration->status()
|
|||||||
</section>
|
</section>
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
|
|
||||||
<section class="forum-board">
|
<?php if ($canModerateForum): ?>
|
||||||
<div class="forum-board__head">
|
<section class="forum-board">
|
||||||
<div>
|
<div class="forum-board__head">
|
||||||
<h2>Offene Meldungen</h2>
|
<div>
|
||||||
<p class="muted">Gemeldete Inhalte prüfen und abschließen.</p>
|
<h2>Offene Meldungen</h2>
|
||||||
|
<p class="muted">Gemeldete Inhalte prüfen und abschließen.</p>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<div class="forum-admin-list">
|
||||||
<div class="forum-admin-list">
|
<?php foreach ($reports as $report): ?>
|
||||||
<?php foreach ($reports as $report): ?>
|
<article class="forum-admin-item">
|
||||||
<article class="forum-admin-item">
|
<div>
|
||||||
<div>
|
<strong><?= htmlspecialchars((string)$report['target_type'], ENT_QUOTES) ?> #<?= (int)$report['target_id'] ?></strong>
|
||||||
<strong><?= htmlspecialchars((string)$report['target_type'], ENT_QUOTES) ?> #<?= (int)$report['target_id'] ?></strong>
|
<p><?= nl2br(htmlspecialchars((string)$report['reason'], ENT_QUOTES)) ?></p>
|
||||||
<p><?= nl2br(htmlspecialchars((string)$report['reason'], ENT_QUOTES)) ?></p>
|
<p class="muted small">Gemeldet von <?= htmlspecialchars((string)($report['reporter_name'] ?: 'Mitglied'), ENT_QUOTES) ?> am <?= htmlspecialchars((string)$report['created_at'], ENT_QUOTES) ?></p>
|
||||||
<p class="muted small">Gemeldet von <?= htmlspecialchars((string)($report['reporter_name'] ?: 'Mitglied'), ENT_QUOTES) ?> am <?= htmlspecialchars((string)$report['created_at'], ENT_QUOTES) ?></p>
|
</div>
|
||||||
</div>
|
<form method="post" class="forum-admin-item__actions">
|
||||||
<form method="post" class="forum-admin-item__actions">
|
<input type="hidden" name="action" value="report_resolve">
|
||||||
<input type="hidden" name="action" value="report_resolve">
|
<input type="hidden" name="report_id" value="<?= (int)$report['id'] ?>">
|
||||||
<input type="hidden" name="report_id" value="<?= (int)$report['id'] ?>">
|
<textarea name="moderator_note" class="textarea" rows="3" placeholder="Moderationsnotiz"></textarea>
|
||||||
<textarea name="moderator_note" class="textarea" rows="3" placeholder="Moderationsnotiz"></textarea>
|
<button class="btn" type="submit">Als erledigt markieren</button>
|
||||||
<button class="btn" type="submit">Als erledigt markieren</button>
|
</form>
|
||||||
</form>
|
</article>
|
||||||
</article>
|
<?php endforeach; ?>
|
||||||
<?php endforeach; ?>
|
<?php if (!$reports): ?>
|
||||||
<?php if (!$reports): ?>
|
<div class="forum-empty">Keine offenen Meldungen.</div>
|
||||||
<div class="forum-empty">Keine offenen Meldungen.</div>
|
<?php endif; ?>
|
||||||
<?php endif; ?>
|
</div>
|
||||||
</div>
|
</section>
|
||||||
</section>
|
<?php endif; ?>
|
||||||
|
|
||||||
<?php if ($canManageRoles || $canManageApplications): ?>
|
<?php if ($canManageRoles || $canManageApplications): ?>
|
||||||
<section class="forum-board">
|
<section class="forum-board">
|
||||||
|
|||||||
@@ -147,8 +147,10 @@ if (!empty($canManageProfileLevels)) {
|
|||||||
}
|
}
|
||||||
$showEventTypeChoice = !$editing && (($section ?? 'events') === 'places') && $requestedCreateListingKind === null;
|
$showEventTypeChoice = !$editing && (($section ?? 'events') === 'places') && $requestedCreateListingKind === null;
|
||||||
$modalSectionTarget = ($editingListing || ($section ?? '') === 'places') ? 'places' : 'events';
|
$modalSectionTarget = ($editingListing || ($section ?? '') === 'places') ? 'places' : 'events';
|
||||||
if (!empty($canManageSystemSettings)) {
|
if (!empty($canManageCategories)) {
|
||||||
$sectionLinks['categories'] = 'Kategorien';
|
$sectionLinks['categories'] = 'Kategorien';
|
||||||
|
}
|
||||||
|
if (!empty($canManageSystemSettings)) {
|
||||||
$sectionLinks['system'] = 'System';
|
$sectionLinks['system'] = 'System';
|
||||||
}
|
}
|
||||||
?>
|
?>
|
||||||
@@ -951,7 +953,7 @@ if (!empty($canManageSystemSettings)) {
|
|||||||
</section>
|
</section>
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
|
|
||||||
<?php if ($section === 'categories' && !empty($canManageSystemSettings)): ?>
|
<?php if ($section === 'categories' && !empty($canManageCategories)): ?>
|
||||||
<section class="account-panel">
|
<section class="account-panel">
|
||||||
<div class="account-panel__head">
|
<div class="account-panel__head">
|
||||||
<h2>Kategorien</h2>
|
<h2>Kategorien</h2>
|
||||||
@@ -991,6 +993,11 @@ if (!empty($canManageSystemSettings)) {
|
|||||||
<button class="btn ghost" type="submit">Zusammenführen</button>
|
<button class="btn ghost" type="submit">Zusammenführen</button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
<form method="post" style="margin-top:12px;">
|
||||||
|
<input type="hidden" name="action" value="category_confirm">
|
||||||
|
<input type="hidden" name="source_category_slug" value="<?= htmlspecialchars((string)$categoryItem['slug'], ENT_QUOTES) ?>">
|
||||||
|
<button class="btn" type="submit">Bestätigen</button>
|
||||||
|
</form>
|
||||||
</div>
|
</div>
|
||||||
<?php endforeach; ?>
|
<?php endforeach; ?>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ $profileInitial = 'P';
|
|||||||
$showAdminLink = false;
|
$showAdminLink = false;
|
||||||
$showSystemLink = false;
|
$showSystemLink = false;
|
||||||
$showProfileLevelsLink = false;
|
$showProfileLevelsLink = false;
|
||||||
|
$showCategoriesLink = false;
|
||||||
|
|
||||||
if ($isLoggedIn) {
|
if ($isLoggedIn) {
|
||||||
try {
|
try {
|
||||||
@@ -25,10 +26,13 @@ if ($isLoggedIn) {
|
|||||||
$communityCfg = dirname(__DIR__, 2) . '/config/community.php';
|
$communityCfg = dirname(__DIR__, 2) . '/config/community.php';
|
||||||
if (file_exists($communityCfg)) {
|
if (file_exists($communityCfg)) {
|
||||||
$communityConfig = require $communityCfg;
|
$communityConfig = require $communityCfg;
|
||||||
|
$community = new \App\Community($pdo, $communityConfig);
|
||||||
$communityAccess = new \App\CommunityAccess($pdo, $communityConfig);
|
$communityAccess = new \App\CommunityAccess($pdo, $communityConfig);
|
||||||
$showAdminLink = $communityAccess->canModerateForum((int)$_SESSION['user_id']);
|
$navCommunityPoints = $community->computePoints((int)$_SESSION['user_id']);
|
||||||
|
$showAdminLink = $communityAccess->canAccessCommunityAdmin((int)$_SESSION['user_id'], $navCommunityPoints);
|
||||||
$showSystemLink = $communityAccess->canManageApplications((int)$_SESSION['user_id']);
|
$showSystemLink = $communityAccess->canManageApplications((int)$_SESSION['user_id']);
|
||||||
$showProfileLevelsLink = $communityAccess->canManageRoles((int)$_SESSION['user_id']);
|
$showProfileLevelsLink = $communityAccess->canManageRoles((int)$_SESSION['user_id']);
|
||||||
|
$showCategoriesLink = $communityAccess->canManageCategories((int)$_SESSION['user_id'], $navCommunityPoints);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (\Throwable) {
|
} catch (\Throwable) {
|
||||||
@@ -37,6 +41,7 @@ if ($isLoggedIn) {
|
|||||||
$showAdminLink = false;
|
$showAdminLink = false;
|
||||||
$showSystemLink = false;
|
$showSystemLink = false;
|
||||||
$showProfileLevelsLink = false;
|
$showProfileLevelsLink = false;
|
||||||
|
$showCategoriesLink = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
?>
|
?>
|
||||||
@@ -68,6 +73,9 @@ if ($isLoggedIn) {
|
|||||||
<a href="/dashboard?section=places" role="menuitem">Orte & Veranstaltungen</a>
|
<a href="/dashboard?section=places" role="menuitem">Orte & Veranstaltungen</a>
|
||||||
<a href="/dashboard?section=community" role="menuitem">Community</a>
|
<a href="/dashboard?section=community" role="menuitem">Community</a>
|
||||||
<a href="/dashboard?section=settings" role="menuitem">Einstellungen</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): ?>
|
<?php if ($showProfileLevelsLink): ?>
|
||||||
<a href="/dashboard?section=profile-levels" role="menuitem">Profil-Levels</a>
|
<a href="/dashboard?section=profile-levels" role="menuitem">Profil-Levels</a>
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
@@ -98,6 +106,9 @@ if ($isLoggedIn) {
|
|||||||
<a class="btn ghost" href="/dashboard?section=places">Orte & Veranstaltungen</a>
|
<a class="btn ghost" href="/dashboard?section=places">Orte & Veranstaltungen</a>
|
||||||
<a class="btn ghost" href="/dashboard?section=community">Community</a>
|
<a class="btn ghost" href="/dashboard?section=community">Community</a>
|
||||||
<a class="btn ghost" href="/dashboard?section=settings">Einstellungen</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): ?>
|
<?php if ($showProfileLevelsLink): ?>
|
||||||
<a class="btn ghost" href="/dashboard?section=profile-levels">Profil-Levels</a>
|
<a class="btn ghost" href="/dashboard?section=profile-levels">Profil-Levels</a>
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
|
|||||||
@@ -180,9 +180,13 @@ final class AccountPages
|
|||||||
$section = (string)($_GET['section'] ?? 'profile');
|
$section = (string)($_GET['section'] ?? 'profile');
|
||||||
$canManageSystemSettings = $communityAccess ? $communityAccess->canManageApplications($userId) : false;
|
$canManageSystemSettings = $communityAccess ? $communityAccess->canManageApplications($userId) : false;
|
||||||
$canManageProfileLevels = $communityAccess ? $communityAccess->canManageRoles($userId) : false;
|
$canManageProfileLevels = $communityAccess ? $communityAccess->canManageRoles($userId) : false;
|
||||||
|
$communityPointsForAccess = $community ? $community->computePoints($userId) : 0.0;
|
||||||
|
$canManageCategories = $communityAccess ? $communityAccess->canManageCategories($userId, $communityPointsForAccess) : false;
|
||||||
$allowedSections = ['profile', 'children', 'events', 'places', 'community', 'settings'];
|
$allowedSections = ['profile', 'children', 'events', 'places', 'community', 'settings'];
|
||||||
if ($canManageSystemSettings) {
|
if ($canManageSystemSettings) {
|
||||||
$allowedSections[] = 'system';
|
$allowedSections[] = 'system';
|
||||||
|
}
|
||||||
|
if ($canManageCategories) {
|
||||||
$allowedSections[] = 'categories';
|
$allowedSections[] = 'categories';
|
||||||
}
|
}
|
||||||
if ($canManageProfileLevels) {
|
if ($canManageProfileLevels) {
|
||||||
@@ -326,7 +330,7 @@ final class AccountPages
|
|||||||
], $userId);
|
], $userId);
|
||||||
$info = 'System-Einstellungen gespeichert.';
|
$info = 'System-Einstellungen gespeichert.';
|
||||||
} elseif ($action === 'category_merge') {
|
} elseif ($action === 'category_merge') {
|
||||||
if (!$canManageSystemSettings || !$listingCatalog) {
|
if (!$canManageCategories || !$listingCatalog) {
|
||||||
throw new \RuntimeException('Keine Berechtigung für die Kategorien-Verwaltung.');
|
throw new \RuntimeException('Keine Berechtigung für die Kategorien-Verwaltung.');
|
||||||
}
|
}
|
||||||
$targetCategorySlug = $listingCatalog->resolveExistingCategorySlug((string)($_POST['target_category_input'] ?? ''));
|
$targetCategorySlug = $listingCatalog->resolveExistingCategorySlug((string)($_POST['target_category_input'] ?? ''));
|
||||||
@@ -338,6 +342,12 @@ final class AccountPages
|
|||||||
$targetCategorySlug
|
$targetCategorySlug
|
||||||
);
|
);
|
||||||
$info = 'Kategorie zusammengeführt.';
|
$info = 'Kategorie zusammengeführt.';
|
||||||
|
} elseif ($action === 'category_confirm') {
|
||||||
|
if (!$canManageCategories || !$listingCatalog) {
|
||||||
|
throw new \RuntimeException('Keine Berechtigung für die Kategorien-Verwaltung.');
|
||||||
|
}
|
||||||
|
$listingCatalog->confirmCategory((string)($_POST['source_category_slug'] ?? ''));
|
||||||
|
$info = 'Kategorie bestätigt.';
|
||||||
} elseif ($action === 'child_add' || $action === 'child_update') {
|
} elseif ($action === 'child_add' || $action === 'child_update') {
|
||||||
$crypto = self::requireCrypto($crypto, 'Kinder');
|
$crypto = self::requireCrypto($crypto, 'Kinder');
|
||||||
$childId = (int)($_POST['child_id'] ?? 0);
|
$childId = (int)($_POST['child_id'] ?? 0);
|
||||||
@@ -857,7 +867,7 @@ final class AccountPages
|
|||||||
$restoreEventDraftActive = $section === 'events' && ((string)($_GET['restore_event_draft'] ?? '')) === '1' && self::hasStoredEventDraft();
|
$restoreEventDraftActive = $section === 'events' && ((string)($_GET['restore_event_draft'] ?? '')) === '1' && self::hasStoredEventDraft();
|
||||||
$restoredEventDraft = $restoreEventDraftActive ? self::getStoredEventDraft() : null;
|
$restoredEventDraft = $restoreEventDraftActive ? self::getStoredEventDraft() : null;
|
||||||
$listingCategories = $listingCatalog ? $listingCatalog->listCategories(['general', 'place', 'food', 'event', 'family']) : [];
|
$listingCategories = $listingCatalog ? $listingCatalog->listCategories(['general', 'place', 'food', 'event', 'family']) : [];
|
||||||
$categoryReviewItems = $listingCatalog && $canManageSystemSettings ? array_values(array_filter(
|
$categoryReviewItems = $listingCatalog && $canManageCategories ? array_values(array_filter(
|
||||||
$listingCatalog->listCategoryReviewItems(),
|
$listingCatalog->listCategoryReviewItems(),
|
||||||
static fn(array $item): bool => !empty($item['needs_review'])
|
static fn(array $item): bool => !empty($item['needs_review'])
|
||||||
)) : [];
|
)) : [];
|
||||||
@@ -940,7 +950,7 @@ final class AccountPages
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$communityPoints = $community ? $community->computePoints($userId) : 0.0;
|
$communityPoints = $communityPointsForAccess;
|
||||||
$communityLevel = $community ? $community->membershipLevel($communityPoints) : ['label' => '', 'icon' => ''];
|
$communityLevel = $community ? $community->membershipLevel($communityPoints) : ['label' => '', 'icon' => ''];
|
||||||
$communityLevelDefinitions = $community ? $community->listMembershipLevels() : [];
|
$communityLevelDefinitions = $community ? $community->listMembershipLevels() : [];
|
||||||
$communityLevelRightDefinitions = Community::membershipRightDefinitions();
|
$communityLevelRightDefinitions = Community::membershipRightDefinitions();
|
||||||
@@ -990,6 +1000,7 @@ final class AccountPages
|
|||||||
'Themen und Antworten moderieren',
|
'Themen und Antworten moderieren',
|
||||||
'Community-Sperren setzen und aufheben',
|
'Community-Sperren setzen und aufheben',
|
||||||
'Meldungen bearbeiten',
|
'Meldungen bearbeiten',
|
||||||
|
'Orte und Veranstaltungen freigeben',
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
@@ -998,6 +1009,7 @@ final class AccountPages
|
|||||||
'rights' => [
|
'rights' => [
|
||||||
'Beinhaltet alle Rechte von Forum-Admin',
|
'Beinhaltet alle Rechte von Forum-Admin',
|
||||||
'Community-Admin-Bewerbungen bearbeiten',
|
'Community-Admin-Bewerbungen bearbeiten',
|
||||||
|
'Kategorien bestätigen und zusammenführen',
|
||||||
'System-Einstellungen verwalten',
|
'System-Einstellungen verwalten',
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
@@ -1061,6 +1073,7 @@ final class AccountPages
|
|||||||
'communityCanApply',
|
'communityCanApply',
|
||||||
'communityRestrictions',
|
'communityRestrictions',
|
||||||
'systemLevelDefinitions',
|
'systemLevelDefinitions',
|
||||||
|
'canManageCategories',
|
||||||
'canManageSystemSettings',
|
'canManageSystemSettings',
|
||||||
'canManageProfileLevels',
|
'canManageProfileLevels',
|
||||||
'systemSettingsValues',
|
'systemSettingsValues',
|
||||||
|
|||||||
@@ -657,6 +657,12 @@ final class Community
|
|||||||
'can_apply_for_forum_admin' => [
|
'can_apply_for_forum_admin' => [
|
||||||
'label' => 'Bewerbung als Forum-Admin',
|
'label' => 'Bewerbung als Forum-Admin',
|
||||||
],
|
],
|
||||||
|
'can_manage_categories' => [
|
||||||
|
'label' => 'Kategorien bestätigen und zusammenführen',
|
||||||
|
],
|
||||||
|
'can_review_listings' => [
|
||||||
|
'label' => 'Orte und Veranstaltungen freigeben',
|
||||||
|
],
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -75,6 +75,35 @@ final class CommunityAccess
|
|||||||
return $this->hasRole($userId, 'owner');
|
return $this->hasRole($userId, 'owner');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function canManageCategories(int $userId, ?float $points = null): bool
|
||||||
|
{
|
||||||
|
if ($this->hasRole($userId, 'owner') || $this->hasRole($userId, 'site_admin')) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->resolveLevelRight($points ?? $this->resolveUserPoints($userId), 'can_manage_categories');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function canReviewListings(int $userId, ?float $points = null): bool
|
||||||
|
{
|
||||||
|
if ($this->hasRole($userId, 'owner')) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if ($this->hasRole($userId, 'site_admin') || $this->hasRole($userId, 'forum_admin')) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->resolveLevelRight($points ?? $this->resolveUserPoints($userId), 'can_review_listings');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function canAccessCommunityAdmin(int $userId, ?float $points = null): bool
|
||||||
|
{
|
||||||
|
return $this->canModerateForum($userId)
|
||||||
|
|| $this->canManageApplications($userId)
|
||||||
|
|| $this->canManageRoles($userId)
|
||||||
|
|| $this->canReviewListings($userId, $points);
|
||||||
|
}
|
||||||
|
|
||||||
public function getRestrictionState(int $userId): array
|
public function getRestrictionState(int $userId): array
|
||||||
{
|
{
|
||||||
$state = [
|
$state = [
|
||||||
@@ -574,4 +603,14 @@ final class CommunityAccess
|
|||||||
$level = $community->membershipLevelMeta($points);
|
$level = $community->membershipLevelMeta($points);
|
||||||
return !empty($level['rights'][$rightKey]);
|
return !empty($level['rights'][$rightKey]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private function resolveUserPoints(int $userId): float
|
||||||
|
{
|
||||||
|
if ($userId <= 0) {
|
||||||
|
return 0.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
$community = new Community($this->pdo, $this->communityConfig);
|
||||||
|
return $community->computePoints($userId);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -368,6 +368,35 @@ final class ListingCatalog
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function confirmCategory(string $sourceSlug): void
|
||||||
|
{
|
||||||
|
$this->ensureSchema();
|
||||||
|
$sourceSlug = trim($sourceSlug);
|
||||||
|
if ($sourceSlug === '') {
|
||||||
|
throw new \RuntimeException('Kategorie nicht gefunden.');
|
||||||
|
}
|
||||||
|
|
||||||
|
$lookup = $this->pdo->prepare('SELECT id, sort_order FROM listing_categories WHERE slug = :slug LIMIT 1');
|
||||||
|
$lookup->execute(['slug' => $sourceSlug]);
|
||||||
|
$source = $lookup->fetch(\PDO::FETCH_ASSOC);
|
||||||
|
if (!$source) {
|
||||||
|
throw new \RuntimeException('Kategorie nicht gefunden.');
|
||||||
|
}
|
||||||
|
if ((int)($source['sort_order'] ?? 0) < 900) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$maxStmt = $this->pdo->query('SELECT COALESCE(MAX(sort_order), 0) FROM listing_categories WHERE sort_order < 900');
|
||||||
|
$maxSortOrder = (int)($maxStmt?->fetchColumn() ?: 0);
|
||||||
|
$newSortOrder = max(1, $maxSortOrder + 1);
|
||||||
|
|
||||||
|
$update = $this->pdo->prepare('UPDATE listing_categories SET sort_order = :sortOrder, updated_at = NOW() WHERE id = :id');
|
||||||
|
$update->execute([
|
||||||
|
'sortOrder' => $newSortOrder,
|
||||||
|
'id' => (int)$source['id'],
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
public function resolveExistingCategorySlug(string $input): ?string
|
public function resolveExistingCategorySlug(string $input): ?string
|
||||||
{
|
{
|
||||||
$this->ensureSchema();
|
$this->ensureSchema();
|
||||||
|
|||||||
Reference in New Issue
Block a user