This commit is contained in:
@@ -21,6 +21,7 @@ Papa-Kind-Treff ist eine PHP-basierte Plattform für Väter. Kernbereiche sind l
|
||||
- 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.
|
||||
- Im Bereich `Events` gibt es jetzt zunächst drei Eintragstypen: eigenes Event, Ort und sonstige Veranstaltung.
|
||||
|
||||
## Technischer Rahmen
|
||||
- eigener Front-Controller in `public/index.php`
|
||||
|
||||
@@ -18,6 +18,7 @@ Stand: 2026-08-03
|
||||
- Consent- und rechtliche Texte: `public/page/datenschutz.php`
|
||||
- System-Einstellungen: `src/App/SystemSettings.php`
|
||||
- neue Listing-/Ort-Basis: `src/App/ListingCatalog.php`
|
||||
- UI für Eintragstyp-Auswahl im Mitgliederbereich: `partials/landing/account/dashboard.php`
|
||||
|
||||
## Dokumentationsregel
|
||||
Kanonische interne Dokumentation:
|
||||
|
||||
@@ -22,6 +22,7 @@ Papa-Kind-Treff ist eine PHP-basierte Plattform für Väter mit Fokus auf lokale
|
||||
- Profiladresse mit verschlüsselter Straße/Hausnummer, Adresssuche, Browser-Übernahme und Validierung
|
||||
- System-Sektion für Seiten-Admins mit Wartungs- und Diensteschaltern
|
||||
- neue Datenbasis für ein späteres Termin-, Ort- und Veranstaltungssystem angelegt
|
||||
- Mitgliederbereich `Events` unterstützt jetzt drei Anlagetypen: `Eigenes Event`, `Ort`, `Sonstige Veranstaltung`
|
||||
|
||||
## Wording-Regel
|
||||
- Primärbegriff im Produkt: `Events`
|
||||
|
||||
@@ -21,6 +21,7 @@ Papa-Kind-Treff is a PHP-based platform for fathers. Core areas are local events
|
||||
- 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 now distinguishes between own events, places, and other time-limited event entries.
|
||||
|
||||
## Technical Frame
|
||||
- custom front controller in `public/index.php`
|
||||
|
||||
@@ -18,6 +18,7 @@ Updated: 2026-08-03
|
||||
- consent and legal texts: `public/page/datenschutz.php`
|
||||
- system settings: `src/App/SystemSettings.php`
|
||||
- new listing/place base: `src/App/ListingCatalog.php`
|
||||
- entry type selector UI in member area: `partials/landing/account/dashboard.php`
|
||||
|
||||
## Documentation Rule
|
||||
- canonical internal documentation lives in `Internal/de/` and `Internal/en/`
|
||||
|
||||
@@ -22,6 +22,7 @@ Papa-Kind-Treff is a PHP-based platform for fathers focused on local events, app
|
||||
- profile address supports encrypted street/house number, address search, browser import, and validation
|
||||
- system section for site admins with maintenance and service toggles
|
||||
- new data foundation prepared for a broader event, place, and listing system
|
||||
- member area `Events` now supports three creation types: `Own Event`, `Place`, and `Other Event`
|
||||
|
||||
## Core Wording Rule
|
||||
- Primary product term: `Events`
|
||||
|
||||
@@ -36,6 +36,7 @@ Papa-Kind-Treff ist eine PHP-basierte Plattform für Väter mit Fokus auf:
|
||||
- Profil-Menü aktuell: `Profil`, `Kinder`, `Events`, `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 unter `Events` können Nutzer jetzt zwischen `Eigenes Event`, `Ort` und `sonstiger Veranstaltung` unterscheiden
|
||||
|
||||
## Datenschutz und Sicherheit
|
||||
- sensible Profilfelder werden app-seitig verschlüsselt gespeichert
|
||||
|
||||
@@ -6,10 +6,32 @@ $avatarPlaceholder = 'data:image/svg+xml,%3Csvg xmlns=%22http://www.w3.org/2000/
|
||||
$avatarStyleKeys = array_keys($avatarBuilder ?? []);
|
||||
$currentAvatarStyle = (string)($profile['avatar_style'] ?? ($avatarStyleKeys[0] ?? 'lorelei'));
|
||||
$dashboardUserId = (int)($_SESSION['user_id'] ?? 0);
|
||||
$editing = isset($editEvent) && $editEvent !== null;
|
||||
$editingEvent = isset($editEvent) && $editEvent !== null;
|
||||
$editingListing = isset($editListing) && $editListing !== null;
|
||||
$editing = $editingEvent || $editingListing;
|
||||
$currentEntryKind = $editingListing ? (string)($editListing['listing_type'] ?? 'editorial_event') : 'event';
|
||||
$actionEvent = $editing ? 'event_update' : 'event_add';
|
||||
$startVal = $editEvent ? date('Y-m-d\TH:i', strtotime((string)$editEvent['starts_at'])) : '';
|
||||
$allowNoKidsChecked = $editEvent ? ((int)$editEvent['allow_kids'] === 0) : false;
|
||||
$startVal = '';
|
||||
if ($editingEvent && !empty($editEvent['starts_at'])) {
|
||||
$startVal = date('Y-m-d\TH:i', strtotime((string)$editEvent['starts_at']));
|
||||
} elseif ($editingListing && !empty($editListing['starts_at'])) {
|
||||
$startVal = date('Y-m-d\TH:i', strtotime((string)$editListing['starts_at']));
|
||||
}
|
||||
$allowNoKidsChecked = $editingEvent ? ((int)$editEvent['allow_kids'] === 0) : false;
|
||||
$eventTitle = $editingEvent ? 'Event bearbeiten' : ($editingListing ? ($currentEntryKind === 'place' ? 'Ort bearbeiten' : 'Veranstaltung bearbeiten') : 'Neuen Eintrag anlegen');
|
||||
$submitLabel = $editingEvent ? 'Event speichern' : ($editingListing ? ($currentEntryKind === 'place' ? 'Ort speichern' : 'Veranstaltung speichern') : 'Eintrag anlegen');
|
||||
$titleValue = $editingEvent ? (string)($editEvent['title'] ?? '') : (string)($editListing['title'] ?? '');
|
||||
$teaserValue = $editingEvent ? (string)($editEvent['teaser_public'] ?? '') : (string)($editListing['teaser_public'] ?? '');
|
||||
$descriptionValue = $editingEvent ? (string)($editEvent['description'] ?? '') : (string)($editListing['description'] ?? '');
|
||||
$locationLabelValue = $editingEvent ? (string)($editEvent['location_label'] ?? '') : '';
|
||||
$streetValue = $editingEvent ? (string)($editEvent['street'] ?? '') : (string)($editListing['street'] ?? '');
|
||||
$zipValue = $editingEvent ? (string)($editEvent['zip'] ?? '') : (string)($editListing['zip'] ?? '');
|
||||
$cityValue = $editingEvent ? (string)($editEvent['city'] ?? '') : (string)($editListing['city'] ?? '');
|
||||
$regionValue = $editingEvent ? (string)($editEvent['region'] ?? '') : (string)($editListing['region'] ?? '');
|
||||
$latValue = $editingEvent ? (string)($editEvent['lat'] ?? '') : (string)($editListing['lat'] ?? '');
|
||||
$lngValue = $editingEvent ? (string)($editEvent['lng'] ?? '') : (string)($editListing['lng'] ?? '');
|
||||
$visibilityValue = $editingEvent ? (string)($editEvent['visibility'] ?? 'public') : (string)($editListing['visibility'] ?? 'public');
|
||||
$categorySlugValue = $editingListing ? (string)($editListing['category_slug'] ?? '') : '';
|
||||
$editingChild = isset($editChild) && $editChild !== null;
|
||||
$childAction = $editingChild ? 'child_update' : 'child_add';
|
||||
$childModalTitle = $editingChild ? 'Kind bearbeiten' : 'Kind hinzufügen';
|
||||
@@ -205,15 +227,19 @@ if (!empty($canManageSystemSettings)) {
|
||||
<?php if ($section === 'events'): ?>
|
||||
<section class="account-panel" id="events">
|
||||
<div class="account-panel__head">
|
||||
<h2>Deine Events</h2>
|
||||
<p class="muted">Erstelle, bearbeite und verwalte deine eigenen Events und finde passende Termine für gemeinsame Treffen.</p>
|
||||
<h2>Deine Einträge</h2>
|
||||
<p class="muted">Lege eigene Events, dauerhafte Orte und sonstige zeitlich begrenzte Veranstaltungen sauber getrennt an.</p>
|
||||
</div>
|
||||
<div class="account-panel__body">
|
||||
<div class="flex gap-12" style="margin:0 0 16px 0; flex-wrap: wrap;">
|
||||
<button class="btn" type="button" data-modal-open="modalEvent">Event anlegen</button>
|
||||
<button class="btn" type="button" data-modal-open="modalEvent">Eintrag anlegen</button>
|
||||
</div>
|
||||
<div class="card" style="margin-bottom:18px;">
|
||||
<strong>Eigene Events</strong>
|
||||
<p class="muted small" style="margin:8px 0 0;">Von dir organisierte Treffen oder Events mit eigener Teilnahme-Logik.</p>
|
||||
</div>
|
||||
<?php if (!$eventsUpcoming): ?>
|
||||
<p class="muted small">Keine zukünftigen Events angelegt.</p>
|
||||
<p class="muted small">Keine zukünftigen eigenen Events angelegt.</p>
|
||||
<?php else: ?>
|
||||
<ul class="dash-list" style="margin-top:10px;">
|
||||
<?php foreach ($eventsUpcoming as $e): ?>
|
||||
@@ -277,6 +303,45 @@ if (!empty($canManageSystemSettings)) {
|
||||
</ul>
|
||||
<?php endif; ?>
|
||||
</details>
|
||||
|
||||
<div class="card" style="margin:22px 0 14px;">
|
||||
<strong>Orte und sonstige Veranstaltungen</strong>
|
||||
<p class="muted small" style="margin:8px 0 0;">Hier erscheinen dauerhafte Orte wie Spielplätze, Cafés oder Restaurants sowie zeitlich begrenzte Hinweise wie Zirkus oder Aktionsflächen.</p>
|
||||
</div>
|
||||
<?php if (!$otherListings): ?>
|
||||
<p class="muted small">Noch keine Orte oder sonstigen Veranstaltungen angelegt.</p>
|
||||
<?php else: ?>
|
||||
<ul class="dash-list" style="margin-top:10px;">
|
||||
<?php foreach ($otherListings as $entry): ?>
|
||||
<li>
|
||||
<div style="display:flex; justify-content:space-between; gap:12px; align-items:center; flex-wrap: wrap;">
|
||||
<div>
|
||||
<strong><?= htmlspecialchars((string)$entry['title'], ENT_QUOTES) ?></strong>
|
||||
<span class="badge"><?= ($entry['listing_type'] ?? '') === 'place' ? 'Ort' : 'Sonstige Veranstaltung' ?></span>
|
||||
<?php if (!empty($entry['category_title'])): ?>
|
||||
<span class="badge"><?= htmlspecialchars((string)$entry['category_title'], ENT_QUOTES) ?></span>
|
||||
<?php endif; ?>
|
||||
<div class="muted small" style="margin-top:4px;">
|
||||
<?= htmlspecialchars(trim(implode(' · ', array_filter([
|
||||
(string)($entry['city'] ?? ''),
|
||||
!empty($entry['starts_at']) ? (string)$entry['starts_at'] : null,
|
||||
(string)($entry['visibility'] ?? ''),
|
||||
]))), ENT_QUOTES) ?>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex gap-8" style="flex-wrap: wrap;">
|
||||
<a class="btn ghost" href="/dashboard?section=events&edit_listing=<?= (int)$entry['id'] ?>#events">Bearbeiten</a>
|
||||
<form method="post" action="/dashboard?section=events#events" onsubmit="return confirm('Eintrag wirklich löschen?');">
|
||||
<input type="hidden" name="action" value="listing_delete">
|
||||
<input type="hidden" name="listing_id" value="<?= (int)$entry['id'] ?>">
|
||||
<button class="btn ghost" type="submit">Löschen</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
<?php endforeach; ?>
|
||||
</ul>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</section>
|
||||
<?php endif; ?>
|
||||
@@ -780,65 +845,99 @@ if (!empty($canManageSystemSettings)) {
|
||||
<div class="modal" id="modalEvent">
|
||||
<div class="panel">
|
||||
<div class="head flex between center-y">
|
||||
<h3><?= $editing ? 'Event bearbeiten' : 'Neues Event' ?></h3>
|
||||
<h3><?= htmlspecialchars($eventTitle, ENT_QUOTES) ?></h3>
|
||||
<button class="btn ghost" type="button" data-modal-close>✕</button>
|
||||
</div>
|
||||
<form class="stack gap-12" style="margin-top: 10px;" method="post" action="/dashboard?section=events#events">
|
||||
<input type="hidden" name="action" value="<?= htmlspecialchars($actionEvent, ENT_QUOTES) ?>">
|
||||
<?php if ($editing && $editEvent): ?>
|
||||
<input type="hidden" name="entry_kind" id="entryKind" value="<?= htmlspecialchars($currentEntryKind, ENT_QUOTES) ?>">
|
||||
<?php if ($editingEvent && $editEvent): ?>
|
||||
<input type="hidden" name="event_id" value="<?= (int)$editEvent['id'] ?>">
|
||||
<?php endif; ?>
|
||||
<?php if ($editingListing && $editListing): ?>
|
||||
<input type="hidden" name="listing_id" value="<?= (int)$editListing['id'] ?>">
|
||||
<?php endif; ?>
|
||||
<?php if (!$editing): ?>
|
||||
<div class="stack gap-8">
|
||||
<label class="label">Was möchtest du anlegen?</label>
|
||||
<div class="chips" style="flex-wrap:wrap;">
|
||||
<button class="btn ghost" type="button" data-entry-kind-choice="event">Eigenes Event</button>
|
||||
<button class="btn ghost" type="button" data-entry-kind-choice="place">Ort</button>
|
||||
<button class="btn ghost" type="button" data-entry-kind-choice="editorial_event">Sonstige Veranstaltung</button>
|
||||
</div>
|
||||
<p class="muted small" id="entryKindHint" style="margin:0;">Eigenes Event: von dir organisiert und mit konkretem Termin.</p>
|
||||
</div>
|
||||
<?php else: ?>
|
||||
<div class="card">
|
||||
<strong>Eintragstyp</strong>
|
||||
<p class="muted small" style="margin:8px 0 0;">
|
||||
<?= $currentEntryKind === 'place' ? 'Ort: dauerhaft verfügbar.' : ($currentEntryKind === 'editorial_event' ? 'Sonstige Veranstaltung: zeitlich begrenzter Hinweis.' : 'Eigenes Event: von dir organisiert.') ?>
|
||||
</p>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
<div class="form-grid">
|
||||
<div class="stack gap-6">
|
||||
<label class="label" for="evTitle">Titel</label>
|
||||
<input id="evTitle" name="title" class="input" placeholder="z. B. Väter-Kaffee im Park" required value="<?= htmlspecialchars($editEvent['title'] ?? '', ENT_QUOTES) ?>">
|
||||
<input id="evTitle" name="title" class="input" placeholder="z. B. Väter-Kaffee im Park" required value="<?= htmlspecialchars($titleValue, ENT_QUOTES) ?>">
|
||||
</div>
|
||||
<div class="stack gap-6">
|
||||
<label class="label" for="evTeaser">Kurzbeschreibung</label>
|
||||
<input id="evTeaser" name="teaser" class="input" placeholder="Kurztext für Gäste" required value="<?= htmlspecialchars($editEvent['teaser_public'] ?? '', ENT_QUOTES) ?>">
|
||||
<input id="evTeaser" name="teaser" class="input" placeholder="Kurztext für Gäste" required value="<?= htmlspecialchars($teaserValue, ENT_QUOTES) ?>">
|
||||
</div>
|
||||
</div>
|
||||
<div class="stack gap-6">
|
||||
<label class="label" for="evDesc">Beschreibung (voll)</label>
|
||||
<textarea id="evDesc" name="description" class="textarea" rows="3" placeholder="Was soll passieren, was mitbringen?" required><?= htmlspecialchars($editEvent['description'] ?? '', ENT_QUOTES) ?></textarea>
|
||||
<textarea id="evDesc" name="description" class="textarea" rows="3" placeholder="Was soll passieren, was mitbringen?" required><?= htmlspecialchars($descriptionValue, ENT_QUOTES) ?></textarea>
|
||||
</div>
|
||||
<div class="form-grid">
|
||||
<div class="form-grid" id="eventDateRow">
|
||||
<div class="stack gap-6">
|
||||
<label class="label" for="evDate">Datum & Uhrzeit</label>
|
||||
<input id="evDate" name="starts_at" class="input" type="datetime-local" required value="<?= htmlspecialchars($startVal, ENT_QUOTES) ?>">
|
||||
</div>
|
||||
<div class="stack gap-6">
|
||||
<div class="stack gap-6" id="eventLocationLabelWrap">
|
||||
<label class="label" for "evLoc">Ort/Label</label>
|
||||
<input id="evLoc" name="location_label" class="input" placeholder="Park / Café" value="<?= htmlspecialchars($editEvent['location_label'] ?? '', ENT_QUOTES) ?>">
|
||||
<input id="evLoc" name="location_label" class="input" placeholder="Park / Café" value="<?= htmlspecialchars($locationLabelValue, ENT_QUOTES) ?>">
|
||||
</div>
|
||||
</div>
|
||||
<div class="stack gap-6" id="eventCategoryWrap">
|
||||
<label class="label" for="listingCategorySlug">Kategorie</label>
|
||||
<select id="listingCategorySlug" name="category_slug" class="select">
|
||||
<option value="">Bitte auswählen</option>
|
||||
<?php foreach ($listingCategories as $category): ?>
|
||||
<option value="<?= htmlspecialchars((string)$category['slug'], ENT_QUOTES) ?>" <?= $categorySlugValue === (string)$category['slug'] ? 'selected' : '' ?>>
|
||||
<?= htmlspecialchars((string)$category['title'], ENT_QUOTES) ?>
|
||||
</option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
<p class="muted small" style="margin:0;">Für Orte und sonstige Veranstaltungen, damit Spielplatz, Café, Restaurant, Zoo oder Zirkus sauber unterscheidbar bleiben.</p>
|
||||
</div>
|
||||
<div class="form-grid">
|
||||
<div class="stack gap-6">
|
||||
<label class="label" for="evZip">PLZ</label>
|
||||
<input id="evZip" name="zip" class="input" maxlength="5" value="<?= htmlspecialchars($editEvent['zip'] ?? '', ENT_QUOTES) ?>">
|
||||
<input id="evZip" name="zip" class="input" maxlength="5" value="<?= htmlspecialchars($zipValue, ENT_QUOTES) ?>">
|
||||
</div>
|
||||
<div class="stack gap-6">
|
||||
<label class="label" for="evCity">Stadt</label>
|
||||
<input id="evCity" name="city" class="input" value="<?= htmlspecialchars($editEvent['city'] ?? '', ENT_QUOTES) ?>">
|
||||
<input id="evCity" name="city" class="input" value="<?= htmlspecialchars($cityValue, ENT_QUOTES) ?>">
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-grid">
|
||||
<div class="stack gap-6">
|
||||
<label class="label" for="evStreet">Straße / Adresse</label>
|
||||
<input id="evStreet" name="street" class="input" placeholder="z. B. Musterstraße 12" value="<?= htmlspecialchars($editEvent['street'] ?? '', ENT_QUOTES) ?>">
|
||||
<input id="evStreet" name="street" class="input" placeholder="z. B. Musterstraße 12" value="<?= htmlspecialchars($streetValue, ENT_QUOTES) ?>">
|
||||
<p class="muted small">Wird zur Karten-/Umkreissuche genutzt.</p>
|
||||
</div>
|
||||
<div class="stack gap-6">
|
||||
<label class="label" for="evRegion">Region/Bezirk</label>
|
||||
<input id="evRegion" name="region" class="input" value="<?= htmlspecialchars($editEvent['region'] ?? '', ENT_QUOTES) ?>">
|
||||
<input id="evRegion" name="region" class="input" value="<?= htmlspecialchars($regionValue, ENT_QUOTES) ?>">
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex gap-8" style="flex-wrap:wrap; align-items:center;">
|
||||
<button class="btn ghost" type="button" id="btnAddrToMap">Adresse auf Karte setzen</button>
|
||||
<span class="muted small">Hält Karte und Adresse synchron.</span>
|
||||
</div>
|
||||
<input type="hidden" id="evLat" name="lat" value="<?= htmlspecialchars($editEvent['lat'] ?? '', ENT_QUOTES) ?>">
|
||||
<input type="hidden" id="evLng" name="lng" value="<?= htmlspecialchars($editEvent['lng'] ?? '', ENT_QUOTES) ?>">
|
||||
<input type="hidden" id="evLat" name="lat" value="<?= htmlspecialchars($latValue, ENT_QUOTES) ?>">
|
||||
<input type="hidden" id="evLng" name="lng" value="<?= htmlspecialchars($lngValue, ENT_QUOTES) ?>">
|
||||
<div class="stack gap-6">
|
||||
<button class="btn ghost" type="button" id="btnMap">Auf Karte suchen</button>
|
||||
<div id="mapWrapper" class="map-wrapper" hidden>
|
||||
@@ -857,17 +956,17 @@ if (!empty($canManageSystemSettings)) {
|
||||
<div class="stack gap-6">
|
||||
<label class="label" for="evVis">Sichtbarkeit</label>
|
||||
<select id="evVis" name="visibility" class="select">
|
||||
<option value="public" <?= (($editEvent['visibility'] ?? '') === 'public') ? 'selected' : '' ?>>Öffentlich</option>
|
||||
<option value="members" <?= (($editEvent['visibility'] ?? '') === 'members') ? 'selected' : '' ?>>Nur Mitglieder</option>
|
||||
<option value="public" <?= $visibilityValue === 'public' ? 'selected' : '' ?>>Öffentlich</option>
|
||||
<option value="members" <?= $visibilityValue === 'members' ? 'selected' : '' ?>>Nur Mitglieder</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<label class="label" style="display:flex; align-items:center; gap:8px;">
|
||||
<label class="label" id="eventKidsWrap" style="display:flex; align-items:center; gap:8px;">
|
||||
<input type="checkbox" name="allow_kids" <?= $allowNoKidsChecked ? 'checked' : '' ?>> Event ohne Kinder
|
||||
</label>
|
||||
<div class="flex gap-12">
|
||||
<button class="btn ghost" type="button" data-modal-close>Abbrechen</button>
|
||||
<button class="btn" type="submit"><?= $editing ? 'Event speichern' : 'Event anlegen' ?></button>
|
||||
<button class="btn" type="submit"><?= htmlspecialchars($submitLabel, ENT_QUOTES) ?></button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
@@ -903,6 +1002,15 @@ if (!empty($canManageSystemSettings)) {
|
||||
const mapSearch = document.getElementById('mapSearch');
|
||||
const latInput = document.getElementById('evLat');
|
||||
const lngInput = document.getElementById('evLng');
|
||||
const entryKindInput = document.getElementById('entryKind');
|
||||
const entryKindHint = document.getElementById('entryKindHint');
|
||||
const entryKindButtons = document.querySelectorAll('[data-entry-kind-choice]');
|
||||
const eventDateRow = document.getElementById('eventDateRow');
|
||||
const eventLocationLabelWrap = document.getElementById('eventLocationLabelWrap');
|
||||
const eventCategoryWrap = document.getElementById('eventCategoryWrap');
|
||||
const eventKidsWrap = document.getElementById('eventKidsWrap');
|
||||
const eventDateInput = document.getElementById('evDate');
|
||||
const categorySelect = document.getElementById('listingCategorySlug');
|
||||
const streetInput = document.getElementById('evStreet');
|
||||
const zipInput = document.getElementById('evZip');
|
||||
const cityInput = document.getElementById('evCity');
|
||||
@@ -923,6 +1031,42 @@ if (!empty($canManageSystemSettings)) {
|
||||
const profileAddressResultsModal = document.getElementById('modalProfileAddressResults');
|
||||
let map, marker;
|
||||
|
||||
function syncEventEntryKind(nextKind) {
|
||||
const kind = nextKind || entryKindInput?.value || 'event';
|
||||
if (entryKindInput) entryKindInput.value = kind;
|
||||
|
||||
entryKindButtons.forEach((button) => {
|
||||
const active = button.getAttribute('data-entry-kind-choice') === kind;
|
||||
button.classList.toggle('is-active', active);
|
||||
button.setAttribute('aria-pressed', active ? 'true' : 'false');
|
||||
});
|
||||
|
||||
if (entryKindHint) {
|
||||
if (kind === 'place') {
|
||||
entryKindHint.textContent = 'Ort: dauerhaft verfügbar, zum Beispiel Spielplatz, Café, Restaurant oder Zoo.';
|
||||
} else if (kind === 'editorial_event') {
|
||||
entryKindHint.textContent = 'Sonstige Veranstaltung: zeitlich begrenzter Hinweis, zum Beispiel Zirkus oder Hüpfburgenstadt.';
|
||||
} else {
|
||||
entryKindHint.textContent = 'Eigenes Event: von dir organisiert und mit konkretem Termin.';
|
||||
}
|
||||
}
|
||||
|
||||
if (eventDateRow) eventDateRow.hidden = kind === 'place';
|
||||
if (eventDateInput) eventDateInput.required = kind !== 'place';
|
||||
if (eventLocationLabelWrap) eventLocationLabelWrap.hidden = kind !== 'event';
|
||||
if (eventCategoryWrap) eventCategoryWrap.hidden = kind === 'event';
|
||||
if (categorySelect) categorySelect.required = kind !== 'event';
|
||||
if (eventKidsWrap) eventKidsWrap.hidden = kind !== 'event';
|
||||
}
|
||||
|
||||
entryKindButtons.forEach((button) => {
|
||||
button.addEventListener('click', () => {
|
||||
syncEventEntryKind(button.getAttribute('data-entry-kind-choice') || 'event');
|
||||
});
|
||||
});
|
||||
|
||||
syncEventEntryKind(entryKindInput?.value || 'event');
|
||||
|
||||
function ensureLeaflet(callback) {
|
||||
if (!window.PKTConsent || !window.PKTConsent.has('external_services')) {
|
||||
alert('Für Karten und Adresssuche bitte zuerst die externen Dienste in den Cookie-Einstellungen erlauben.');
|
||||
|
||||
@@ -372,70 +372,81 @@ final class AccountPages
|
||||
}
|
||||
$info = 'Kind gelöscht.';
|
||||
} elseif ($action === 'event_add' || $action === 'event_update') {
|
||||
$street = trim((string)($_POST['street'] ?? ''));
|
||||
$zip = trim((string)($_POST['zip'] ?? ''));
|
||||
$city = trim((string)($_POST['city'] ?? ''));
|
||||
$region = trim((string)($_POST['region'] ?? ''));
|
||||
$lat = isset($_POST['lat']) && $_POST['lat'] !== '' ? (float)$_POST['lat'] : null;
|
||||
$lng = isset($_POST['lng']) && $_POST['lng'] !== '' ? (float)$_POST['lng'] : null;
|
||||
$needsGeocode = ($lat === null || $lng === null || $region === '');
|
||||
if ($needsGeocode) {
|
||||
[$geoLat, $geoLng, $geoRegion] = self::geocodeAddress($street, $zip, $city, $region);
|
||||
if ($lat === null) { $lat = $geoLat; }
|
||||
if ($lng === null) { $lng = $geoLng; }
|
||||
if ($region === '' && $geoRegion) { $region = $geoRegion; }
|
||||
}
|
||||
|
||||
if ($action === 'event_add') {
|
||||
$stmt = $pdo?->prepare('INSERT INTO events (created_by, title, teaser_public, description, location_label, street, zip, city, region, lat, lng, starts_at, allow_kids, visibility, status, created_at, updated_at) VALUES (:uid, :title, :teaser, :descr, :loc, :street, :zip, :city, :region, :lat, :lng, :start, :allow, :vis, :status, NOW(), NOW())');
|
||||
$stmt?->execute([
|
||||
'uid' => $userId,
|
||||
'title' => trim((string)$_POST['title']),
|
||||
'teaser' => trim((string)$_POST['teaser']),
|
||||
'descr' => trim((string)$_POST['description']),
|
||||
'loc' => trim((string)$_POST['location_label']),
|
||||
'street' => $street ?: null,
|
||||
'zip' => $zip,
|
||||
'city' => $city,
|
||||
'region' => $region,
|
||||
'lat' => $lat,
|
||||
'lng' => $lng,
|
||||
'start' => $_POST['starts_at'] ?? null,
|
||||
'allow' => isset($_POST['allow_kids']) ? 0 : 1,
|
||||
'vis' => $_POST['visibility'] ?? 'public',
|
||||
'status' => 'published',
|
||||
]);
|
||||
$info = 'Event gespeichert.';
|
||||
// Punkte für Event-Erstellung vergeben
|
||||
try {
|
||||
$cfgPath = dirname(__DIR__, 2) . '/config/community.php';
|
||||
$communityCfg = file_exists($cfgPath) ? require $cfgPath : [];
|
||||
$community = new Community($pdo, $communityCfg);
|
||||
$community->addPoints($userId, 'event', 'create', ['event_id' => $pdo?->lastInsertId()]);
|
||||
} catch (\Throwable) {
|
||||
// still continue, points optional
|
||||
$entryKind = (string)($_POST['entry_kind'] ?? 'event');
|
||||
if (in_array($entryKind, ['place', 'editorial_event'], true)) {
|
||||
if (!$listingCatalog) {
|
||||
throw new \RuntimeException('Die neue Eintragslogik ist aktuell nicht verfügbar.');
|
||||
}
|
||||
$listingCatalog->saveDashboardEntry(
|
||||
$userId,
|
||||
$_POST,
|
||||
$action === 'event_update' ? (int)($_POST['listing_id'] ?? 0) : null
|
||||
);
|
||||
$info = $entryKind === 'place' ? 'Ort gespeichert.' : 'Veranstaltung gespeichert.';
|
||||
} else {
|
||||
$eventId = (int)($_POST['event_id'] ?? 0);
|
||||
$stmt = $pdo?->prepare('UPDATE events SET title=:title, teaser_public=:teaser, description=:descr, location_label=:loc, street=:street, zip=:zip, city=:city, region=:region, lat=:lat, lng=:lng, starts_at=:start, allow_kids=:allow, visibility=:vis, updated_at=NOW() WHERE id=:id AND created_by=:uid');
|
||||
$stmt?->execute([
|
||||
'id' => $eventId,
|
||||
'uid' => $userId,
|
||||
'title' => trim((string)$_POST['title']),
|
||||
'teaser' => trim((string)$_POST['teaser']),
|
||||
'descr' => trim((string)$_POST['description']),
|
||||
'loc' => trim((string)$_POST['location_label']),
|
||||
'street' => $street ?: null,
|
||||
'zip' => $zip,
|
||||
'city' => $city,
|
||||
'region' => $region,
|
||||
'lat' => $lat,
|
||||
'lng' => $lng,
|
||||
'start' => $_POST['starts_at'] ?? null,
|
||||
'allow' => isset($_POST['allow_kids']) ? 0 : 1,
|
||||
'vis' => $_POST['visibility'] ?? 'public',
|
||||
]);
|
||||
$info = 'Event aktualisiert.';
|
||||
$street = trim((string)($_POST['street'] ?? ''));
|
||||
$zip = trim((string)($_POST['zip'] ?? ''));
|
||||
$city = trim((string)($_POST['city'] ?? ''));
|
||||
$region = trim((string)($_POST['region'] ?? ''));
|
||||
$lat = isset($_POST['lat']) && $_POST['lat'] !== '' ? (float)$_POST['lat'] : null;
|
||||
$lng = isset($_POST['lng']) && $_POST['lng'] !== '' ? (float)$_POST['lng'] : null;
|
||||
$needsGeocode = ($lat === null || $lng === null || $region === '');
|
||||
if ($needsGeocode) {
|
||||
[$geoLat, $geoLng, $geoRegion] = self::geocodeAddress($street, $zip, $city, $region);
|
||||
if ($lat === null) { $lat = $geoLat; }
|
||||
if ($lng === null) { $lng = $geoLng; }
|
||||
if ($region === '' && $geoRegion) { $region = $geoRegion; }
|
||||
}
|
||||
|
||||
if ($action === 'event_add') {
|
||||
$stmt = $pdo?->prepare('INSERT INTO events (created_by, title, teaser_public, description, location_label, street, zip, city, region, lat, lng, starts_at, allow_kids, visibility, status, created_at, updated_at) VALUES (:uid, :title, :teaser, :descr, :loc, :street, :zip, :city, :region, :lat, :lng, :start, :allow, :vis, :status, NOW(), NOW())');
|
||||
$stmt?->execute([
|
||||
'uid' => $userId,
|
||||
'title' => trim((string)$_POST['title']),
|
||||
'teaser' => trim((string)$_POST['teaser']),
|
||||
'descr' => trim((string)$_POST['description']),
|
||||
'loc' => trim((string)$_POST['location_label']),
|
||||
'street' => $street ?: null,
|
||||
'zip' => $zip,
|
||||
'city' => $city,
|
||||
'region' => $region,
|
||||
'lat' => $lat,
|
||||
'lng' => $lng,
|
||||
'start' => $_POST['starts_at'] ?? null,
|
||||
'allow' => isset($_POST['allow_kids']) ? 0 : 1,
|
||||
'vis' => $_POST['visibility'] ?? 'public',
|
||||
'status' => 'published',
|
||||
]);
|
||||
$info = 'Event gespeichert.';
|
||||
try {
|
||||
$cfgPath = dirname(__DIR__, 2) . '/config/community.php';
|
||||
$communityCfg = file_exists($cfgPath) ? require $cfgPath : [];
|
||||
$community = new Community($pdo, $communityCfg);
|
||||
$community->addPoints($userId, 'event', 'create', ['event_id' => $pdo?->lastInsertId()]);
|
||||
} catch (\Throwable) {
|
||||
}
|
||||
} else {
|
||||
$eventId = (int)($_POST['event_id'] ?? 0);
|
||||
$stmt = $pdo?->prepare('UPDATE events SET title=:title, teaser_public=:teaser, description=:descr, location_label=:loc, street=:street, zip=:zip, city=:city, region=:region, lat=:lat, lng=:lng, starts_at=:start, allow_kids=:allow, visibility=:vis, updated_at=NOW() WHERE id=:id AND created_by=:uid');
|
||||
$stmt?->execute([
|
||||
'id' => $eventId,
|
||||
'uid' => $userId,
|
||||
'title' => trim((string)$_POST['title']),
|
||||
'teaser' => trim((string)$_POST['teaser']),
|
||||
'descr' => trim((string)$_POST['description']),
|
||||
'loc' => trim((string)$_POST['location_label']),
|
||||
'street' => $street ?: null,
|
||||
'zip' => $zip,
|
||||
'city' => $city,
|
||||
'region' => $region,
|
||||
'lat' => $lat,
|
||||
'lng' => $lng,
|
||||
'start' => $_POST['starts_at'] ?? null,
|
||||
'allow' => isset($_POST['allow_kids']) ? 0 : 1,
|
||||
'vis' => $_POST['visibility'] ?? 'public',
|
||||
]);
|
||||
$info = 'Event aktualisiert.';
|
||||
}
|
||||
}
|
||||
} elseif ($action === 'event_delete') {
|
||||
$eventId = (int)($_POST['event_id'] ?? 0);
|
||||
@@ -463,6 +474,13 @@ final class AccountPages
|
||||
'id' => $eventId,
|
||||
]);
|
||||
$info = 'Event wurde abgesagt.';
|
||||
} elseif ($action === 'listing_delete') {
|
||||
$listingId = (int)($_POST['listing_id'] ?? 0);
|
||||
if (!$listingCatalog) {
|
||||
throw new \RuntimeException('Die neue Eintragslogik ist aktuell nicht verfügbar.');
|
||||
}
|
||||
$listingCatalog->deleteDashboardEntry($userId, $listingId);
|
||||
$info = 'Eintrag gelöscht.';
|
||||
} elseif ($action === 'community_admin_apply') {
|
||||
if (!$community || !$communityAccess) {
|
||||
throw new \RuntimeException('Community-Funktionen sind aktuell nicht verfügbar.');
|
||||
@@ -584,6 +602,9 @@ final class AccountPages
|
||||
$eventsUpcoming = [];
|
||||
$eventsPast = [];
|
||||
$editEvent = null;
|
||||
$otherListings = [];
|
||||
$editListing = null;
|
||||
$listingCategories = $listingCatalog ? $listingCatalog->listCategories(['place', 'food', 'event', 'family']) : [];
|
||||
$stmt = $pdo?->prepare(
|
||||
'SELECT e.id, e.title, e.teaser_public, e.description, e.location_label, e.street, e.zip, e.city, e.region, e.starts_at, e.allow_kids, e.visibility, e.status, e.lat, e.lng,
|
||||
(SELECT COUNT(*) FROM event_participants ep WHERE ep.event_id = e.id) AS participant_count
|
||||
@@ -610,6 +631,15 @@ final class AccountPages
|
||||
$stmt?->execute(['id' => $editId, 'uid' => $userId]);
|
||||
$editEvent = $stmt?->fetch(\PDO::FETCH_ASSOC) ?: null;
|
||||
}
|
||||
if ($listingCatalog) {
|
||||
$otherListings = $listingCatalog->listDashboardEntries($userId);
|
||||
if (isset($_GET['edit_listing'])) {
|
||||
$editListingId = (int)$_GET['edit_listing'];
|
||||
if ($editListingId > 0) {
|
||||
$editListing = $listingCatalog->getDashboardEntry($userId, $editListingId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$communityPoints = $community ? $community->computePoints($userId) : 0.0;
|
||||
$communityLevel = $community ? $community->membershipLevel($communityPoints) : ['label' => '', 'icon' => ''];
|
||||
@@ -638,6 +668,9 @@ final class AccountPages
|
||||
'eventsUpcoming',
|
||||
'eventsPast',
|
||||
'editEvent',
|
||||
'otherListings',
|
||||
'editListing',
|
||||
'listingCategories',
|
||||
'communityPoints',
|
||||
'communityLevel',
|
||||
'communityRoles',
|
||||
|
||||
@@ -182,6 +182,260 @@ final class ListingCatalog
|
||||
];
|
||||
}
|
||||
|
||||
public function listCategories(?array $groups = null): array
|
||||
{
|
||||
$this->ensureSchema();
|
||||
$params = [];
|
||||
$sql = 'SELECT id, slug, title, category_group FROM listing_categories';
|
||||
if ($groups && $groups !== []) {
|
||||
$placeholders = implode(',', array_fill(0, count($groups), '?'));
|
||||
$sql .= " WHERE category_group IN ($placeholders)";
|
||||
$params = array_values($groups);
|
||||
}
|
||||
$sql .= ' ORDER BY sort_order ASC, title ASC';
|
||||
$stmt = $this->pdo->prepare($sql);
|
||||
$stmt->execute($params);
|
||||
return $stmt->fetchAll(\PDO::FETCH_ASSOC) ?: [];
|
||||
}
|
||||
|
||||
public function listDashboardEntries(int $userId): array
|
||||
{
|
||||
$this->ensureSchema();
|
||||
$stmt = $this->pdo->prepare(
|
||||
'SELECT l.id, l.listing_type, l.title, l.teaser_public, l.visibility, l.status,
|
||||
lp.title AS place_title, lp.city, lp.region, lp.place_kind,
|
||||
lo.starts_at, lo.ends_at, lo.occurrence_type,
|
||||
lc.title AS category_title, lc.slug AS category_slug
|
||||
FROM listings l
|
||||
LEFT JOIN listing_places lp ON lp.id = l.primary_place_id
|
||||
LEFT JOIN listing_occurrences lo ON lo.listing_id = l.id
|
||||
LEFT JOIN listing_category_map lcm ON lcm.listing_id = l.id
|
||||
LEFT JOIN listing_categories lc ON lc.id = lcm.category_id
|
||||
WHERE l.created_by = :uid
|
||||
ORDER BY
|
||||
CASE l.listing_type
|
||||
WHEN "place" THEN 1
|
||||
WHEN "editorial_event" THEN 2
|
||||
ELSE 3
|
||||
END,
|
||||
COALESCE(lo.starts_at, l.created_at) ASC,
|
||||
l.created_at DESC'
|
||||
);
|
||||
$stmt->execute(['uid' => $userId]);
|
||||
return $stmt->fetchAll(\PDO::FETCH_ASSOC) ?: [];
|
||||
}
|
||||
|
||||
public function getDashboardEntry(int $userId, int $listingId): ?array
|
||||
{
|
||||
$this->ensureSchema();
|
||||
$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,
|
||||
lo.id AS occurrence_id, lo.starts_at, lo.ends_at, lo.occurrence_type,
|
||||
lc.slug AS category_slug
|
||||
FROM listings l
|
||||
LEFT JOIN listing_places lp ON lp.id = l.primary_place_id
|
||||
LEFT JOIN listing_occurrences lo ON lo.listing_id = l.id
|
||||
LEFT JOIN listing_category_map lcm ON lcm.listing_id = l.id
|
||||
LEFT JOIN listing_categories lc ON lc.id = lcm.category_id
|
||||
WHERE l.id = :id AND l.created_by = :uid
|
||||
LIMIT 1'
|
||||
);
|
||||
$stmt->execute([
|
||||
'id' => $listingId,
|
||||
'uid' => $userId,
|
||||
]);
|
||||
$row = $stmt->fetch(\PDO::FETCH_ASSOC);
|
||||
return $row ?: null;
|
||||
}
|
||||
|
||||
public function saveDashboardEntry(int $userId, array $data, ?int $listingId = null): int
|
||||
{
|
||||
$this->ensureSchema();
|
||||
$entryType = (string)($data['entry_kind'] ?? 'editorial_event');
|
||||
if (!in_array($entryType, ['place', 'editorial_event'], true)) {
|
||||
throw new \RuntimeException('Ungültiger Eintragstyp.');
|
||||
}
|
||||
|
||||
$title = trim((string)($data['title'] ?? ''));
|
||||
$teaser = trim((string)($data['teaser'] ?? ''));
|
||||
$description = trim((string)($data['description'] ?? ''));
|
||||
$visibility = (string)($data['visibility'] ?? 'public');
|
||||
$placeKind = trim((string)($data['category_slug'] ?? ''));
|
||||
$street = trim((string)($data['street'] ?? ''));
|
||||
$zip = trim((string)($data['zip'] ?? ''));
|
||||
$city = trim((string)($data['city'] ?? ''));
|
||||
$region = trim((string)($data['region'] ?? ''));
|
||||
$lat = isset($data['lat']) && $data['lat'] !== '' ? (float)$data['lat'] : null;
|
||||
$lng = isset($data['lng']) && $data['lng'] !== '' ? (float)$data['lng'] : null;
|
||||
$startsAt = trim((string)($data['starts_at'] ?? ''));
|
||||
|
||||
if ($title === '' || $teaser === '' || $description === '') {
|
||||
throw new \RuntimeException('Bitte fülle Titel, Kurzbeschreibung und Beschreibung aus.');
|
||||
}
|
||||
if (!in_array($visibility, ['public', 'members'], true)) {
|
||||
$visibility = 'public';
|
||||
}
|
||||
if ($entryType === 'editorial_event' && $startsAt === '') {
|
||||
throw new \RuntimeException('Bitte gib für sonstige Veranstaltungen ein Datum an.');
|
||||
}
|
||||
|
||||
$this->pdo->beginTransaction();
|
||||
try {
|
||||
$categoryId = null;
|
||||
if ($placeKind !== '') {
|
||||
$catStmt = $this->pdo->prepare('SELECT id FROM listing_categories WHERE slug = :slug LIMIT 1');
|
||||
$catStmt->execute(['slug' => $placeKind]);
|
||||
$categoryId = $catStmt->fetchColumn();
|
||||
$categoryId = $categoryId !== false ? (int)$categoryId : null;
|
||||
}
|
||||
|
||||
if ($listingId !== null && $listingId > 0) {
|
||||
$existing = $this->getDashboardEntry($userId, $listingId);
|
||||
if (!$existing) {
|
||||
throw new \RuntimeException('Eintrag nicht gefunden.');
|
||||
}
|
||||
$placeId = isset($existing['primary_place_id']) ? (int)$existing['primary_place_id'] : 0;
|
||||
if ($placeId > 0) {
|
||||
$placeStmt = $this->pdo->prepare(
|
||||
'UPDATE listing_places
|
||||
SET title = :title, description = :description, street = :street, zip = :zip, city = :city, region = :region, lat = :lat, lng = :lng, place_kind = :placeKind, updated_at = NOW()
|
||||
WHERE id = :id'
|
||||
);
|
||||
$placeStmt->execute([
|
||||
'title' => $title,
|
||||
'description' => $description,
|
||||
'street' => $street !== '' ? $street : null,
|
||||
'zip' => $zip !== '' ? $zip : null,
|
||||
'city' => $city !== '' ? $city : null,
|
||||
'region' => $region !== '' ? $region : null,
|
||||
'lat' => $lat,
|
||||
'lng' => $lng,
|
||||
'placeKind' => $placeKind !== '' ? $placeKind : null,
|
||||
'id' => $placeId,
|
||||
]);
|
||||
}
|
||||
$listingStmt = $this->pdo->prepare(
|
||||
'UPDATE listings
|
||||
SET listing_type = :listingType, title = :title, teaser_public = :teaser, description = :description, visibility = :visibility,
|
||||
supports_registration = 0, supports_capacity = 0, supports_pricing = 0, is_recurring = 0, status = "published", updated_at = NOW()
|
||||
WHERE id = :id AND created_by = :uid'
|
||||
);
|
||||
$listingStmt->execute([
|
||||
'listingType' => $entryType,
|
||||
'title' => $title,
|
||||
'teaser' => $teaser,
|
||||
'description' => $description,
|
||||
'visibility' => $visibility,
|
||||
'id' => $listingId,
|
||||
'uid' => $userId,
|
||||
]);
|
||||
|
||||
$this->pdo->prepare('DELETE FROM listing_occurrences WHERE listing_id = :listingId')->execute(['listingId' => $listingId]);
|
||||
if ($entryType === 'editorial_event') {
|
||||
$occStmt = $this->pdo->prepare(
|
||||
'INSERT INTO listing_occurrences (listing_id, occurrence_type, starts_at, status, created_at, updated_at)
|
||||
VALUES (:listingId, :occurrenceType, :startsAt, "scheduled", NOW(), NOW())'
|
||||
);
|
||||
$occStmt->execute([
|
||||
'listingId' => $listingId,
|
||||
'occurrenceType' => 'single',
|
||||
'startsAt' => $startsAt !== '' ? $startsAt : null,
|
||||
]);
|
||||
}
|
||||
} else {
|
||||
$placeStmt = $this->pdo->prepare(
|
||||
'INSERT INTO listing_places (created_by, source_type, title, description, street, zip, city, region, lat, lng, place_kind, provider_hint, status, created_at, updated_at)
|
||||
VALUES (:uid, "user", :title, :description, :street, :zip, :city, :region, :lat, :lng, :placeKind, "manual", "published", NOW(), NOW())'
|
||||
);
|
||||
$placeStmt->execute([
|
||||
'uid' => $userId,
|
||||
'title' => $title,
|
||||
'description' => $description,
|
||||
'street' => $street !== '' ? $street : null,
|
||||
'zip' => $zip !== '' ? $zip : null,
|
||||
'city' => $city !== '' ? $city : null,
|
||||
'region' => $region !== '' ? $region : null,
|
||||
'lat' => $lat,
|
||||
'lng' => $lng,
|
||||
'placeKind' => $placeKind !== '' ? $placeKind : null,
|
||||
]);
|
||||
$placeId = (int)$this->pdo->lastInsertId();
|
||||
|
||||
$listingStmt = $this->pdo->prepare(
|
||||
'INSERT INTO listings (created_by, owner_type, listing_type, primary_place_id, title, teaser_public, description, visibility, status, supports_registration, supports_capacity, supports_pricing, is_recurring, created_at, updated_at)
|
||||
VALUES (:uid, "user", :listingType, :placeId, :title, :teaser, :description, :visibility, "published", 0, 0, 0, 0, NOW(), NOW())'
|
||||
);
|
||||
$listingStmt->execute([
|
||||
'uid' => $userId,
|
||||
'listingType' => $entryType,
|
||||
'placeId' => $placeId,
|
||||
'title' => $title,
|
||||
'teaser' => $teaser,
|
||||
'description' => $description,
|
||||
'visibility' => $visibility,
|
||||
]);
|
||||
$listingId = (int)$this->pdo->lastInsertId();
|
||||
|
||||
if ($entryType === 'editorial_event') {
|
||||
$occStmt = $this->pdo->prepare(
|
||||
'INSERT INTO listing_occurrences (listing_id, occurrence_type, starts_at, status, created_at, updated_at)
|
||||
VALUES (:listingId, :occurrenceType, :startsAt, "scheduled", NOW(), NOW())'
|
||||
);
|
||||
$occStmt->execute([
|
||||
'listingId' => $listingId,
|
||||
'occurrenceType' => 'single',
|
||||
'startsAt' => $startsAt !== '' ? $startsAt : null,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
$this->pdo->prepare('DELETE FROM listing_category_map WHERE listing_id = :listingId')->execute(['listingId' => $listingId]);
|
||||
if ($categoryId !== null) {
|
||||
$mapStmt = $this->pdo->prepare('INSERT INTO listing_category_map (listing_id, category_id) VALUES (:listingId, :categoryId)');
|
||||
$mapStmt->execute([
|
||||
'listingId' => $listingId,
|
||||
'categoryId' => $categoryId,
|
||||
]);
|
||||
}
|
||||
|
||||
$this->pdo->commit();
|
||||
return (int)$listingId;
|
||||
} catch (\Throwable $e) {
|
||||
if ($this->pdo->inTransaction()) {
|
||||
$this->pdo->rollBack();
|
||||
}
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
|
||||
public function deleteDashboardEntry(int $userId, int $listingId): void
|
||||
{
|
||||
$this->ensureSchema();
|
||||
$entry = $this->getDashboardEntry($userId, $listingId);
|
||||
if (!$entry) {
|
||||
throw new \RuntimeException('Eintrag nicht gefunden.');
|
||||
}
|
||||
|
||||
$this->pdo->beginTransaction();
|
||||
try {
|
||||
$this->pdo->prepare('DELETE FROM listings WHERE id = :id AND created_by = :uid')->execute([
|
||||
'id' => $listingId,
|
||||
'uid' => $userId,
|
||||
]);
|
||||
if (!empty($entry['primary_place_id'])) {
|
||||
$this->pdo->prepare('DELETE FROM listing_places WHERE id = :id')->execute([
|
||||
'id' => (int)$entry['primary_place_id'],
|
||||
]);
|
||||
}
|
||||
$this->pdo->commit();
|
||||
} catch (\Throwable $e) {
|
||||
if ($this->pdo->inTransaction()) {
|
||||
$this->pdo->rollBack();
|
||||
}
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
|
||||
private function hasTable(string $table): bool
|
||||
{
|
||||
if (array_key_exists($table, $this->tableCache)) {
|
||||
|
||||
Reference in New Issue
Block a user