| Server IP : 202.61.199.114 / Your IP : 216.73.217.139 Web Server : nginx/1.22.1 System : Linux de.arni-solutions.de 6.1.0-49-amd64 #1 SMP PREEMPT_DYNAMIC Debian 6.1.174-1 (2026-05-26) x86_64 User : web20 ( 1018) PHP Version : 8.4.23 Disable Function : NONE MySQL : OFF | cURL : ON | WGET : ON | Perl : ON | Python : OFF | Sudo : ON | Pkexec : ON Directory : /var/www/clients/client1/seaside.pacim.de/web/ |
Upload File : |
<?php
/**
* events-api.php – Admin-Verwaltung der Veranstaltungen (pacim_events) und
* Veranstalter (pacim_organizers). Nur Admins (is_admin).
*
* Veranstaltungen:
* action=events GET [q,organizer,status,range,limit,offset] -> { ok, events:[...], total }
* action=event GET id -> { ok, event:{...} }
* action=event-save POST <felder> -> { ok, id }
* action=event-delete POST id -> { ok }
* Veranstalter:
* action=organizers GET -> { ok, organizers:[...,events] }
* action=organizer GET id -> { ok, organizer:{...} }
* action=organizer-save POST <felder> -> { ok, id }
* action=organizer-delete POST id -> { ok }
* action=organizer-options GET -> { ok, organizers:[{id,name,active}] }
*/
require_once( __DIR__ . '/includes/config.inc.php' );
@ini_set( 'display_errors', '0' );
error_reporting( 0 );
ob_start();
function ev_json( $a ) { while ( ob_get_level() > 0 ) ob_end_clean(); header( 'Content-Type: application/json; charset=utf-8' ); echo json_encode( $a ); exit(); }
function ev_fail( $msg ) { ev_json( array( 'ok' => false, 'error' => $msg ) ); }
if ( ! function_exists( 'is_admin' ) || ! is_admin() ) { http_response_code( 403 ); ev_json( array( 'ok' => false, 'error' => 'forbidden' ) ); }
$action = isset( $_GET['action'] ) ? $_GET['action'] : ( isset( $_POST['action'] ) ? $_POST['action'] : '' );
$db = new MysqliDb( MYSQL_HOST, MYSQL_USER, MYSQL_PASS, MYSQL_DB );
// Anreise-Infos können 4-Byte-Zeichen (Emojis) im Inhalt enthalten -> utf8mb4-Verbindung.
if ( strpos( (string) $action, 'arrival' ) === 0 ) $db->rawQuery( "SET NAMES utf8mb4" );
/** datetime-local (Y-m-dTH:i) -> Y-m-d H:i:s; '' wenn ungültig. */
function ev_dt( $v ) {
$v = trim( (string) $v );
if ( $v === '' ) return '';
$v = str_replace( 'T', ' ', $v );
$ts = strtotime( $v );
return $ts ? date( 'Y-m-d H:i:s', $ts ) : '';
}
/* =====================================================================
* VERANSTALTUNGEN
* ===================================================================== */
if ( $action === 'events' ) {
$q = trim( (string) ( $_GET['q'] ?? '' ) );
$organizer = (int) ( $_GET['organizer'] ?? 0 );
$status = (string) ( $_GET['status'] ?? 'all' );
$range = (string) ( $_GET['range'] ?? 'upcoming' );
$limit = min( 500, max( 1, (int) ( $_GET['limit'] ?? 100 ) ) );
$offset = max( 0, (int) ( $_GET['offset'] ?? 0 ) );
$where = array( '1=1' ); $params = array();
if ( $q !== '' ) { $where[] = '(e.event_calendar_title LIKE ? OR e.event_calendar_cruise_id LIKE ?)'; $params[] = '%'.$q.'%'; $params[] = '%'.$q.'%'; }
if ( $organizer > 0 ) { $where[] = 'e.event_calendar_organizer = ?'; $params[] = $organizer; }
elseif ( $organizer === -1 ) { $where[] = 'e.event_calendar_organizer IS NULL'; }
if ( $status === 'active' ) $where[] = 'e.event_calendar_active = 1';
if ( $status === 'inactive' ) $where[] = 'e.event_calendar_active = 0';
if ( $range === 'upcoming' ) $where[] = 'e.event_calendar_end >= NOW()';
if ( $range === 'past' ) $where[] = 'e.event_calendar_end < NOW()';
$w = implode( ' AND ', $where );
$order = ( $range === 'past' ) ? 'e.event_calendar_begin DESC' : 'e.event_calendar_begin ASC';
$total = $db->rawQueryOne( "SELECT COUNT(*) c FROM pacim_events e WHERE $w", $params );
$rows = $db->rawQuery(
"SELECT e.event_calendar_id AS id, e.event_calendar_title AS title, e.event_calendar_cruise_id AS cruise_id,
e.event_calendar_organizer AS organizer_id, o.organizer_name AS organizer,
e.event_calendar_indoor AS indoor, e.event_calendar_outdoor AS outdoor, e.event_calendar_valet AS valet,
e.event_calendar_active AS active,
DATE_FORMAT(e.event_calendar_begin,'%d.%m.%Y %H:%i') AS begin_fmt,
DATE_FORMAT(e.event_calendar_end,'%d.%m.%Y %H:%i') AS end_fmt,
e.event_calendar_begin AS begin_raw, e.event_calendar_end AS end_raw
FROM pacim_events e
LEFT JOIN pacim_organizers o ON o.organizer_id = e.event_calendar_organizer
WHERE $w ORDER BY $order LIMIT $offset, $limit",
$params
);
$out = array();
foreach ( (array) $rows as $r ) {
$out[] = array(
'id' => (int) $r['id'], 'title' => $r['title'], 'cruise_id' => $r['cruise_id'],
'organizer_id' => $r['organizer_id'] ? (int) $r['organizer_id'] : null, 'organizer' => $r['organizer'],
'indoor' => $r['indoor'] === null ? null : (int) $r['indoor'],
'outdoor' => $r['outdoor'] === null ? null : (int) $r['outdoor'],
'valet' => $r['valet'] === null ? null : (int) $r['valet'],
'active' => (int) $r['active'], 'begin' => $r['begin_fmt'], 'end' => $r['end_fmt'],
'begin_raw' => $r['begin_raw'], 'end_raw' => $r['end_raw'],
);
}
ev_json( array( 'ok' => true, 'events' => $out, 'total' => (int) $total['c'] ) );
}
if ( $action === 'event' ) {
$id = (int) ( $_GET['id'] ?? 0 );
$r = $db->rawQueryOne( "SELECT * FROM pacim_events WHERE event_calendar_id = ?", array( $id ) );
if ( ! $r ) ev_fail( 'not_found' );
ev_json( array( 'ok' => true, 'event' => $r ) );
}
if ( $action === 'event-save' ) {
$id = (int) ( $_POST['id'] ?? 0 );
$title = trim( (string) ( $_POST['title'] ?? '' ) );
$begin = ev_dt( $_POST['begin'] ?? '' );
$end = ev_dt( $_POST['end'] ?? '' );
if ( $title === '' ) ev_fail( 'Bitte einen Titel angeben.' );
if ( $begin === '' || $end === '' ) ev_fail( 'Bitte Beginn und Ende angeben.' );
if ( strtotime( $end ) < strtotime( $begin ) ) ev_fail( 'Das Ende muss nach dem Beginn liegen.' );
$org = ( isset( $_POST['organizer'] ) && $_POST['organizer'] !== '' ) ? (int) $_POST['organizer'] : null;
$intOrNull = function( $k ) { return ( isset( $_POST[$k] ) && $_POST[$k] !== '' ) ? (int) $_POST[$k] : null; };
$fields = array(
'event_calendar_title' => $title,
'event_calendar_cruise_id' => trim( (string) ( $_POST['cruise_id'] ?? '' ) ),
'event_calendar_organizer' => $org,
'event_calendar_description' => trim( (string) ( $_POST['description'] ?? '' ) ), // HTML erlaubt (WYSIWYG)
'event_calendar_description_public' => ! empty( $_POST['description_public'] ) ? 1 : 0,
'event_calendar_indoor' => $intOrNull( 'indoor' ),
'event_calendar_outdoor' => $intOrNull( 'outdoor' ),
'event_calendar_valet' => $intOrNull( 'valet' ),
'event_calendar_begin' => $begin,
'event_calendar_end' => $end,
'event_calendar_active' => ! empty( $_POST['active'] ) ? 1 : 0,
);
if ( ! $id ) {
$fields['event_calendar_info_id'] = 0; // manuell angelegt
$newId = $db->insert( 'pacim_events', $fields );
if ( ! $newId ) ev_fail( 'Event konnte nicht angelegt werden.' );
ev_json( array( 'ok' => true, 'id' => (int) $newId ) );
} else {
$db->where( 'event_calendar_id', $id );
$db->update( 'pacim_events', $fields );
ev_json( array( 'ok' => true, 'id' => $id ) );
}
}
if ( $action === 'event-delete' ) {
$id = (int) ( $_POST['id'] ?? 0 );
if ( $id <= 0 ) ev_fail( 'not_found' );
$db->where( 'event_calendar_id', $id );
$db->delete( 'pacim_events' );
ev_json( array( 'ok' => true ) );
}
/* =====================================================================
* VERANSTALTER
* ===================================================================== */
if ( $action === 'organizers' ) {
$rows = $db->rawQuery(
"SELECT o.*, (SELECT COUNT(*) FROM pacim_events e WHERE e.event_calendar_organizer = o.organizer_id) AS events
FROM pacim_organizers o ORDER BY o.organizer_name ASC"
);
$out = array();
foreach ( (array) $rows as $r ) {
$out[] = array(
'id' => (int) $r['organizer_id'], 'name' => $r['organizer_name'], 'contact' => $r['organizer_contact'],
'email' => $r['organizer_email'], 'phone' => $r['organizer_phone'], 'city' => $r['organizer_city'],
'website' => $r['organizer_website'], 'active' => (int) $r['organizer_active'], 'events' => (int) $r['events'],
);
}
ev_json( array( 'ok' => true, 'organizers' => $out ) );
}
if ( $action === 'organizer-options' ) {
$rows = $db->rawQuery( "SELECT organizer_id AS id, organizer_name AS name, organizer_active AS active FROM pacim_organizers ORDER BY organizer_name ASC" );
ev_json( array( 'ok' => true, 'organizers' => $rows ? $rows : array() ) );
}
if ( $action === 'organizer' ) {
$id = (int) ( $_GET['id'] ?? 0 );
$r = $db->rawQueryOne( "SELECT * FROM pacim_organizers WHERE organizer_id = ?", array( $id ) );
if ( ! $r ) ev_fail( 'not_found' );
ev_json( array( 'ok' => true, 'organizer' => $r ) );
}
if ( $action === 'organizer-save' ) {
$id = (int) ( $_POST['id'] ?? 0 );
$name = trim( (string) ( $_POST['name'] ?? '' ) );
if ( $name === '' ) ev_fail( 'Bitte einen Namen angeben.' );
$now = date( 'Y-m-d H:i:s' );
$fields = array(
'organizer_name' => $name,
'organizer_contact' => trim( (string) ( $_POST['contact'] ?? '' ) ),
'organizer_email' => trim( (string) ( $_POST['email'] ?? '' ) ),
'organizer_phone' => trim( (string) ( $_POST['phone'] ?? '' ) ),
'organizer_street' => trim( (string) ( $_POST['street'] ?? '' ) ),
'organizer_zipcode' => trim( (string) ( $_POST['zipcode'] ?? '' ) ),
'organizer_city' => trim( (string) ( $_POST['city'] ?? '' ) ),
'organizer_country' => trim( (string) ( $_POST['country'] ?? '' ) ),
'organizer_website' => trim( (string) ( $_POST['website'] ?? '' ) ),
'organizer_notes' => trim( (string) ( $_POST['notes'] ?? '' ) ),
'organizer_active' => ! empty( $_POST['active'] ) ? 1 : 0,
'organizer_updated' => $now,
);
if ( ! $id ) {
$fields['organizer_created'] = $now;
$newId = $db->insert( 'pacim_organizers', $fields );
if ( ! $newId ) ev_fail( 'Veranstalter konnte nicht angelegt werden.' );
ev_json( array( 'ok' => true, 'id' => (int) $newId ) );
} else {
$db->where( 'organizer_id', $id );
$db->update( 'pacim_organizers', $fields );
ev_json( array( 'ok' => true, 'id' => $id ) );
}
}
if ( $action === 'organizer-delete' ) {
$id = (int) ( $_POST['id'] ?? 0 );
if ( $id <= 0 ) ev_fail( 'not_found' );
$used = $db->rawQueryOne( "SELECT COUNT(*) c FROM pacim_events WHERE event_calendar_organizer = ?", array( $id ) );
if ( $used && (int) $used['c'] > 0 ) ev_fail( 'Veranstalter ist ' . (int) $used['c'] . ' Event(s) zugeordnet. Bitte zuerst neu zuordnen.' );
$db->where( 'organizer_id', $id );
$db->delete( 'pacim_organizers' );
ev_json( array( 'ok' => true ) );
}
/* =====================================================================
* ANREISE-INFOS (pacim_arrival_info)
* ===================================================================== */
/** Erlaubte Parkplatztypen + Labels (zentral, auch von der öffentlichen API genutzt). */
function ai_parking_types() {
return array(
'all' => 'Alle Parkplatztypen',
'indoor' => 'Hallenparkplatz',
'outdoor' => 'Außenparkplatz',
'valet' => 'Valet-Service',
);
}
function ai_parking_label( $t ) { $m = ai_parking_types(); return isset( $m[$t] ) ? $m[$t] : 'Alle Parkplatztypen'; }
function ai_norm_parking( $t ) { $t = strtolower( trim( (string) $t ) ); return array_key_exists( $t, ai_parking_types() ) ? $t : 'all'; }
/** Schiff-IDs (CSV) -> [ {id,title}, ... ] aus pacim_events_info. */
function ai_ship_list( $db, $csv ) {
$ids = array_values( array_filter( array_map( 'intval', preg_split( '/[\s,;]+/', (string) $csv ) ) ) );
if ( ! count( $ids ) ) return array();
$rows = $db->rawQuery( "SELECT event_id AS id, event_title AS title FROM pacim_events_info WHERE event_id IN (" . implode( ',', $ids ) . ") ORDER BY event_title" );
$out = array();
foreach ( (array) $rows as $r ) $out[] = array( 'id' => (int) $r['id'], 'title' => (string) $r['title'] );
return $out;
}
/** „HH:MM:SS"/null -> „HH:MM"/null. */
function ai_hm( $v ) { $v = trim( (string) $v ); return $v === '' ? null : substr( $v, 0, 5 ); }
/** Eingabe „HH:MM" -> „HH:MM:00" oder null. */
function ai_time_in( $v ) {
$v = trim( (string) $v );
return preg_match( '/^([01]?\d|2[0-3]):([0-5]\d)$/', $v ) ? ( ( strlen( $v ) === 4 ? '0' . $v : $v ) . ':00' ) : null;
}
/** Einen DB-Datensatz in die API-/UI-Struktur überführen. */
function ai_format( $db, $r ) {
$ships = ( (int) $r['arrival_info_all_ships'] === 1 ) ? array() : ai_ship_list( $db, $r['arrival_info_ship_ids'] );
$type = ai_norm_parking( $r['arrival_info_parking_type'] );
$noLimit = ( (int) $r['arrival_info_no_time_limit'] === 1 );
return array(
'id' => (int) $r['arrival_info_id'],
'title' => (string) $r['arrival_info_title'],
'content' => (string) $r['arrival_info_content'],
'ships' => array_map( function ( $s ) { return $s['title']; }, $ships ),
'ship_ids' => array_map( function ( $s ) { return $s['id']; }, $ships ),
'all_ships' => ( (int) $r['arrival_info_all_ships'] === 1 ),
'parking_type' => $type,
'parking_type_label' => ai_parking_label( $type ),
'time_from' => $noLimit ? null : ai_hm( $r['arrival_info_time_from'] ),
'time_to' => $noLimit ? null : ai_hm( $r['arrival_info_time_to'] ),
'no_time_limit' => $noLimit,
'priority' => (int) $r['arrival_info_priority'],
'active' => ( (int) $r['arrival_info_active'] === 1 ),
'updated' => $r['arrival_info_updated'],
'updated_fmt' => $r['arrival_info_updated'] ? date( 'd.m.Y H:i', strtotime( $r['arrival_info_updated'] ) ) : '',
);
}
if ( $action === 'arrival-ships' ) {
// Schiffe (Stammdaten) für Dropdowns – nur tatsächlich verwendete zuerst, dann alle.
$rows = $db->rawQuery( "SELECT event_id AS id, event_title AS title FROM pacim_events_info WHERE event_title <> '' ORDER BY event_title ASC" );
ev_json( array( 'ok' => true, 'ships' => $rows ? $rows : array(), 'parking_types' => ai_parking_types() ) );
}
if ( $action === 'arrival-infos' ) {
$q = trim( (string) ( $_GET['q'] ?? '' ) );
$ship = (int) ( $_GET['ship'] ?? 0 );
$parking = (string) ( $_GET['parking'] ?? 'all' );
$active = (string) ( $_GET['active'] ?? 'all' );
$tFrom = ai_time_in( $_GET['time_from'] ?? '' );
$tTo = ai_time_in( $_GET['time_to'] ?? '' );
$where = array( '1=1' ); $params = array();
if ( $q !== '' ) { $where[] = 'arrival_info_title LIKE ?'; $params[] = '%' . $q . '%'; }
if ( $parking !== '' && $parking !== 'any' && array_key_exists( $parking, ai_parking_types() ) ) { $where[] = 'arrival_info_parking_type = ?'; $params[] = $parking; }
if ( $active === 'active' ) $where[] = 'arrival_info_active = 1';
if ( $active === 'inactive' ) $where[] = 'arrival_info_active = 0';
if ( $ship > 0 ) { $where[] = '(arrival_info_all_ships = 1 OR FIND_IN_SET(?, arrival_info_ship_ids))'; $params[] = $ship; }
// Zeitraum-Filter (Überschneidung des Zeitfensters; „immer anzeigen" zählt immer mit)
if ( $tFrom !== null && $tTo !== null ) { $where[] = '(arrival_info_no_time_limit = 1 OR (arrival_info_time_from <= ? AND arrival_info_time_to >= ?))'; $params[] = $tTo; $params[] = $tFrom; }
elseif ( $tFrom !== null ) { $where[] = '(arrival_info_no_time_limit = 1 OR arrival_info_time_to >= ?)'; $params[] = $tFrom; }
elseif ( $tTo !== null ) { $where[] = '(arrival_info_no_time_limit = 1 OR arrival_info_time_from <= ?)'; $params[] = $tTo; }
$w = implode( ' AND ', $where );
$rows = $db->rawQuery( "SELECT * FROM pacim_arrival_info WHERE $w ORDER BY arrival_info_priority DESC, arrival_info_id DESC", $params );
$out = array();
foreach ( (array) $rows as $r ) $out[] = ai_format( $db, $r );
ev_json( array( 'ok' => true, 'items' => $out, 'total' => count( $out ) ) );
}
if ( $action === 'arrival-info' ) {
$id = (int) ( $_GET['id'] ?? 0 );
$r = $db->rawQueryOne( "SELECT * FROM pacim_arrival_info WHERE arrival_info_id = ?", array( $id ) );
if ( ! $r ) ev_fail( 'not_found' );
ev_json( array( 'ok' => true, 'item' => ai_format( $db, $r ) ) );
}
if ( $action === 'arrival-info-save' ) {
$id = (int) ( $_POST['id'] ?? 0 );
$title = trim( (string) ( $_POST['title'] ?? '' ) );
if ( $title === '' ) ev_fail( 'Bitte einen Titel angeben.' );
$allShips = ! empty( $_POST['all_ships'] ) ? 1 : 0;
$shipIds = '';
if ( ! $allShips ) {
$raw = $_POST['ship_ids'] ?? array();
if ( ! is_array( $raw ) ) $raw = preg_split( '/[\s,;]+/', (string) $raw );
$ids = array_values( array_unique( array_filter( array_map( 'intval', $raw ) ) ) );
if ( ! count( $ids ) ) ev_fail( 'Bitte mindestens ein Schiff wählen oder „Alle Schiffe" aktivieren.' );
$shipIds = implode( ',', $ids );
}
$type = ai_norm_parking( $_POST['parking_type'] ?? 'all' );
$noLimit = ! empty( $_POST['no_time_limit'] ) ? 1 : 0;
$tFrom = $noLimit ? null : ai_time_in( $_POST['time_from'] ?? '' );
$tTo = $noLimit ? null : ai_time_in( $_POST['time_to'] ?? '' );
if ( ! $noLimit && ( $tFrom === null || $tTo === null ) ) ev_fail( 'Bitte gültige Uhrzeiten (von/bis) angeben oder „Immer anzeigen" wählen.' );
if ( ! $noLimit && $tFrom !== null && $tTo !== null && $tTo < $tFrom ) ev_fail( '„Bis" muss nach „Von" liegen.' );
$now = date( 'Y-m-d H:i:s' );
$fields = array(
'arrival_info_title' => $title,
'arrival_info_content' => (string) ( $_POST['content'] ?? '' ), // HTML erlaubt (WYSIWYG)
'arrival_info_ship_ids' => $shipIds,
'arrival_info_all_ships' => $allShips,
'arrival_info_parking_type' => $type,
'arrival_info_all_parking' => ( $type === 'all' ? 1 : 0 ),
'arrival_info_time_from' => $tFrom,
'arrival_info_time_to' => $tTo,
'arrival_info_no_time_limit' => $noLimit,
'arrival_info_priority' => (int) ( $_POST['priority'] ?? 0 ),
'arrival_info_active' => ! empty( $_POST['active'] ) ? 1 : 0,
'arrival_info_updated' => $now,
);
if ( ! $id ) {
$fields['arrival_info_created'] = $now;
$newId = $db->insert( 'pacim_arrival_info', $fields );
if ( ! $newId ) ev_fail( 'Anreise-Info konnte nicht angelegt werden.' );
ev_json( array( 'ok' => true, 'id' => (int) $newId ) );
}
$db->where( 'arrival_info_id', $id );
$db->update( 'pacim_arrival_info', $fields );
ev_json( array( 'ok' => true, 'id' => $id ) );
}
if ( $action === 'arrival-info-toggle' ) {
$id = (int) ( $_POST['id'] ?? 0 );
$r = $db->rawQueryOne( "SELECT arrival_info_active FROM pacim_arrival_info WHERE arrival_info_id = ?", array( $id ) );
if ( ! $r ) ev_fail( 'not_found' );
$new = ( (int) $r['arrival_info_active'] === 1 ) ? 0 : 1;
$db->where( 'arrival_info_id', $id );
$db->update( 'pacim_arrival_info', array( 'arrival_info_active' => $new, 'arrival_info_updated' => date( 'Y-m-d H:i:s' ) ) );
ev_json( array( 'ok' => true, 'active' => ( $new === 1 ) ) );
}
if ( $action === 'arrival-info-duplicate' ) {
$id = (int) ( $_POST['id'] ?? 0 );
$r = $db->rawQueryOne( "SELECT * FROM pacim_arrival_info WHERE arrival_info_id = ?", array( $id ) );
if ( ! $r ) ev_fail( 'not_found' );
$now = date( 'Y-m-d H:i:s' );
unset( $r['arrival_info_id'] );
$r['arrival_info_title'] = $r['arrival_info_title'] . ' (Kopie)';
$r['arrival_info_active'] = 0; // Kopien zunächst inaktiv
$r['arrival_info_created'] = $now;
$r['arrival_info_updated'] = $now;
$newId = $db->insert( 'pacim_arrival_info', $r );
if ( ! $newId ) ev_fail( 'Kopie konnte nicht angelegt werden.' );
ev_json( array( 'ok' => true, 'id' => (int) $newId ) );
}
if ( $action === 'arrival-info-delete' ) {
$id = (int) ( $_POST['id'] ?? 0 );
if ( $id <= 0 ) ev_fail( 'not_found' );
$db->where( 'arrival_info_id', $id );
$db->delete( 'pacim_arrival_info' );
ev_json( array( 'ok' => true ) );
}
http_response_code( 400 );
ev_json( array( 'ok' => false, 'error' => 'unknown_action' ) );