Community Orte Veranstalungen
All checks were successful
Deploy / deploy (push) Successful in 57s

This commit is contained in:
2026-08-21 02:02:24 +02:00
parent a303d9ff70
commit fae2dcc830
11 changed files with 89 additions and 24 deletions

View File

@@ -29,6 +29,7 @@ Papa-Kind-Treff ist eine PHP-basierte Plattform für Väter. Kernbereiche sind l
- Orte und Veranstaltungen sind vorerst in einen separaten Mitgliederbereichspunkt `Orte & Veranstaltungen` 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. - 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. - Neue Orte und Veranstaltungen bleiben bis zur Freigabe im Status `wartet auf Freigabe` und werden erst danach systemweit veröffentlicht.
- Noch nicht veröffentlichte Orts- und Veranstaltungsvorschläge können vom Ersteller im Mitgliederbereich direkt weiterbearbeitet werden.
- Änderungs- und Löschwünsche für veröffentlichte Orte und Veranstaltungen laufen immer über eine begründete Moderationsanfrage. - Ä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. - 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. - Dubletten bei Orten und Veranstaltungen sollen serverseitig mindestens über Namen und Adresse abgefangen werden.
@@ -50,6 +51,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. - 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`. - 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. - `Forum-Admin` kann Kategorien bereits bestätigen und zusammenführen sowie Orte und Veranstaltungen freigeben.
- `SiteOwner` und `Site-Admin` erben diese Freigaberechte vollständig mit.
- 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. - Neue Kategorien bleiben dort sichtbar, bis sie von einem Berechtigten bestätigt oder mit einer bestehenden Kategorie zusammengeführt werden.

View File

@@ -28,6 +28,7 @@ Stand: 2026-08-10
- 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` - 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`
- Direktbearbeitung noch nicht veröffentlichter Orts- und Veranstaltungsvorschläge sowie Rechteauflösung für deren Freigabe: `src/App/CommunityAccess.php`, `src/App/AccountPages.php`, `src/App/ListingCatalog.php`, `partials/landing/account/dashboard.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`
- Legacy-Eigen-Events wurden erweitert in `src/App/AccountPages.php` und `Internal/db/schema.sql` um Kategorie- und Bildfelder - 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` - 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`

View File

@@ -29,6 +29,8 @@ Papa-Kind-Treff ist eine PHP-basierte Plattform für Väter mit Fokus auf lokale
- Der Zwischenschritt vom eigenen Event zum Anlegen eines neuen Orts oder einer neuen Veranstaltung springt danach wieder in das Event zurück und wählt den neuen Eintrag direkt aus - Der Zwischenschritt vom eigenen Event zum Anlegen eines neuen Orts oder einer neuen Veranstaltung springt danach wieder in das Event zurück und wählt den neuen Eintrag direkt aus
- Orte und sonstige Veranstaltungen wurden vorerst in einen separaten Bereich `Orte & Veranstaltungen` 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 - 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
- Noch nicht veröffentlichte Orts- und Veranstaltungsvorschläge können im Mitgliederbereich direkt weiterbearbeitet werden
- `SiteOwner` übernimmt bei Orts- und Veranstaltungsanfragen vollständig die Freigaberechte von `Forum-Admin`
- Die Eingabemasken sind jetzt progressiv aufgebaut: erst Pflichtangaben, optionale Daten in einklappbaren Bereichen - 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 - 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 - 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

View File

@@ -29,6 +29,7 @@ Papa-Kind-Treff is a PHP-based platform for fathers. Core areas are local events
- Places and event-like entries were moved for now into a separate member-area section `Places & Events`. - 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. - 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. - New places and event-style entries stay in a pending state until an admin approves them for publication.
- Not-yet-published place and event-style suggestions can be edited directly by their creator inside the member area.
- Change and deletion wishes for published places and event-style entries always run through a reasoned moderation request. - 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. - 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. - Duplicate submissions for places and event-style entries should be blocked server-side at least by name and address.
@@ -50,6 +51,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. - `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 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. - `Forum Admin` can already confirm and merge categories as well as approve places and event-style entries.
- `SiteOwner` and `Site Admin` fully inherit those approval rights.
- 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. - New categories remain visible there until an authorized user confirms them or merges them into an existing category.

View File

@@ -28,6 +28,7 @@ Updated: 2026-08-10
- 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` - 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`
- direct editing for not-yet-published place and event-style suggestions plus approval-right resolution: `src/App/CommunityAccess.php`, `src/App/AccountPages.php`, `src/App/ListingCatalog.php`, `partials/landing/account/dashboard.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`
- legacy own events were extended in `src/App/AccountPages.php` and `Internal/db/schema.sql` with category and image fields - legacy own events were extended in `src/App/AccountPages.php` and `Internal/db/schema.sql` with category and image fields

View File

@@ -29,6 +29,8 @@ Papa-Kind-Treff is a PHP-based platform for fathers focused on local events, app
- when a user creates a new place or event-style listing from inside an own event, the flow returns to that event afterwards and preselects the newly created entry - when a user creates a new place or event-style listing from inside an own event, the flow returns to that event afterwards and preselects the newly created entry
- places and other event-style entries were moved for now into a separate member-area section `Places & Events` - 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 - new places and event-style entries must be approved by an admin before publication; change and deletion wishes also run as reasoned requests
- not-yet-published place and event-style suggestions can be edited directly in the member area
- `SiteOwner` fully inherits the place and event approval rights from `Forum Admin`
- the member-area entry forms now follow a progressive approach: required fields first, optional data inside collapsible sections - 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 - 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 - the `Events` area now also includes an ICS download and a personal subscribable calendar feed for all own events and event participations

View File

@@ -54,6 +54,8 @@ Papa-Kind-Treff ist eine PHP-basierte Plattform für Väter mit Fokus auf:
- 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
- Orte und sonstige Veranstaltungen laufen jetzt separat unter `Orte & Veranstaltungen` - 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 - 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
- Noch nicht veröffentlichte Orts- und Veranstaltungsvorschläge können im Mitgliederbereich direkt weiterbearbeitet werden; veröffentlichte Einträge laufen für normale Mitglieder weiter über begründete Änderungsanfragen
- `SiteOwner` erbt bei Orts- und Veranstaltungsanfragen vollständig die Freigaberechte von `Forum-Admin`
- Eigene Events unterstützen aktuell optionalen Beschreibungstext, klare Kinderangabe, optionale Platzzahl, Bild-Upload sowie eine Location aus Datenbank oder benutzerdefinierte Adresse - Eigene Events unterstützen aktuell optionalen Beschreibungstext, klare Kinderangabe, optionale Platzzahl, Bild-Upload sowie eine Location aus Datenbank oder benutzerdefinierte Adresse
- Bei eigener Event-Location aus der Datenbank werden Adresse und Kategorie nur informativ aus dem gewählten Ort bzw. der gewählten Veranstaltung übernommen und nicht manuell geändert - Bei eigener Event-Location aus der Datenbank werden Adresse und Kategorie nur informativ aus dem gewählten Ort bzw. der gewählten Veranstaltung übernommen und nicht manuell geändert
- Aus dem Formular für eigene Events kann direkt in das Anlegen eines neuen Orts oder einer neuen Veranstaltung verzweigt werden - Aus dem Formular für eigene Events kann direkt in das Anlegen eines neuen Orts oder einer neuen Veranstaltung verzweigt werden

View File

@@ -13,6 +13,7 @@ $restoreEventDraftActive = !empty($restoreEventDraftActive);
$editingEvent = isset($editEvent) && $editEvent !== null; $editingEvent = isset($editEvent) && $editEvent !== null;
$editingListing = isset($editListing) && $editListing !== null; $editingListing = isset($editListing) && $editListing !== null;
$editing = $editingEvent || $editingListing; $editing = $editingEvent || $editingListing;
$editingSuggestionDirectly = $editingListing && (string)($editListing['status'] ?? '') !== 'published';
$eventDraftValues = (!$editing && (($section ?? '') === 'events') && $restoreEventDraftActive && is_array($restoredEventDraft ?? null)) $eventDraftValues = (!$editing && (($section ?? '') === 'events') && $restoreEventDraftActive && is_array($restoredEventDraft ?? null))
? $restoredEventDraft ? $restoredEventDraft
: []; : [];
@@ -31,8 +32,8 @@ if ($editingEvent && !empty($editEvent['starts_at'])) {
$withChildrenValue = $editingEvent $withChildrenValue = $editingEvent
? (((int)($editEvent['allow_kids'] ?? 1)) === 1 ? 'yes' : 'no') ? (((int)($editEvent['allow_kids'] ?? 1)) === 1 ? 'yes' : 'no')
: (!empty($eventDraftValues['with_children']) ? (string)$eventDraftValues['with_children'] : (($editingListing && $currentEntryKind === 'editorial_event' && !empty($editListing['supports_registration'])) ? 'yes' : 'yes')); : (!empty($eventDraftValues['with_children']) ? (string)$eventDraftValues['with_children'] : (($editingListing && $currentEntryKind === 'editorial_event' && !empty($editListing['supports_registration'])) ? 'yes' : 'yes'));
$eventTitle = $editingEvent ? 'Event bearbeiten' : ($editingListing ? ($currentEntryKind === 'place' ? 'Änderung für Ort anfragen' : 'Änderung für Veranstaltung anfragen') : ((($section ?? 'events') === 'places') ? 'Ort oder Veranstaltung anlegen' : 'Neues Event anlegen')); $eventTitle = $editingEvent ? 'Event bearbeiten' : ($editingListing ? ($editingSuggestionDirectly ? (($currentEntryKind === 'place') ? 'Ort bearbeiten' : 'Veranstaltung bearbeiten') : (($currentEntryKind === 'place') ? 'Änderung für Ort anfragen' : 'Änderung für Veranstaltung anfragen')) : ((($section ?? 'events') === 'places') ? 'Ort oder Veranstaltung anlegen' : 'Neues Event anlegen'));
$submitLabel = $editingEvent ? 'Event speichern' : ($editingListing ? 'Änderungsanfrage senden' : ((($section ?? 'events') === 'places') ? 'Eintrag zur Freigabe einreichen' : 'Event anlegen')); $submitLabel = $editingEvent ? 'Event speichern' : ($editingListing ? ($editingSuggestionDirectly ? 'Änderungen speichern' : 'Änderungsanfrage senden') : ((($section ?? 'events') === 'places') ? 'Eintrag zur Freigabe einreichen' : 'Event anlegen'));
$titleValue = $editingEvent ? (string)($editEvent['title'] ?? '') : (!empty($eventDraftValues['title']) ? (string)$eventDraftValues['title'] : (string)($editListing['title'] ?? '')); $titleValue = $editingEvent ? (string)($editEvent['title'] ?? '') : (!empty($eventDraftValues['title']) ? (string)$eventDraftValues['title'] : (string)($editListing['title'] ?? ''));
$teaserValue = $editingEvent ? (string)($editEvent['teaser_public'] ?? '') : (string)($editListing['teaser_public'] ?? ''); $teaserValue = $editingEvent ? (string)($editEvent['teaser_public'] ?? '') : (string)($editListing['teaser_public'] ?? '');
$descriptionValue = $editingEvent ? (string)($editEvent['description'] ?? '') : (!empty($eventDraftValues['description']) ? (string)$eventDraftValues['description'] : (string)($editListing['description'] ?? '')); $descriptionValue = $editingEvent ? (string)($editEvent['description'] ?? '') : (!empty($eventDraftValues['description']) ? (string)$eventDraftValues['description'] : (string)($editListing['description'] ?? ''));
@@ -751,15 +752,25 @@ if (!empty($canManageSystemSettings)) {
<?php else: ?> <?php else: ?>
<ul class="dash-list" style="margin-top:12px;"> <ul class="dash-list" style="margin-top:12px;">
<?php foreach ($pendingListings as $entry): ?> <?php foreach ($pendingListings as $entry): ?>
<?php $canEditPendingEntry = ((int)($entry['created_by'] ?? 0) === $dashboardUserId) || !empty($canReviewListings); ?>
<li> <li>
<strong><?= htmlspecialchars((string)$entry['title'], ENT_QUOTES) ?></strong> <div style="display:flex; justify-content:space-between; gap:12px; align-items:flex-start; flex-wrap:wrap;">
<span class="badge"><?= ($entry['listing_type'] ?? '') === 'place' ? 'Ort' : 'Veranstaltung' ?></span> <div>
<span class="badge" style="background:#fff7e6; color:#8a5a00;"> <strong><?= htmlspecialchars((string)$entry['title'], ENT_QUOTES) ?></strong>
<?= (string)($entry['moderation_request_status'] ?? '') === 'rejected' ? 'Abgelehnt' : 'Wartet auf Freigabe' ?> <span class="badge"><?= ($entry['listing_type'] ?? '') === 'place' ? 'Ort' : 'Veranstaltung' ?></span>
</span> <span class="badge" style="background:#fff7e6; color:#8a5a00;">
<?php if (!empty($entry['moderation_review_note'])): ?> <?= (string)($entry['moderation_request_status'] ?? '') === 'rejected' ? 'Abgelehnt' : 'Wartet auf Freigabe' ?>
<div class="muted small" style="margin-top:6px;">Admin-Hinweis: <?= htmlspecialchars((string)$entry['moderation_review_note'], ENT_QUOTES) ?></div> </span>
<?php endif; ?> <?php if (!empty($entry['moderation_review_note'])): ?>
<div class="muted small" style="margin-top:6px;">Admin-Hinweis: <?= htmlspecialchars((string)$entry['moderation_review_note'], ENT_QUOTES) ?></div>
<?php endif; ?>
</div>
<?php if ($canEditPendingEntry): ?>
<div class="flex gap-8" style="flex-wrap:wrap;">
<a class="btn ghost" href="/dashboard?section=places&edit_listing=<?= (int)$entry['id'] ?>#places">Bearbeiten</a>
</div>
<?php endif; ?>
</div>
</li> </li>
<?php endforeach; ?> <?php endforeach; ?>
</ul> </ul>
@@ -1593,12 +1604,14 @@ if (!empty($canManageSystemSettings)) {
</select> </select>
</div> </div>
<?php endif; ?> <?php endif; ?>
<?php if ($editingListing): ?> <?php if ($editingListing && !$editingSuggestionDirectly): ?>
<div class="stack gap-6"> <div class="stack gap-6">
<label class="label" for="listingModerationReason">Begründung für die Änderungsanfrage</label> <label class="label" for="listingModerationReason">Begründung für die Änderungsanfrage</label>
<textarea id="listingModerationReason" name="moderation_reason" class="textarea" rows="3" placeholder="Was stimmt nicht oder was soll angepasst werden?" required></textarea> <textarea id="listingModerationReason" name="moderation_reason" class="textarea" rows="3" placeholder="Was stimmt nicht oder was soll angepasst werden?" required></textarea>
<p class="muted small" style="margin:0;">Die Änderung wird erst nach Prüfung durch einen Admin freigegeben.</p> <p class="muted small" style="margin:0;">Die Änderung wird erst nach Prüfung durch einen Admin freigegeben.</p>
</div> </div>
<?php elseif ($editingSuggestionDirectly): ?>
<p class="muted small" style="margin:0;">Dieser Vorschlag ist noch nicht veröffentlicht und kann direkt aktualisiert werden.</p>
<?php elseif (($section ?? '') === 'places'): ?> <?php elseif (($section ?? '') === 'places'): ?>
<p class="muted small" style="margin:0;">Neue Orte und Veranstaltungen werden erst nach Prüfung durch einen Admin freigegeben.</p> <p class="muted small" style="margin:0;">Neue Orte und Veranstaltungen werden erst nach Prüfung durch einen Admin freigegeben.</p>
<?php endif; ?> <?php endif; ?>

View File

@@ -182,6 +182,7 @@ final class AccountPages
$canManageProfileLevels = $communityAccess ? $communityAccess->canManageRoles($userId) : false; $canManageProfileLevels = $communityAccess ? $communityAccess->canManageRoles($userId) : false;
$communityPointsForAccess = $community ? $community->computePoints($userId) : 0.0; $communityPointsForAccess = $community ? $community->computePoints($userId) : 0.0;
$canManageCategories = $communityAccess ? $communityAccess->canManageCategories($userId, $communityPointsForAccess) : false; $canManageCategories = $communityAccess ? $communityAccess->canManageCategories($userId, $communityPointsForAccess) : false;
$canReviewListings = $communityAccess ? $communityAccess->canReviewListings($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';
@@ -471,14 +472,28 @@ final class AccountPages
trim((string)($payload['description'] ?? '')), trim((string)($payload['description'] ?? '')),
trim((string)($payload['title'] ?? '')) trim((string)($payload['title'] ?? ''))
); );
if ($action === 'event_update') { $canDirectListingEdit = is_array($existingListing)
$listingCatalog->submitUpdateRequest( && (string)($existingListing['status'] ?? '') !== 'published'
$userId, && (
(int)($_POST['listing_id'] ?? 0), (int)($existingListing['created_by'] ?? 0) === $userId
$payload, || $canReviewListings
(string)($_POST['moderation_reason'] ?? '')
); );
$info = $entryKind === 'place' ? 'Änderungsanfrage für den Ort eingereicht.' : 'Änderungsanfrage für die Veranstaltung eingereicht.'; if ($action === 'event_update') {
if ($canDirectListingEdit) {
$listingCatalog->updateEditableSuggestion(
(int)($_POST['listing_id'] ?? 0),
$payload
);
$info = $entryKind === 'place' ? 'Ort aktualisiert.' : 'Veranstaltung aktualisiert.';
} else {
$listingCatalog->submitUpdateRequest(
$userId,
(int)($_POST['listing_id'] ?? 0),
$payload,
(string)($_POST['moderation_reason'] ?? '')
);
$info = $entryKind === 'place' ? 'Änderungsanfrage für den Ort eingereicht.' : 'Änderungsanfrage für die Veranstaltung eingereicht.';
}
} else { } else {
$createdListingId = $listingCatalog->submitCreateSuggestion($userId, $payload); $createdListingId = $listingCatalog->submitCreateSuggestion($userId, $payload);
$info = $entryKind === 'place' ? 'Ort zur Freigabe eingereicht.' : 'Veranstaltung zur Freigabe eingereicht.'; $info = $entryKind === 'place' ? 'Ort zur Freigabe eingereicht.' : 'Veranstaltung zur Freigabe eingereicht.';
@@ -957,7 +972,13 @@ final class AccountPages
$editListingId = (int)$_GET['edit_listing']; $editListingId = (int)$_GET['edit_listing'];
if ($editListingId > 0) { if ($editListingId > 0) {
$candidate = $listingCatalog->getMemberEntry($editListingId); $candidate = $listingCatalog->getMemberEntry($editListingId);
if (is_array($candidate) && (string)($candidate['status'] ?? '') === 'published') { $canEditCandidateDirectly = is_array($candidate)
&& (string)($candidate['status'] ?? '') !== 'published'
&& (
(int)($candidate['created_by'] ?? 0) === $userId
|| $canReviewListings
);
if (is_array($candidate) && ((string)($candidate['status'] ?? '') === 'published' || $canEditCandidateDirectly)) {
$editListing = $candidate; $editListing = $candidate;
} else { } else {
$error = 'Der gewünschte Eintrag ist für eine Änderungsanfrage nicht verfügbar.'; $error = 'Der gewünschte Eintrag ist für eine Änderungsanfrage nicht verfügbar.';
@@ -1082,6 +1103,7 @@ final class AccountPages
'communityLevelDefinitions', 'communityLevelDefinitions',
'communityLevelRightDefinitions', 'communityLevelRightDefinitions',
'communityRoles', 'communityRoles',
'canReviewListings',
'systemRoleAssignments', 'systemRoleAssignments',
'recentHighLevelUsers', 'recentHighLevelUsers',
'recentHighLevelThresholdLabel', 'recentHighLevelThresholdLabel',

View File

@@ -86,10 +86,7 @@ final class CommunityAccess
public function canReviewListings(int $userId, ?float $points = null): bool public function canReviewListings(int $userId, ?float $points = null): bool
{ {
if ($this->hasRole($userId, 'owner')) { if ($this->hasRole($userId, 'owner') || $this->hasRole($userId, 'site_admin') || $this->hasRole($userId, 'forum_admin')) {
return false;
}
if ($this->hasRole($userId, 'site_admin') || $this->hasRole($userId, 'forum_admin')) {
return true; return true;
} }

View File

@@ -649,7 +649,7 @@ final class ListingCatalog
{ {
$this->ensureSchema(); $this->ensureSchema();
$stmt = $this->pdo->prepare( $stmt = $this->pdo->prepare(
'SELECT l.*, lp.title AS place_title, lp.description AS place_description, lp.street, lp.zip, lp.city, lp.region, lp.lat, lp.lng, lp.place_kind, lp.phone, lp.website_url, lp.opening_hours_note, lp.opening_hours_json, 'SELECT l.*, lp.status AS place_status, lp.title AS place_title, lp.description AS place_description, lp.street, lp.zip, lp.city, lp.region, lp.lat, lp.lng, lp.place_kind, lp.phone, lp.website_url, lp.opening_hours_note, lp.opening_hours_json,
lo.id AS occurrence_id, lo.starts_at, lo.ends_at, lo.occurrence_type, lo.id AS occurrence_id, lo.starts_at, lo.ends_at, lo.occurrence_type,
lo.recurrence_rule, lo.recurrence_until, lo.capacity_total, lo.recurrence_rule, lo.recurrence_until, lo.capacity_total,
lc.slug AS category_slug lc.slug AS category_slug
@@ -850,6 +850,27 @@ final class ListingCatalog
return $this->createModerationRequest($listingId, 'delete', $userId, $reason, null); return $this->createModerationRequest($listingId, 'delete', $userId, $reason, null);
} }
public function updateEditableSuggestion(int $listingId, array $data): void
{
$this->ensureSchema();
$entry = $this->getMemberEntry($listingId);
if (!$entry) {
throw new \RuntimeException('Eintrag nicht gefunden.');
}
$listingStatus = (string)($entry['status'] ?? 'draft');
if ($listingStatus === 'published') {
throw new \RuntimeException('Veröffentlichte Einträge dürfen nicht direkt überschrieben werden.');
}
$placeStatus = (string)($entry['place_status'] ?? 'draft');
$normalizedListingStatus = in_array($listingStatus, ['draft', 'archived'], true) ? $listingStatus : 'draft';
$normalizedPlaceStatus = in_array($placeStatus, ['draft', 'archived'], true) ? $placeStatus : $normalizedListingStatus;
$this->assertNoDuplicateEntry($data, $listingId);
$this->saveDashboardEntry((int)($entry['created_by'] ?? 0), $data, $listingId, $normalizedListingStatus, $normalizedPlaceStatus);
}
public function listOpenModerationRequests(): array public function listOpenModerationRequests(): array
{ {
$this->ensureSchema(); $this->ensureSchema();