<?php
/**
* Datei: stonebreaker-booking.php
* Version: 3.11 (Mit Food-Lock UND Modell-Bearbeitung)
* Funktion: Regelt Buchung, Stornierung und nachträgliche Modell-Änderungen.
*/

if ( ! defined( ‘ABSPATH’ ) ) { exit; }

class Stonebreaker_Booking_Manager {

public function __construct() {
add_action( ‘init’, array( $this, ‘register_form_post_type’ ) );
add_action( ‘add_meta_boxes’, array( $this, ‘add_custom_meta_boxes’ ) );
add_action( ‘save_post’, array( $this, ‘save_custom_meta_data’ ) );

add_filter( ‘manage_sb_formular_posts_columns’, array( $this, ‘set_form_columns’ ) );
add_action( ‘manage_sb_formular_posts_custom_column’, array( $this, ‘fill_form_columns’ ), 10, 2 );

add_action( ‘init’, array( $this, ‘handle_booking_submission’ ) );
add_action( ‘init’, array( $this, ‘handle_cancellation’ ) );
add_action( ‘init’, array( $this, ‘handle_guest_deletion’ ) );

// NEU WIEDER DA: Nachträgliches Bearbeiten der Modelle
add_action( ‘init’, array( $this, ‘handle_model_update’ ) );
}

// — CPT & BACKEND —
public function register_form_post_type() {
register_post_type( ‘sb_formular’, array(
‘labels’=>array(‘name’=>’Buchungsformulare’,’singular_name’=>’Formular’),
‘public’=>false,
‘show_ui’=>true,
‘show_in_menu’=>’stonebreaker-app’,
‘supports’=>array(‘title’)
));
}

public function add_custom_meta_boxes() {
add_meta_box( ‘sb_form_roles’, ‘1. Sichtbarkeit’, array( $this, ‘render_role_meta_box’ ), ‘sb_formular’, ‘normal’, ‘high’ );
add_meta_box( ‘sb_form_prices’, ‘2. Preise’, array( $this, ‘render_price_meta_box’ ), ‘sb_formular’, ‘normal’, ‘high’ );
add_meta_box( ‘sb_form_models’, ‘3. Modelle’, array( $this, ‘render_models_meta_box’ ), ‘sb_formular’, ‘normal’, ‘high’ );
add_meta_box( ‘sb_form_config’, ‘4. Einstellungen’, array( $this, ‘render_config_meta_box’ ), ‘sb_formular’, ‘side’, ‘default’ );
add_meta_box( ‘sb_booking_full_details’, ‘📋 Buchungs-Details’, array( $this, ‘render_booking_details_box’ ), ‘sb_buchung’, ‘normal’, ‘high’ );
}

public function render_booking_details_box($post) {
$data = get_post_meta($post->ID, ‘_sb_booking_data’, true);
$total = get_post_meta($post->ID, ‘_sb_total_price’, true);
$uid = get_post_meta($post->ID, ‘_sb_user_id’, true);
$eid = get_post_meta($post->ID, ‘_sb_event_id’, true);
$pay_status = get_post_meta($post->ID, ‘_sb_payment_status’, true);
$user = get_userdata($uid);

echo ‘<style>.sb-detail-table th { text-align:left; width:150px; vertical-align:top; padding:10px 0; border-bottom:1px solid #eee; } .sb-detail-table td { padding:10px 0; border-bottom:1px solid #eee; }</style>’;
echo ‘<table class=”sb-detail-table” style=”width:100%;”>’;
echo ‘<tr><th>👤 User:</th><td>’;
if($user) echo ‘<strong>’ . esc_html($user->display_name) . ‘</strong><br>’ . esc_html($user->user_email);
else echo ‘Gelöschter User (ID: ‘.$uid.’)’;
echo ‘</td></tr>’;
echo ‘<tr><th>📅 Event:</th><td><a href=”post.php?post=’.$eid.’&action=edit”>’.get_the_title($eid).'</a></td></tr>’;
echo ‘<tr><th>Zahlungsstatus:</th><td>’;
echo ($pay_status === ‘paid’) ? ‘<span style=”color:green;font-weight:bold;”>✔ Bezahlt</span>’ : ‘<span style=”color:red;”>Offen</span>’;
echo ‘</td></tr>’;
echo ‘<tr><th>👥 Teilnehmer:</th><td>’;
if(isset($data[‘user_participates’]) && $data[‘user_participates’]) echo ‘- User selbst<br>’;
if(!empty($data[‘guests’])) { foreach($data[‘guests’] as $g) echo ‘- Gast: ‘ . esc_html($g) . ‘<br>’; }
echo ‘</td></tr>’;
echo ‘<tr><th>🍽️ Essen:</th><td>’;
if(!empty($data[‘days’])) {
foreach($data[‘days’] as $date => $info) {
echo ‘<strong>’ . date_i18n(‘d.m.Y (l)’, strtotime($date)) . ‘:</strong> ‘;
if(empty($info[‘guests_eating’])) echo ‘<span style=”color:#999;”>Kein Essen</span>’;
else echo ‘<span style=”color:green;”>Essen für: ‘ . implode(‘, ‘, $info[‘guests_eating’]) . ‘</span>’;
echo ‘<br>’;
}
}
echo ‘</td></tr>’;
echo ‘<tr><th>🚜 Fahrzeuge:</th><td>’;
if(!empty($data[‘models’])) {
echo ‘<strong>Modelle:</strong><br>’;
foreach($data[‘models’] as $m) echo ‘- ‘ . esc_html($m[‘desc’]) . ‘<br>’;
} else { echo ‘- Keine Modelle<br>’; }
if(!empty($data[‘trailers’])) {
echo ‘<br><strong>Anhänger:</strong><br>’;
foreach($data[‘trailers’] as $t) echo ‘- ‘ . esc_html($t[‘desc’]) . ‘<br>’;
}
echo ‘</td></tr>’;
echo ‘<tr><th>💰 Gesamtpreis:</th><td><strong style=”font-size:1.2em;”>’.number_format((float)$total, 2, ‘,’, ‘.’).’ €</strong></td></tr>’;
echo ‘</table>’;
}

public function render_role_meta_box($p){ global $wp_roles; $all=$wp_roles->get_names(); $s=get_post_meta($p->ID,’_sb_allowed_roles’,true)?:[]; echo ‘<div style=”max-height:100px;overflow-y:auto;”>’; foreach($all as $k=>$n){ $c=in_array($k,$s)?’checked’:”; echo “<label style=’display:block’><input type=’checkbox’ name=’sb_allowed_roles[]’ value=’$k’ $c> $n</label>”; } echo ‘</div>’; }
public function render_price_meta_box($p){ $prices=get_posts(array(‘post_type’=>’sb_preis’,’numberposts’=>-1,’orderby’=>’title’,’order’=>’ASC’)); usort($prices, function($a,$b){ $da=(int)get_post_meta($a->ID,’_sb_day_index’,true)?:1; $db=(int)get_post_meta($b->ID,’_sb_day_index’,true)?:1; return ($da==$db)?0:($da<$db?-1:1); }); $s=get_post_meta($p->ID,’_sb_allowed_prices’,true)?:[]; echo ‘<div style=”max-height:150px;overflow-y:auto;”>’; foreach($prices as $pr){ $d=get_post_meta($pr->ID,’_sb_day_index’,true)?:1; $v=get_post_meta($pr->ID,’_sb_price_value’,true); $c=in_array($pr->ID,$s)?’checked’:”; echo “<label style=’display:block;border-bottom:1px solid #eee;padding:2px;’><input type=’checkbox’ name=’sb_allowed_prices[]’ value='{$pr->ID}’ $c> <strong>Tag $d</strong>: {$pr->post_title} ($v €)</label>”; } echo ‘</div>’; }
public function render_models_meta_box($p){ $sm=get_post_meta($p->ID,’_sb_allowed_models’,true)?:[]; $st=get_post_meta($p->ID,’_sb_allowed_trailers’,true)?:[]; $cats=get_posts(array(‘post_type’=>’sb_modell_kat’,’numberposts’=>-1)); $tr=get_posts(array(‘post_type’=>’sb_anhaenger’,’numberposts’=>-1)); echo ‘<div style=”display:flex;gap:20px;”><div style=”flex:1;”><strong>Modell-Kategorien:</strong><br><div style=”max-height:150px;overflow-y:auto;border:1px solid #ddd;padding:5px;”>’; foreach($cats as $c){ $chk=in_array($c->ID,$sm)?’checked’:”; echo “<label style=’display:block’><input type=’checkbox’ name=’sb_allowed_models[]’ value='{$c->ID}’ $chk> {$c->post_title}</label>”; } echo ‘</div></div><div style=”flex:1;”><strong>Anhänger:</strong><br><div style=”max-height:150px;overflow-y:auto;border:1px solid #ddd;padding:5px;”>’; foreach($tr as $t){ $chk=in_array($t->ID,$st)?’checked’:”; echo “<label style=’display:block’><input type=’checkbox’ name=’sb_allowed_trailers[]’ value='{$t->ID}’ $chk> {$t->post_title}</label>”; } echo ‘</div></div></div>’; }
public function render_config_meta_box($p){ $af=get_post_meta($p->ID,’_sb_allow_family’,true); echo “<label><input type=’checkbox’ name=’sb_allow_family’ value=’1′ “.checked($af,’1′,false).”> <strong>Familie / Begleitung erlauben?</strong></label>”; }
public function save_custom_meta_data($pid){ $fields=[‘sb_allowed_roles’,’sb_allowed_prices’,’sb_allowed_models’,’sb_allowed_trailers’]; foreach($fields as $f){ if(isset($_POST[$f])) update_post_meta($pid,’_’.$f,$_POST[$f]); else delete_post_meta($pid,’_’.$f); } if(isset($_POST[‘sb_allow_family’])) update_post_meta($pid,’_sb_allow_family’,’1′); else delete_post_meta($pid,’_sb_allow_family’); }
public function set_form_columns($c){ return array_merge($c,[‘roles’=>’Rollen’]); }
public function fill_form_columns($c,$id){ if($c==’roles’) echo implode(‘, ‘,get_post_meta($id,’_sb_allowed_roles’,true)?:[]); }

private function get_event_stats($eid){$l=(int)get_post_meta($eid,’_sb_limit_total’,true);$bs=get_posts(array(‘post_type’=>’sb_buchung’,’post_status’=>’publish’,’meta_key’=>’_sb_event_id’,’meta_value’=>$eid,’posts_per_page’=>-1,’fields’=>’ids’));$cnt=0; foreach($bs as $b){ $d=get_post_meta($b,’_sb_booking_data’,true); $g=isset($d[‘guests’])?count($d[‘guests’]):0; $u=isset($d[‘user_participates’])?$d[‘user_participates’]:true; $cnt+=($g+($u?1:0)); }return array(‘limit’=>$l,’booked’=>$cnt);}
private function has_user_booked_event($uid,$eid){ return !empty(get_posts(array(‘post_type’=>’sb_buchung’,’post_status’=>array(‘publish’,’pending’),’author’=>$uid,’meta_key’=>’_sb_event_id’,’meta_value’=>$eid,’fields’=>’ids’))); }

// — NEU: Handler für Modell-Updates —
public function handle_model_update() {
if ( isset($_POST[‘sb_action’]) && $_POST[‘sb_action’] === ‘update_booking_models’ ) {

if ( ! wp_verify_nonce( $_POST[‘sb_update_nonce’], ‘sb_update_models_action’ ) ) wp_die(‘Sicherheitsfehler. Bitte neu laden.’);
if ( ! is_user_logged_in() ) wp_die(‘Bitte einloggen.’);

$booking_id = (int) $_POST[‘sb_booking_id’];
$booking = get_post($booking_id);

// Prüfung: Gehört die Buchung dem User?
if ( ! $booking || $booking->post_author != get_current_user_id() ) wp_die(‘Nicht erlaubt.’);

$eid = get_post_meta($booking_id, ‘_sb_event_id’, true);
$data = get_post_meta($booking_id, ‘_sb_booking_data’, true);

// Limit prüfen (Total Persons aus gespeicherter Buchung)
$total_persons = 0;
if(isset($data[‘user_participates’]) && $data[‘user_participates’]) $total_persons++;
if(isset($data[‘guests’])) $total_persons += count($data[‘guests’]);

$model_limit = (int) get_post_meta($eid, ‘_sb_limit_models_per_person’, true);

if ( $model_limit > 0 ) {
$max = $model_limit * $total_persons;
$cnt = 0;
if(isset($_POST[‘sb_fleet_models’])) $cnt += count($_POST[‘sb_fleet_models’]);
if(isset($_POST[‘sb_models’])) foreach($_POST[‘sb_models’] as $d) { if(!empty(trim($d))) $cnt++; }

if($cnt > $max) wp_die(“Limit überschritten: Du darfst maximal $max Fahrzeuge mitbringen.”);
}

// Modelle sammeln
$saved_models=[];
$saved_trailers=[];

if(isset($_POST[‘sb_fleet_models’])) foreach($_POST[‘sb_fleet_models’] as $vid) $saved_models[]=array(‘cat_id’=>get_post_meta($vid,’_sb_vehicle_cat_id’,true),’desc’=>get_the_title($vid));
if(isset($_POST[‘sb_fleet_trailers’])) foreach($_POST[‘sb_fleet_trailers’] as $vid) $saved_trailers[]=array(‘trailer_id’=>get_post_meta($vid,’_sb_vehicle_cat_id’,true),’desc’=>get_the_title($vid));
if(isset($_POST[‘sb_models’])) foreach($_POST[‘sb_models’] as $cid=>$desc){ if(!empty(trim($desc)))$saved_models[]=array(‘cat_id’=>$cid,’desc’=>sanitize_text_field($desc)); }
if(isset($_POST[‘sb_trailers’])) foreach($_POST[‘sb_trailers’] as $tid=>$desc){ if(!empty(trim($desc)))$saved_trailers[]=array(‘trailer_id’=>$tid,’desc’=>sanitize_text_field($desc)); }

// Update Meta
$data[‘models’] = $saved_models;
$data[‘trailers’] = $saved_trailers;

update_post_meta($booking_id, ‘_sb_booking_data’, $data);

// Redirect zur History
wp_redirect(add_query_arg(array(‘tab’=>’history’, ‘updated’=>’models’), remove_query_arg(‘sb_action’))); exit;
}
}

public function handle_guest_deletion() {
if ( isset($_POST[‘sb_action’]) && $_POST[‘sb_action’] === ‘delete_saved_guest’ ) {
if ( ! isset($_POST[‘sb_delete_nonce’]) || ! wp_verify_nonce( $_POST[‘sb_delete_nonce’], ‘sb_delete_guest_action’ ) ) { wp_send_json(array(‘status’ => ‘error’, ‘message’ => ‘Sicherheitsfehler.’)); exit; }
if ( ! is_user_logged_in() ) exit;
$guest_name = sanitize_text_field($_POST[‘guest_name’]); $uid = get_current_user_id();
$saved_family = get_user_meta($uid, ‘_sb_saved_family’, true); if ( ! is_array($saved_family) ) $saved_family = [];
$key = array_search($guest_name, $saved_family);
if ( $key !== false ) { unset($saved_family[$key]); update_user_meta($uid, ‘_sb_saved_family’, array_values($saved_family)); wp_send_json(array(‘status’ => ‘success’)); } else { wp_send_json(array(‘status’ => ‘error’, ‘message’ => ‘Gast nicht gefunden.’)); } exit;
}
}

public function handle_booking_submission() {
if(!isset($_POST[‘sb_action’])||$_POST[‘sb_action’]!==’submit_booking’) return;
if(!wp_verify_nonce($_POST[‘sb_booking_nonce’],’sb_book_ride’)) wp_die(‘Security Error’);

$uid=get_current_user_id(); $fid=(int)$_POST[‘sb_form_id’]; $eid=(int)$_POST[‘sb_fahrtag_id’];
if($this->has_user_booked_event($uid,$eid)) wp_die(‘Bereits gebucht.’);

$guests=isset($_POST[‘sb_guests’])?array_map(‘sanitize_text_field’,$_POST[‘sb_guests’]):[];
$user_participates=isset($_POST[‘sb_user_participates’])&&$_POST[‘sb_user_participates’]==’1′;
$total_persons=count($guests)+($user_participates?1:0);
if($total_persons===0) wp_die(‘Mindestens 1 Teilnehmer erforderlich.’);

$model_limit = (int) get_post_meta($eid, ‘_sb_limit_models_per_person’, true);
if ( $model_limit > 0 ) {
$max = $model_limit * $total_persons;
$cnt = 0;
if(isset($_POST[‘sb_fleet_models’])) $cnt += count($_POST[‘sb_fleet_models’]);
if(isset($_POST[‘sb_models’])) foreach($_POST[‘sb_models’] as $d) { if(!empty(trim($d))) $cnt++; }
if($cnt > $max) wp_die(“Zu viele Modelle. Erlaubt: $max.”);
}

$stats = $this->get_event_stats($eid);
$is_full = ($stats[‘booked’] + $total_persons) > $stats[‘limit’];
$post_status = $is_full ? ‘pending’ : ‘publish’;

$food_deadline_days = (int) get_post_meta($eid, ‘_sb_food_deadline’, true);
$days_input = isset($_POST[‘days’]) ? $_POST[‘days’] : [];
$today = new DateTime(); $today->setTime(0,0,0);

$event_start = get_post_meta($eid, ‘_sb_start_date’, true);
$p_ids = get_post_meta($fid, ‘_sb_allowed_prices’, true) ?: [];
$raw_prices = get_posts(array(‘post_type’=>’sb_preis’,’include’=>$p_ids,’posts_per_page’=>-1));
$price_map = []; foreach($raw_prices as $rp){ $ix=(int)get_post_meta($rp->ID,’_sb_day_index’,true)?:1; $price_map[$ix]=array(‘ticket’=>(float)get_post_meta($rp->ID,’_sb_price_value’,true),’food’=>(float)get_post_meta($rp->ID,’_sb_food_price’,true)); }
$getPrice = function($d)use($price_map){ $ks=array_keys($price_map); sort($ks); $b=$ks[0]; foreach($ks as $k){ if($k<=$d)$b=$k; } return $price_map[$b]; };

$total_sum=0; $bd=[]; $saved_models=[]; $saved_trailers=[];
if(isset($_POST[‘sb_fleet_models’])) foreach($_POST[‘sb_fleet_models’] as $vid) $saved_models[]=array(‘cat_id’=>get_post_meta($vid,’_sb_vehicle_cat_id’,true),’desc’=>get_the_title($vid));
if(isset($_POST[‘sb_fleet_trailers’])) foreach($_POST[‘sb_fleet_trailers’] as $vid) $saved_trailers[]=array(‘trailer_id’=>get_post_meta($vid,’_sb_vehicle_cat_id’,true),’desc’=>get_the_title($vid));
if(isset($_POST[‘sb_models’])) foreach($_POST[‘sb_models’] as $cid=>$desc){ if(!empty(trim($desc)))$saved_models[]=array(‘cat_id’=>$cid,’desc’=>sanitize_text_field($desc)); }
if(isset($_POST[‘sb_trailers’])) foreach($_POST[‘sb_trailers’] as $tid=>$desc){ if(!empty(trim($desc)))$saved_trailers[]=array(‘trailer_id’=>$tid,’desc’=>sanitize_text_field($desc)); }

if(!empty($guests)){ $sf=get_user_meta($uid,’_sb_saved_family’,true); if(!is_array($sf))$sf=[]; foreach($guests as $g){ if(!in_array($g,$sf))$sf[]=$g; } update_user_meta($uid,’_sb_saved_family’,$sf); }

foreach($days_input as $dateStr => $dayData) {
if(!isset($dayData[‘selected’])) continue;
$target_dt = new DateTime($dateStr); $target_dt->setTime(0,0,0);
$ev_start_obj = new DateTime(get_post_meta($eid, ‘_sb_start_date’, true));
$diff = $ev_start_obj->diff($target_dt);
$day_index = $diff->days + 1;
$prices = $getPrice($day_index);
$day_ticket_sum = $prices[‘ticket’] * $total_persons;

$eaters = isset($dayData[‘food_names’]) ? $dayData[‘food_names’] : array();
if ( !empty($eaters) && $food_deadline_days > 0 ) {
$interval = $today->diff($target_dt);
$days_until = $interval->invert ? -$interval->days : $interval->days;
if ( $days_until < $food_deadline_days ) $eaters = [];
}
$day_food_sum = count($eaters) * $prices[‘food’];
$total_sum += ($day_ticket_sum + $day_food_sum);
$bd[$dateStr] = array(‘day_index’=>$day_index, ‘guests_eating’=>$eaters);
}

$title_prefix = $is_full ? ‘Warteliste: ‘ : ‘Buchung ‘;
$pid=wp_insert_post(array(‘post_title’=>$title_prefix.wp_get_current_user()->display_name,’post_type’=>’sb_buchung’,’post_status’=>$post_status,’post_author’=>$uid));

if($pid){
update_post_meta($pid,’_sb_user_id’,$uid);
update_post_meta($pid,’_sb_form_id’,$fid);
update_post_meta($pid,’_sb_event_id’,$eid);
update_post_meta($pid,’_sb_total_price’,$total_sum);
update_post_meta($pid,’_sb_booking_data’,array(‘guests’=>$guests,’user_participates’=>$user_participates,’days’=>$bd,’models’=>$saved_models,’trailers’=>$saved_trailers));

do_action(‘sb_event_new_booking’, $pid, $post_status);

$param = $is_full ? ‘waitlist_success’ : ‘booking_success’;
wp_redirect(add_query_arg(array(‘tab’=>’history’, $param=>’1′),remove_query_arg(‘sb_action’))); exit;
}
}

public function handle_cancellation() {
if(isset($_POST[‘sb_action’])&&$_POST[‘sb_action’]===’cancel_booking’&&wp_verify_nonce($_POST[‘sb_cancel_nonce’],’sb_cancel_action’)){
$pid = (int)$_POST[‘sb_booking_id’];
do_action(‘sb_event_cancelled’, $pid);
wp_trash_post($pid);
wp_redirect(add_query_arg(array(‘tab’=>’history’,’cancel_success’=>’1′),remove_query_arg(‘sb_action’))); exit;
}
}

public function render_frontend_form() {
if(!is_user_logged_in()) return ‘<p>Bitte einloggen.</p>’;
$user=wp_get_current_user(); $roles=(array)$user->roles;
$forms=get_posts(array(‘post_type’=>’sb_formular’,’posts_per_page’=>-1));
$matched=null; foreach($forms as $f){ $al=get_post_meta($f->ID,’_sb_allowed_roles’,true)?:[]; if(array_intersect($roles,$al)){ $matched=$f; break; } }
if(!$matched) return ‘<div class=”sb-alert”>Kein Formular.</div>’;

$allow_family=get_post_meta($matched->ID,’_sb_allow_family’,true);
$allowed_model_cat_ids = get_post_meta($matched->ID, ‘_sb_allowed_models’, true) ?: [];
$allowed_trailer_cat_ids = get_post_meta($matched->ID, ‘_sb_allowed_trailers’, true) ?: [];

$my_vehicles = get_posts(array(‘post_type’=>’sb_user_vehicle’,’author’=>$user->ID,’numberposts’=>-1,’post_status’=>’publish’));
$fleet_has_items = !empty($my_vehicles);
$valid_fleet_models = []; $valid_fleet_trailers = [];
if($fleet_has_items) {
foreach($my_vehicles as $v) {
$cat = get_post_meta($v->ID, ‘_sb_vehicle_cat_id’, true); $type = get_post_meta($v->ID, ‘_sb_vehicle_type’, true);
if ( $type === ‘model’ && in_array($cat, $allowed_model_cat_ids) ) { $valid_fleet_models[get_the_title($cat)][] = $v; }
elseif ( $type === ‘trailer’ && in_array($cat, $allowed_trailer_cat_ids) ) { $valid_fleet_trailers[get_the_title($cat)][] = $v; }
}
}
$manual_models = !empty($allowed_model_cat_ids) ? get_posts(array(‘post_type’=>’sb_modell_kat’, ‘include’=>$allowed_model_cat_ids, ‘posts_per_page’=>-1)) : [];
$manual_trailers = !empty($allowed_trailer_cat_ids) ? get_posts(array(‘post_type’=>’sb_anhaenger’, ‘include’=>$allowed_trailer_cat_ids, ‘posts_per_page’=>-1)) : [];

$p_ids=get_post_meta($matched->ID,’_sb_allowed_prices’,true)?:[];
if(empty($p_ids)) return ‘<p>Fehler: Preise.</p>’;
$raw_prices=get_posts(array(‘post_type’=>’sb_preis’,’include’=>$p_ids,’posts_per_page’=>-1));
$price_map=[]; foreach($raw_prices as $rp){ $ix=(int)get_post_meta($rp->ID,’_sb_day_index’,true)?:1; $price_map[$ix]=array(‘ticket’=>(float)get_post_meta($rp->ID,’_sb_price_value’,true),’food’=>(float)get_post_meta($rp->ID,’_sb_food_price’,true)); }
ksort($price_map); $price_map_json=json_encode($price_map);

$saved_family=get_user_meta($user->ID,’_sb_saved_family’,true)?:[];
$booked_ids=[]; $ex=get_posts(array(‘post_type’=>’sb_buchung’,’author’=>$user->ID,’post_status’=>array(‘publish’,’pending’),’posts_per_page’=>-1,’fields’=>’ids’));
foreach($ex as $b){ $booked_ids[]=get_post_meta($b,’_sb_event_id’,true); }

$fahrtage=get_posts(array(‘post_type’=>’sb_fahrtag’,’posts_per_page’=>-1,’meta_key’=>’_sb_start_date’,’orderby’=>’meta_value’,’order’=>’ASC’,’meta_query’=>array(array(‘key’=>’_sb_start_date’,’value’=>date(‘Y-m-d’),’compare’=>’>=’))));
$grouped=[]; foreach($fahrtage as $ft){ $ts=get_the_terms($ft->ID,’sb_fahrtag_cat’); $gn=($ts&&!is_wp_error($ts))?$ts[0]->name:’Allgemein’; $grouped[$gn][]=$ft; }

ob_start();
?>
<div class=”sb-booking-container”>
<h3 style=”border-bottom:2px solid #eee; padding-bottom:10px; margin-bottom:20px;”><?php echo esc_html($matched->post_title); ?></h3>
<?php if(empty($fahrtage)): ?><p>Keine Termine.</p><?php else: ?>
<form method=”post” action=”” id=”sb-booking-form”>

<div class=”sb-section” style=”margin-bottom:20px;”>
<label style=”font-weight:bold;display:block;margin-bottom:5px;”>1. Event wählen:</label>
<select name=”sb_fahrtag_id” id=”sb_fahrtag_select” style=”width:100%;padding:10px;”>
<option value=””>– Wählen –</option>
<?php foreach($grouped as $gn=>$posts): ?>
<optgroup label=”<?php echo esc_attr($gn); ?>”>
<?php foreach($posts as $ft):
$s=get_post_meta($ft->ID,’_sb_start_date’,true); $e=get_post_meta($ft->ID,’_sb_end_date’,true);
$l=($e&&$e!=$s)?date(‘d.m.’,strtotime($s)).’-‘.date(‘d.m.Y’,strtotime($e)):date(‘d.m.Y’,strtotime($s));
$isb=in_array($ft->ID,$booked_ids); $st=$this->get_event_stats($ft->ID); $full=($st[‘booked’]>=$st[‘limit’]);
$mlimit=(int)get_post_meta($ft->ID,’_sb_limit_models_per_person’,true);
$fdeadline=(int)get_post_meta($ft->ID,’_sb_food_deadline’,true);
$dis = $isb ? ‘disabled’ : ”; $suf = $isb ? ‘ (Bereits gebucht)’ : ($full ? ‘ (Warteliste möglich)’ : ”);
?>
<option value=”<?php echo $ft->ID; ?>” <?php echo $dis; ?> data-start=”<?php echo $s; ?>” data-end=”<?php echo ($e?:$s); ?>” data-limit=”<?php echo $st[‘limit’]; ?>” data-booked=”<?php echo $st[‘booked’]; ?>” data-model-limit=”<?php echo $mlimit; ?>” data-food-deadline=”<?php echo $fdeadline; ?>”><?php echo esc_html($ft->post_title.’ (‘.$l.’)’.$suf); ?></option>
<?php endforeach; ?>
</optgroup>
<?php endforeach; ?>
</select>
<div id=”sb-waitlist-alert” style=”display:none;margin-top:10px;padding:10px;background:#fff3cd;color:#856404;border:1px solid #ffeeba;border-radius:4px;”><strong>Achtung:</strong> Ausgebucht. Du kannst dich auf die <strong>Warteliste</strong> setzen.</div>
<div id=”sb-availability-box” style=”display:none;margin-top:10px;padding:10px;background:#fff;border:1px solid #ddd;border-radius:4px;”>
<div style=”display:flex;justify-content:space-between;margin-bottom:5px;”><span style=”font-weight:bold;color:#555;”>Verfügbarkeit:</span><span id=”sb-availability-text” style=”font-weight:bold;”></span></div>
<div style=”background:#eee;height:12px;border-radius:6px;overflow:hidden;”><div id=”sb-availability-bar” style=”height:100%;width:0%;transition:width 0.5s ease;background:#4caf50;”></div></div>
</div>
</div>

<div class=”sb-section” style=”background:#f4f4f4;padding:15px;border-radius:5px;margin-bottom:20px;border-left:4px solid #0073aa;”>
<h4 style=”margin-top:0;”>2. Teilnehmer</h4>
<div style=”margin-bottom:10px;”><label style=”font-weight:bold;”><input type=”checkbox” name=”sb_user_participates” id=”sb_user_participates” value=”1″ checked style=”transform:scale(1.2);margin-right:5px;”> Ich nehme teil</label></div>
<?php if($allow_family===’1′): ?>
<div style=”display:flex;gap:10px;margin-bottom:10px;”><input type=”text” id=”sb-guest-name” placeholder=”Name Gast” style=”flex:1;”><button type=”button” id=”sb-add-guest-btn” class=”button”>Hinzufügen</button></div>
<?php if(!empty($saved_family)): ?><div style=”margin-bottom:10px;” id=”sb-saved-guest-wrapper”><small>Gespeichert:</small><br><?php foreach($saved_family as $fm): ?><span class=”sb-saved-guest-item” style=”display:inline-block; margin-right:5px; margin-bottom:5px; white-space:nowrap;”><button type=”button” onclick=”addSavedGuest(‘<?php echo esc_js($fm); ?>’)” style=”background:#e5e5e5;border:none;padding:5px 10px;border-radius:15px 0 0 15px;cursor:pointer; margin-right:0;”>+ <?php echo esc_html($fm); ?></button><button type=”button” onclick=”deleteSavedGuest(‘<?php echo esc_js($fm); ?>’, this)” style=”background:#ffdddd; color:red; border:none; padding:5px 8px; border-radius:0 15px 15px 0; cursor:pointer; font-weight:bold; margin-left:-2px;”>&times;</button></span><?php endforeach; ?></div><?php endif; ?>
<div id=”sb-guest-list”></div>
<?php endif; ?>
</div>

<div id=”sb-user-data” data-username=”<?php echo esc_attr($user->display_name); ?>”></div>
<div id=”sb-price-config” data-config='<?php echo esc_attr($price_map_json); ?>’ style=”display:none;”></div>

<div id=”sb-days-container” class=”sb-section” style=”display:none;border-top:2px solid #eee;padding-top:20px;”>
<h4 style=”margin-bottom:10px;”>3. Tage & Verpflegung</h4>
<div id=”sb-days-list”></div>
<div id=”sb-error-msg” style=”color:red;font-weight:bold;margin-top:10px;display:none;”>Bitte Teilnehmer wählen.</div>
</div>

<div class=”sb-section” id=”sb-models-section” style=”margin-top:20px;background:#fffcf5;padding:15px;border-radius:5px;border:1px solid #eee; display:none;”>
<h4 style=”margin-top:0;”>4. Fahrzeuge & Geräte</h4>
<?php if ( $fleet_has_items ): ?>
<p style=”font-size:0.9em;color:#666;” id=”sb-model-limit-msg”>Bitte wählen.</p>
<div style=”display:flex; flex-wrap:wrap; gap:20px;”>
<?php if(!empty($valid_fleet_models)): ?><div style=”flex:1; min-width:300px;”><h5>Meine Fahrzeuge</h5><?php foreach($valid_fleet_models as $cat_name => $vehicles): ?><strong style=”display:block; margin-top:5px; font-size:0.9em; color:#555;”><?php echo esc_html($cat_name); ?></strong><?php foreach($vehicles as $v): ?><label style=”display:block; margin-bottom:5px;”><input type=”checkbox” name=”sb_fleet_models[]” value=”<?php echo $v->ID; ?>” class=”sb-model-check sb-is-vehicle” style=”transform:scale(1.2); margin-right:5px;”> <?php echo esc_html($v->post_title); ?></label><?php endforeach; ?><?php endforeach; ?></div><?php endif; ?>
<?php if(!empty($valid_fleet_trailers)): ?><div style=”flex:1; min-width:300px;”><h5>Meine Anhänger</h5><?php foreach($valid_fleet_trailers as $cat_name => $vehicles): ?><strong style=”display:block; margin-top:5px; font-size:0.9em; color:#555;”><?php echo esc_html($cat_name); ?></strong><?php foreach($vehicles as $v): ?><label style=”display:block; margin-bottom:5px;”><input type=”checkbox” name=”sb_fleet_trailers[]” value=”<?php echo $v->ID; ?>” class=”sb-model-check” style=”transform:scale(1.2); margin-right:5px;”> <?php echo esc_html($v->post_title); ?></label><?php endforeach; ?><?php endforeach; ?></div><?php endif; ?>
</div>
<?php else: ?>
<div class=”sb-alert” style=”background:#e7f7fd; color:#004375; border:1px solid #bce8f1; padding:10px; margin-bottom:15px; border-radius:4px;”><span class=”dashicons dashicons-info”></span> Tipp: Lege deinen <a href=”?tab=fleet”>Fuhrpark</a> an!</div>
<div style=”display:flex; flex-wrap:wrap; gap:20px;”>
<?php if(!empty($manual_models)): ?><div style=”flex:1; min-width:300px;”><h5>Modelle</h5><?php foreach($manual_models as $m): ?><div style=”margin-bottom:8px; display:flex; align-items:center;”><input type=”checkbox” class=”sb-model-check sb-is-vehicle” style=”transform:scale(1.2); margin-right:10px;”><label style=”min-width:100px;”><?php echo esc_html($m->post_title); ?></label><input type=”text” name=”sb_models[<?php echo $m->ID; ?>]” placeholder=”Bezeichnung…” style=”flex:1; margin-left:10px;” disabled></div><?php endforeach; ?></div><?php endif; ?>
<?php if(!empty($manual_trailers)): ?><div style=”flex:1; min-width:300px;”><h5>Anhänger</h5><?php foreach($manual_trailers as $t): ?><div style=”margin-bottom:8px; display:flex; align-items:center;”><input type=”checkbox” class=”sb-model-check” style=”transform:scale(1.2); margin-right:10px;”><label style=”min-width:100px;”><?php echo esc_html($t->post_title); ?></label><input type=”text” name=”sb_trailers[<?php echo $t->ID; ?>]” placeholder=”Bezeichnung…” style=”flex:1; margin-left:10px;” disabled></div><?php endforeach; ?></div><?php endif; ?>
</div>
<?php endif; ?>
</div>

<input type=”hidden” name=”sb_action” value=”submit_booking”>
<input type=”hidden” name=”sb_form_id” value=”<?php echo $matched->ID; ?>”>
<?php wp_nonce_field(‘sb_book_ride’,’sb_booking_nonce’); ?>
<input type=”hidden” id=”sb_delete_nonce” value=”<?php echo wp_create_nonce(‘sb_delete_guest_action’); ?>”>

<div style=”margin-top:25px;text-align:right;”><button type=”submit” id=”sb-submit-btn” class=”button button-primary” style=”padding:12px 30px;font-size:1.2em;”>Kostenpflichtig buchen</button></div>
</form>

<script>
document.addEventListener(‘DOMContentLoaded’, function() {
const select=document.getElementById(‘sb_fahrtag_select’);
const daysContainer=document.getElementById(‘sb-days-container’);
const daysList=document.getElementById(‘sb-days-list’);
const availBox=document.getElementById(‘sb-availability-box’);
const availText=document.getElementById(‘sb-availability-text’);
const availBar=document.getElementById(‘sb-availability-bar’);
const waitlistAlert=document.getElementById(‘sb-waitlist-alert’);
const priceMap=JSON.parse(document.getElementById(‘sb-price-config’).getAttribute(‘data-config’));
const userName=document.getElementById(‘sb-user-data’).getAttribute(‘data-username’);
const userCheck=document.getElementById(‘sb_user_participates’);
const guestInput=document.getElementById(‘sb-guest-name’);
const addGuestBtn=document.getElementById(‘sb-add-guest-btn’);
const guestList=document.getElementById(‘sb-guest-list’);
const submitBtn=document.getElementById(‘sb-submit-btn’);
const errorMsg=document.getElementById(‘sb-error-msg’);
const modelsSection=document.getElementById(‘sb-models-section’);
const limitMsg=document.getElementById(‘sb-model-limit-msg’);
let guests=[];

document.querySelectorAll(‘.sb-model-check’).forEach(chk=>{
chk.addEventListener(‘change’,function(){
const inp=this.parentNode.querySelector(‘input[type=”text”]’);
if(inp){ inp.disabled=!this.checked; if(this.checked)inp.focus(); else inp.value=”; }
updateModelLimit();
});
});

if(guestList) renderGuestList();
if(userCheck) userCheck.addEventListener(‘change’, updateForm);
if(addGuestBtn){ addGuestBtn.addEventListener(‘click’,function(){ addGuest(guestInput.value); guestInput.value=”; }); }

window.addSavedGuest=function(n){ addGuest(n); }
window.deleteSavedGuest = function(name, btn) {
if(!confirm(‘Möchtest du “‘ + name + ‘” wirklich dauerhaft aus deiner Liste löschen?’)) return;
const formData = new FormData(); formData.append(‘sb_action’, ‘delete_saved_guest’);
formData.append(‘guest_name’, name);
formData.append(‘sb_delete_nonce’, document.getElementById(‘sb_delete_nonce’).value);
fetch(window.location.href, { method: ‘POST’, body: formData }).then(r => r.json()).then(res => {
if(res.status === ‘success’) { const span = btn.parentNode; span.parentNode.removeChild(span); }
else { alert(‘Fehler: ‘ + (res.message || ‘Unbekannt’)); }
}).catch(err => alert(‘Verbindungsfehler.’));
}

function addGuest(n){ n=n.trim(); if(!n)return; if(guests.includes(n)){ alert(‘Vorhanden’); return; } guests.push(n); renderGuestList(); updateForm(); }
function renderGuestList(){
guestList.innerHTML=”;
guests.forEach((n,i)=>{
const d=document.createElement(‘div’);
d.style.cssText=”padding:5px;border-bottom:1px solid #ddd;display:flex;justify-content:space-between;align-items:center;background:#fff;”;
d.innerHTML=`<span>${n}</span><input type=’hidden’ name=’sb_guests[]’ value=’${n}’><span style=’cursor:pointer;color:red;font-weight:bold;’ onclick=’removeGuest(${i})’>&times;</span>`;
guestList.appendChild(d);
});
}
window.removeGuest=function(i){ guests.splice(i,1); renderGuestList(); updateForm(); }

function getParticipantsCount(){ let c=0; if(userCheck&&userCheck.checked)c++; c+=guests.length; return c; }
function getParticipants() { let parts = []; if(userCheck && userCheck.checked) parts.push({name: userName + ‘ (Ich)’, value: ‘self’}); guests.forEach(g => parts.push({name: g, value: g})); return parts; }

function getPriceForDay(i){ if(priceMap[i])return priceMap[i]; var k=Object.keys(priceMap).map(Number).sort((a,b)=>a-b); var b=k[0]; for(var x=0;x<k.length;x++){ if(k[x]<=i)b=k[x]; } return priceMap[b]; }
function formatDate(d){ return d.toLocaleDateString(‘de-DE’,{weekday:’short’,day:’2-digit’,month:’2-digit’}); }
function getDates(s,e){ var a=[],c=new Date(s),st=new Date(e); while(c<=st){ a.push(new Date(c)); c.setDate(c.getDate()+1); } return a; }

function updateForm(){
const o=select.options[select.selectedIndex];
const pc=getParticipantsCount();
const participants=getParticipants();

if(pc===0){
daysList.innerHTML=”;
if(daysContainer.style.display!==’none’) errorMsg.style.display=’block’;
submitBtn.disabled=true;
if(o.value) updateAvailability(o); return;
} else { errorMsg.style.display=’none’; submitBtn.disabled=false; }

if(!o.value){
daysContainer.style.display=’none’;
availBox.style.display=’none’;
waitlistAlert.style.display=’none’;
if(modelsSection)modelsSection.style.display=’none’;
return;
}

updateAvailability(o);
if(modelsSection) { modelsSection.style.display=’block’; updateModelLimit(); }

const s=o.getAttribute(‘data-start’); const e=o.getAttribute(‘data-end’);
const foodDeadlineDays = parseInt(o.getAttribute(‘data-food-deadline’)) || 0;
const today = new Date(); today.setHours(0,0,0,0);

daysList.innerHTML=”;
const dates=getDates(s,e);

dates.forEach((d,i)=>{
var dn=i+1; var pr=getPriceForDay(dn); var bt=parseFloat(pr.ticket); var bf=parseFloat(pr.food);
var tt=bt*pc; var tStr=tt.toFixed(2).replace(‘.’,’,’); var fStr=bf.toFixed(2).replace(‘.’,’,’);
const dv=d.toISOString().split(‘T’)[0]; const dl=formatDate(d);

const r=document.createElement(‘div’);
r.style.cssText=”display:flex;flex-wrap:wrap;padding:15px 10px;border-bottom:1px solid #eee;”;

const l=document.createElement(‘div’); l.style.cssText=”flex:2;min-width:250px;”;
l.innerHTML=`<input type=”checkbox” name=”days[${dv}][selected]” id=”d_${i}” value=”1″ class=”sb-day-cb” style=”transform:scale(1.2);margin-right:10px;” checked><label for=”d_${i}” style=”font-weight:bold;font-size:1.1em;color:#333;”>${dl}</label><div style=”margin-left:28px;margin-top:5px;color:#0073aa;”><strong>Eintritt: ${tStr} €</strong><br><small>(${pc} Personen x ${pr.ticket}€)</small></div>`;

const rg=document.createElement(‘div’); rg.style.cssText=”flex:2;min-width:250px;”;
rg.className = ‘sb-food-box’;

if(bf>0){
const diffTime = d.getTime() – today.getTime();
const diffDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24));
if(foodDeadlineDays > 0 && diffDays < foodDeadlineDays) {
rg.innerHTML=`<div style=”background:#f8d7da; color:#721c24; padding:8px; border-radius:4px; font-size:0.9em;”>Anmeldeschluss für Essen vorbei.</div>`;
} else {
let fh=`<div style=”background:#fafafa;padding:10px;border:1px solid #ddd;border-radius:4px;”><div style=”font-weight:bold;margin-bottom:5px;”>Essen (+${fStr} € p.P.):</div>`;
participants.forEach(p=>{
let val=(p.value===’self’)?userName:p.value;
fh+=`<label style=”display:block;cursor:pointer;”><input type=”checkbox” name=”days[${dv}][food_names][]” value=”${val}”> ${p.name}</label>`;
});
fh+=`</div>`;
rg.innerHTML=fh;
}
} else {
rg.innerHTML=`<span style=”color:#ccc;font-style:italic;”>Keine Verpflegung.</span>`;
}

r.appendChild(l); r.appendChild(rg);
daysList.appendChild(r);
});
daysContainer.style.display=’block’;
}

daysList.addEventListener(‘change’, function(e) {
if (e.target.classList.contains(‘sb-day-cb’)) {
const row = e.target.closest(‘div’).parentNode;
const foodBox = row.querySelector(‘.sb-food-box’);
if (foodBox) {
const foodInputs = foodBox.querySelectorAll(‘input’);
foodInputs.forEach(fi => {
fi.disabled = !e.target.checked;
if (!e.target.checked) fi.checked = false;
});
foodBox.style.opacity = e.target.checked ? ‘1’ : ‘0.5’;
}
}
});

function updateModelLimit() {
if(!limitMsg) return;
const o=select.options[select.selectedIndex]; if(!o||!o.value)return;
const lpp = parseInt(o.getAttribute(‘data-model-limit’)) || 0;
const pc = getParticipantsCount();
const vChecks = document.querySelectorAll(‘.sb-is-vehicle’);
const cCount = Array.from(vChecks).filter(c => c.checked).length;

if(lpp > 0) {
const tl = lpp * pc;
limitMsg.textContent = `Erlaubt: ${tl} Fahrzeuge (${lpp} pro Person x ${pc} Personen). Gewählt: ${cCount}`;
limitMsg.style.color = (cCount > tl) ? ‘red’ : ‘#0073aa’;
vChecks.forEach(chk => {
if(!chk.checked) {
chk.disabled = (cCount >= tl);
const inp=chk.parentNode.querySelector(‘input[type=”text”]’);
if(inp && chk.disabled) inp.disabled=true;
} else { chk.disabled = false; }
});
} else {
limitMsg.textContent = “Kein Limit für Fahrzeuge.”;
vChecks.forEach(c => c.disabled = false);
}
}

function updateAvailability(o) {
const l=parseInt(o.getAttribute(‘data-limit’))||0;
const b=parseInt(o.getAttribute(‘data-booked’))||0;
const f=l-b;
const p=l>0?(b/l)*100:0;

if(f<=0){
submitBtn.textContent=’Auf Warteliste setzen’; submitBtn.style.backgroundColor=’#ffc107′;
submitBtn.style.color=’#333′; submitBtn.style.borderColor=’#e0a800′;
waitlistAlert.style.display=’block’;
}else {
submitBtn.textContent=’Kostenpflichtig buchen’; submitBtn.style.backgroundColor=”;
submitBtn.style.color=”; submitBtn.style.borderColor=”;
waitlistAlert.style.display=’none’;
}
availText.textContent=f+’ von ‘+l+’ Plätzen frei’;
availBar.style.width=p+’%’;
if(p<50) availBar.style.backgroundColor = ‘#4caf50’;
else if(p<90) availBar.style.backgroundColor = ‘#ff9800’;
else availBar.style.backgroundColor = ‘#f44336′;
availBox.style.display=’block’;
}

select.addEventListener(‘change’, updateForm);
});
</script>
<?php endif; ?>
</div>
<?php
return ob_get_clean();
}
}

global $stonebreaker_booking_manager;
$stonebreaker_booking_manager = new Stonebreaker_Booking_Manager();

This entry was posted in Allgemein. Bookmark the permalink.

Comments are closed.