-
+
+
+
+
Für Orte und sonstige Veranstaltungen, damit Spielplatz, Café, Restaurant, Zoo oder Zirkus sauber unterscheidbar bleiben.
+
Hält Karte und Adresse synchron.
-
-
+
+
@@ -857,17 +956,17 @@ if (!empty($canManageSystemSettings)) {
-
@@ -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.');
diff --git a/src/App/AccountPages.php b/src/App/AccountPages.php
index 0c950e2..6030307 100755
--- a/src/App/AccountPages.php
+++ b/src/App/AccountPages.php
@@ -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',
diff --git a/src/App/ListingCatalog.php b/src/App/ListingCatalog.php
index eee81a7..f805656 100644
--- a/src/App/ListingCatalog.php
+++ b/src/App/ListingCatalog.php
@@ -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)) {