687 lines
28 KiB
PHP
687 lines
28 KiB
PHP
<?php
|
||
/**
|
||
* Plugin Name: Nazar Kebap MVP
|
||
* Description: Restaurant landing page styling and simple table reservation workflow for Nazar Kebap - Gasthaus.
|
||
* Version: 1.2.0
|
||
* Author: OpenAI Codex
|
||
*/
|
||
|
||
if (!defined('ABSPATH')) {
|
||
exit;
|
||
}
|
||
|
||
class Nazar_Kebap_MVP {
|
||
const POST_TYPE = 'nazar_reservation';
|
||
const STATUS_META = '_reservation_status';
|
||
const AVAILABILITY_NONCE = 'nazar_reservation_availability';
|
||
const OPENING_TIME = '09:00';
|
||
const LAST_BOOKING_TIME = '23:30';
|
||
const TIME_STEP_MINUTES = 5;
|
||
|
||
public function __construct() {
|
||
add_action('init', [$this, 'register_post_type']);
|
||
add_action('init', [$this, 'register_shortcodes']);
|
||
add_action('wp_enqueue_scripts', [$this, 'enqueue_assets']);
|
||
add_action('admin_post_nazar_reserve_table', [$this, 'handle_reservation']);
|
||
add_action('admin_post_nopriv_nazar_reserve_table', [$this, 'handle_reservation']);
|
||
add_action('wp_ajax_nazar_get_available_times', [$this, 'ajax_get_available_times']);
|
||
add_action('wp_ajax_nopriv_nazar_get_available_times', [$this, 'ajax_get_available_times']);
|
||
add_filter('body_class', [$this, 'body_classes']);
|
||
|
||
add_filter('manage_' . self::POST_TYPE . '_posts_columns', [$this, 'admin_columns']);
|
||
add_action('manage_' . self::POST_TYPE . '_posts_custom_column', [$this, 'render_admin_columns'], 10, 2);
|
||
add_action('add_meta_boxes', [$this, 'add_meta_boxes']);
|
||
add_action('save_post_' . self::POST_TYPE, [$this, 'save_reservation_meta']);
|
||
}
|
||
|
||
public function register_post_type() {
|
||
register_post_type(self::POST_TYPE, [
|
||
'labels' => [
|
||
'name' => 'Reservierungen',
|
||
'singular_name' => 'Reservierung',
|
||
'menu_name' => 'Reservierungen',
|
||
'add_new_item' => 'Neue Reservierung',
|
||
'edit_item' => 'Reservierung bearbeiten',
|
||
'view_item' => 'Reservierung ansehen',
|
||
'search_items' => 'Reservierungen suchen',
|
||
],
|
||
'public' => false,
|
||
'show_ui' => true,
|
||
'show_in_menu' => true,
|
||
'menu_icon' => 'dashicons-calendar-alt',
|
||
'supports' => ['title'],
|
||
'map_meta_cap' => true,
|
||
]);
|
||
}
|
||
|
||
public function register_shortcodes() {
|
||
add_shortcode('nazar_reservation_form', [$this, 'reservation_form_shortcode']);
|
||
}
|
||
|
||
public function enqueue_assets() {
|
||
$plugin_url = plugin_dir_url(__FILE__);
|
||
$plugin_path = plugin_dir_path(__FILE__);
|
||
|
||
wp_enqueue_style(
|
||
'nazar-kebap-mvp',
|
||
$plugin_url . 'assets/site.css',
|
||
[],
|
||
filemtime($plugin_path . 'assets/site.css')
|
||
);
|
||
|
||
wp_enqueue_script(
|
||
'nazar-kebap-reservations',
|
||
$plugin_url . 'assets/reservation.js',
|
||
[],
|
||
filemtime($plugin_path . 'assets/reservation.js'),
|
||
true
|
||
);
|
||
|
||
wp_localize_script('nazar-kebap-reservations', 'nazarReservationData', [
|
||
'ajaxUrl' => admin_url('admin-ajax.php'),
|
||
'nonce' => wp_create_nonce(self::AVAILABILITY_NONCE),
|
||
'messages' => [
|
||
'chooseDateAndParty' => 'Bitte wählen Sie zuerst Datum und Personenzahl.',
|
||
'loading' => 'Verfügbare Uhrzeiten werden geladen …',
|
||
'empty' => 'Für diese Auswahl ist aktuell keine freie Uhrzeit verfügbar.',
|
||
'error' => 'Die verfügbaren Uhrzeiten konnten gerade nicht geladen werden. Bitte versuchen Sie es erneut.',
|
||
],
|
||
]);
|
||
}
|
||
|
||
public function body_classes($classes) {
|
||
$classes[] = 'nazar-site';
|
||
return $classes;
|
||
}
|
||
|
||
public function reservation_form_shortcode() {
|
||
$status = isset($_GET['reservation']) ? sanitize_key(wp_unslash($_GET['reservation'])) : '';
|
||
$messages = [
|
||
'success' => 'Danke! Ihre Reservierungsanfrage wurde erfolgreich übermittelt. Sie erhalten in Kürze eine Bestätigung per E-Mail mit allen Buchungsdetails.',
|
||
'error' => 'Bitte prüfen Sie Ihre Eingaben. Alle Pflichtfelder müssen ausgefüllt sein.',
|
||
'conflict' => 'Diese Uhrzeit ist leider nicht mehr verfügbar. Bitte wählen Sie eine andere freie Uhrzeit.',
|
||
];
|
||
|
||
ob_start();
|
||
?>
|
||
<div class="nazar-form-wrap">
|
||
<?php if ($status && isset($messages[$status])) : ?>
|
||
<div class="nazar-alert nazar-alert-<?php echo esc_attr($status === 'conflict' ? 'error' : $status); ?>">
|
||
<?php echo esc_html($messages[$status]); ?>
|
||
</div>
|
||
<?php endif; ?>
|
||
<form class="nazar-reservation-form" method="post" action="<?php echo esc_url(admin_url('admin-post.php')); ?>" data-availability-form>
|
||
<input type="hidden" name="action" value="nazar_reserve_table">
|
||
<?php wp_nonce_field('nazar_reserve_table', 'nazar_reserve_nonce'); ?>
|
||
<div class="nazar-form-grid">
|
||
<label>
|
||
<span>Name *</span>
|
||
<input type="text" name="guest_name" required>
|
||
</label>
|
||
<label>
|
||
<span>Telefon *</span>
|
||
<input type="tel" name="guest_phone" required>
|
||
</label>
|
||
<label>
|
||
<span>E-Mail *</span>
|
||
<input type="email" name="guest_email" required>
|
||
</label>
|
||
<label>
|
||
<span>Personen *</span>
|
||
<select name="party_size" required data-party-size>
|
||
<option value="">Bitte wählen</option>
|
||
<option value="1">1</option>
|
||
<option value="2">2</option>
|
||
<option value="3">3</option>
|
||
<option value="4">4</option>
|
||
<option value="5">5</option>
|
||
<option value="6">6</option>
|
||
<option value="7">7</option>
|
||
<option value="8+">8+</option>
|
||
</select>
|
||
</label>
|
||
<label>
|
||
<span>Datum *</span>
|
||
<input type="date" name="reservation_date" min="<?php echo esc_attr(wp_date('Y-m-d')); ?>" required data-reservation-date>
|
||
</label>
|
||
<label>
|
||
<span>Uhrzeit *</span>
|
||
<select name="reservation_time" required data-reservation-time disabled>
|
||
<option value="">Bitte erst Datum und Personen wählen</option>
|
||
</select>
|
||
</label>
|
||
</div>
|
||
<p class="nazar-availability-note" data-availability-note>Bitte wählen Sie zuerst Datum und Personenzahl.</p>
|
||
<label>
|
||
<span>Hinweise</span>
|
||
<textarea name="reservation_notes" rows="4" placeholder="z. B. Kinderstuhl, ruhiger Tisch, Allergien"></textarea>
|
||
</label>
|
||
<p class="nazar-form-note">Reservierungen werden direkt gegen bestehende Buchungen geprüft. Gesperrte Zeiten richten sich nach der Personenzahl: 1 Person = 10 Min., 2 Personen = 15 Min., 3–4 Personen = 30 Min., ab 5 Personen = 60 Min.</p>
|
||
<button type="submit" class="wp-element-button">Tisch anfragen</button>
|
||
</form>
|
||
</div>
|
||
<?php
|
||
return ob_get_clean();
|
||
}
|
||
|
||
public function handle_reservation() {
|
||
$redirect_url = wp_get_referer() ?: home_url('/reservierung/');
|
||
|
||
if (!isset($_POST['nazar_reserve_nonce']) || !wp_verify_nonce(sanitize_text_field(wp_unslash($_POST['nazar_reserve_nonce'])), 'nazar_reserve_table')) {
|
||
wp_safe_redirect(add_query_arg('reservation', 'error', $redirect_url));
|
||
exit;
|
||
}
|
||
|
||
$fields = [
|
||
'guest_name' => sanitize_text_field(wp_unslash($_POST['guest_name'] ?? '')),
|
||
'guest_phone' => sanitize_text_field(wp_unslash($_POST['guest_phone'] ?? '')),
|
||
'guest_email' => sanitize_email(wp_unslash($_POST['guest_email'] ?? '')),
|
||
'party_size' => sanitize_text_field(wp_unslash($_POST['party_size'] ?? '')),
|
||
'reservation_date' => sanitize_text_field(wp_unslash($_POST['reservation_date'] ?? '')),
|
||
'reservation_time' => sanitize_text_field(wp_unslash($_POST['reservation_time'] ?? '')),
|
||
'reservation_notes' => sanitize_textarea_field(wp_unslash($_POST['reservation_notes'] ?? '')),
|
||
];
|
||
|
||
if (!$fields['guest_name'] || !$fields['guest_phone'] || !$fields['guest_email'] || !$fields['party_size'] || !$fields['reservation_date'] || !$fields['reservation_time']) {
|
||
wp_safe_redirect(add_query_arg('reservation', 'error', $redirect_url));
|
||
exit;
|
||
}
|
||
|
||
$fields['reservation_time'] = $this->normalize_time($fields['reservation_time']);
|
||
|
||
if (!$fields['reservation_time'] || !$this->is_time_available($fields['reservation_date'], $fields['reservation_time'], $fields['party_size'])) {
|
||
wp_safe_redirect(add_query_arg('reservation', 'conflict', $redirect_url));
|
||
exit;
|
||
}
|
||
|
||
$title = sprintf(
|
||
'Reservierung – %s – %s %s',
|
||
$fields['guest_name'],
|
||
$fields['reservation_date'],
|
||
$fields['reservation_time']
|
||
);
|
||
|
||
$post_id = wp_insert_post([
|
||
'post_type' => self::POST_TYPE,
|
||
'post_status' => 'publish',
|
||
'post_title' => $title,
|
||
'meta_input' => [
|
||
'_guest_name' => $fields['guest_name'],
|
||
'_guest_phone' => $fields['guest_phone'],
|
||
'_guest_email' => $fields['guest_email'],
|
||
'_party_size' => $fields['party_size'],
|
||
'_reservation_date' => $fields['reservation_date'],
|
||
'_reservation_time' => $fields['reservation_time'],
|
||
'_reservation_notes' => $fields['reservation_notes'],
|
||
'_reservation_duration' => $this->get_duration_minutes($fields['party_size']),
|
||
self::STATUS_META => 'neu',
|
||
],
|
||
]);
|
||
|
||
if (is_wp_error($post_id) || !$post_id) {
|
||
wp_safe_redirect(add_query_arg('reservation', 'error', $redirect_url));
|
||
exit;
|
||
}
|
||
|
||
$notification_results = $this->send_new_reservation_notifications($post_id, $fields);
|
||
update_post_meta($post_id, '_admin_notification_sent', $notification_results['admin'] ? 'yes' : 'no');
|
||
update_post_meta($post_id, '_customer_notification_sent', $notification_results['customer'] ? 'yes' : 'no');
|
||
update_post_meta($post_id, '_notification_sent_at', current_time('mysql'));
|
||
|
||
wp_safe_redirect(add_query_arg('reservation', 'success', home_url('/reservierung/')));
|
||
exit;
|
||
}
|
||
|
||
public function ajax_get_available_times() {
|
||
check_ajax_referer(self::AVAILABILITY_NONCE, 'nonce');
|
||
|
||
$date = sanitize_text_field(wp_unslash($_POST['reservation_date'] ?? ''));
|
||
$party_size = sanitize_text_field(wp_unslash($_POST['party_size'] ?? ''));
|
||
|
||
if (!$date || !$party_size) {
|
||
wp_send_json_error([
|
||
'message' => 'Datum und Personenzahl sind erforderlich.',
|
||
], 400);
|
||
}
|
||
|
||
$times = $this->get_available_time_slots($date, $party_size);
|
||
$duration = $this->get_duration_minutes($party_size);
|
||
|
||
wp_send_json_success([
|
||
'times' => $times,
|
||
'duration' => $duration,
|
||
'message' => sprintf(
|
||
'Für %s blockiert eine Reservierung %d Minuten. Es werden nur freie Uhrzeiten angezeigt.',
|
||
$party_size,
|
||
$duration
|
||
),
|
||
]);
|
||
}
|
||
|
||
public function admin_columns($columns) {
|
||
return [
|
||
'cb' => $columns['cb'],
|
||
'title' => 'Anfrage',
|
||
'reservation_datetime' => 'Datum & Uhrzeit',
|
||
'party_size' => 'Personen',
|
||
'contact' => 'Kontakt',
|
||
'reservation_status' => 'Status',
|
||
'date' => 'Eingegangen',
|
||
];
|
||
}
|
||
|
||
public function render_admin_columns($column, $post_id) {
|
||
switch ($column) {
|
||
case 'reservation_datetime':
|
||
echo esc_html(get_post_meta($post_id, '_reservation_date', true));
|
||
echo '<br>';
|
||
echo esc_html(get_post_meta($post_id, '_reservation_time', true));
|
||
break;
|
||
case 'party_size':
|
||
echo esc_html(get_post_meta($post_id, '_party_size', true));
|
||
break;
|
||
case 'contact':
|
||
echo esc_html(get_post_meta($post_id, '_guest_name', true));
|
||
echo '<br>';
|
||
echo esc_html(get_post_meta($post_id, '_guest_phone', true));
|
||
$email = get_post_meta($post_id, '_guest_email', true);
|
||
if ($email) {
|
||
echo '<br>' . esc_html($email);
|
||
}
|
||
break;
|
||
case 'reservation_status':
|
||
echo esc_html(ucfirst(get_post_meta($post_id, self::STATUS_META, true) ?: 'neu'));
|
||
break;
|
||
}
|
||
}
|
||
|
||
public function add_meta_boxes() {
|
||
add_meta_box(
|
||
'nazar_reservation_details',
|
||
'Reservierungsdetails',
|
||
[$this, 'render_meta_box'],
|
||
self::POST_TYPE,
|
||
'normal',
|
||
'high'
|
||
);
|
||
}
|
||
|
||
public function render_meta_box($post) {
|
||
wp_nonce_field('nazar_save_reservation', 'nazar_save_reservation_nonce');
|
||
$status = get_post_meta($post->ID, self::STATUS_META, true) ?: 'neu';
|
||
?>
|
||
<table class="form-table">
|
||
<tr><th>Name</th><td><?php echo esc_html(get_post_meta($post->ID, '_guest_name', true)); ?></td></tr>
|
||
<tr><th>Telefon</th><td><?php echo esc_html(get_post_meta($post->ID, '_guest_phone', true)); ?></td></tr>
|
||
<tr><th>E-Mail</th><td><?php echo esc_html(get_post_meta($post->ID, '_guest_email', true)); ?></td></tr>
|
||
<tr><th>Datum</th><td><?php echo esc_html(get_post_meta($post->ID, '_reservation_date', true)); ?></td></tr>
|
||
<tr><th>Uhrzeit</th><td><?php echo esc_html(get_post_meta($post->ID, '_reservation_time', true)); ?></td></tr>
|
||
<tr><th>Personen</th><td><?php echo esc_html(get_post_meta($post->ID, '_party_size', true)); ?></td></tr>
|
||
<tr><th>Blockiert bis</th><td><?php echo esc_html($this->get_blocked_until_label($post->ID)); ?></td></tr>
|
||
<tr><th>Admin-E-Mail</th><td><?php echo esc_html($this->humanize_notification_status(get_post_meta($post->ID, '_admin_notification_sent', true))); ?></td></tr>
|
||
<tr><th>Kunden-E-Mail</th><td><?php echo esc_html($this->humanize_notification_status(get_post_meta($post->ID, '_customer_notification_sent', true))); ?></td></tr>
|
||
<tr><th>Status-E-Mail</th><td><?php echo esc_html($this->humanize_notification_status(get_post_meta($post->ID, '_status_notification_sent', true))); ?></td></tr>
|
||
<tr><th>Hinweise</th><td><?php echo nl2br(esc_html(get_post_meta($post->ID, '_reservation_notes', true))); ?></td></tr>
|
||
<tr>
|
||
<th><label for="nazar_reservation_status">Status</label></th>
|
||
<td>
|
||
<select name="nazar_reservation_status" id="nazar_reservation_status">
|
||
<?php foreach (['neu', 'bestätigt', 'abgelehnt', 'erledigt'] as $option) : ?>
|
||
<option value="<?php echo esc_attr($option); ?>" <?php selected($status, $option); ?>><?php echo esc_html(ucfirst($option)); ?></option>
|
||
<?php endforeach; ?>
|
||
</select>
|
||
</td>
|
||
</tr>
|
||
</table>
|
||
<?php
|
||
}
|
||
|
||
public function save_reservation_meta($post_id) {
|
||
if (!isset($_POST['nazar_save_reservation_nonce']) || !wp_verify_nonce(sanitize_text_field(wp_unslash($_POST['nazar_save_reservation_nonce'])), 'nazar_save_reservation')) {
|
||
return;
|
||
}
|
||
|
||
if (defined('DOING_AUTOSAVE') && DOING_AUTOSAVE) {
|
||
return;
|
||
}
|
||
|
||
if (!current_user_can('edit_post', $post_id)) {
|
||
return;
|
||
}
|
||
|
||
$status = sanitize_text_field(wp_unslash($_POST['nazar_reservation_status'] ?? 'neu'));
|
||
$previous_status = get_post_meta($post_id, self::STATUS_META, true) ?: 'neu';
|
||
|
||
update_post_meta($post_id, self::STATUS_META, $status);
|
||
|
||
if ($status !== $previous_status) {
|
||
$status_sent = $this->send_status_update_notification($post_id, $status, $previous_status);
|
||
update_post_meta($post_id, '_status_notification_sent', $status_sent ? 'yes' : 'no');
|
||
update_post_meta($post_id, '_status_notification_sent_at', current_time('mysql'));
|
||
}
|
||
}
|
||
|
||
public function get_available_time_slots($date, $party_size) {
|
||
if (!$this->is_valid_reservation_date($date) || !$this->parse_party_size($party_size)) {
|
||
return [];
|
||
}
|
||
|
||
$slots = [];
|
||
$start = $this->date_time_to_timestamp($date, self::OPENING_TIME);
|
||
$end = $this->date_time_to_timestamp($date, self::LAST_BOOKING_TIME);
|
||
|
||
if (!$start || !$end || $start > $end) {
|
||
return [];
|
||
}
|
||
|
||
for ($cursor = $start; $cursor <= $end; $cursor += self::TIME_STEP_MINUTES * MINUTE_IN_SECONDS) {
|
||
$time = wp_date('H:i', $cursor);
|
||
if ($this->is_time_available($date, $time, $party_size)) {
|
||
$slots[] = [
|
||
'value' => $time,
|
||
'label' => $time,
|
||
];
|
||
}
|
||
}
|
||
|
||
return $slots;
|
||
}
|
||
|
||
public function is_time_available($date, $time, $party_size, $exclude_post_id = 0) {
|
||
if (!$this->is_valid_reservation_date($date)) {
|
||
return false;
|
||
}
|
||
|
||
$time = $this->normalize_time($time);
|
||
$party_count = $this->parse_party_size($party_size);
|
||
|
||
if (!$time || !$party_count) {
|
||
return false;
|
||
}
|
||
|
||
$requested_start = $this->date_time_to_timestamp($date, $time);
|
||
$requested_end = $requested_start ? $requested_start + ($this->get_duration_minutes($party_size) * MINUTE_IN_SECONDS) : false;
|
||
|
||
if (!$requested_start || !$requested_end) {
|
||
return false;
|
||
}
|
||
|
||
foreach ($this->get_reservations_for_date($date, $exclude_post_id) as $reservation) {
|
||
if (($reservation['status'] ?? '') === 'abgelehnt') {
|
||
continue;
|
||
}
|
||
|
||
if (!$reservation['start'] || !$reservation['end']) {
|
||
continue;
|
||
}
|
||
|
||
if ($requested_start < $reservation['end'] && $requested_end > $reservation['start']) {
|
||
return false;
|
||
}
|
||
}
|
||
|
||
return true;
|
||
}
|
||
|
||
private function send_new_reservation_notifications($post_id, $fields) {
|
||
$admin_email = sanitize_email(get_option('admin_email'));
|
||
$admin_sent = false;
|
||
$customer_sent = false;
|
||
|
||
$details = $this->get_reservation_email_details($post_id, $fields);
|
||
|
||
if ($admin_email) {
|
||
$subject = sprintf('Neue Reservierung: %s am %s um %s', $details['name'], $details['date_label'], $details['time_label']);
|
||
$message = implode("
|
||
", [
|
||
'Eine neue Reservierungsanfrage wurde über die Website gesendet.',
|
||
'',
|
||
'Name: ' . $details['name'],
|
||
'Telefon: ' . $details['phone'],
|
||
'E-Mail: ' . $details['email'],
|
||
'Personen: ' . $details['party_size'],
|
||
'Datum: ' . $details['date_label'],
|
||
'Uhrzeit: ' . $details['time_label'],
|
||
'Blockiert bis: ' . $details['blocked_until_label'],
|
||
'Hinweise: ' . ($details['notes'] ?: 'Keine'),
|
||
'',
|
||
'Im Admin bearbeiten: ' . admin_url('post.php?post=' . $post_id . '&action=edit'),
|
||
]);
|
||
|
||
$admin_sent = wp_mail($admin_email, $subject, $message, $this->get_email_headers());
|
||
}
|
||
|
||
if ($details['email']) {
|
||
$subject = sprintf('Ihre Reservierungsanfrage bei %s', get_bloginfo('name'));
|
||
$message = implode("
|
||
", [
|
||
'Vielen Dank für Ihre Reservierungsanfrage bei ' . get_bloginfo('name') . '.',
|
||
'',
|
||
'Ihre Buchungsdaten:',
|
||
'Name: ' . $details['name'],
|
||
'Telefon: ' . $details['phone'],
|
||
'E-Mail: ' . $details['email'],
|
||
'Personen: ' . $details['party_size'],
|
||
'Datum: ' . $details['date_label'],
|
||
'Uhrzeit: ' . $details['time_label'],
|
||
'Reservierungsdauer: ' . $details['duration_label'],
|
||
'Hinweise: ' . ($details['notes'] ?: 'Keine'),
|
||
'',
|
||
'Falls wir Rückfragen haben, melden wir uns unter der angegebenen Telefonnummer oder E-Mail-Adresse.',
|
||
'Telefon Restaurant: +49 781 96643005',
|
||
'Adresse: Saarlandstraße 2, 77652 Offenburg',
|
||
]);
|
||
|
||
$customer_sent = wp_mail($details['email'], $subject, $message, $this->get_email_headers());
|
||
}
|
||
|
||
return [
|
||
'admin' => (bool) $admin_sent,
|
||
'customer' => (bool) $customer_sent,
|
||
];
|
||
}
|
||
|
||
private function send_status_update_notification($post_id, $new_status, $old_status) {
|
||
$email = sanitize_email(get_post_meta($post_id, '_guest_email', true));
|
||
|
||
if (!$email) {
|
||
return false;
|
||
}
|
||
|
||
$details = $this->get_reservation_email_details($post_id);
|
||
$status_labels = [
|
||
'neu' => 'neu',
|
||
'bestätigt' => 'bestätigt',
|
||
'abgelehnt' => 'abgelehnt',
|
||
'erledigt' => 'erledigt',
|
||
];
|
||
|
||
$new_label = $status_labels[$new_status] ?? $new_status;
|
||
$old_label = $status_labels[$old_status] ?? $old_status;
|
||
|
||
$subject = sprintf('Update zu Ihrer Reservierung bei %s', get_bloginfo('name'));
|
||
$message = implode("
|
||
", [
|
||
'Es gibt ein Update zu Ihrer Reservierung bei ' . get_bloginfo('name') . '.',
|
||
'',
|
||
'Bisheriger Status: ' . $old_label,
|
||
'Neuer Status: ' . $new_label,
|
||
'',
|
||
'Name: ' . $details['name'],
|
||
'Personen: ' . $details['party_size'],
|
||
'Datum: ' . $details['date_label'],
|
||
'Uhrzeit: ' . $details['time_label'],
|
||
'',
|
||
'Bei Fragen erreichen Sie uns unter +49 781 96643005.',
|
||
]);
|
||
|
||
return (bool) wp_mail($email, $subject, $message, $this->get_email_headers());
|
||
}
|
||
|
||
private function get_reservation_email_details($post_id = 0, $fields = []) {
|
||
$name = $fields['guest_name'] ?? get_post_meta($post_id, '_guest_name', true);
|
||
$phone = $fields['guest_phone'] ?? get_post_meta($post_id, '_guest_phone', true);
|
||
$email = $fields['guest_email'] ?? get_post_meta($post_id, '_guest_email', true);
|
||
$party_size = $fields['party_size'] ?? get_post_meta($post_id, '_party_size', true);
|
||
$date = $fields['reservation_date'] ?? get_post_meta($post_id, '_reservation_date', true);
|
||
$time = $fields['reservation_time'] ?? get_post_meta($post_id, '_reservation_time', true);
|
||
$notes = $fields['reservation_notes'] ?? get_post_meta($post_id, '_reservation_notes', true);
|
||
|
||
$date_label = $date ? wp_date('d.m.Y', strtotime($date)) : '–';
|
||
$time_label = $this->normalize_time($time) ?: '–';
|
||
$duration = $this->get_duration_minutes($party_size);
|
||
$blocked_until_label = '–';
|
||
|
||
if ($date && $time_label) {
|
||
$start = $this->date_time_to_timestamp($date, $time_label);
|
||
if ($start) {
|
||
$blocked_until_label = wp_date('d.m.Y H:i', $start + ($duration * MINUTE_IN_SECONDS));
|
||
}
|
||
}
|
||
|
||
return [
|
||
'name' => $name ?: '–',
|
||
'phone' => $phone ?: '–',
|
||
'email' => $email ?: '',
|
||
'party_size' => $party_size ?: '–',
|
||
'date_label' => $date_label,
|
||
'time_label' => $time_label,
|
||
'duration_label' => $duration ? ($duration . ' Minuten') : '–',
|
||
'blocked_until_label' => $blocked_until_label,
|
||
'notes' => $notes ?: '',
|
||
];
|
||
}
|
||
|
||
private function get_email_headers() {
|
||
$blogname = wp_specialchars_decode(get_bloginfo('name'), ENT_QUOTES);
|
||
$admin_email = sanitize_email(get_option('admin_email'));
|
||
|
||
return [
|
||
'Content-Type: text/plain; charset=UTF-8',
|
||
sprintf('Reply-To: %s <%s>', $blogname, $admin_email),
|
||
];
|
||
}
|
||
|
||
private function humanize_notification_status($status) {
|
||
return $status === 'yes' ? 'Gesendet' : ($status === 'no' ? 'Fehlgeschlagen' : '–');
|
||
}
|
||
|
||
private function get_reservations_for_date($date, $exclude_post_id = 0) {
|
||
$query = new WP_Query([
|
||
'post_type' => self::POST_TYPE,
|
||
'post_status' => 'publish',
|
||
'posts_per_page' => -1,
|
||
'fields' => 'ids',
|
||
'meta_key' => '_reservation_date',
|
||
'meta_value' => $date,
|
||
'orderby' => 'meta_value',
|
||
'order' => 'ASC',
|
||
'post__not_in' => $exclude_post_id ? [(int) $exclude_post_id] : [],
|
||
]);
|
||
|
||
$reservations = [];
|
||
|
||
foreach ($query->posts as $post_id) {
|
||
$time = $this->normalize_time(get_post_meta($post_id, '_reservation_time', true));
|
||
$party_size = get_post_meta($post_id, '_party_size', true);
|
||
$start = $time ? $this->date_time_to_timestamp($date, $time) : false;
|
||
$duration = $this->get_duration_minutes($party_size);
|
||
|
||
$reservations[] = [
|
||
'post_id' => $post_id,
|
||
'status' => get_post_meta($post_id, self::STATUS_META, true) ?: 'neu',
|
||
'time' => $time,
|
||
'party_size' => $party_size,
|
||
'duration' => $duration,
|
||
'start' => $start,
|
||
'end' => $start ? $start + ($duration * MINUTE_IN_SECONDS) : false,
|
||
];
|
||
}
|
||
|
||
wp_reset_postdata();
|
||
|
||
return $reservations;
|
||
}
|
||
|
||
private function get_duration_minutes($party_size) {
|
||
$count = $this->parse_party_size($party_size);
|
||
|
||
if ($count <= 0) {
|
||
return 0;
|
||
}
|
||
|
||
if ($count === 1) {
|
||
return 10;
|
||
}
|
||
|
||
if ($count === 2) {
|
||
return 15;
|
||
}
|
||
|
||
if ($count <= 4) {
|
||
return 30;
|
||
}
|
||
|
||
return 60;
|
||
}
|
||
|
||
private function parse_party_size($party_size) {
|
||
if (is_numeric($party_size)) {
|
||
return max(0, (int) $party_size);
|
||
}
|
||
|
||
if (is_string($party_size) && preg_match('/^(\d+)\+?$/', $party_size, $matches)) {
|
||
return max(0, (int) $matches[1]);
|
||
}
|
||
|
||
return 0;
|
||
}
|
||
|
||
private function normalize_time($time) {
|
||
if (!is_string($time) || $time === '') {
|
||
return '';
|
||
}
|
||
|
||
$date_time = date_create_from_format('H:i', $time, wp_timezone()) ?: date_create_from_format('H:i:s', $time, wp_timezone());
|
||
|
||
if (!$date_time) {
|
||
return '';
|
||
}
|
||
|
||
return $date_time->format('H:i');
|
||
}
|
||
|
||
private function is_valid_reservation_date($date) {
|
||
if (!preg_match('/^\d{4}-\d{2}-\d{2}$/', (string) $date)) {
|
||
return false;
|
||
}
|
||
|
||
$candidate = date_create_immutable_from_format('Y-m-d', $date, wp_timezone());
|
||
|
||
return $candidate && $candidate->format('Y-m-d') === $date;
|
||
}
|
||
|
||
private function date_time_to_timestamp($date, $time) {
|
||
$date_time = date_create_immutable_from_format('Y-m-d H:i', $date . ' ' . $time, wp_timezone());
|
||
|
||
return $date_time ? $date_time->getTimestamp() : false;
|
||
}
|
||
|
||
private function get_blocked_until_label($post_id) {
|
||
$date = get_post_meta($post_id, '_reservation_date', true);
|
||
$time = $this->normalize_time(get_post_meta($post_id, '_reservation_time', true));
|
||
$party_size = get_post_meta($post_id, '_party_size', true);
|
||
$start = ($date && $time) ? $this->date_time_to_timestamp($date, $time) : false;
|
||
|
||
if (!$start) {
|
||
return '–';
|
||
}
|
||
|
||
$end = $start + ($this->get_duration_minutes($party_size) * MINUTE_IN_SECONDS);
|
||
|
||
return wp_date('Y-m-d H:i', $end);
|
||
}
|
||
}
|
||
|
||
$GLOBALS['nazar_kebap_mvp'] = new Nazar_Kebap_MVP();
|