From 39dfc97ae62249a057841d5eb99f8f197a79fe33 Mon Sep 17 00:00:00 2001 From: Lars Gebhardt-Kusche Date: Wed, 19 Aug 2026 20:14:15 +0200 Subject: [PATCH] yyxc --- Internal/de/PROJECT_CONTEXT.md | 2 + Internal/en/PROJECT_CONTEXT.md | 2 + README.md | 1 + partials/landing/account/dashboard.php | 77 ++++++++++++++++++++++++-- src/App/AccountPages.php | 8 ++- src/App/CommunityAccess.php | 2 +- src/App/ListingCatalog.php | 62 +++++++++++++++++++++ 7 files changed, 147 insertions(+), 7 deletions(-) diff --git a/Internal/de/PROJECT_CONTEXT.md b/Internal/de/PROJECT_CONTEXT.md index 42aa391..fc1b91d 100644 --- a/Internal/de/PROJECT_CONTEXT.md +++ b/Internal/de/PROJECT_CONTEXT.md @@ -49,9 +49,11 @@ Papa-Kind-Treff ist eine PHP-basierte Plattform für Väter. Kernbereiche sind l - In `Profil-Levels` kann der Owner zusätzlich Benutzer suchen und Community-Punkte manuell mit Begründung erhöhen. - 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`. +- `Forum-Admin` kann Kategorien bereits bestätigen und zusammenführen sowie Orte und Veranstaltungen freigeben. - 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. - Neue Kategorien bleiben dort sichtbar, bis sie von einem Berechtigten bestätigt oder mit einer bestehenden Kategorie zusammengeführt werden. +- Im Bereich `Kategorien` gibt es zusätzlich eine Suche über bestehende Kategorien, damit sie gezielt gefunden und zusammengeführt werden können. - Community-Level können zusätzlich die Rechte für Kategorien sowie für die Freigabe von Orten und Veranstaltungen tragen. ## Technischer Rahmen diff --git a/Internal/en/PROJECT_CONTEXT.md b/Internal/en/PROJECT_CONTEXT.md index 5f5266c..771a38d 100644 --- a/Internal/en/PROJECT_CONTEXT.md +++ b/Internal/en/PROJECT_CONTEXT.md @@ -49,9 +49,11 @@ Papa-Kind-Treff is a PHP-based platform for fathers. Core areas are local events - Inside `Profile Levels`, the owner can additionally search users and manually increase community points with a required reason. - `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`. +- `Forum Admin` can already confirm and merge categories as well as approve places and event-style entries. - 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. - New categories remain visible there until an authorized user confirms them or merges them into an existing category. +- The `Categories` area also includes a search across existing categories so they can be found and merged directly. - Community levels can now additionally carry the rights for category handling as well as place and event approvals. ## Technical Frame diff --git a/README.md b/README.md index dba2de6..86f8d8f 100644 --- a/README.md +++ b/README.md @@ -43,6 +43,7 @@ Papa-Kind-Treff ist eine PHP-basierte Plattform für Väter mit Fokus auf: - 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 Kategorien bleiben im Bereich `Kategorien` sichtbar, bis sie von einem Berechtigten bestätigt oder zusammengeführt werden +- Im Bereich `Kategorien` gibt es zusätzlich eine Suche über bestehende Kategorien, damit sie gezielt gefunden und zusammengeführt werden können - 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` - Seiten-Admins haben zusätzlich eine System-Sektion für globale Wartungs- und Diensteschalter diff --git a/partials/landing/account/dashboard.php b/partials/landing/account/dashboard.php index 916d378..345e3b4 100755 --- a/partials/landing/account/dashboard.php +++ b/partials/landing/account/dashboard.php @@ -959,12 +959,79 @@ if (!empty($canManageSystemSettings)) {

Kategorien

+ + + + + + +
+
+ +
+
+ + +
+
+ + +
+
+
+
+ + +
+ Suchergebnisse + +
+ +
+
+ + + Neu + +
+ Slug: · Einträge: · Orte: · Eigene Events: +
+
+
+ + +
+ + +
+
+ +
+
+ +
+ + + +
+ +
+ +
+ +

Keine passenden Kategorien gefunden.

+ +
+ + - - - - -
diff --git a/src/App/AccountPages.php b/src/App/AccountPages.php index 10adcce..aba57ba 100755 --- a/src/App/AccountPages.php +++ b/src/App/AccountPages.php @@ -871,6 +871,10 @@ final class AccountPages $listingCatalog->listCategoryReviewItems(), static fn(array $item): bool => !empty($item['needs_review']) )) : []; + $categorySearchQuery = $canManageCategories ? trim((string)($_GET['category_query'] ?? '')) : ''; + $categorySearchResults = ($listingCatalog && $canManageCategories && $categorySearchQuery !== '') + ? $listingCatalog->searchCategories($categorySearchQuery, 20) + : []; $placeSuggestions = $listingCatalog ? $listingCatalog->listPlaceSuggestions(60) : []; $eventLocationOptions = $listingCatalog ? $listingCatalog->listEventLocationOptions(120) : []; $stmt = $pdo?->prepare( @@ -1000,6 +1004,7 @@ final class AccountPages 'Themen und Antworten moderieren', 'Community-Sperren setzen und aufheben', 'Meldungen bearbeiten', + 'Kategorien bestätigen und zusammenführen', 'Orte und Veranstaltungen freigeben', ], ], @@ -1009,7 +1014,6 @@ final class AccountPages 'rights' => [ 'Beinhaltet alle Rechte von Forum-Admin', 'Community-Admin-Bewerbungen bearbeiten', - 'Kategorien bestätigen und zusammenführen', 'System-Einstellungen verwalten', ], ], @@ -1057,6 +1061,8 @@ final class AccountPages 'editListing', 'listingCategories', 'categoryReviewItems', + 'categorySearchQuery', + 'categorySearchResults', 'placeSuggestions', 'eventLocationOptions', 'communityPoints', diff --git a/src/App/CommunityAccess.php b/src/App/CommunityAccess.php index 0260774..fa2bd5d 100644 --- a/src/App/CommunityAccess.php +++ b/src/App/CommunityAccess.php @@ -77,7 +77,7 @@ final class CommunityAccess public function canManageCategories(int $userId, ?float $points = null): bool { - if ($this->hasRole($userId, 'owner') || $this->hasRole($userId, 'site_admin')) { + if ($this->hasRole($userId, 'owner') || $this->hasRole($userId, 'site_admin') || $this->hasRole($userId, 'forum_admin')) { return true; } diff --git a/src/App/ListingCatalog.php b/src/App/ListingCatalog.php index dc53548..d46578b 100644 --- a/src/App/ListingCatalog.php +++ b/src/App/ListingCatalog.php @@ -314,6 +314,68 @@ final class ListingCatalog return $rows; } + public function searchCategories(string $query, int $limit = 20): array + { + $this->ensureSchema(); + $query = trim($query); + if ($query === '') { + return []; + } + + $limit = max(1, min(100, $limit)); + $tokens = array_values(array_filter(preg_split('/\s+/u', mb_strtolower($query)) ?: [], static fn(string $token): bool => $token !== '')); + if ($tokens === []) { + return []; + } + + $conditions = []; + $params = []; + foreach ($tokens as $index => $token) { + $titleKey = ':title_' . $index; + $slugKey = ':slug_' . $index; + $conditions[] = '(LOWER(c.title) LIKE ' . $titleKey . ' OR LOWER(c.slug) LIKE ' . $slugKey . ')'; + $params[$titleKey] = '%' . $token . '%'; + $params[$slugKey] = '%' . $token . '%'; + } + + $sql = ' + SELECT c.id, c.slug, c.title, c.category_group, c.sort_order, + (SELECT COUNT(*) FROM listing_category_map m WHERE m.category_id = c.id) AS listing_count, + (SELECT COUNT(*) FROM listing_places p WHERE p.place_kind = c.slug) AS place_count + FROM listing_categories c + WHERE ' . implode(' AND ', $conditions) . ' + ORDER BY + CASE + WHEN LOWER(c.title) = :exactTitle THEN 0 + WHEN LOWER(c.slug) = :exactSlug THEN 1 + WHEN c.sort_order >= 900 THEN 2 + ELSE 3 + END, + c.sort_order ASC, + c.title ASC + LIMIT ' . $limit; + $params[':exactTitle'] = mb_strtolower($query); + $params[':exactSlug'] = $this->slugify($query); + + $stmt = $this->pdo->prepare($sql); + $stmt->execute($params); + $rows = $stmt->fetchAll(\PDO::FETCH_ASSOC) ?: []; + + $eventStmt = $this->pdo->query('SELECT category_slug, COUNT(*) AS total FROM events WHERE category_slug IS NOT NULL AND category_slug <> "" GROUP BY category_slug'); + $eventCounts = []; + foreach (($eventStmt ? $eventStmt->fetchAll(\PDO::FETCH_ASSOC) : []) as $row) { + $eventCounts[(string)$row['category_slug']] = (int)$row['total']; + } + + foreach ($rows as &$row) { + $row['event_count'] = $eventCounts[(string)$row['slug']] ?? 0; + $row['needs_review'] = ((int)($row['sort_order'] ?? 0)) >= 900; + } + unset($row); + + return $rows; + } + public function mergeCategories(string $sourceSlug, string $targetSlug): void { $this->ensureSchema();