Berichte

Hello world!

Welcome to WordPress. This is your first post. Edit or delete it, then start writing!

Posted in Uncategorized | 1 Comment

<?php
/**
* Datei: stonebreaker-pwa.php
* Version: 1.7 (Mit Auto-Redirect für eingeloggte User)
* Beschreibung: PWA Management inkl. Weiterleitung beim App-Start.
*/

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

class Stonebreaker_PWA_Manager {

public function __construct() {
add_action( ‘admin_menu’, array( $this, ‘add_admin_menu’ ) );
add_action( ‘admin_init’, array( $this, ‘register_settings’ ) );

add_action( ‘wp_head’, array( $this, ‘add_pwa_headers’ ) );
add_action( ‘wp_footer’, array( $this, ‘register_service_worker’ ) );
add_action( ‘wp_footer’, array( $this, ‘render_login_popup’ ), 100 );

add_action( ‘init’, array( $this, ‘serve_dynamic_files’ ) );

// NEU: Weiterleitung beim App-Start, wenn schon eingeloggt
add_action( ‘template_redirect’, array( $this, ‘handle_already_logged_in’ ) );
}

// — NEU: AUTO-REDIRECT BEIM ÖFFNEN —
public function handle_already_logged_in() {
// Wir prüfen: Kommt der Aufruf von der App (Start-Icon)?
if ( isset($_GET[‘app_mode’]) && $_GET[‘app_mode’] === ‘true’ ) {

// Ist der User schon eingeloggt?
if ( is_user_logged_in() ) {

// Wohin sollen mobile User?
$mobile_target = get_option(‘sb_pwa_redirect_mobile’);

// Wenn ein Ziel definiert ist -> Sofort hin da!
if ( ! empty($mobile_target) ) {
wp_redirect( $mobile_target );
exit;
}
}
}
}

// — 1. BACKEND EINSTELLUNGEN —
public function add_admin_menu() {
add_submenu_page(‘stonebreaker-app’,’App Einstellungen (PWA)’,’App / PWA’,’manage_options’,’stonebreaker-pwa’,array( $this, ‘render_settings_page’ ));
}

public function register_settings() {
register_setting( ‘sb_pwa_group’, ‘sb_pwa_name’ );
register_setting( ‘sb_pwa_group’, ‘sb_pwa_short_name’ );
register_setting( ‘sb_pwa_group’, ‘sb_pwa_icon_url’ );
register_setting( ‘sb_pwa_group’, ‘sb_pwa_theme_color’ );
register_setting( ‘sb_pwa_group’, ‘sb_pwa_bg_color’ );
register_setting( ‘sb_pwa_group’, ‘sb_pwa_bg_image’ );
register_setting( ‘sb_pwa_group’, ‘sb_pwa_redirect_mobile’ );
register_setting( ‘sb_pwa_group’, ‘sb_pwa_redirect_desktop’ );
}

public function render_settings_page() {
?>
<div class=”wrap”>
<h1>App & PWA Einstellungen</h1>
<form method=”post” action=”options.php”>
<?php settings_fields( ‘sb_pwa_group’ ); ?>
<table class=”form-table”>
<tr><th>App Name (Lang)</th><td><input type=”text” name=”sb_pwa_name” value=”<?php echo esc_attr(get_option(‘sb_pwa_name’, get_bloginfo(‘name’))); ?>” class=”regular-text”></td></tr>
<tr><th>App Name (Kurz)</th><td><input type=”text” name=”sb_pwa_short_name” value=”<?php echo esc_attr(get_option(‘sb_pwa_short_name’, ‘Stonebreaker’)); ?>” class=”regular-text”></td></tr>
<tr><th>App Icon URL</th><td><input type=”text” name=”sb_pwa_icon_url” value=”<?php echo esc_attr(get_option(‘sb_pwa_icon_url’)); ?>” class=”large-text” placeholder=”https://…”><p class=”description”>Pfad zu einem PNG-Bild (mind. 512×512 Pixel). Wird für das Homescreen-Symbol genutzt.</p></td></tr>
<tr><th>Login Hintergrundbild</th><td><input type=”text” name=”sb_pwa_bg_image” value=”<?php echo esc_attr(get_option(‘sb_pwa_bg_image’)); ?>” class=”large-text”><p class=”description”>Hintergrundbild nur für das Login-Popup.</p></td></tr>

<tr style=”background:#e0f7fa;”>
<th>Weiterleitung (Mobil / App)</th>
<td>
<input type=”text” name=”sb_pwa_redirect_mobile” value=”<?php echo esc_attr(get_option(‘sb_pwa_redirect_mobile’)); ?>” class=”large-text” placeholder=”https://deine-seite.de/dashboard/”>
<p class=”description”><strong>Wichtig:</strong> Hierhin werden App-Nutzer geleitet (nach Login UND beim Öffnen der App).</p>
</td>
</tr>
<tr style=”background:#e0f7fa;”>
<th>Weiterleitung (PC / Desktop)</th>
<td>
<input type=”text” name=”sb_pwa_redirect_desktop” value=”<?php echo esc_attr(get_option(‘sb_pwa_redirect_desktop’)); ?>” class=”large-text” placeholder=”https://deine-seite.de/”>
<p class=”description”>Ziel für normale Desktop-Computer.</p>
</td>
</tr>

<tr>
<th>Design Farben</th>
<td>
<label>Theme Color: <input type=”color” name=”sb_pwa_theme_color” value=”<?php echo esc_attr(get_option(‘sb_pwa_theme_color’, ‘#0073aa’)); ?>”></label><br><br>
<label>Ladescreen Farbe: <input type=”color” name=”sb_pwa_bg_color” value=”<?php echo esc_attr(get_option(‘sb_pwa_bg_color’, ‘#ffffff’)); ?>”></label>
</td>
</tr>
</table>
<?php submit_button(); ?>
</form>
</div>
<?php
}

// — 2. FRONTEND HEADER —
public function add_pwa_headers() {
$manifest_url = site_url(‘/?sb_pwa=manifest’);
$theme_color = get_option(‘sb_pwa_theme_color’, ‘#0073aa’);
$icon = get_option(‘sb_pwa_icon_url’);

echo ‘<link rel=”manifest” href=”‘ . esc_url($manifest_url) . ‘”>’ . “\n”;
echo ‘<meta name=”theme-color” content=”‘ . esc_attr($theme_color) . ‘”>’ . “\n”;
echo ‘<meta name=”apple-mobile-web-app-capable” content=”yes”>’ . “\n”;
echo ‘<meta name=”apple-mobile-web-app-status-bar-style” content=”black-translucent”>’ . “\n”;
if($icon) echo ‘<link rel=”apple-touch-icon” href=”‘ . esc_url($icon) . ‘”>’ . “\n”;
}

// — 3. LOGIN POPUP —
public function render_login_popup() {
if ( is_user_logged_in() ) return;

$bg_image = get_option(‘sb_pwa_bg_image’);

// Logik: Welches Gerät?
if ( wp_is_mobile() ) {
$redirect_to = get_option(‘sb_pwa_redirect_mobile’) ?: home_url();
} else {
$redirect_to = get_option(‘sb_pwa_redirect_desktop’) ?: home_url();
}

// App Mode Prüfung
$is_app_mode_php = (isset($_GET[‘app_mode’]) && $_GET[‘app_mode’] == ‘true’);

?>
<style>
#sb-pwa-login-overlay {
position: fixed; top: 0; left: 0; width: 100%; height: 100%; z-index: 999999;
background-color: #f0f0f0;
<?php if($bg_image): ?>
background: url(‘<?php echo esc_url($bg_image); ?>’) no-repeat center center fixed;
background-size: cover;
<?php endif; ?>
justify-content: center; align-items: center;
backdrop-filter: blur(4px);
display: none;
}
.sb-login-box {
background: rgba(255, 255, 255, 0.98); padding: 60px 50px; border-radius: 12px;
box-shadow: 0 25px 60px rgba(0,0,0,0.5); width: 95%; max-width: 800px; text-align: center;
animation: sbFadeIn 0.4s ease-out;
}
@keyframes sbFadeIn { from { opacity: 0; transform: scale(0.95); } to { opacity: 1; transform: scale(1); } }
.sb-login-box h2 { margin-top: 0; color: #333; font-size: 36px; margin-bottom: 15px; }
.sb-login-box p { color: #666; margin-bottom: 40px; font-size: 20px; }
.sb-login-box .login-username, .sb-login-box .login-password { margin-bottom: 25px; text-align: left; }
.sb-login-box label { font-weight: bold; font-size: 18px; color: #333; display: block; margin-bottom: 10px; }
.sb-login-box input[type=”text”], .sb-login-box input[type=”password”] { width: 100%; padding: 18px; border: 1px solid #ccc; border-radius: 8px; font-size: 20px; background:#fdfdfd; box-sizing: border-box; height: auto; }
.sb-login-box .login-submit { margin-top: 40px; }
.sb-login-box input[type=”submit”] { background: <?php echo get_option(‘sb_pwa_theme_color’, ‘#0073aa’); ?>; color: #fff; border: none; padding: 20px 0; width: 100%; border-radius: 8px; font-size: 24px; font-weight: bold; cursor: pointer; transition: opacity 0.2s; text-transform: uppercase; letter-spacing: 1px; }
.sb-login-box input[type=”submit”]:hover { opacity: 0.9; }
.sb-login-box .login-remember { margin-top: 25px; font-size: 16px; text-align: left; color:#555; }
.sb-login-box input[type=”checkbox”] { transform: scale(1.5); margin-right: 10px; }
</style>

<div id=”sb-pwa-login-overlay”>
<div class=”sb-login-box”>
<h2>Willkommen</h2>
<p>Bitte melde dich an.</p>
<?php
wp_login_form(array(
‘echo’ => true,
‘redirect’ => $redirect_to,
‘remember’ => true,
‘value_remember’ => true,
‘label_username’ => ‘Benutzername / E-Mail’,
‘label_password’ => ‘Passwort’
));
?>
</div>
</div>

<script>
document.addEventListener(“DOMContentLoaded”, function() {
var isStandalone = window.matchMedia(‘(display-mode: standalone)’).matches || window.navigator.standalone === true;
var isAppModeParam = window.location.search.includes(‘app_mode=true’);

if ( isStandalone || isAppModeParam ) {
var overlay = document.getElementById(‘sb-pwa-login-overlay’);
if(overlay) {
overlay.style.display = ‘flex’;
document.body.style.overflow = ‘hidden’;
}
}
});
</script>
<?php
}

// — 4. SERVICE WORKER —
public function register_service_worker() {
$sw_url = site_url(‘/?sb_pwa=sw’);
echo “<script>if(‘serviceWorker’ in navigator){window.addEventListener(‘load’,function(){navigator.serviceWorker.register(‘$sw_url’);});}</script>”;
}

// — 5. VIRTUELLE DATEIEN —
public function serve_dynamic_files() {
if ( ! isset( $_GET[‘sb_pwa’] ) ) return;
$mode = $_GET[‘sb_pwa’];

if ( $mode === ‘manifest’ ) {
header(‘Content-Type: application/json’);
$name = get_option(‘sb_pwa_name’, get_bloginfo(‘name’));
$short = get_option(‘sb_pwa_short_name’, ‘Stonebreaker’);
$theme = get_option(‘sb_pwa_theme_color’, ‘#0073aa’);
$bg = get_option(‘sb_pwa_bg_color’, ‘#ffffff’);
$icon_url = get_option(‘sb_pwa_icon_url’);
$start_url = site_url(‘/?app_mode=true’); // Hierhin startet die App immer

$manifest = array(
‘name’ => $name,
‘short_name’ => $short,
‘start_url’ => $start_url,
‘display’ => ‘standalone’,
‘background_color’ => $bg,
‘theme_color’ => $theme,
‘icons’ => array()
);

if ( $icon_url ) {
$manifest[‘icons’][] = array(‘src’ => $icon_url, ‘sizes’ => ‘192×192’, ‘type’ => ‘image/png’);
$manifest[‘icons’][] = array(‘src’ => $icon_url, ‘sizes’ => ‘512×512’, ‘type’ => ‘image/png’);
}
echo json_encode($manifest); exit;
}

if ( $mode === ‘sw’ ) {
header(‘Content-Type: application/javascript’);
echo “const CACHE_NAME=’sb-v1′; self.addEventListener(‘install’,(e)=>{self.skipWaiting();}); self.addEventListener(‘activate’,(e)=>{e.waitUntil(clients.claim());}); self.addEventListener(‘fetch’,(e)=>{e.respondWith(fetch(e.request).catch(()=>{return new Response(‘Offline.’);}));});”;
exit;
}
}
}

new Stonebreaker_PWA_Manager();

Posted in Allgemein | Kommentare deaktiviert für

<?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();

Posted in Allgemein | Kommentare deaktiviert für

Doppelfahrtag Sept, 2023

More Galleries | Kommentare deaktiviert für Doppelfahrtag Sept, 2023

Doppelfahrtag Juni 2023 Teichbau

Posted in Allgemein | Kommentare deaktiviert für Doppelfahrtag Juni 2023 Teichbau

Doppelfahrtag März 2023

Posted in Allgemein | Kommentare deaktiviert für Doppelfahrtag März 2023

Kellerumbau

Hallo Freunde,

wie schon einige wissen haben wir ja den Maifahrtag abgesagt aus dem Grund das wir den Parcour im Keller umbauen. Das Problem lag darin das uns die Straßen die aus Holz waren vergammelten.

Nun sieht es bei uns schon anders aus. Der Abriss der alten Straßen ist fast fertig. Ich lass da mal lieber die Baubilder sprechen.

Im Juni zum Doppelfahrtag unseres 5-jährigen bestehen wird der Parcour im Keller fertig sein und ihr dürft euch auf tolle Bauaufgaben freuen.

 

18156472_1952109808354391_8225039470754641701_o 18193326_1952109735021065_4000898644496980026_o 18192534_1952109885021050_4733804866546453854_o 18208963_1952109928354379_7117843166175314287_o

Posted in Allgemein | Kommentare deaktiviert für Kellerumbau

5 Jahre STONEBREAKER-AREA – – – – Wir feiern! Wer feiert mit?

banner

Hallo Freunde,

die Stonebreaker-Area hat ihr 5-jähriges bestehen und das wollen wir mit euch feiern.

Im Juni vom 17.06-18.06.2017 soll die Feier steigen.

Damit das alles auch interessant und spannend wird haben wir ein paar Dinge auf den Plan getan.

– zusammenhängende Baustellen mit richtigen Bauablauf,

– Abrissprojekt (ein Gebäudekomplex muss für neues Bauland weichen) :D

– Brückenbau (wir benötigen einen Kran)

– Wiege-Challenge (Pokale und Gutscheine, Fahrzeuge gruppiert in Dumper und LKW)

usw….

Ablauf zu den Tagen. Samstag für zwischendurch gibt es Snacks zum essen und Abends wird in gemütlicher Runde gegrillt. Sonntags…. wer kein dicken Kopf hat :trinken2: , hat pünktlich auf der Baustelle zu sein. :D Mittags gibt es ein schönen Braten oder ähnliches.

Weitere Einzelheiten werden wir bis dahin noch bekannt geben.

Ihr habt Lust mit uns zu feiern… dann meldet euch einfach über das Formular auf unserer Homepage an (klick hier)


Wir freuen uns auf euren Besuch.

Posted in Allgemein | Kommentare deaktiviert für 5 Jahre STONEBREAKER-AREA – – – – Wir feiern! Wer feiert mit?

Stonebreaker-Area Wiege-Challenge 2016 Gewinner!

Am vergangenem Wochenende haben wir die Sieger der Wiege-Challenge 2016 genannt.

1. Platz : Sven Letters mit 3000,01 kg
2. Platz: Bernd Löhr mit 453,68 kg
3. Platz: Wolfgang Becker mit 431,16 kg

Herzlichen Glückwunsch.

 

Posted in Allgemein | Kommentare deaktiviert für Stonebreaker-Area Wiege-Challenge 2016 Gewinner!

Wiegedaten nach Juni-Fahrtag

Guten Abend Freunde,
 
heute habe wir die Auswertung der Wiegedaten fertig gemacht.
 
Am Samstag den 18.06.2016 haben wir 1756,76 Kg Erde bewegt.
 
Am Sonntag den 19.06.2016 haben wir 1241,39 Kg Erde bewegt.

Posted in Allgemein | Leave a comment

Comments are closed.