diff --git a/Internal/db/schema.sql b/Internal/db/schema.sql
index 624ce8a..606b033 100755
--- a/Internal/db/schema.sql
+++ b/Internal/db/schema.sql
@@ -258,6 +258,27 @@ CREATE TABLE listing_benefits (
INDEX idx_listing_benefits_listing (listing_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
+CREATE TABLE listing_moderation_requests (
+ id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
+ listing_id BIGINT UNSIGNED NOT NULL,
+ request_type ENUM('create','update','delete') NOT NULL,
+ request_status ENUM('open','approved','rejected') NOT NULL DEFAULT 'open',
+ requested_by BIGINT UNSIGNED NOT NULL,
+ reviewed_by BIGINT UNSIGNED NULL,
+ request_reason TEXT NULL,
+ review_note TEXT NULL,
+ payload_json LONGTEXT NULL,
+ created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ reviewed_at DATETIME NULL,
+ updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+ CONSTRAINT fk_listing_moderation_listing FOREIGN KEY (listing_id) REFERENCES listings(id) ON DELETE CASCADE,
+ CONSTRAINT fk_listing_moderation_requested_by FOREIGN KEY (requested_by) REFERENCES users(id) ON DELETE CASCADE,
+ CONSTRAINT fk_listing_moderation_reviewed_by FOREIGN KEY (reviewed_by) REFERENCES users(id) ON DELETE SET NULL,
+ INDEX idx_listing_moderation_status (request_status),
+ INDEX idx_listing_moderation_type (request_type),
+ INDEX idx_listing_moderation_user (requested_by)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
+
-- Community / Forum
CREATE TABLE forum_categories (
id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
diff --git a/Internal/de/PROJECT_CONTEXT.md b/Internal/de/PROJECT_CONTEXT.md
index d3b1c60..31c64c8 100644
--- a/Internal/de/PROJECT_CONTEXT.md
+++ b/Internal/de/PROJECT_CONTEXT.md
@@ -16,16 +16,21 @@ 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`, `Ausflugsziele`, `Community`, `Einstellungen`, `Abmelden`
+- Profil-Menü aktuell: `Profil`, `Kinder`, `Events`, `Orte & Veranstaltungen`, `Community`, `Einstellungen`, `Abmelden`
- 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.
- Profiladresse kann per Suche oder Browser-Standort übernommen werden.
- Der Bereich `Events` ist wieder auf echte eigene Events und Event-Teilnahmen begrenzt.
- Der Bereich `Events` bietet zusätzlich einen manuellen ICS-Export und einen persönlichen abonnierbaren Kalender-Feed für alle eigenen Events und Event-Teilnahmen.
-- Der Bereich `Events` ist im UI in `Meine Events`, `Synchronisation` und `Abgelaufene Events` getrennt, damit aktive Events, Kalender-Themen und Vergangenes sauber getrennt bleiben.
+- Der Bereich `Events` ist im UI in `Meine Events`, `Kalendersynchronisation` und `Abgelaufene Events` getrennt, damit aktive Events, Kalender-Themen und Vergangenes sauber getrennt bleiben.
- In `Meine Events` sitzt der Button `Neues Event anlegen` direkt in der Box `Eigene Events`; die bisherige Teilnahme-Box heißt jetzt `Angemeldete Events` und enthält zusätzlich einen direkten Link zur `Event Suche`.
-- Orte und Veranstaltungen als Ausflugsziele sind vorerst in einen separaten Mitgliederbereichspunkt `Ausflugsziele` verschoben.
+- Orte und Veranstaltungen sind vorerst in einen separaten Mitgliederbereichspunkt `Orte & Veranstaltungen` verschoben.
+- In `Orte & Veranstaltungen` können Nutzer dauerhafte Orte und zeitlich begrenzte Veranstaltungen neu anlegen.
+- Neue Orte und Veranstaltungen bleiben bis zur Freigabe im Status `wartet auf Freigabe` und werden erst danach systemweit veröffentlicht.
+- Änderungs- und Löschwünsche für veröffentlichte Orte und Veranstaltungen laufen immer über eine begründete Moderationsanfrage.
+- Vorschläge für neue Orte und Veranstaltungen dürfen schon vor der Freigabe als Ortsvorschlag für Events genutzt werden, solange sie noch offen geprüft werden.
+- Dubletten bei Orten und Veranstaltungen sollen serverseitig mindestens über Namen und Adresse abgefangen werden.
- Die Eingabe im Bereich `Events` soll so einfach wie möglich bleiben und zeigt deshalb nur die für echte Termine relevanten Felder.
- Kategorien werden im Mitgliederbereich per Sucheingabe mit bestehenden Vorschlägen und automatischer Neuanlage gepflegt.
- Eigene Events unterstützen Kategorie, optionales Bild, Ja/Nein-Angabe `Mit Kindern`, optionale Platzzahl und direkte Karten-/Adress-Synchronisierung.
diff --git a/Internal/de/PROJECT_STRUCTURE.md b/Internal/de/PROJECT_STRUCTURE.md
index e65c71c..eeb0db3 100644
--- a/Internal/de/PROJECT_STRUCTURE.md
+++ b/Internal/de/PROJECT_STRUCTURE.md
@@ -21,8 +21,9 @@ Stand: 2026-08-10
- System-Einstellungen: `src/App/SystemSettings.php`
- 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 `Ausflugsziele` im Mitgliederbereich: `partials/landing/account/dashboard.php`
-- Preis-, Bild- und Ortseingabe für Ausflugsziele sowie reduzierte Event-Maske: `partials/landing/account/dashboard.php` plus Speicherung in `src/App/ListingCatalog.php`
+- UI für getrennte Bereiche `Events` und `Orte & Veranstaltungen` im Mitgliederbereich: `partials/landing/account/dashboard.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`
- Legacy-Eigen-Events wurden erweitert in `src/App/AccountPages.php` und `Internal/db/schema.sql` um Kategorie- und Bildfelder
- Kategorien-Prüfung und Zusammenführung für Seiten-Admins liegen ebenfalls in `partials/landing/account/dashboard.php` mit Logik in `src/App/ListingCatalog.php`
diff --git a/Internal/de/README.md b/Internal/de/README.md
index 9186b1e..4dfeed4 100644
--- a/Internal/de/README.md
+++ b/Internal/de/README.md
@@ -16,7 +16,7 @@ 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`, `Ausflugsziele`, `Community`, `Einstellungen`
+- Profil-Menü mit `Profil`, `Kinder`, `Events`, `Orte & Veranstaltungen`, `Community`, `Einstellungen`
- 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
@@ -24,7 +24,8 @@ Papa-Kind-Treff ist eine PHP-basierte Plattform für Väter mit Fokus auf lokale
- neue Datenbasis für ein späteres Termin-, Ort- und Veranstaltungssystem angelegt
- Mitgliederbereich `Events` ist wieder auf echte eigene Events und Event-Teilnahmen fokussiert
- Eigene Events haben jetzt Kategorie, optionales Bild, klare Kinderangabe, optionale Platzzahl und eine direktere Karten-/Adress-Synchronisierung
-- Orte und sonstige Veranstaltungen wurden vorerst in einen separaten Bereich `Ausflugsziele` verschoben
+- Orte und sonstige Veranstaltungen wurden vorerst in einen separaten Bereich `Orte & Veranstaltungen` verschoben
+- Neue Orte und Veranstaltungen müssen vor Veröffentlichung erst durch einen Admin freigegeben werden; Änderungs- und Löschwünsche laufen ebenfalls nur als begründete Anfrage
- Die Eingabemasken sind jetzt progressiv aufgebaut: erst Pflichtangaben, optionale Daten in einklappbaren Bereichen
- Kategorien laufen jetzt über eine Sucheingabe mit bestehenden Vorschlägen; neue Kategorien werden automatisch angelegt und können im Systembereich von Seiten-Admins zusammengeführt werden
- Im Bereich `Events` gibt es jetzt zusätzlich einen ICS-Download und einen persönlichen abonnierbaren Kalender-Feed für alle eigenen Events und Event-Teilnahmen
diff --git a/Internal/en/PROJECT_CONTEXT.md b/Internal/en/PROJECT_CONTEXT.md
index ae6e3f3..44d61b3 100644
--- a/Internal/en/PROJECT_CONTEXT.md
+++ b/Internal/en/PROJECT_CONTEXT.md
@@ -16,16 +16,21 @@ 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`, `Outings`, `Community`, `Settings`, `Logout`.
+- Profile menu currently contains `Profile`, `Children`, `Events`, `Places & Events`, `Community`, `Settings`, `Logout`.
- 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.
- Profile address can be completed via address search or browser-based location import.
- The `Events` area is limited again to real own events and event participations.
- The `Events` area now also provides a manual ICS export and a personal subscribable calendar feed for all own events and event participations.
-- The `Events` area is now separated in the UI into `My Events`, `Synchronization`, and `Expired Events` so that active events, calendar tasks, and past items stay clearly separated.
+- The `Events` area is now separated in the UI into `My Events`, `Calendar Sync`, and `Expired Events` so that active events, calendar tasks, and past items stay clearly separated.
- Inside `My Events`, the `Create New Event` button now lives directly in the `Own Events` box; the previous participation box is now labeled `Registered Events` and also includes a direct link to `Event Search`.
-- Places and event-like outing targets were moved for now into a separate member-area section `Outings`.
+- Places and event-like entries were moved for now into a separate member-area section `Places & Events`.
+- Inside `Places & Events`, users can create permanent places and time-limited event-style entries.
+- New places and event-style entries stay in a pending state until an admin approves them for publication.
+- Change and deletion wishes for published places and event-style entries always run through a reasoned moderation request.
+- Pending new places and event-style entries can already be used as location suggestions for events while they are still under review.
+- Duplicate submissions for places and event-style entries should be blocked server-side at least by name and address.
- The `Events` input flow should stay as simple as possible and therefore only shows fields relevant for real scheduled events.
- Categories in the member area are handled via a search input with existing suggestions and automatic creation when needed.
- Own events support category, optional image, a clear `with children` yes/no field, optional capacity, and direct map/address synchronization.
diff --git a/Internal/en/PROJECT_STRUCTURE.md b/Internal/en/PROJECT_STRUCTURE.md
index 617ff1f..97fae36 100644
--- a/Internal/en/PROJECT_STRUCTURE.md
+++ b/Internal/en/PROJECT_STRUCTURE.md
@@ -21,8 +21,9 @@ Updated: 2026-08-10
- system settings: `src/App/SystemSettings.php`
- 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 `Outings` UI in member area: `partials/landing/account/dashboard.php`
-- pricing, image upload, and place/address input for outings plus a reduced own-event form: `partials/landing/account/dashboard.php` with persistence in `src/App/ListingCatalog.php`
+- separated `Events` and `Places & Events` UI in member area: `partials/landing/account/dashboard.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`
- legacy own events were extended in `src/App/AccountPages.php` and `Internal/db/schema.sql` with category and image fields
## Documentation Rule
diff --git a/Internal/en/README.md b/Internal/en/README.md
index d7ecb8f..1cbcb81 100644
--- a/Internal/en/README.md
+++ b/Internal/en/README.md
@@ -16,7 +16,7 @@ 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`, `Outings`, `Community`, `Settings`
+- profile menu with `Profile`, `Children`, `Events`, `Places & Events`, `Community`, `Settings`
- 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
@@ -24,7 +24,8 @@ Papa-Kind-Treff is a PHP-based platform for fathers focused on local events, app
- new data foundation prepared for a broader event, place, and listing system
- member area `Events` is now limited again to real own events and event participations
- own events now include category, optional image, explicit child suitability, optional capacity, and more direct map/address synchronization
-- places and other events were moved for now into a separate member-area section `Outings`
+- places and other event-style entries were moved for now into a separate member-area section `Places & Events`
+- new places and event-style entries must be approved by an admin before publication; change and deletion wishes also run as reasoned requests
- the member-area entry forms now follow a progressive approach: required fields first, optional data inside collapsible sections
- categories now use a search input with existing suggestions; new categories are created automatically and can be merged by site admins in the system area
- the `Events` area now also includes an ICS download and a personal subscribable calendar feed for all own events and event participations
diff --git a/README.md b/README.md
index 4dd8f95..6e1d469 100644
--- a/README.md
+++ b/README.md
@@ -36,16 +36,18 @@ 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`, `Ausflugsziele`, `Community`, `Einstellungen`, `Abmelden`
+- Profil-Menü aktuell: `Profil`, `Kinder`, `Events`, `Orte & Veranstaltungen`, `Community`, `Einstellungen`, `Abmelden`
- 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
-- Orte und sonstige Veranstaltungen laufen jetzt separat unter `Ausflugsziele`
+- Orte und sonstige Veranstaltungen laufen jetzt separat unter `Orte & Veranstaltungen`
+- Neue Orte und Veranstaltungen müssen vor Veröffentlichung erst durch einen Admin freigegeben werden; Änderungs- und Löschwünsche laufen ebenfalls als begründete Anfrage
- Eigene Events unterstützen aktuell Kategorie, Bild-Upload, klare Kinderangabe, optionale Platzzahl und direkte Karten-/Adress-Synchronisierung
- Die Eingabe im Mitgliederbereich ist jetzt bewusst vereinfacht: zuerst nur Pflichtangaben, optionale Angaben in einklappbaren Bereichen
- Kategorien werden jetzt über eine Sucheingabe mit bestehenden Vorschlägen gepflegt; neue Kategorien werden automatisch angelegt und sind für Seiten-Admins im Systembereich zusammenführbar
- Im Mitgliederbereich `Events` gibt es jetzt zusätzlich einen ICS-Download und einen persönlichen abonnierbaren Kalender-Feed für alle eigenen Events und Event-Teilnahmen
-- Der Bereich `Events` ist im Mitgliederbereich jetzt zusätzlich in die Tabs `Meine Events`, `Synchronisation` und `Abgelaufene Events` gegliedert
+- Der Bereich `Events` ist im Mitgliederbereich jetzt zusätzlich in die Tabs `Meine Events`, `Kalendersynchronisation` und `Abgelaufene Events` gegliedert
+- Vorschläge für neue Orte und Veranstaltungen werden schon vor der Freigabe als mögliche Ortsauswahl für Events berücksichtigt, solange sie nicht abgelehnt oder archiviert wurden
- Für später vorgemerkt: direkte Kalender-Anbindung großer Anbieter wie Google und Microsoft/Outlook per OAuth, zusätzlich zum bestehenden ICS-Feed
- Interne Projektdateien und das Datenbankschema liegen nicht mehr im Root, sondern unter `Internal/`
diff --git a/partials/landing/account/community-admin.php b/partials/landing/account/community-admin.php
index 07e18e6..7c30868 100644
--- a/partials/landing/account/community-admin.php
+++ b/partials/landing/account/community-admin.php
@@ -12,6 +12,7 @@ if (!$userId) {
$communityCfg = require __DIR__ . '/../../../config/community.php';
$access = $pdo ? new \App\CommunityAccess($pdo, $communityCfg) : null;
$migration = $pdo ? new \App\CommunityMigration($pdo) : null;
+$listingCatalog = $pdo ? new \App\ListingCatalog($pdo) : null;
if (!$access || !$access->canModerateForum((int)$userId)) {
http_response_code(403);
@@ -23,11 +24,18 @@ $error = '';
$info = '';
$canManageApplications = $access->canManageApplications((int)$userId) && $access->supportsApplications();
$canManageRoles = $access->canManageRoles((int)$userId);
+$canReviewListings = $listingCatalog !== null && !$access->hasRole((int)$userId, 'owner');
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$action = (string)($_POST['action'] ?? '');
try {
- if ($action === 'application_decide') {
+ if ($action === 'listing_request_decide') {
+ if (!$canReviewListings || !$listingCatalog) {
+ throw new \RuntimeException('Keine Berechtigung für Listing-Freigaben.');
+ }
+ $listingCatalog->decideModerationRequest((int)$userId, (int)($_POST['request_id'] ?? 0), (string)($_POST['decision'] ?? ''), (string)($_POST['review_note'] ?? ''));
+ $info = 'Listing-Anfrage wurde bearbeitet.';
+ } elseif ($action === 'application_decide') {
$access->decideApplication((int)$userId, (int)($_POST['application_id'] ?? 0), (string)($_POST['decision'] ?? ''), (string)($_POST['decision_reason'] ?? ''));
$info = 'Bewerbung wurde bearbeitet.';
} elseif ($action === 'report_resolve') {
@@ -51,6 +59,7 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
}
}
+$listingRequests = $canReviewListings && $listingCatalog ? $listingCatalog->listOpenModerationRequests() : [];
$applications = $canManageApplications ? $access->listApplications('open') : [];
$reports = $access->supportsReports() ? $access->listOpenReports() : [];
$roleAssignments = $canManageRoles ? $access->listRoleAssignments() : [];
@@ -84,6 +93,70 @@ $migrationStatus = ($migration && $canManageApplications) ? $migration->status()
+
+
+
+
+
Offene Orts- und Veranstaltungsanfragen
+
Neue Einträge sowie Änderungs- und Löschwünsche prüfen.