| 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 V17.2: safe, limited-scope patcher of the existing PHP app. */
require_once dirname(__DIR__) . '/includes/functions.php';
require_once dirname(__DIR__) . '/includes/polana_v172_newsletter.php';
require_admin_role();
header('Cache-Control: private, no-store, max-age=0');
header('X-Robots-Tag: noindex,nofollow');
function pi172_root():string { return dirname(__DIR__); }
function pi172_backupdir():string { return dirname(pi172_root()).'/.polana_v172_private_backups'; }
function pi172_marker():string { return 'POLANA_V172_NEWSLETTER_ADAPTER'; }
function pi172_targets():array {
$root=pi172_root();
return [
'includes/newsletter.php'=>$root.'/includes/newsletter.php',
'newsletter.php'=>$root.'/newsletter.php',
'rezerwacja.php'=>$root.'/rezerwacja.php',
'includes/reservations.php'=>$root.'/includes/reservations.php',
'includes/functions.php'=>$root.'/includes/functions.php',
];
}
function pi172_one(string $contents,string $needle,string $replacement,string $label):string {
if (strpos($contents,$needle)===false) throw new RuntimeException('Nie znaleziono wzorca: '.$label.'. Instalacja przerwana, bez nadpisywania nieznanego kodu.');
if (substr_count($contents,$needle)!==1) throw new RuntimeException('Wzorzec niejednoznaczny: '.$label);
return str_replace($needle,$replacement,$contents);
}
function pi172_patch(string $relative,string $content):string {
if ($relative==='includes/newsletter.php') {
if (str_contains($content,pi172_marker())) return $content;
$regex='~function newsletter_subscribe\(.*?\): array\s*\{.*?\n\}\s*(?=function newsletter_unsubscribe\()~s';
if(preg_match_all($regex,$content,$m)!==1) throw new RuntimeException('Niezgodna struktura funkcji newsletter_subscribe.');
$replacement = <<<'REPLACE'
/** POLANA_V172_NEWSLETTER_ADAPTER: double opt-in instead of automatic activation */
function newsletter_subscribe(string $email, string $name='', string $source='footer'): array {
require_once __DIR__ . '/polana_v172_newsletter.php';
return pv172_subscribe($email, $name, $source);
}
REPLACE;
return preg_replace_callback($regex,static fn($match)=>$replacement,$content,1) ?? throw new RuntimeException('Nie udało się dostosować newslettera.');
}
if ($relative==='newsletter.php') {
if(str_contains($content,'POLANA_V172_CONFIRM_FLOW')) return $content;
$content=pi172_one($content,"require_once __DIR__ . '/includes/newsletter.php';", "require_once __DIR__ . '/includes/newsletter.php';\nrequire_once __DIR__ . '/includes/polana_v172_newsletter.php';\n/* POLANA_V172_CONFIRM_FLOW */\nheader('Cache-Control: private, no-store');\nheader('Referrer-Policy: no-referrer');\nheader('X-Robots-Tag: noindex,nofollow');
start_session();",'newsletter public include');
$branch=<<<'BRANCH'
if (isset($_GET['confirm'])) {
$mode = 'confirm';
$confirmToken = trim((string)$_GET['confirm']);
$message = preg_match('/^[a-f0-9]{64}$/D', $confirmToken)
? 'Kliknij przycisk poniżej, aby potwierdzić zapis. Samo otwarcie linku nie aktywuje adresu.'
: 'Nieprawidłowy link potwierdzenia.';
} elseif ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['newsletter_confirm_token'])) {
$mode = 'confirm';
verify_csrf();
try {
$ok = pv172_confirm(trim((string)$_POST['newsletter_confirm_token']));
$message = $ok ? 'Adres potwierdzony. Dziękujemy za zapis!' : 'Link już wykorzystany, wygasły lub nieprawidłowy.';
} catch (Throwable $ex) {
$message = 'Nie udało się potwierdzić adresu. Spróbuj ponownie.';
error_log('[POLANA NEWSLETTER confirm] '.$ex->getMessage());
}
} elseif (isset($_GET['unsubscribe']))
BRANCH;
$content=pi172_one($content,"if (isset(\$_GET['unsubscribe']))",$branch,'newsletter confirmation routing');
$content=pi172_one($content,"<h1><?= \$mode === 'unsubscribe' ? 'Newsletter' : 'Dołącz do newslettera' ?></h1>","<h1><?= \$mode === 'unsubscribe' ? 'Newsletter' : (\$mode === 'confirm' ? 'Potwierdź zapis' : 'Dołącz do newslettera') ?></h1>",'newsletter confirmation title');
$form=<<<'FORM'
<?php if (!$ok && $mode === 'confirm' && !empty($confirmToken) && preg_match('/^[a-f0-9]{64}$/D', $confirmToken)): ?>
<form method="post" action="/newsletter.php" class="newsletter-page__form">
<?= csrf_field() ?>
<input type="hidden" name="newsletter_confirm_token" value="<?= e($confirmToken) ?>">
<button type="submit">Potwierdzam zapis na newsletter</button>
</form>
<?php elseif (!$ok && $mode !== 'unsubscribe' && $mode !== 'confirm'): ?>
FORM;
$content=pi172_one($content,"<?php if (!\$ok && \$mode !== 'unsubscribe'): ?>",$form,'newsletter form confirmation');
return $content;
}
if ($relative==='rezerwacja.php') {
if(str_contains($content,'POLANA_V172_RESERVATION_OPTIN_AFTER_SAVE'))return $content;
$content=pi172_one($content,"/* POLANA_NEWSLETTER_RESERVATION_POST_V1_1 */\nif (function_exists('newsletter_handle_reservation_optin')) newsletter_handle_reservation_optin();\n",'','premature reservation newsletter opt-in');
$target="\$_SESSION['reservation_last_submit'] = time();";
$replacement=$target."\n /* POLANA_V172_RESERVATION_OPTIN_AFTER_SAVE — record consent only after successful reservation */\n if (function_exists('newsletter_handle_reservation_optin')) newsletter_handle_reservation_optin();";
return pi172_one($content,$target,$replacement,'post-success reservation newsletter opt-in');
}
if ($relative==='includes/reservations.php') {
if(str_contains($content,'POLANA_V172_REPLY_TO')) return $content;
$before=<<<'BEFORE'
smtp_send_mail(
(string)$reservation['email'],
$subject,
reservation_email_html($subject, $body),
$body
);
BEFORE;
$after=<<<'AFTER'
/* POLANA_V172_REPLY_TO: gość odpisuje do rezerwacji, nie do no-reply */
smtp_send_mail(
(string)$reservation['email'],
$subject,
reservation_email_html($subject, $body),
$body,
trim(reservation_setting('reservation_admin_email')) ?: null
);
AFTER;
return pi172_one($content,$before,$after,'guest autoresponder Reply-To');
}
if ($relative==='includes/functions.php') {
if(str_contains($content,'POLANA_V172_ADMIN_TOP_LINKS')) return $content;
$needle='<a href="/admin/">Pulpit</a>';
$insert=$needle."\n <!-- POLANA_V172_ADMIN_TOP_LINKS -->\n <?php if (\$role === 'admin'): ?>\n <a href=\"/admin/polana-komunikacja.php\">Poczta i komunikacja</a>\n <a href=\"/admin/polana-newsletter.php\">Newsletter</a>\n <?php endif; ?>";
return pi172_one($content,$needle,$insert,'admin top navigation');
}
throw new RuntimeException('Nieobsługiwany plik '.$relative);
}
function pi172_write_atomic(string $file,string $contents):void {
$temp=$file.'.p172_'.bin2hex(random_bytes(6)).'.tmp';
if(@file_put_contents($temp,$contents,LOCK_EX)!==strlen($contents)){
@unlink($temp);throw new RuntimeException('Nie można zapisać pliku '.basename($file));
}
$mode=@fileperms($file);if($mode!==false)@chmod($temp,$mode&0777);
if(!@rename($temp,$file)){
@unlink($temp);throw new RuntimeException('Nie można podmienić pliku '.basename($file));
}
}
function pi172_settings_targets():array {
return [
'reservation_admin_email'=>'[email protected]',
'reservation_auto_reply_enabled'=>'1',
];
}
function pi172_setting_snapshot(array $settings):array {
$select=db()->prepare('SELECT setting_value FROM settings WHERE setting_key=? LIMIT 1');
$rows=[];
foreach($settings as $k=>$value){$select->execute([$k]);$old=$select->fetchColumn();$rows[$k]=['had'=>$old!==false,'before'=>$old!==false?(string)$old:null,'after'=>$value];}
return $rows;
}
function pi172_save_settings(array $settings):void {
$stmt=db()->prepare('INSERT INTO settings(setting_key,setting_value) VALUES(?,?) ON DUPLICATE KEY UPDATE setting_value=VALUES(setting_value)');
foreach($settings as $key=>$value)$stmt->execute([$key,$value]);
}
function pi172_install():string {
if(!is_file(pi172_root().'/includes/polana_v172_newsletter.php') || !is_file(pi172_root().'/admin/polana-newsletter.php') || !is_file(pi172_root().'/admin/polana-komunikacja.php')){
throw new RuntimeException('Najpierw wgraj WSZYSTKIE pliki ZIP do odpowiednich katalogów.');
}
$changes=[];
foreach(pi172_targets() as $relative=>$file){
if(!is_file($file) || !is_readable($file) || !is_writable($file))throw new RuntimeException('Brak odczytu/zapisu: '.$relative);
$before=file_get_contents($file);
if(!is_string($before))throw new RuntimeException('Brak odczytu: '.$relative);
$normalized=str_replace("\r\n","\n",$before);
$patched=pi172_patch($relative,$normalized);
$after=str_contains($before,"\r\n") ? str_replace("\n","\r\n",$patched) : $patched;
if($after!==$before)$changes[$relative]=['file'=>$file,'before'=>$before,'after'=>$after];
}
if (!$changes) {
return 'V17.2 jest już zainstalowana — nie utworzono kolejnego backupu ani nie zmieniono ustawień.';
}
$settingSnapshot=pi172_setting_snapshot(pi172_settings_targets());
$dir=pi172_backupdir();
if(!is_dir($dir)&&!@mkdir($dir,0700,true))throw new RuntimeException('Nie można utworzyć prywatnych backupów poza WWW.');
if(!is_writable($dir))throw new RuntimeException('Katalog backupu nie jest zapisywalny.');
$path=$dir.'/'.date('Ymd_His').'_'.bin2hex(random_bytes(4));
if(!@mkdir($path,0700))throw new RuntimeException('Nie można utworzyć folderu kopii.');
$manifest=['version'=>'17.2','files'=>[],'settings'=>$settingSnapshot];
foreach($changes as $relative=>$c){
$backup='original-'.count($manifest['files']).'.bak';
if(@file_put_contents($path.'/'.$backup,$c['before'],LOCK_EX)!==strlen($c['before']))throw new RuntimeException('Nie udało się zapisać backupu. Nic nie zmieniono.');
@chmod($path.'/'.$backup,0600);
$manifest['files'][$relative]=['backup'=>$backup,'before_sha'=>hash('sha256',$c['before']),'after_sha'=>hash('sha256',$c['after'])];
}
$manifestJson=json_encode($manifest,JSON_UNESCAPED_UNICODE|JSON_PRETTY_PRINT);
if(!$manifestJson || @file_put_contents($path.'/manifest.json',$manifestJson,LOCK_EX)!==strlen($manifestJson))throw new RuntimeException('Brak zapisu manifestu. Nic nie zmieniono.');
@chmod($path.'/manifest.json',0600);
// MariaDB DDL is additive. It does not alter existing subscriber status, consent or data.
pv172_upgrade_schema();
$written=[];
try {
foreach($changes as $relative=>$c){
$current=@file_get_contents($c['file']);
if($current!==$c['before'])throw new RuntimeException('Plik zmienił się w trakcie instalacji: '.$relative);
pi172_write_atomic($c['file'],$c['after']);$written[$relative]=$c;
}
$pdo=db();$pdo->beginTransaction();
try{pi172_save_settings(pi172_settings_targets());$pdo->commit();}catch(Throwable $e){if($pdo->inTransaction())$pdo->rollBack();throw $e;}
}catch(Throwable $e){
foreach(array_reverse($written) as $c){
if(@file_get_contents($c['file'])===$c['after']){
try{pi172_write_atomic($c['file'],$c['before']);}catch(Throwable $ignored){}
}
}
throw $e;
}
try { admin_log('polana_v172_install','Aktywowano newsletter double opt-in, komunikację i Reply-To (bez haseł)'); } catch(Throwable $ignored) {}
return 'Instalacja ukończona. Zmieniono '.count($changes).' plików i skonfigurowano odbiorcę rezerwacji. Kliknij „Poczta i komunikacja”.';
}
function pi172_rollback():string {
$paths=glob(pi172_backupdir().'/*/manifest.json')?:[];rsort($paths);
$last=null;
foreach($paths as $candidate){if(str_ends_with($candidate,'/manifest.json')){$last=$candidate;break;}}
if(!$last)throw new RuntimeException('Nie znaleziono wykonanej kopii V17.2.');
$manifest=json_decode((string)file_get_contents($last),true);
if(!is_array($manifest)||($manifest['version']??'')!=='17.2')throw new RuntimeException('Nieprawidłowa kopia V17.2.');
$restores=[];$dir=dirname($last);
foreach(($manifest['files']??[]) as $relative=>$item){
$targets=pi172_targets();
if(!isset($targets[$relative]))throw new RuntimeException('Nieznany plik w kopii: '.$relative);
$target=$targets[$relative];
$saved=@file_get_contents($dir.'/'.basename((string)$item['backup']));
$current=@file_get_contents($target);
if(!is_string($saved)||!is_string($current)||hash('sha256',$saved)!==$item['before_sha']||hash('sha256',$current)!==$item['after_sha']){
throw new RuntimeException('Plik '.$relative.' został później zmieniony lub kopia jest uszkodzona. Nie cofnięto niczego.');
}
$restores[]=[$target,$saved];
}
$currentSettings=pi172_setting_snapshot(pi172_settings_targets());
foreach(($manifest['settings']??[]) as $key=>$v){
if(!isset($currentSettings[$key]) || $currentSettings[$key]['before']!==(string)$v['after'])throw new RuntimeException('Ustawienie '.$key.' było zmieniane później — rollback zablokowany.');
}
foreach($restores as [$path,$saved])pi172_write_atomic($path,$saved);
$pdo=db();$pdo->beginTransaction();
try{
$up=$pdo->prepare('INSERT INTO settings(setting_key,setting_value) VALUES(?,?) ON DUPLICATE KEY UPDATE setting_value=VALUES(setting_value)');
$del=$pdo->prepare('DELETE FROM settings WHERE setting_key=?');
foreach(($manifest['settings']??[]) as $key=>$v){if($v['had'])$up->execute([$key,$v['before']]);else $del->execute([$key]);}
$pdo->commit();
}catch(Throwable $e){if($pdo->inTransaction())$pdo->rollBack();throw $e;}
@rename($last,$last.'.restored');
return 'Przywrócono pliki i ustawienia z poprzedniej wersji. Dane zgód newslettera zostały zachowane. Dodatkowe kolumny bazy pozostają bezpiecznie w tabeli.';
}
if (($_SERVER['REQUEST_METHOD']??'GET')==='POST'){
verify_csrf();
try{
$action=(string)($_POST['action']??'');
if($action==='install')$message=pi172_install();
elseif($action==='rollback' && !empty($_POST['confirm_rollback']))$message=pi172_rollback();
else throw new RuntimeException('Nieznana lub niepotwierdzona operacja.');
flash('success',$message);
}catch(Throwable $ex){flash('error','Nie wykonano operacji: '.$ex->getMessage());}
redirect('/admin/polana-v172-instalator.php');
}
render_admin_header('POLANA V17.2 — instalator');
?>
<div style="max-width:950px;margin:20px auto 65px;padding:15px">
<h1>POLANA V17.2 — instalator komunikacji</h1>
<p>Przygotowuje centrum poczty, newsletter z potwierdzeniem e-mail oraz automatyczne odpowiedzi rezerwacji z adresem <strong>[email protected]</strong> do odpowiedzi. Żadnych haseł w paczce.</p>
<div class="form-card" style="margin:18px 0;padding:20px"><h2>Bezpieczna instalacja</h2><p>Najpierw wgraj wszystkie pliki ZIP do głównego katalogu strony. Instalator sprawdzi znane fragmenty PHP, zapisze kopie poza WWW, doda dwa przyciski do górnego menu i rozszerzy tabelę newslettera o potwierdzenie zgody.</p><p>Jeżeli aktualny kod jest inny niż oczekiwany, instalator odmówi modyfikacji zamiast nadpisać funkcje.</p>
<form method="post"><?=csrf_field()?><input type="hidden" name="action" value="install"><button class="btn btn-primary" type="submit">Zainstaluj V17.2 z backupem</button></form></div>
<div class="form-card" style="margin:18px 0;padding:20px"><h2>Po instalacji</h2><a class="btn btn-primary" href="/admin/polana-komunikacja.php">Poczta i komunikacja</a> <a class="btn btn-light" href="/admin/polana-newsletter.php">Newsletter — subskrybenci</a> <a class="btn btn-light" href="/admin/polana-poczta.php">SMTP i test</a><p><strong>Ważne:</strong> dla poprawnego działania utwórz skrzynki <code>[email protected]</code> i <code>[email protected]</code>, wpisz hasło no-reply w panelu SMTP i sprawdź testową rezerwację.</p></div>
<div class="form-card" style="margin:18px 0;padding:20px"><h2>Rollback kodu</h2><p>Nie usuwa subskrybentów ani śladów zgód. Przywraca pliki i konfigurację tylko jeżeli nie zmieniono ich od instalacji.</p><form method="post" onsubmit="return confirm('Przywrócić ostatnią wersję plików V17.2?')"><?=csrf_field()?><input type="hidden" name="action" value="rollback"><label><input type="checkbox" name="confirm_rollback" value="1" required> Potwierdzam przywrócenie</label><button class="btn btn-light" type="submit">Cofnij V17.2</button></form></div>
</div>
<?php render_admin_footer(); ?>