| 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 MENU SEO V19.1
* Surgical installer: adds one visual H1 to the active public menu rendering.
* Does not touch QR/SVG, reservations, database, redirects or calendar.
* Copy to /admin/polana-menu-seo-v191.php (authenticated admin only).
*/
require_once dirname(__DIR__) . '/includes/functions.php';
require_admin_role();
header('Cache-Control: private, no-store');
header('X-Robots-Tag: noindex, nofollow');
const PM191_TAG = 'POLANA_MENU_SEO_V191';
const PM191_REL = 'menu.php';
const PM191_VERSION = '19.1';
function pm191_e($value): string { return htmlspecialchars((string)$value, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8'); }
function pm191_root(): string {
$root=realpath(dirname(__DIR__));
if ($root===false || !is_file($root.'/menu.php')) throw new RuntimeException('Nie znaleziono aktywnego katalogu witryny.');
return $root;
}
function pm191_read(string $path): string {
if (!is_file($path) || is_link($path)) throw new RuntimeException('Nieprawidłowy plik źródłowy.');
$value=@file_get_contents($path);
if (!is_string($value)) throw new RuntimeException('Brak możliwości odczytu pliku.');
return $value;
}
function pm191_patch(string $before): string {
if (str_contains($before, PM191_TAG)) throw new RuntimeException('Plik zawiera znacznik V19.1. Instalacja nie nadpisuje istniejącej poprawki.');
$needle=' <?php p15_menu_tabs($menuType); ?>';
if (substr_count($before,$needle)!==1) throw new RuntimeException('menu.php ma inną strukturę niż zatwierdzony wariant. Instalacja wstrzymana.');
// One visible, localized H1 per menu variant; independent of availability of database items.
$insertion=<<<'CODE'
<!-- POLANA_MENU_SEO_V191: H1 for normal menu (including wine and drinks) -->
<?php
$pm191Lang = p15_menu_lang();
$pm191Headings = [
'pl' => [
'food' => 'Menu restauracji Andrzeja Polana w Krakowie',
'wine' => 'Karta win – Moja Polana Smaków w Krakowie',
'soft' => 'Karta napojów – Moja Polana Smaków w Krakowie',
],
'en' => [
'food' => 'Restaurant menu by Andrzej Polan in Krakow',
'wine' => 'Wine list – Moja Polana Smaków, Krakow',
'soft' => 'Drinks menu – Moja Polana Smaków, Krakow',
],
'de' => [
'food' => 'Speisekarte von Andrzej Polan in Krakau',
'wine' => 'Weinkarte – Moja Polana Smaków, Krakau',
'soft' => 'Getränkekarte – Moja Polana Smaków, Krakau',
],
'es' => [
'food' => 'Menú del restaurante de Andrzej Polan en Cracovia',
'wine' => 'Carta de vinos – Moja Polana Smaków, Cracovia',
'soft' => 'Carta de bebidas – Moja Polana Smaków, Cracovia',
],
];
$pm191Heading = $pm191Headings[$pm191Lang][$menuType]
?? $pm191Headings['pl'][$menuType]
?? $pm191Headings['pl']['food'];
?>
<h1 class="menu-v191-heading"><?= e($pm191Heading) ?></h1>
<?php p15_menu_tabs($menuType); ?>
CODE;
$new = str_replace($needle,$insertion,$before);
$cssNeedle=' .menu-v18-head p{';
if (substr_count($new,$cssNeedle)!==1) throw new RuntimeException('Nie rozpoznano stylów menu.php; bez zmian.');
$css=" /* POLANA_MENU_SEO_V191: responsive SEO H1, shown in the normal menu. */\n".
" .menu-v18-head .menu-v191-heading{font-size:clamp(24px,4.1vw,39px);line-height:1.16;\n".
" font-weight:800;letter-spacing:-.02em;max-width:740px;margin:12px auto 18px;\n".
" text-align:center;color:#1c281d;text-wrap:balance;}\n";
$new = str_replace($cssNeedle,$css.$cssNeedle,$new);
if (substr_count($new, '<h1 class="menu-v191-heading">')!==1) throw new RuntimeException('Nie można bezpiecznie wstawić H1.');
return $new;
}
function pm191_private(string $root): string {
$candidates=[];
$helper=$root.'/includes/polana_backup_v182.php';
if (is_file($helper)) {
try {
require_once $helper;
if (function_exists('pbk_private')) $candidates[]=pbk_private();
} catch (Throwable $ignored) { }
}
$candidates[]=dirname($root).'/.polana-private-ops';
$documentRoot=realpath((string)($_SERVER['DOCUMENT_ROOT']??''));
foreach($candidates as $candidate) {
if (!is_string($candidate)||$candidate==='')continue;
if (!is_dir($candidate)&&!@mkdir($candidate,0700,true))continue;
$real=realpath($candidate);
if ($real===false||!is_writable($real))continue;
if ($real===$root||str_starts_with($real.'/', $root.'/'))continue;
if ($documentRoot!==false&&($real===$documentRoot||str_starts_with($real.'/', $documentRoot.'/')))continue;
$folder=$real.'/polana-menu-seo-v191';
if (!is_dir($folder)&&!@mkdir($folder,0700,true))continue;
$actual=realpath($folder);
if ($actual!==false && is_writable($actual)) { @chmod($actual,0700);return $actual; }
}
throw new RuntimeException('Brak prywatnego, zapisywalnego katalogu backupu poza WWW. Instalacja zablokowana.');
}
function pm191_atomic(string $path,string $value): void {
if (!is_file($path)||is_link($path))throw new RuntimeException('Nieprawidłowy plik do zapisania.');
$tmp=@tempnam(dirname($path),'.pm191-');
if ($tmp===false)throw new RuntimeException('Brak możliwości utworzenia pliku tymczasowego.');
try {
$n=@file_put_contents($tmp,$value,LOCK_EX);
if ($n===false||$n!==strlen($value))throw new RuntimeException('Niepełny zapis pliku tymczasowego.');
@chmod($tmp,fileperms($path)&0777);
if (!@rename($tmp,$path))throw new RuntimeException('Nie udało się atomowo podmienić menu.php.');
} finally {if(is_file($tmp))@unlink($tmp);}
}
function pm191_json(string $path,array $manifest): void {
$data=json_encode($manifest,JSON_UNESCAPED_UNICODE|JSON_UNESCAPED_SLASHES|JSON_PRETTY_PRINT|JSON_THROW_ON_ERROR);
$tmp=@tempnam(dirname($path),'.pm191-json-');
if ($tmp===false)throw new RuntimeException('Nie można przygotować manifestu.');
try {
if (@file_put_contents($tmp,$data,LOCK_EX)!==strlen($data))throw new RuntimeException('Niepełny zapis manifestu.');
@chmod($tmp,0600);
if (!@rename($tmp,$path))throw new RuntimeException('Błąd zapisu manifestu.');
} finally {if (is_file($tmp))@unlink($tmp);}
}
function pm191_load(string $path): ?array {
if (!is_file($path))return null;
$data=json_decode(pm191_read($path),true);
if (!is_array($data)||($data['version']??'')!==PM191_VERSION)throw new RuntimeException('Manifest V19.1 jest uszkodzony.');
return $data;
}
function pm191_lock(string $folder) {
$fp=@fopen($folder.'/install.lock','c+');
if ($fp===false||!flock($fp,LOCK_EX|LOCK_NB))throw new RuntimeException('Inna operacja V19.1 trwa.');
return $fp;
}
function pm191_install(string $root,string $folder): string {
$lock=pm191_lock($folder);
$file=$root.'/'.PM191_REL;
$before='';$wrote=false;$backup='';$manifest='';
try {
if (is_file($folder.'/active.json'))throw new RuntimeException('Instalacja jest już aktywna. Nie nadpisujemy jej.');
$before=pm191_read($file);
$after=pm191_patch($before);
if (!is_writable($file)||!is_writable(dirname($file)))throw new RuntimeException('Brak prawa zapisu do menu.php.');
$stamp=date('Ymd-His').'-'.bin2hex(random_bytes(4));
$backup=$folder.'/'.$stamp;
if (!@mkdir($backup,0700))throw new RuntimeException('Nie można utworzyć katalogu kopii.');
$original=$backup.'/menu.php.original';
if (@file_put_contents($original,$before,LOCK_EX)!==strlen($before))throw new RuntimeException('Nie można zapisać kopii źródłowej.');
@chmod($original,0600);
if (hash_file('sha256',$original)!==hash('sha256',$before))throw new RuntimeException('Weryfikacja SHA256 kopii nie powiodła się.');
$manifest=['version'=>PM191_VERSION,'created_at'=>date('c'),'backup_dir'=>$stamp,
'before_sha256'=>hash('sha256',$before),'after_sha256'=>hash('sha256',$after)];
pm191_json($backup.'/manifest.json',$manifest);
// Revalidate on a busy shared host so that foreign changes are not overwritten.
if (hash_file('sha256',$file)!==$manifest['before_sha256'])throw new RuntimeException('menu.php zmienił się podczas przygotowania instalacji; nic nie nadpisano.');
pm191_atomic($file,$after);$wrote=true;
if (hash_file('sha256',$file)!==$manifest['after_sha256'])throw new RuntimeException('Kontrola zapisu po instalacji nie powiodła się.');
pm191_json($folder.'/active.json',$manifest);
try {admin_log('polana_menu_seo_v191_install','H1 menu food/wine/soft');}catch(Throwable $ignored){}
return 'Zainstalowano V19.1. Zmieniono wyłącznie menu.php; backup w prywatnym katalogu: '.$stamp.'.';
} catch(Throwable $e) {
if($wrote) {
try {pm191_atomic($file,$before);}catch(Throwable $restoreError){error_log('POLANA V19.1 CRITICAL AUTO RESTORE: '.$restoreError->getMessage());}
}
throw new RuntimeException('Instalacja przerwana: '.$e->getMessage());
} finally {flock($lock,LOCK_UN);fclose($lock);}
}
function pm191_rollback(string $root,string $folder): string {
$lock=pm191_lock($folder);
$file=$root.'/'.PM191_REL;
try {
$m=pm191_load($folder.'/active.json');
if ($m===null)throw new RuntimeException('Brak aktywnej instalacji V19.1.');
$current=pm191_read($file);
if (hash('sha256',$current)!==($m['after_sha256']??''))throw new RuntimeException('menu.php został zmieniony od instalacji. Rollback zablokowany, żeby nie skasować nowszych zmian.');
$stamp=(string)($m['backup_dir']??'');
if (!preg_match('/^\d{8}-\d{6}-[a-f0-9]{8}$/',$stamp))throw new RuntimeException('Nieprawidłowa ścieżka kopii.');
$original=pm191_read($folder.'/'.$stamp.'/menu.php.original');
if (hash('sha256',$original)!==($m['before_sha256']??''))throw new RuntimeException('Kopia backupu jest uszkodzona.');
pm191_atomic($file,$original);
try {pm191_json($folder.'/'.$stamp.'/rollback.json',['rolled_back_at'=>date('c'),'version'=>PM191_VERSION]);}catch(Throwable $ignored){}
// Successful file restore takes priority. If unlink is blocked, status will clearly show a stale manifest.
if (!@unlink($folder.'/active.json'))throw new RuntimeException('Plik menu.php cofnięto, ale nie można usunąć manifestu active.json. Zgłoś administratorowi.');
try {admin_log('polana_menu_seo_v191_rollback','Przywrócono menu.php');}catch(Throwable $ignored){}
return 'Rollback V19.1 zakończony. Przywrócono wcześniejszy menu.php. V19.0 i inne moduły bez zmian.';
} finally {flock($lock,LOCK_UN);fclose($lock);}
}
$root=pm191_root();$error='';$notice='';$private=null;$active=null;$status='';
try {
$private=pm191_private($root);
if (($_SERVER['REQUEST_METHOD']??'GET')==='POST') {
verify_csrf();
$action=(string)($_POST['action']??'');
if ($action==='install') $notice=pm191_install($root,$private);
elseif ($action==='rollback') $notice=pm191_rollback($root,$private);
else throw new RuntimeException('Nieznana operacja.');
}
$active=pm191_load($private.'/active.json');
$current=pm191_read($root.'/menu.php');
if ($active!==null) {
$status=hash('sha256',$current)===($active['after_sha256']??'')?'AKTYWNA / zgodna':'UWAGA: plik został zmieniony po instalacji';
}elseif(str_contains($current,PM191_TAG)){
$status='Znacznik V19.1 obecny bez aktywnego manifestu — sprawdź ręcznie';
} else {
pm191_patch($current);
$status='GOTOWE — źródło zgodne z V18.8 (bez zapisu)';
}
} catch(Throwable $e){$error=$e->getMessage();}
render_admin_header('POLANA MENU SEO V19.1 — H1 menu');
?>
<style>
.pm191{max-width:900px;margin:20px auto 60px;padding:0 16px}.pm191 section{padding:20px;border:1px solid #dde2db;border-radius:14px;margin:15px 0;background:#fff}.pm191 h1{font-size:30px}.pm191 h2{font-size:21px}.pm191 p{line-height:1.6}.pm191 .ok{background:#e9f7e9;padding:13px;border-radius:9px}.pm191 .err{background:#fff0ee;padding:13px;color:#922c1f;border-radius:9px}.pm191 button{border:0;border-radius:9px;background:#204f34;color:#fff;padding:12px 20px;cursor:pointer;font-weight:700}.pm191 button.danger{background:#9d2b2b}.pm191 button:disabled{opacity:.5;cursor:not-allowed}.pm191 a{text-decoration:underline;color:#20543c}.pm191 small{color:#555}
</style>
<div class="pm191">
<h1>POLANA SEO V19.1 — nagłówki menu</h1>
<p>Naprawiamy brak H1 na <a href="/menu" target="_blank" rel="noopener">/menu</a> i <a href="/menu?kind=wine" target="_blank" rel="noopener">/menu?kind=wine</a> oraz wersji z napojami. Bez zmian w bazie, formularzach, SVG, kalendarzu i przekierowaniach.</p>
<section>
<h2>Stan instalacji</h2>
<p><strong><?= pm191_e($status?:'Weryfikacja niedostępna') ?></strong></p>
<?php if($notice):?><p class="ok"><?=pm191_e($notice)?></p><?php endif;?>
<?php if($error):?><p class="err"><?=pm191_e($error)?></p><?php endif;?>
<p><small>Źródło: menu.php · prywatny backup poza WWW · sprawdzanie SHA256 · instalacja z kontrolą CSRF</small></p>
<?php if(!$error && $active===null):?><form method="post"><?=csrf_field()?><input type="hidden" name="action" value="install"><button type="submit">Zainstaluj V19.1 z backupem</button></form><?php endif;?>
<?php if(!$error && $active!==null):?><form method="post"><?=csrf_field()?><input type="hidden" name="action" value="rollback"><button type="submit" class="danger" <?=str_starts_with($status,'AKTYWNA')?'':'disabled'?>>Rollback V19.1</button></form><?php endif;?>
</section>
<section><h2>Oczekiwane nagłówki</h2>
<ul><li>/menu — Menu restauracji Andrzeja Polana w Krakowie</li>
<li>/menu?kind=wine — Karta win – Moja Polana Smaków w Krakowie</li>
<li>/menu?kind=soft — Karta napojów – Moja Polana Smaków w Krakowie</li></ul>
<p><a href="/admin/polana-seo-audyt-v190.php">Otwórz audyt SEO V19.0</a> · <a href="/admin/polana-seo-v190.php">Otwórz SEO V19.0</a></p>
</section>
</div>
<?php render_admin_footer(); ?>