| 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/admin/ |
Upload File : |
<?php
declare(strict_types=1);
/** POLANA V18.13 — ostrozny instalator numerow i powiadomien o rezerwacjach.
* Bez migracji DB, bez nowych hasel SMTP, bez automatycznego wysylania starych wiadomosci.
*/
require_once dirname(__DIR__) . '/includes/functions.php';
require_admin_role();
header('Cache-Control: private, no-store, max-age=0');
header('X-Robots-Tag: noindex,nofollow');
const PV1813_URL = '/admin/polana-v1813-numeracja-maile.php';
const PV1813_EMAIL = '[email protected]';
const PV1813_MARK = 'POLANA_V1813_STAFF_STATUS';
function pv1813_h($s): string { return htmlspecialchars((string)$s, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8'); }
function pv1813_root(): string { return dirname(__DIR__); }
function pv1813_files(): array {
return [
'includes/reservations.php',
'includes/polana_v175_booking.php',
'admin/reservations.php',
'admin/reservation.php',
'rezerwacja.php',
];
}
function pv1813_backup_base(): string {
$lib = pv1813_root() . '/includes/polana_backup_v182.php';
if (!is_file($lib)) throw new RuntimeException('Brak biblioteki prywatnych backupów V18.3. Instalacja wstrzymana.');
require_once $lib;
if (!function_exists('pbk_private')) throw new RuntimeException('Brak funkcji lokalizacji kopii poza WWW.');
$dir = pbk_private() . '/polana-v1813-code-rollback';
if (!is_dir($dir) && !@mkdir($dir, 0700, true)) throw new RuntimeException('Brak możliwości utworzenia prywatnej kopii kodu.');
@chmod($dir, 0700);
$public = realpath(pv1813_root()); $real = realpath($dir);
if (!$real || !$public || $real === $public || str_starts_with($real . '/', $public . '/'))
throw new RuntimeException('Niebezpieczna lokalizacja kopii kodu.');
return $real;
}
function pv1813_replace_once(string $s, string $needle, string $replacement, string $label): string {
if (str_contains($s, $replacement) && !str_contains($s, $needle)) return $s;
if (substr_count($s, $needle) !== 1) throw new RuntimeException('Nieznana wersja: ' . $label . '. Żadnych zmian nie wykonano.');
return str_replace($needle, $replacement, $s);
}
function pv1813_source_transform(string $rel, string $s): string {
if ($rel === 'includes/reservations.php') {
// Istniejacy generator MPS-YYYYMMDD-ID lub obecny POLANA-ID (V17.5.2).
if (!preg_match('~function\s+reservation_code_for_id\s*\(int\s+\$id\s*,\s*string\s+\$date\s*\)\s*:\s*string\s*\{(?<body>[^{}]*)\}~s', $s, $match))
throw new RuntimeException('Nieznany generator numerow w includes/reservations.php.');
if (str_contains($match['body'], "'MPS-%s-%04d'")) {
$replacement = "function reservation_code_for_id(int \$id, string \$date): string\n{\n return sprintf('POLANA-%04d', \$id);\n}";
$s = pv1813_replace_once($s, $match[0], $replacement, 'generator numerow');
} elseif (!str_contains($match['body'], "'POLANA-%04d'")) {
throw new RuntimeException('Inny format generatora numerow — nie nadpisano.');
}
// Kod przechowywany w DB pozostaje stabilny; nazwa widoczna moze pokazywac stary kod jako alias.
$display = <<<'FUN'
/** 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;
}
FUN;
if (preg_match('~function\s+reservation_display_code\s*\(string\s+\$code\)\s*:\s*string\s*\{.*?\n\}~s', $s, $matches)) {
$old = $matches[0];
if (!str_contains($old, 'POLANA-') || !str_contains($old, 'preg_match'))
throw new RuntimeException('Nierozpoznana wlasna funkcja prezentacji numerow — przerwano.');
$s = str_replace($old, substr($display, strpos($display, 'function reservation_display_code')), $s);
} else {
$s = pv1813_replace_once($s, 'function reservation_create(array $data): array', $display . "\n\nfunction reservation_create(array \$data): array", 'wstawienie numeracji');
}
$old = " '{code}' => (string)\$reservation['reservation_code'],";
$new = " '{code}' => reservation_display_code((string)\$reservation['reservation_code']),";
if (str_contains($s, $old)) $s = pv1813_replace_once($s, $old, $new, 'numer w szablonach e-mail');
elseif (!str_contains($s, $new)) throw new RuntimeException('Nieznany szablon numeracji e-mail.');
if (!str_contains($s, PV1813_MARK)) {
if (str_contains($s, 'function reservation_send_admin_status_notification('))
throw new RuntimeException('Istnieje juz niestandardowa funkcja powiadomien obslugi — nie dubluje e-maili.');
$staffFunction = <<<'FUN'
/** 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.');
}
FUN;
$s = pv1813_replace_once($s, 'function reservation_send_after_create(array $reservation): array', $staffFunction . "\n\nfunction reservation_send_after_create(array \$reservation): array", 'funkcja e-mail do restauracji');
$needle = ' // Nie wysyłamy powtórnego ntfy, jeśli status nie uległ zmianie.';
$call = <<<'FUN'
// 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);
}
}
FUN;
$s = pv1813_replace_once($s, $needle, $call . $needle, 'hook potwierdzenia do obslugi');
}
// Brak ponownej wysylki do goscia przy klikaniu tego samego statusu.
$needle = <<<'FUN'
setting('smtp_enabled', '0') === '1' &&
in_array($status, ['confirmed', 'rejected', 'cancelled'], true)
FUN;
$replacement = <<<'FUN'
setting('smtp_enabled', '0') === '1' &&
(string)$previous['status'] !== $status &&
in_array($status, ['confirmed', 'rejected', 'cancelled'], true)
FUN;
if (str_contains($s, $needle)) $s = pv1813_replace_once($s, $needle, $replacement, 'powtorne maile statusowe');
// Jesli kod statusow zmieniono w produkcji, nie ingerujemy w jego logike.
return $s;
}
if ($rel === 'includes/polana_v175_booking.php') {
$old = " \$code=(string)(\$reservation['reservation_code']??'');";
$new = " \$code=reservation_display_code((string)(\$reservation['reservation_code']??'')); // POLANA V18.13";
if (str_contains($s, $old)) return pv1813_replace_once($s, $old, $new, 'numer HTML e-mail');
if (str_contains($s, 'reservation_display_code((string)')) return $s;
throw new RuntimeException('Nieznany szablon HTML email; brak automatycznej zmiany.');
}
if ($rel === 'admin/reservations.php') {
// Takze historyczny MPS-YYYYMMDD-ID musi byc odnajdywany po aliasie #POLANA-ID.
if (!str_contains($s, 'POLANA_V1813_SEARCH_ALIAS')) {
$old = <<<'OLD'
if ($q !== '') {
$where[] = '(reservation_code LIKE ? OR guest_name LIKE ? OR phone LIKE ? OR email LIKE ?)';
$like = '%' . $q . '%';
array_push($params, $like, $like, $like, $like);
}
OLD;
$old1752 = <<<'OLD'
if ($q !== '') {
$where[] = '(reservation_code LIKE ? OR guest_name LIKE ? OR phone LIKE ? OR email LIKE ?)';
$like = '%' . ltrim($q, '#') . '%'; // wyszukuje również #POLANA-1042
array_push($params, $like, $like, $like, $like);
}
OLD;
$new = <<<'NEW'
if ($q !== '') {
// POLANA_V1813_SEARCH_ALIAS: znajdz tez stary rekord po widocznym numerze #POLANA-XXXX.
$searchCode = ltrim($q, '#');
$like = '%' . $searchCode . '%';
if (preg_match('/^POLANA-(\d+)$/iD', $searchCode, $m) && (int)$m[1] > 0) {
$where[] = '(reservation_code LIKE ? OR guest_name LIKE ? OR phone LIKE ? OR email LIKE ? OR id = ?)';
array_push($params, $like, $like, $like, $like, (int)$m[1]);
} else {
$where[] = '(reservation_code LIKE ? OR guest_name LIKE ? OR phone LIKE ? OR email LIKE ?)';
array_push($params, $like, $like, $like, $like);
}
}
NEW;
if (str_contains($s, $old)) $s = pv1813_replace_once($s, $old, $new, 'wyszukiwanie po aliasie');
elseif (str_contains($s, $old1752)) $s = pv1813_replace_once($s, $old1752, $new, 'wyszukiwanie po aliasie V17.5.2');
else throw new RuntimeException('Nieznany blok wyszukiwania rezerwacji.');
}
$old = "<?= e(\$reservation['reservation_code']) ?>";
$new = "<?= e(reservation_display_code((string)\$reservation['reservation_code'])) ?>";
if (str_contains($s, $old)) $s = str_replace($old, $new, $s);
elseif (!str_contains($s, 'reservation_display_code')) throw new RuntimeException('Nieznany format listy rezerwacji.');
return $s;
}
if ($rel === 'admin/reservation.php') {
$old = "render_admin_header('Rezerwacja ' . \$reservation['reservation_code']);";
$new = "render_admin_header('Rezerwacja ' . reservation_display_code((string)\$reservation['reservation_code']));";
if (str_contains($s, $old)) $s = pv1813_replace_once($s, $old, $new, 'naglowek rezerwacji');
elseif (!str_contains($s, $new)) throw new RuntimeException('Nieznany naglowek szczegolow rezerwacji.');
$old2 = "<h1><?= e(\$reservation['reservation_code']) ?></h1>";
$new2 = "<h1><?= e(reservation_display_code((string)\$reservation['reservation_code'])) ?></h1>";
if (str_contains($s, $old2)) $s = pv1813_replace_once($s, $old2, $new2, 'widok numeru rezerwacji');
elseif (!str_contains($s, 'reservation_display_code((string)')) throw new RuntimeException('Nieznany widok numeru rezerwacji.');
return $s;
}
if ($rel === 'rezerwacja.php') {
$old = 'Numer rezerwacji: <strong><?= e($successCode) ?></strong>';
$new = 'Numer rezerwacji: <strong><?= e(reservation_display_code($successCode)) ?></strong>';
if (str_contains($s, $old)) return pv1813_replace_once($s, $old, $new, 'numer po zlozeniu rezerwacji');
if (str_contains($s, 'reservation_display_code($successCode)')) return $s;
throw new RuntimeException('Nieznany komunikat potwierdzenia rezerwacji.');
}
throw new RuntimeException('Niedozwolony plik w transformacji.');
}
function pv1813_plan(): array {
$plan = [];
foreach (pv1813_files() as $rel) {
$file = pv1813_root() . '/' . $rel;
if (!is_file($file) || !is_readable($file) || !is_writable($file) || is_link($file))
throw new RuntimeException('Brak dostepu lub dowiazanie symboliczne: ' . $rel);
$source = file_get_contents($file);
if (!is_string($source)) throw new RuntimeException('Nie odczytano ' . $rel);
$normalized = str_replace("\r\n", "\n", $source);
$result = pv1813_source_transform($rel, $normalized);
if (str_contains($source, "\r\n")) $result = str_replace("\n", "\r\n", $result);
if ($source !== $result) $plan[$rel] = ['path' => $file, 'before' => $source, 'after' => $result];
}
return $plan;
}
function pv1813_write(string $file, string $data): void {
$tmp = $file . '.v1813.' . bin2hex(random_bytes(5)) . '.tmp';
if (@file_put_contents($tmp, $data, LOCK_EX) !== strlen($data)) { @unlink($tmp); throw new RuntimeException('Blad zapisu pliku tymczasowego.'); }
$perms = @fileperms($file);
if (is_int($perms)) @chmod($tmp, $perms & 0777);
if (!@rename($tmp, $file)) { @unlink($tmp); throw new RuntimeException('Blad bezpiecznej podmiany pliku.'); }
}
function pv1813_install(): string {
$base = pv1813_backup_base();
$plans = pv1813_plan(); // sprawdzamy wszystkie wersje zanim cokolwiek zmienimy
if (!$plans) return 'Kod jest juz zgodny — brak potrzeby instalowania.';
$dir = $base . '/b-' . date('Ymd-His') . '-' . bin2hex(random_bytes(4));
if (!@mkdir($dir, 0700)) throw new RuntimeException('Nie utworzono prywatnego katalogu backupu.');
$manifest = ['version'=>'18.13','created'=>date('c'),'rolled_back'=>false,'files'=>[]];
foreach ($plans as $rel => $change) {
$name = str_replace('/', '__', $rel) . '.bak';
if (@file_put_contents($dir . '/' . $name, $change['before'], LOCK_EX) !== strlen($change['before']))
throw new RuntimeException('Nie utworzono pelnego backupu kodu: ' . $rel);
$manifest['files'][$rel] = ['file'=>$name, 'old_sha'=>hash('sha256',$change['before']), 'new_sha'=>hash('sha256',$change['after'])];
}
$man = $dir . '/manifest.json';
if (@file_put_contents($man, json_encode($manifest, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE | JSON_THROW_ON_ERROR), LOCK_EX) === false)
throw new RuntimeException('Nie udalo sie zapisac manifestu.');
$written = [];
try {
foreach ($plans as $rel => $change) {
// Kod może ulec zmianie w trakcie przygotowania instalatora.
if (!hash_equals(hash('sha256',$change['before']), (string)@hash_file('sha256', $change['path'])))
throw new RuntimeException('Plik zmienil sie w trakcie instalacji: ' . $rel);
pv1813_write($change['path'], $change['after']);
$written[] = $rel;
}
} catch (Throwable $e) {
$fail = [];
foreach (array_reverse($written) as $rel) {
try { pv1813_write($plans[$rel]['path'], $plans[$rel]['before']); }
catch (Throwable $ignored) { $fail[] = $rel; }
}
$manifest['rolled_back'] = count($fail) === 0;
@file_put_contents($man, json_encode($manifest, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT));
throw new RuntimeException($e->getMessage() . ($fail ? ' Nie udalo sie przywrocic: ' . implode(', ', $fail) : ' Kod przywrocono.'));
}
return 'Poprawiono numery #POLANA i powiadomienia o decyzji. Stare rekordy bazy pozostaja bez zmian.';
}
function pv1813_rollback(): string {
$base = pv1813_backup_base();
$list = glob($base . '/b-*/manifest.json') ?: [];
rsort($list, SORT_STRING);
foreach ($list as $m) {
$info = json_decode((string)@file_get_contents($m), true);
if (!is_array($info) || ($info['version']??'')!=='18.13' || !empty($info['rolled_back'])) continue;
$restores = [];
foreach ($info['files'] ?? [] as $rel => $meta) {
if (!in_array($rel, pv1813_files(), true)) throw new RuntimeException('Nieznany plik w manifeście.');
$path = pv1813_root() . '/' . $rel;
if (!is_file($path) || !hash_equals($meta['new_sha'], (string)@hash_file('sha256',$path)))
throw new RuntimeException('Plik ' . $rel . ' zmieniono po instalacji. Rollback wstrzymany.');
$previous = @file_get_contents(dirname($m) . '/' . $meta['file']);
if (!is_string($previous) || !hash_equals($meta['old_sha'], hash('sha256', $previous)))
throw new RuntimeException('Kopia jest uszkodzona: ' . $rel);
$restores[$path] = $previous;
}
foreach ($restores as $path => $source) pv1813_write($path, $source);
$info['rolled_back'] = true;
@file_put_contents($m, json_encode($info, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE), LOCK_EX);
return 'Przywrocono kod sprzed V18.13. Istniejace rezerwacje i numery w bazie bez zmian.';
}
throw new RuntimeException('Nie znaleziono aktywnego rollbacku kodu V18.13.');
}
function pv1813_change_destination(bool $restore = false): string {
$base = pv1813_backup_base();
$file = $base . '/destination.json';
$current = trim(setting('reservation_admin_email', ''));
if (!$restore) {
if ($current === PV1813_EMAIL) return 'Odbiorca juz jest ustawiony prawidlowo.';
if (is_file($file)) throw new RuntimeException('Istnieje poprzedni aktywny zapis — najpierw przywroc lub rozstrzygnij konfiguracje.');
$previous = json_encode(['old'=>$current,'new'=>PV1813_EMAIL, 'created'=>date('c')], JSON_THROW_ON_ERROR);
if (@file_put_contents($file, $previous, LOCK_EX) !== strlen($previous)) throw new RuntimeException('Nie zapisano kopii ustawienia odbiorcy.');
@chmod($file, 0600);
$target = PV1813_EMAIL;
} else {
$info = json_decode((string)@file_get_contents($file), true);
if (!is_array($info) || ($info['new']??'') !== $current)
throw new RuntimeException('Ustawienie odbiorcy zmieniono pozniej. Nie nadpisano zmian.');
$target = (string)$info['old'];
}
try {
$stmt = db()->prepare('INSERT INTO settings (setting_key, setting_value) VALUES (?, ?) ON DUPLICATE KEY UPDATE setting_value=VALUES(setting_value)');
$stmt->execute(['reservation_admin_email', $target]);
} catch (Throwable $e) {
if (!$restore) @unlink($file);
throw $e;
}
if ($restore) @unlink($file);
try { admin_log('polana_v1813_reservation_address', 'Zmieniono odbiorce powiadomien restauracji (bez zmiany loginu SMTP).'); } catch (Throwable $ignored) { }
return $restore ? 'Przywrocono poprzedni adres odbiorcy.' : 'Wiadomosci o nowych rezerwacjach i statusach trafia do ' . PV1813_EMAIL . '.';
}
function pv1813_test(): string {
$to = PV1813_EMAIL;
if (setting('smtp_enabled','0') !== '1') throw new RuntimeException('SMTP jest wylaczony.');
$username = trim(setting('smtp_username','')); $sender = trim(setting('smtp_from_email',''));
if ($username !== '' && strcasecmp($username, $sender) !== 0)
throw new RuntimeException('Nadawca SMTP jest rozny od loginu. Najpierw napraw V18.12 (mozliwy blad 553).');
$body = 'To kontrolna wiadomosc z POLANA V18.13. Sprawdz odebranie przez ' . $to . '.';
smtp_send_mail($to, 'TEST POLANA — powiadomienia rezerwacji', reservation_email_html('Test powiadomien', $body), $body);
return 'Serwer SMTP zaakceptowal test dla ' . $to . '. Sprawdz rzeczywiste doreczenie i folder SPAM.';
}
if (($_SERVER['REQUEST_METHOD'] ?? 'GET') === 'POST') {
verify_csrf();
try {
$action = (string)($_POST['action'] ?? '');
if ($action === 'install') $msg = pv1813_install();
elseif ($action === 'rollback') {
if (($_POST['sure'] ?? '') !== 'TAK') throw new RuntimeException('Potwierdz rollback.');
$msg = pv1813_rollback();
} elseif ($action === 'destination') {
if (($_POST['confirmed_mailbox'] ?? '') !== '1') throw new RuntimeException('Potwierdz istnienie skrzynki rezerwacja@.');
$msg = pv1813_change_destination();
} elseif ($action === 'restore_destination') {
if (($_POST['sure'] ?? '') !== 'TAK') throw new RuntimeException('Potwierdz przywrocenie.');
$msg = pv1813_change_destination(true);
} elseif ($action === 'test') {
start_session();
if ((int)($_SESSION['pv1813_test_at'] ?? 0) > time() - 30) throw new RuntimeException('Odczekaj 30 sekund miedzy testami.');
$_SESSION['pv1813_test_at'] = time();
$msg = pv1813_test();
} else throw new RuntimeException('Nieprawidlowa operacja.');
flash('success', $msg);
} catch (Throwable $e) {
// Sekrety nie sa prezentowane przez diagnostyke.
$message = preg_replace('/(?i)(password|secret|token)\s*[:=]\s*\S+/', '[ukryto]', $e->getMessage()) ?: 'Nieznany blad';
flash('error', $message);
}
redirect(PV1813_URL);
}
try { $plan = pv1813_plan(); $planError = ''; } catch (Throwable $e) { $plan = []; $planError = $e->getMessage(); }
$staff = setting('reservation_admin_email', '');
$from = setting('smtp_from_email', '');
$user = setting('smtp_username', '');
$mailEnabled = setting('smtp_enabled', '0') === '1';
render_admin_header('POLANA V18.13 — Numeracja i powiadomienia');
?>
<style>
.pv1813{max-width:1040px;margin:20px auto 60px;padding:0 14px}.pv1813 .cardx{background:var(--card,#fff);color:inherit;border:1px solid #d7d9d3;border-radius:16px;padding:22px;margin:15px 0}.pv1813 button{padding:12px 17px;border:0;border-radius:9px;background:#9b4919;color:#fff;font-weight:700;cursor:pointer}.pv1813 button:disabled{background:#999}.pv1813 button.alt{background:#515e58}.pv1813 table{border-collapse:collapse;width:100%}.pv1813 td{border-bottom:1px solid #ddd;padding:9px}.pv1813 p{line-height:1.6}.pv1813 .muted{opacity:.75}
</style>
<div class="pv1813">
<h1>POLANA V18.13 — numeracja i powiadomienia o rezerwacjach</h1>
<p>Nowe rezerwacje: np. <b>#POLANA-0013</b>, <b>#POLANA-0014</b> itd. — wedlug istniejacego ID w bazie, bez resetowania licznika i bez zmiany starych rekordow. Stare oznaczenia MPS pokazywane sa w czytelnej postaci #POLANA.</p>
<section class="cardx"><h2>1. Wdrozenie kodu z backupem</h2>
<p><b>Stan:</b> <?= pv1813_h($planError === '' ? ($plan ? 'Gotowe do poprawy (' . count($plan) . ' plikow)' : 'Juz zgodne, brak zmian') : 'WYMAGA UWAGI: ' . $planError) ?></p>
<p>Dodaje powiadomienie obslugi o faktycznej zmianie statusu Potwierdzona / Odrzucona / Anulowana. Nowa rezerwacja juz korzysta z ustawienia „E-mail obslugi”. Nie wysyla ponownie historycznych wiadomosci.</p>
<form method="post"><?= csrf_field() ?><input type="hidden" name="action" value="install"><button <?= $planError !== '' || !$plan ? 'disabled' : '' ?>>Zainstaluj kod — backup / kontrola wersji</button></form>
<p class="muted">Gdy pliki produkcyjne maja nieznana wersje, instalator nie dokonuje zmian. Nie uruchamia migracji SQL.</p></section>
<section class="cardx"><h2>2. Adres restauracji: <?= pv1813_h(PV1813_EMAIL) ?></h2>
<table><tr><td>Obecny odbiorca powiadomien</td><td><?= pv1813_h($staff ?: 'brak') ?></td></tr><tr><td>SMTP</td><td><?= $mailEnabled ? 'WŁĄCZONE' : 'WYŁĄCZONE' ?></td></tr><tr><td>Login / nadawca</td><td><?= pv1813_h($user) ?> / <?= pv1813_h($from) ?></td></tr></table>
<p>Przy ustawianiu adresu odbiorcy <strong>nie zmieniamy loginu SMTP, adresu From ani hasla</strong>. Sprawdz, ze istnieje dokladnie skrzynka „rezerwacja@” (a nie „rezerwacje@”).</p>
<form method="post"><?= csrf_field() ?><input type="hidden" name="action" value="test"><button type="submit" class="alt">Wyslij test na rezerwacja@</button></form>
<form method="post"><?= csrf_field() ?><input type="hidden" name="action" value="destination"><label><input type="checkbox" name="confirmed_mailbox" value="1" required> Potwierdzam, ze adres <?= pv1813_h(PV1813_EMAIL) ?> istnieje i odbiera maile.</label> <button type="submit">Ustaw odbiorce powiadomien</button></form>
<form method="post"><?= csrf_field() ?><input type="hidden" name="action" value="restore_destination"><label><input type="checkbox" name="sure" value="TAK" required> Przywroc poprzedni adres odbiorcy</label> <button type="submit" class="alt">Cofnij adres</button></form></section>
<section class="cardx"><h2>3. Rollback kodu</h2>
<form method="post"><?= csrf_field() ?><input type="hidden" name="action" value="rollback"><label><input type="checkbox" name="sure" value="TAK" required> Przywroc stare pliki z prywatnej kopii</label> <button type="submit" class="alt">Rollback V18.13</button></form>
<p><a href="/admin/reservations.php">Rezerwacje</a> · <a href="/admin/polana-v1812-naprawa-maili.php">Diagnostyka SMTP V18.12</a> · <a href="/admin/polana-poczta.php">Poczta</a></p></section>
</div>
<?php render_admin_footer();