This commit is contained in:
@@ -189,6 +189,9 @@ final class AccountPages
|
||||
if ($listingCatalog) {
|
||||
$listingCatalog->ensureSchema();
|
||||
}
|
||||
if ($pdo) {
|
||||
self::ensureLegacyEventSchema($pdo);
|
||||
}
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
$action = $_POST['action'] ?? '';
|
||||
@@ -378,6 +381,8 @@ final class AccountPages
|
||||
throw new \RuntimeException('Die neue Eintragslogik ist aktuell nicht verfügbar.');
|
||||
}
|
||||
$payload = $_POST;
|
||||
$existingListingId = $action === 'event_update' ? (int)($_POST['listing_id'] ?? 0) : 0;
|
||||
$existingListing = $existingListingId > 0 ? $listingCatalog->getDashboardEntry($userId, $existingListingId) : null;
|
||||
$street = trim((string)($_POST['street'] ?? ''));
|
||||
$zip = trim((string)($_POST['zip'] ?? ''));
|
||||
$city = trim((string)($_POST['city'] ?? ''));
|
||||
@@ -404,6 +409,7 @@ final class AccountPages
|
||||
$payload['lat'] = $resolvedAddress['lat'];
|
||||
$payload['lng'] = $resolvedAddress['lng'];
|
||||
}
|
||||
$payload['image_path'] = self::storeUploadedImage($_FILES['image_file'] ?? null, is_array($existingListing) ? (string)($existingListing['image_path'] ?? '') : '');
|
||||
$payload['teaser'] = self::generateTeaser(
|
||||
trim((string)($payload['description'] ?? '')),
|
||||
trim((string)($payload['title'] ?? ''))
|
||||
@@ -415,27 +421,59 @@ final class AccountPages
|
||||
);
|
||||
$info = $entryKind === 'place' ? 'Ort gespeichert.' : 'Veranstaltung gespeichert.';
|
||||
} else {
|
||||
$existingEvent = null;
|
||||
if ($action === 'event_update') {
|
||||
$existingEventId = (int)($_POST['event_id'] ?? 0);
|
||||
if ($existingEventId > 0) {
|
||||
$stmt = $pdo?->prepare('SELECT * FROM events WHERE id = :id AND created_by = :uid LIMIT 1');
|
||||
$stmt?->execute(['id' => $existingEventId, 'uid' => $userId]);
|
||||
$existingEvent = $stmt?->fetch(\PDO::FETCH_ASSOC) ?: null;
|
||||
}
|
||||
}
|
||||
$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; }
|
||||
$hasAddress = $street !== '' || $zip !== '' || $city !== '' || $region !== '';
|
||||
if ($hasAddress) {
|
||||
$resolvedAddress = null;
|
||||
if ($lat !== null && $lng !== null) {
|
||||
$resolvedAddress = self::reverseGeocodeAddress($lat, $lng);
|
||||
}
|
||||
if ($resolvedAddress === null) {
|
||||
$searchResults = self::geocodeAddressCandidates($street, $zip, $city, $region, 5);
|
||||
if (!$searchResults) {
|
||||
throw new \RuntimeException('Die Adresse konnte nicht validiert werden. Bitte wähle einen passenden Adress-Treffer.');
|
||||
}
|
||||
$resolvedAddress = $searchResults[0];
|
||||
}
|
||||
$street = $resolvedAddress['street'] !== '' ? $resolvedAddress['street'] : $street;
|
||||
$zip = $resolvedAddress['zip'] !== '' ? $resolvedAddress['zip'] : $zip;
|
||||
$city = $resolvedAddress['city'] !== '' ? $resolvedAddress['city'] : $city;
|
||||
$region = $resolvedAddress['region'] !== '' ? $resolvedAddress['region'] : $region;
|
||||
$lat = $resolvedAddress['lat'];
|
||||
$lng = $resolvedAddress['lng'];
|
||||
}
|
||||
$imagePath = self::storeUploadedImage($_FILES['image_file'] ?? null, is_array($existingEvent) ? (string)($existingEvent['image_path'] ?? '') : '');
|
||||
$allowKids = ((string)($_POST['with_children'] ?? 'yes')) === 'yes' ? 1 : 0;
|
||||
$capacity = isset($_POST['max_participants']) && $_POST['max_participants'] !== '' ? (int)$_POST['max_participants'] : null;
|
||||
$categorySlug = trim((string)($_POST['category_slug'] ?? ''));
|
||||
$customCategoryTitle = trim((string)($_POST['category_custom_title'] ?? ''));
|
||||
if ($customCategoryTitle !== '') {
|
||||
$categorySlug = self::slugifyValue($customCategoryTitle);
|
||||
}
|
||||
|
||||
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 = $pdo?->prepare('INSERT INTO events (created_by, title, teaser_public, description, category_slug, image_path, location_label, street, zip, city, region, lat, lng, starts_at, max_participants, allow_kids, visibility, status, created_at, updated_at) VALUES (:uid, :title, :teaser, :descr, :categorySlug, :imagePath, :loc, :street, :zip, :city, :region, :lat, :lng, :start, :capacity, :allow, :vis, :status, NOW(), NOW())');
|
||||
$stmt?->execute([
|
||||
'uid' => $userId,
|
||||
'title' => trim((string)$_POST['title']),
|
||||
'teaser' => self::generateTeaser(trim((string)$_POST['description']), trim((string)$_POST['title'])),
|
||||
'descr' => trim((string)$_POST['description']),
|
||||
'categorySlug' => $categorySlug !== '' ? $categorySlug : null,
|
||||
'imagePath' => $imagePath !== '' ? $imagePath : null,
|
||||
'loc' => trim((string)$_POST['location_label']),
|
||||
'street' => $street ?: null,
|
||||
'zip' => $zip,
|
||||
@@ -444,7 +482,8 @@ final class AccountPages
|
||||
'lat' => $lat,
|
||||
'lng' => $lng,
|
||||
'start' => $_POST['starts_at'] ?? null,
|
||||
'allow' => isset($_POST['allow_kids']) ? 0 : 1,
|
||||
'capacity' => $capacity,
|
||||
'allow' => $allowKids,
|
||||
'vis' => $_POST['visibility'] ?? 'public',
|
||||
'status' => 'published',
|
||||
]);
|
||||
@@ -458,13 +497,15 @@ final class AccountPages
|
||||
}
|
||||
} 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 = $pdo?->prepare('UPDATE events SET title=:title, teaser_public=:teaser, description=:descr, category_slug=:categorySlug, image_path=:imagePath, location_label=:loc, street=:street, zip=:zip, city=:city, region=:region, lat=:lat, lng=:lng, starts_at=:start, max_participants=:capacity, 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' => self::generateTeaser(trim((string)$_POST['description']), trim((string)$_POST['title'])),
|
||||
'descr' => trim((string)$_POST['description']),
|
||||
'categorySlug' => $categorySlug !== '' ? $categorySlug : null,
|
||||
'imagePath' => $imagePath !== '' ? $imagePath : null,
|
||||
'loc' => trim((string)$_POST['location_label']),
|
||||
'street' => $street ?: null,
|
||||
'zip' => $zip,
|
||||
@@ -473,7 +514,8 @@ final class AccountPages
|
||||
'lat' => $lat,
|
||||
'lng' => $lng,
|
||||
'start' => $_POST['starts_at'] ?? null,
|
||||
'allow' => isset($_POST['allow_kids']) ? 0 : 1,
|
||||
'capacity' => $capacity,
|
||||
'allow' => $allowKids,
|
||||
'vis' => $_POST['visibility'] ?? 'public',
|
||||
]);
|
||||
$info = 'Event aktualisiert.';
|
||||
@@ -481,7 +523,7 @@ final class AccountPages
|
||||
}
|
||||
} elseif ($action === 'event_delete') {
|
||||
$eventId = (int)($_POST['event_id'] ?? 0);
|
||||
$stmt = $pdo?->prepare('SELECT id, created_by, status, (SELECT COUNT(*) FROM event_participants ep WHERE ep.event_id = events.id) AS participant_count FROM events WHERE id = :id LIMIT 1');
|
||||
$stmt = $pdo?->prepare('SELECT id, created_by, status, image_path, (SELECT COUNT(*) FROM event_participants ep WHERE ep.event_id = events.id) AS participant_count FROM events WHERE id = :id LIMIT 1');
|
||||
$stmt?->execute(['id' => $eventId]);
|
||||
$ev = $stmt?->fetch(\PDO::FETCH_ASSOC);
|
||||
if (!$ev || (int)$ev['created_by'] !== $userId) {
|
||||
@@ -490,6 +532,7 @@ final class AccountPages
|
||||
if ((int)$ev['participant_count'] > 0) {
|
||||
throw new \RuntimeException('Event hat Anmeldungen und kann nicht gelöscht werden.');
|
||||
}
|
||||
self::deleteStoredImage((string)($ev['image_path'] ?? ''));
|
||||
$pdo?->prepare('DELETE FROM events WHERE id = :id')->execute(['id' => $eventId]);
|
||||
$info = 'Event gelöscht.';
|
||||
} elseif ($action === 'event_cancel') {
|
||||
@@ -510,6 +553,10 @@ final class AccountPages
|
||||
if (!$listingCatalog) {
|
||||
throw new \RuntimeException('Die neue Eintragslogik ist aktuell nicht verfügbar.');
|
||||
}
|
||||
$entry = $listingCatalog->getDashboardEntry($userId, $listingId);
|
||||
if (is_array($entry)) {
|
||||
self::deleteStoredImage((string)($entry['image_path'] ?? ''));
|
||||
}
|
||||
$listingCatalog->deleteDashboardEntry($userId, $listingId);
|
||||
$info = 'Eintrag gelöscht.';
|
||||
} elseif ($action === 'community_admin_apply') {
|
||||
@@ -638,7 +685,7 @@ final class AccountPages
|
||||
$listingCategories = $listingCatalog ? $listingCatalog->listCategories(['place', 'food', 'event', 'family']) : [];
|
||||
$placeSuggestions = $listingCatalog ? $listingCatalog->listPlaceSuggestions(60) : [];
|
||||
$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 e.id, e.title, e.teaser_public, e.description, e.category_slug, e.image_path, e.location_label, e.street, e.zip, e.city, e.region, e.starts_at, e.max_participants, 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
|
||||
FROM events e
|
||||
WHERE e.created_by = :id AND e.starts_at >= NOW()
|
||||
@@ -648,7 +695,7 @@ final class AccountPages
|
||||
$eventsUpcoming = $stmt?->fetchAll(\PDO::FETCH_ASSOC) ?: [];
|
||||
|
||||
$stmt = $pdo?->prepare(
|
||||
'SELECT e.id, e.title, e.teaser_public, e.starts_at, e.city, e.visibility, e.status,
|
||||
'SELECT e.id, e.title, e.teaser_public, e.image_path, e.starts_at, e.city, e.visibility, e.status,
|
||||
(SELECT COUNT(*) FROM event_participants ep WHERE ep.event_id = e.id) AS participant_count
|
||||
FROM events e
|
||||
WHERE e.created_by = :id AND e.starts_at < NOW()
|
||||
@@ -845,6 +892,87 @@ final class AccountPages
|
||||
return $results;
|
||||
}
|
||||
|
||||
private static function ensureLegacyEventSchema(\PDO $pdo): void
|
||||
{
|
||||
$columns = [
|
||||
'category_slug' => 'ALTER TABLE events ADD COLUMN category_slug VARCHAR(120) NULL AFTER description',
|
||||
'image_path' => 'ALTER TABLE events ADD COLUMN image_path VARCHAR(255) NULL AFTER category_slug',
|
||||
];
|
||||
foreach ($columns as $column => $sql) {
|
||||
$stmt = $pdo->prepare('SELECT 1 FROM information_schema.columns WHERE table_schema = DATABASE() AND table_name = "events" AND column_name = :column LIMIT 1');
|
||||
$stmt->execute(['column' => $column]);
|
||||
if (!$stmt->fetchColumn()) {
|
||||
$pdo->exec($sql);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static function storeUploadedImage(mixed $file, string $currentPath = ''): string
|
||||
{
|
||||
if (!is_array($file) || (int)($file['error'] ?? UPLOAD_ERR_NO_FILE) === UPLOAD_ERR_NO_FILE) {
|
||||
return $currentPath;
|
||||
}
|
||||
if ((int)($file['error'] ?? UPLOAD_ERR_OK) !== UPLOAD_ERR_OK) {
|
||||
throw new \RuntimeException('Der Bild-Upload konnte nicht verarbeitet werden.');
|
||||
}
|
||||
$tmpName = (string)($file['tmp_name'] ?? '');
|
||||
if ($tmpName === '' || !is_uploaded_file($tmpName)) {
|
||||
throw new \RuntimeException('Die hochgeladene Datei ist ungültig.');
|
||||
}
|
||||
if ((int)($file['size'] ?? 0) > 5 * 1024 * 1024) {
|
||||
throw new \RuntimeException('Bitte ein Bild mit maximal 5 MB hochladen.');
|
||||
}
|
||||
|
||||
$imageInfo = @getimagesize($tmpName);
|
||||
if (!is_array($imageInfo) || empty($imageInfo['mime'])) {
|
||||
throw new \RuntimeException('Bitte eine gültige Bilddatei hochladen.');
|
||||
}
|
||||
$mime = (string)$imageInfo['mime'];
|
||||
$extension = match ($mime) {
|
||||
'image/jpeg' => 'jpg',
|
||||
'image/png' => 'png',
|
||||
'image/webp' => 'webp',
|
||||
default => '',
|
||||
};
|
||||
if ($extension === '') {
|
||||
throw new \RuntimeException('Erlaubt sind JPG, PNG oder WebP.');
|
||||
}
|
||||
|
||||
$uploadDir = dirname(__DIR__, 2) . '/public/uploads/listings';
|
||||
if (!is_dir($uploadDir) && !mkdir($uploadDir, 0775, true) && !is_dir($uploadDir)) {
|
||||
throw new \RuntimeException('Der Upload-Ordner konnte nicht erstellt werden.');
|
||||
}
|
||||
|
||||
$filename = 'listing-' . date('Ymd-His') . '-' . bin2hex(random_bytes(6)) . '.' . $extension;
|
||||
$targetPath = $uploadDir . '/' . $filename;
|
||||
if (!move_uploaded_file($tmpName, $targetPath)) {
|
||||
throw new \RuntimeException('Das Bild konnte nicht gespeichert werden.');
|
||||
}
|
||||
|
||||
self::deleteStoredImage($currentPath);
|
||||
return '/uploads/listings/' . $filename;
|
||||
}
|
||||
|
||||
private static function deleteStoredImage(string $path): void
|
||||
{
|
||||
$path = trim($path);
|
||||
if ($path === '' || !str_starts_with($path, '/uploads/listings/')) {
|
||||
return;
|
||||
}
|
||||
$absolutePath = dirname(__DIR__, 2) . '/public' . $path;
|
||||
if (is_file($absolutePath)) {
|
||||
@unlink($absolutePath);
|
||||
}
|
||||
}
|
||||
|
||||
private static function slugifyValue(string $value): string
|
||||
{
|
||||
$value = mb_strtolower(trim($value));
|
||||
$value = strtr($value, ['ä' => 'ae', 'ö' => 'oe', 'ü' => 'ue', 'ß' => 'ss']);
|
||||
$value = preg_replace('/[^a-z0-9]+/u', '-', $value) ?: '';
|
||||
return trim($value, '-');
|
||||
}
|
||||
|
||||
private static function reverseGeocodeAddress(float $lat, float $lng): ?array
|
||||
{
|
||||
$url = 'https://nominatim.openstreetmap.org/reverse?' . http_build_query([
|
||||
|
||||
Reference in New Issue
Block a user