This commit is contained in:
@@ -50,6 +50,7 @@ Papa-Kind-Treff ist eine PHP-basierte Plattform für Väter. Kernbereiche sind l
|
||||
- In `Profil-Levels` gibt es zusätzlich eine Liste von Nutzern, die in den letzten 30 Tagen mindestens den Rang `Säule der Väter-Community` erreicht haben.
|
||||
- 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.
|
||||
- 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.
|
||||
|
||||
## Technischer Rahmen
|
||||
- eigener Front-Controller in `public/index.php`
|
||||
|
||||
@@ -24,6 +24,7 @@ Stand: 2026-08-10
|
||||
- neue Listing-/Ort-Basis: `src/App/ListingCatalog.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`
|
||||
- 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`
|
||||
- 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`
|
||||
- Rollenvergabe per Benutzersuche im Community-Admin: `partials/landing/account/community-admin.php`, `src/App/CommunityAccess.php`
|
||||
|
||||
@@ -50,6 +50,7 @@ Papa-Kind-Treff is a PHP-based platform for fathers. Core areas are local events
|
||||
- `Profile Levels` also includes a list of users who reached at least the rank `Pillar of the Fathers Community` within the last 30 days.
|
||||
- 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.
|
||||
- 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.
|
||||
|
||||
## Technical Frame
|
||||
- custom front controller in `public/index.php`
|
||||
|
||||
@@ -24,6 +24,7 @@ Updated: 2026-08-10
|
||||
- new listing/place base: `src/App/ListingCatalog.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`
|
||||
- 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`
|
||||
- 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`
|
||||
- role assignment via user search in community admin: `partials/landing/account/community-admin.php`, `src/App/CommunityAccess.php`
|
||||
|
||||
@@ -41,6 +41,7 @@ Papa-Kind-Treff ist eine PHP-basierte Plattform für Väter mit Fokus auf:
|
||||
- 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
|
||||
- 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 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
|
||||
- Im Mitgliederbereich ist `Events` jetzt wieder auf echte eigene Events und Event-Teilnahmen beschränkt
|
||||
|
||||
@@ -148,6 +148,7 @@ if (!empty($canManageProfileLevels)) {
|
||||
$showEventTypeChoice = !$editing && (($section ?? 'events') === 'places') && $requestedCreateListingKind === null;
|
||||
$modalSectionTarget = ($editingListing || ($section ?? '') === 'places') ? 'places' : 'events';
|
||||
if (!empty($canManageSystemSettings)) {
|
||||
$sectionLinks['categories'] = 'Kategorien';
|
||||
$sectionLinks['system'] = 'System';
|
||||
}
|
||||
?>
|
||||
@@ -946,45 +947,57 @@ if (!empty($canManageSystemSettings)) {
|
||||
<li>Fehlende Tabellen: <?= !empty($listingCatalogStatus['missing']) ? htmlspecialchars(implode(', ', $listingCatalogStatus['missing']), ENT_QUOTES) : 'Keine' ?></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<?php endif; ?>
|
||||
|
||||
<div class="card" style="margin-top:18px;">
|
||||
<strong>Kategorien prüfen und zusammenführen</strong>
|
||||
<p class="muted small" style="margin:10px 0 14px;">Neue Kategorien werden automatisch angelegt. Hier kannst du sie prüfen und bei Bedarf mit einer bestehenden Kategorie zusammenführen.</p>
|
||||
<?php if ($section === 'categories' && !empty($canManageSystemSettings)): ?>
|
||||
<section class="account-panel">
|
||||
<div class="account-panel__head">
|
||||
<h2>Kategorien</h2>
|
||||
</div>
|
||||
<div class="account-panel__body">
|
||||
<?php if (!empty($categoryReviewItems)): ?>
|
||||
<datalist id="categoryMergeSuggestions">
|
||||
<?php foreach ($listingCategories as $targetCategory): ?>
|
||||
<option value="<?= htmlspecialchars((string)$targetCategory['title'], ENT_QUOTES) ?>"></option>
|
||||
<?php endforeach; ?>
|
||||
</datalist>
|
||||
<div class="stack gap-10">
|
||||
<?php foreach ($categoryReviewItems as $categoryItem): ?>
|
||||
<div class="card" style="padding:12px;">
|
||||
<div class="flex between center-y" style="gap:12px; flex-wrap:wrap;">
|
||||
<div>
|
||||
<strong><?= htmlspecialchars((string)$categoryItem['title'], ENT_QUOTES) ?></strong>
|
||||
<?php if (!empty($categoryItem['needs_review'])): ?>
|
||||
<span class="badge" style="margin-left:8px;">Neu</span>
|
||||
<?php endif; ?>
|
||||
<div class="muted small" style="margin-top:6px;">
|
||||
Slug: <?= htmlspecialchars((string)$categoryItem['slug'], ENT_QUOTES) ?> · Einträge: <?= (int)($categoryItem['listing_count'] ?? 0) ?> · Orte: <?= (int)($categoryItem['place_count'] ?? 0) ?> · Eigene Events: <?= (int)($categoryItem['event_count'] ?? 0) ?>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<form method="post" class="flex gap-8" style="margin-top:12px; flex-wrap:wrap;">
|
||||
<form method="post" class="stack gap-8" style="margin-top:12px;">
|
||||
<input type="hidden" name="action" value="category_merge">
|
||||
<input type="hidden" name="source_category_slug" value="<?= htmlspecialchars((string)$categoryItem['slug'], ENT_QUOTES) ?>">
|
||||
<select name="target_category_slug" class="select" style="min-width:260px;">
|
||||
<option value="">Mit Kategorie zusammenführen</option>
|
||||
<?php foreach ($listingCategories as $targetCategory): ?>
|
||||
<?php if ((string)$targetCategory['slug'] === (string)$categoryItem['slug']) { continue; } ?>
|
||||
<option value="<?= htmlspecialchars((string)$targetCategory['slug'], ENT_QUOTES) ?>"><?= htmlspecialchars((string)$targetCategory['title'], ENT_QUOTES) ?></option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
<div class="stack gap-6">
|
||||
<label class="label" for="targetCategoryInput<?= (int)($categoryItem['id'] ?? 0) ?>">Mit bestehender Kategorie zusammenführen</label>
|
||||
<input
|
||||
id="targetCategoryInput<?= (int)($categoryItem['id'] ?? 0) ?>"
|
||||
name="target_category_input"
|
||||
class="input"
|
||||
list="categoryMergeSuggestions"
|
||||
placeholder="Kategorie suchen"
|
||||
required
|
||||
>
|
||||
</div>
|
||||
<div class="flex gap-8" style="flex-wrap:wrap;">
|
||||
<button class="btn ghost" type="submit">Zusammenführen</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
<?php else: ?>
|
||||
<p class="muted small" style="margin:12px 0 0;">Aktuell gibt es keine Kategorien zur Prüfung.</p>
|
||||
<p class="muted small">Aktuell gibt es keine neuen Kategorien.</p>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<?php endif; ?>
|
||||
|
||||
|
||||
@@ -183,6 +183,7 @@ final class AccountPages
|
||||
$allowedSections = ['profile', 'children', 'events', 'places', 'community', 'settings'];
|
||||
if ($canManageSystemSettings) {
|
||||
$allowedSections[] = 'system';
|
||||
$allowedSections[] = 'categories';
|
||||
}
|
||||
if ($canManageProfileLevels) {
|
||||
$allowedSections[] = 'profile-levels';
|
||||
@@ -328,9 +329,13 @@ final class AccountPages
|
||||
if (!$canManageSystemSettings || !$listingCatalog) {
|
||||
throw new \RuntimeException('Keine Berechtigung für die Kategorien-Verwaltung.');
|
||||
}
|
||||
$targetCategorySlug = $listingCatalog->resolveExistingCategorySlug((string)($_POST['target_category_input'] ?? ''));
|
||||
if ($targetCategorySlug === null) {
|
||||
throw new \RuntimeException('Bitte wähle eine vorhandene Zielkategorie aus den Vorschlägen.');
|
||||
}
|
||||
$listingCatalog->mergeCategories(
|
||||
(string)($_POST['source_category_slug'] ?? ''),
|
||||
(string)($_POST['target_category_slug'] ?? '')
|
||||
$targetCategorySlug
|
||||
);
|
||||
$info = 'Kategorie zusammengeführt.';
|
||||
} elseif ($action === 'child_add' || $action === 'child_update') {
|
||||
@@ -852,7 +857,10 @@ final class AccountPages
|
||||
$restoreEventDraftActive = $section === 'events' && ((string)($_GET['restore_event_draft'] ?? '')) === '1' && self::hasStoredEventDraft();
|
||||
$restoredEventDraft = $restoreEventDraftActive ? self::getStoredEventDraft() : null;
|
||||
$listingCategories = $listingCatalog ? $listingCatalog->listCategories(['general', 'place', 'food', 'event', 'family']) : [];
|
||||
$categoryReviewItems = $listingCatalog && $canManageSystemSettings ? $listingCatalog->listCategoryReviewItems() : [];
|
||||
$categoryReviewItems = $listingCatalog && $canManageSystemSettings ? array_values(array_filter(
|
||||
$listingCatalog->listCategoryReviewItems(),
|
||||
static fn(array $item): bool => !empty($item['needs_review'])
|
||||
)) : [];
|
||||
$placeSuggestions = $listingCatalog ? $listingCatalog->listPlaceSuggestions(60) : [];
|
||||
$eventLocationOptions = $listingCatalog ? $listingCatalog->listEventLocationOptions(120) : [];
|
||||
$stmt = $pdo?->prepare(
|
||||
|
||||
@@ -368,6 +368,29 @@ final class ListingCatalog
|
||||
}
|
||||
}
|
||||
|
||||
public function resolveExistingCategorySlug(string $input): ?string
|
||||
{
|
||||
$this->ensureSchema();
|
||||
$input = trim($input);
|
||||
if ($input === '') {
|
||||
return null;
|
||||
}
|
||||
|
||||
$stmt = $this->pdo->prepare(
|
||||
'SELECT slug
|
||||
FROM listing_categories
|
||||
WHERE slug = :slug OR LOWER(title) = LOWER(:title)
|
||||
ORDER BY sort_order ASC, id ASC
|
||||
LIMIT 1'
|
||||
);
|
||||
$stmt->execute([
|
||||
'slug' => $input,
|
||||
'title' => $input,
|
||||
]);
|
||||
$slug = $stmt->fetchColumn();
|
||||
return $slug !== false ? (string)$slug : null;
|
||||
}
|
||||
|
||||
public function listPublishedDirectoryEntries(): array
|
||||
{
|
||||
$this->ensureSchema();
|
||||
|
||||
Reference in New Issue
Block a user