| 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.15 R3 — authenticated menu migration.
* No server-side copy of source code. Admin explicitly downloads original
* functions.php to a LOCAL device before installing or exporting the patch.
* No integration with backup module V18.3, no database/SMTP/Calendar mutations.
*/
require_once dirname(__DIR__).'/includes/functions.php';
require_admin_role();
const PV1815R3_MARK = 'POLANA_V1815_NAV_ACTIVE';
const PV1815R3_SESSION = 'pv1815r3_saved_original_sha256';
const PV1815R3_SESSION_TIME = 'pv1815r3_saved_original_time';
function pv1815r3_root(): string { return dirname(__DIR__); }
function pv1815r3_target(): string { return pv1815r3_root().'/includes/functions.php'; }
function pv1815r3_escape(string $s): string { return htmlspecialchars($s, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8'); }
/** Transform the CURRENT code only if its template matches a known safe shape. */
function pv1815r3_transform(string $src): string {
if (str_contains($src, PV1815R3_MARK)) {
throw new RuntimeException('Menu V18.15 już jest aktywne. Nie wykonuję ponownej modyfikacji.');
}
$needle='<nav id="admin-main-nav" class="admin-nav" data-admin-nav>';
if (substr_count($src,$needle)!==1) {
throw new RuntimeException('Nieznany nagłówek includes/functions.php — odmowa automatycznej modyfikacji.');
}
$a=strpos($src,$needle);
$b=strpos($src,'</nav>',$a+strlen($needle));
if ($b===false) throw new RuntimeException('Nie odnaleziono końca menu.');
$old=substr($src,$a,$b+strlen('</nav>')-$a);
foreach (['/admin/reservations.php','/admin/tips.php','/admin/polana-menu-centrum.php','$role'] as $required) {
if (!str_contains($old,$required)) {
throw new RuntimeException('Obecne menu różni się od rozpoznanej wersji (brak '.$required.'). Niczego nie zmieniono.');
}
}
$after=substr($src,$b+strlen('</nav>'),1200);
if (!str_contains($after,'<details class="admin-account">')) {
throw new RuntimeException('Niestandardowy nagłówek użytkownika — odmowa automatycznej modyfikacji.');
}
$replace='<!-- '.PV1815R3_MARK.' -->' . "\n " . $needle . "\n" .
" <?php require_once __DIR__ . '/polana_v1815_nav.php'; pv1815_nav_render(\$role); ?>\n </nav>";
$result=substr_replace($src,$replace,$a,strlen($old));
$css='<link rel="stylesheet" href="<?= e(asset_v(\'css/admin.css\')) ?>">';
if (substr_count($result,$css)!==1) {
throw new RuntimeException('Nie znaleziono jednoznacznego odwołania do admin.css. Niczego nie zmieniono.');
}
$result=str_replace($css,$css."\n <link rel=\"stylesheet\" href=\"/assets/css/polana-v1815-nav.css?v=1815\">",$result);
return $result;
}
function pv1815r3_read(): string {
$file=pv1815r3_target();
if (!is_file($file) || !is_readable($file) || is_link($file)) {
throw new RuntimeException('Plik includes/functions.php jest niedostępny albo jest dowiązaniem symbolicznym.');
}
$bytes=@file_get_contents($file);
if ($bytes===false || strlen($bytes)<2000) throw new RuntimeException('Nie udało się odczytać functions.php.');
return $bytes;
}
function pv1815r3_require_files(): void {
foreach (['/includes/polana_v1815_nav.php','/assets/css/polana-v1815-nav.css'] as $name) {
if (!is_file(pv1815r3_root().$name)) {
throw new RuntimeException('Wgraj cały katalog DO_WGRANIA — brakuje '.$name);
}
}
}
function pv1815r3_saved_hash(): string {
start_session();
$hash=(string)($_SESSION[PV1815R3_SESSION]??'');
$time=(int)($_SESSION[PV1815R3_SESSION_TIME]??0);
if (!preg_match('/^[a-f0-9]{64}$/D',$hash) || $time<time()-1800) return '';
return $hash;
}
function pv1815r3_check_local_backup(string $source): void {
$hash=hash('sha256',$source);
if (pv1815r3_saved_hash()==='' || !hash_equals(pv1815r3_saved_hash(),$hash)) {
throw new RuntimeException('Najpierw pobierz aktualny ORYGINAŁ na komputer. Jeśli plik zmienił się od pobrania, pobierz go ponownie.');
}
if (($_POST['backup_confirmed']??'')!=='yes') {
throw new RuntimeException('Zaznacz potwierdzenie, że oryginalny plik został zapisany NA KOMPUTERZE.');
}
}
function pv1815r3_download(string $bytes,string $filename): never {
if (headers_sent()) throw new RuntimeException('Nie można rozpocząć pobierania, nagłówki HTTP już wysłano.');
header('Content-Type: application/octet-stream');
header('Content-Length: '.strlen($bytes));
header('Content-Disposition: attachment; filename="'.$filename.'"');
header('Cache-Control: no-store, private, max-age=0');
header('Pragma: no-cache');
header('X-Content-Type-Options: nosniff');
echo $bytes;
exit;
}
function pv1815r3_atomic_replace(string $original,string $modified): void {
$file=pv1815r3_target();
$dir=dirname($file);
if (!is_writable($dir)) throw new RuntimeException('PHP nie może zmieniać plików w includes/. Użyj trybu: pobierz poprawiony plik i wgraj przez FileZilla.');
// Detect a concurrent deployment and refuse to overwrite it.
$current=pv1815r3_read();
if (!hash_equals(hash('sha256',$original),hash('sha256',$current))) {
throw new RuntimeException('Plik functions.php zmienił się w trakcie operacji. Niczego nie nadpisano.');
}
$tmp=@tempnam($dir,'.pv1815r3-');
if (!is_string($tmp) || $tmp==='') throw new RuntimeException('Brak zapisu w includes/. Użyj pobrania pliku i FileZilla.');
try {
$h=@fopen($tmp,'wb');
if (!$h) throw new RuntimeException('Nie udało się otworzyć tymczasowego pliku.');
$len=strlen($modified); $off=0;
try {
while ($off<$len) {
$w=fwrite($h,substr($modified,$off,65536));
if (!is_int($w) || $w<1) throw new RuntimeException('Błąd zapisu tymczasowego pliku PHP.');
$off+=$w;
}
fflush($h);
} finally {fclose($h);}
$perms=@fileperms($file);
if ($perms!==false) @chmod($tmp,$perms & 0777);
if (hash_file('sha256',$tmp)!==hash('sha256',$modified)) throw new RuntimeException('Niepoprawna suma SHA-256 pliku tymczasowego.');
if (!@rename($tmp,$file)) {
throw new RuntimeException('Serwer zablokował podmianę functions.php. Wgraj pobraną poprawkę przez FileZilla.');
}
if (hash_file('sha256',$file)!==hash('sha256',$modified)) {
throw new RuntimeException('Niepotwierdzony zapis! Przywróć lokalną kopię functions.php przez FileZilla.');
}
if (function_exists('opcache_invalidate')) @opcache_invalidate($file,true);
} finally { if (is_file($tmp)) @unlink($tmp); }
}
$notice=''; $error='';
if (($_SERVER['REQUEST_METHOD']??'GET')==='POST') {
verify_csrf();
try {
$action=(string)($_POST['action']??'');
$src=pv1815r3_read();
if ($action==='download_original') {
start_session();
$_SESSION[PV1815R3_SESSION]=hash('sha256',$src);
$_SESSION[PV1815R3_SESSION_TIME]=time();
session_write_close();
pv1815r3_download($src,'POLANA_functions_ORIGINAL_'.date('Ymd_His').'.txt');
}
if ($action==='download_patch' || $action==='install') {
pv1815r3_require_files();
pv1815r3_check_local_backup($src);
$modified=pv1815r3_transform($src);
if ($action==='download_patch') {
pv1815r3_download($modified,'functions.php');
}
pv1815r3_atomic_replace($src,$modified);
$notice='Zainstalowano nową nawigację. Lokalną kopię oryginału zachowaj na komputerze do rollbacku.';
} else {
throw new RuntimeException('Nieznana operacja.');
}
} catch (Throwable $e) {
$error=$e->getMessage();
}
}
try {
$src=pv1815r3_read();
$installed=str_contains($src,PV1815R3_MARK);
if ($installed) {$preflight='Menu V18.15 jest aktywne.';}
else {
pv1815r3_require_files();
pv1815r3_transform($src);
$preflight='Obecny functions.php pasuje do bezpiecznej poprawki.';
}
$sha=hash('sha256',$src);
$downloaded=(pv1815r3_saved_hash()!=='' && hash_equals(pv1815r3_saved_hash(),$sha));
$canWrite=is_writable(dirname(pv1815r3_target()));
} catch (Throwable $e) {
$installed=false;$preflight=$e->getMessage();$sha='';$downloaded=false;$canWrite=false;
}
render_admin_header('POLANA V18.15 R3 — Menu bez katalogu backupów');
?>
<style>
.pv1815r3{max-width:970px;margin:0 auto;padding:12px}.pv1815r3 .card{border-radius:16px;background:#fff;border:1px solid #ddd;margin:16px 0;padding:22px}.pv1815r3 .ok{color:#286208}.pv1815r3 .warn{color:#a43d0b}.pv1815r3 button{border:0;border-radius:10px;padding:11px 17px;background:#131313;color:#fff;cursor:pointer}.pv1815r3 button:disabled{opacity:.43;cursor:not-allowed}.pv1815r3 label{display:block;margin:14px 0}.pv1815r3 code{word-break:break-word}.pv1815r3 .grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(280px,1fr));gap:14px}.pv1815r3 small{color:#646464}
</style>
<div class="pv1815r3">
<h1>POLANA V18.15 R3 — Naprawa menu bez V18.3</h1>
<p>Ten wariant <strong>nie wymaga zapisu poza WWW</strong>. Oryginał pobierasz na komputer, bez tworzenia publicznej kopii PHP na hostingu. Nie dotykamy SQL, rezerwacji, SMTP ani Google Calendar.</p>
<?php if($notice):?><div class="card ok"><?=pv1815r3_escape($notice)?></div><?php endif;?>
<?php if($error):?><div class="card warn"><?=pv1815r3_escape($error)?></div><?php endif;?>
<div class="card">
<h2>Stan: <?=$installed?'Zainstalowano':'Nie zainstalowano'?></h2>
<p><?=pv1815r3_escape($preflight)?></p>
<?php if($sha):?><small>SHA-256 aktualnego functions.php: <code><?=pv1815r3_escape($sha)?></code></small><?php endif;?>
<p>Możliwość zapisu przez PHP w <code>includes/</code>: <strong><?=$canWrite?'TAK':'NIE (pozostaje FileZilla)'?></strong></p>
<p><a href="/admin/polana-v1815-diagnostyka.php">Otwórz diagnostykę modułów</a></p>
</div>
<?php if(!$installed && $sha):?>
<div class="grid">
<div class="card"><h2>1. Pobierz oryginał</h2>
<p>Zapisz na komputerze. <strong>Nie wgrywaj pliku ORIGINAL.txt do katalogu strony.</strong> Będzie potrzebny do rollbacku. <strong>Po pobraniu naciśnij F5, aby odblokować krok 2.</strong></p>
<form method="post"><?=csrf_field()?><input type="hidden" name="action" value="download_original"><button>Pobierz oryginalny functions.php</button></form>
<p><small><?=$downloaded?'W tej sesji pobrano oryginał zgodny z aktualną wersją.':'Najpierw pobierz oryginał (wymagane).'?></small></p>
</div>
<div class="card"><h2>2. Popraw menu</h2>
<p>Po zapisaniu oryginału wybierz jedną z dwóch metod. Obie korzystają z aktualnego pliku serwera.</p>
<form method="post">
<?=csrf_field()?>
<label><input type="checkbox" name="backup_confirmed" value="yes" required> Potwierdzam, że oryginał zapisałem na komputerze i mogę go przywrócić przez FileZilla.</label>
<button name="action" value="download_patch" <?=$downloaded?'':'disabled'?>>Pobierz poprawiony functions.php (FileZilla)</button>
<button name="action" value="install" <?=$downloaded&&$canWrite?'':'disabled'?>>Zainstaluj automatycznie</button>
</form>
</div>
</div>
<?php endif;?>
<div class="card"><h2>Rollback</h2><p>W razie awarii wgraj przez FileZilla pobrany wcześniej <code>POLANA_functions_ORIGINAL_....txt</code> jako <code>includes/functions.php</code>. Przywraca to stary nagłówek. Nie zapisujemy dodatkowych plików PHP na publicznym hostingu.</p>
<p>Jeśli wybierzesz metodę ręczną, pobrany <code>functions.php</code> wgraj do <code>includes/</code>, nadpisując istniejący, <strong>dopiero po zapisaniu kopii oryginału</strong>. Wgraj także <code>includes/polana_v1815_nav.php</code> i <code>assets/css/polana-v1815-nav.css</code> z paczki.</p>
</div>
</div>
<?php render_admin_footer(); ?>