| 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 V20 — authenticated, conservative, reversible installer. */
require_once dirname(__DIR__).'/includes/functions.php';
require_once dirname(__DIR__).'/includes/polana_v20_opening_hours.php';
require_admin_role();
header('Cache-Control: private, no-store');
header('X-Robots-Tag: noindex,nofollow');
function pv20_root(): string { return dirname(__DIR__); }
function pv20_target_paths(): array {
$r=pv20_root();
return ['includes/polana_v175_booking.php'=>$r.'/includes/polana_v175_booking.php',
'admin/polana-godziny-rezerwacji.php'=>$r.'/admin/polana-godziny-rezerwacji.php'];
}
function pv20_once(string $source,string $needle,string $replacement,string $path):string {
if(substr_count($source,$needle)!==1) throw new RuntimeException('Struktura '.$path.' jest inna od oczekiwanej (' . substr($needle,0,70) . ' / ' . substr_count($source,$needle) . '); nic nie zmieniono.');
return str_replace($needle,$replacement,$source);
}
function pv20_patch_booking(string $s): string {
if(str_contains($s,'/* POLANA_V20_OPENING_HOURS */')) return $s;
if(!str_contains($s,'function pv175_available_slots(') || !str_contains($s,'function pv175_validate_slot('))
throw new RuntimeException('Brak istniejącej walidacji terminów V17.5.');
$s=pv20_once($s,"declare(strict_types=1);\n", "declare(strict_types=1);\n/* POLANA_V20_OPENING_HOURS */\nrequire_once __DIR__.'/polana_v20_opening_hours.php';\n", 'booking.php');
$s=pv20_once($s,'if (!is_array($stored)) return $cfg;','if (!is_array($stored)) $stored = [];','booking.php');
$p=strpos($s,'function pv175_config(): array {');
$q=strpos($s,'function pv175_enabled(): bool', $p===false?0:$p);
if($p===false||$q===false) throw new RuntimeException('Nie rozpoznano konfiguracji V17.5.');
$part=substr($s,$p,$q-$p);
$part=pv20_once($part,' return $cfg;', ' return pv20_apply_opening_hours($cfg);', 'booking.php/config');
$s=substr($s,0,$p).$part.substr($s,$q);
$s=pv20_once($s,"function pv175_enabled(): bool { return setting('polana_v175_booking_enabled', '0') === '1'; }", "function pv175_enabled(): bool { return pv20_source_present() || setting('polana_v175_booking_enabled', '0') === '1'; }", 'booking.php/enabled');
$s=pv20_once($s," \$cfg = pv175_config();\n \$window = pv175_day_window(\$date, \$cfg);", " \$cfg = pv175_config();\n if (!empty(\$cfg['_opening_hours_error'])) return ['slots'=>[], 'message'=>'Trwa aktualizacja godzin otwarcia. Skontaktuj się z restauracją lub wybierz termin później.'];\n \$window = pv175_day_window(\$date, \$cfg);", 'booking.php/slots');
return $s;
}
function pv20_patch_admin(string $s): string {
if(str_contains($s,'<!-- POLANA_V20_ADMIN_SYNC -->'))return $s;
if(!str_contains($s,'pv175_valid_window')||!str_contains($s,'pv175_config()')) throw new RuntimeException('Panel godzin ma inną strukturę.');
$s=pv20_once($s," \$cfg['duration']=(int)(\$_POST['duration']??90);", " // The website's opening_hours is canonical; preserve it on form submit.\n if (pv20_source_present()) {\n \$canonical=pv20_opening_status();\n if (\$canonical['errors']) throw new RuntimeException('Popraw godziny w Ustawieniach: '.implode(' ',\$canonical['errors']));\n \$cfg['days']=\$canonical['days'];\n \$openDays=count(array_filter(\$cfg['days'],'pv175_valid_window'));\n }\n \$cfg['duration']=(int)(\$_POST['duration']??90);", 'panel/zapis');
$s=pv20_once($s," \$enabled=!empty(\$_POST['enabled']);", " \$enabled=pv20_source_present() ? true : !empty(\$_POST['enabled']);", 'panel/aktywacja');
$s=pv20_once($s, " if (\$enabled && \$openDays===0 && !array_filter(\$cfg['exceptions'],'pv175_valid_window'))", " if (\$enabled && !pv20_source_present() && \$openDays===0 && !array_filter(\$cfg['exceptions'],'pv175_valid_window'))", 'panel/zamkniete');
$s=pv20_once($s,"\$cfg=pv175_config();\n\$enabled=pv175_enabled();", "\$cfg=pv175_config();\n\$enabled=pv175_enabled();\n\$pv20Auto=pv20_source_present();\n\$pv20Status=pv20_opening_status();", 'panel/zrodlo');
$s=pv20_once($s," <p>Godziny ustawiasz osobno na każdy dzień. Niezaznaczony dzień oznacza brak rezerwacji online.</p>", " <!-- POLANA_V20_ADMIN_SYNC -->\n <?php if (\$pv20Auto): ?>\n <div style=\"padding:15px 17px;margin:12px 0 18px;border:1px solid #b2c6a3;background:#f1f6ec;border-radius:12px\"><strong>Automatycznie z godzin otwarcia</strong><p style=\"margin:7px 0\">Godziny pobieramy z <a href=\"/admin/settings.php\">Ustawienia → Godziny otwarcia</a>. Poniżej wyświetla się aktualny plan tylko do podglądu. Zmiany godzin wprowadza się w ustawieniach strony, bez ponownego instalowania. Wyjątki i czas stolika można nadal zmieniać tutaj.</p>\n <?php if (!empty(\$pv20Status['errors'])): ?><strong style=\"color:#9f2929\">Popraw format godzin w Ustawieniach:</strong><ul><?php foreach (\$pv20Status['errors'] as \$err): ?><li><?= e(\$err) ?></li><?php endforeach; ?></ul><?php endif; ?></div>\n <?php else: ?>\n <p>Godziny ustawiasz osobno na każdy dzień. Niezaznaczony dzień oznacza brak rezerwacji online.</p>\n <?php endif; ?>", 'panel/opis');
$s=pv20_once($s,' <div class="pv175-days">', ' <?php if ($pv20Auto): ?><fieldset disabled style="border:0;padding:0;margin:0;min-width:0"><legend style="font-size:13px;color:#637063;margin-bottom:10px">Godziny odczytane z ustawień strony</legend><?php endif; ?>' . "\n" . ' <div class="pv175-days">', 'panel/pola');
$s=pv20_once($s," </div>\n <div class=\"pv175-grid\" style=\"margin:22px 0\">", " </div>\n <?php if (\$pv20Auto): ?></fieldset><?php endif; ?>\n <div class=\"pv175-grid\" style=\"margin:22px 0\">", 'panel/fieldset');
$marker='<label class="pv175-on" style="margin:20px 0">';
$s=pv20_once($s,$marker, '<?php if (!$pv20Auto): ?>' . "\n " .$marker, 'panel/blokada');
$needle=" <button class=\"pv175-btn\" type=\"submit\">Zapisz godziny i ustawienia</button>";
$replacement=' <?php else: ?><div class="pv175-on" style="margin:20px 0"><strong>Blokada godzin aktywna automatycznie</strong> — terminy spoza harmonogramu są odrzucane także po ręcznym wysłaniu formularza.</div><?php endif; ?>' . "\n" . $needle;
$s=pv20_once($s,$needle,$replacement,'panel/status');
return $s;
}
function pv20_changes():array {
$changes=[];
foreach(pv20_target_paths() as $rel=>$path) {
if(!is_file($path)||!is_readable($path)||!is_writable($path)) throw new RuntimeException('Brak dostępu zapisu do '.$rel);
$old=@file_get_contents($path);
if(!is_string($old)) throw new RuntimeException('Błąd odczytu '.$rel);
$lf=str_replace("\r\n","\n",$old);
$new=$rel==='includes/polana_v175_booking.php'?pv20_patch_booking($lf):pv20_patch_admin($lf);
if(str_contains($old,"\r\n")) $new=str_replace("\n","\r\n",$new);
if($new!==$old)$changes[$rel]=['path'=>$path,'before'=>$old,'after'=>$new];
}
return $changes;
}
function pv20_backup_base():string {return dirname(pv20_root()).'/.polana_v20_private_backups';}
function pv20_write_atomic(string $path,string $data):void {
$tmp=$path.'.pv20_'.bin2hex(random_bytes(6)).'.tmp';
if(@file_put_contents($tmp,$data,LOCK_EX)!==strlen($data)) { @unlink($tmp);throw new RuntimeException('Błąd zapisu '.basename($path)); }
$mode=@fileperms($path); if($mode!==false)@chmod($tmp,$mode&0777);
if(!@rename($tmp,$path)) { @unlink($tmp);throw new RuntimeException('Błąd podmiany '.basename($path)); }
if(function_exists('opcache_invalidate'))@opcache_invalidate($path,true);
}
function pv20_install():string {
$s=pv20_opening_status();
if(!$s['active'])throw new RuntimeException('W Ustawieniach strony brakuje godzin otwarcia. Uzupełnij je przed instalacją.');
if($s['errors'])throw new RuntimeException('Nie można zainstalować: '.implode(' ',$s['errors']));
$changes=pv20_changes();
if(!$changes) return 'Synchronizacja V20 jest już zainstalowana.';
$base=pv20_backup_base();
if(!is_dir($base)&&!@mkdir($base,0700,true))throw new RuntimeException('Nie można utworzyć katalogu kopii poza WWW.');
@chmod($base,0700);
@file_put_contents($base.'/.htaccess',"Require all denied\nDeny from all\n");
@file_put_contents($base.'/index.html','');
$lock=@fopen($base.'/.install.lock','c');
if(!$lock||!@flock($lock,LOCK_EX|LOCK_NB))throw new RuntimeException('Trwa inna instalacja.');
$dir=$base.'/'.date('Ymd_His').'_'.bin2hex(random_bytes(4));
if(!@mkdir($dir,0700))throw new RuntimeException('Nie można utworzyć katalogu kopii.');
$manifest=['version'=>'20','created_at'=>date(DATE_ATOM),'rolled_back'=>false,'files'=>[]];
foreach($changes as $rel=>$item) {
$file=str_replace('/','__',$rel).'.bak';
if(@file_put_contents($dir.'/'.$file,$item['before'],LOCK_EX)!==strlen($item['before']))throw new RuntimeException('Nie zapisano kopii '.$rel);
$manifest['files'][$rel]=['backup'=>$file,'before_sha256'=>hash('sha256',$item['before']),'after_sha256'=>hash('sha256',$item['after'])];
}
$manifestFile=$dir.'/manifest.json';
if(@file_put_contents($manifestFile,json_encode($manifest,JSON_PRETTY_PRINT|JSON_THROW_ON_ERROR),LOCK_EX)===false)throw new RuntimeException('Brak manifestu kopii.');
$written=[];
try {
foreach($changes as $rel=>$item){pv20_write_atomic($item['path'],$item['after']);$written[]=$rel;}
}catch(Throwable $ex) {
foreach(array_reverse($written) as $rel){ try{pv20_write_atomic($changes[$rel]['path'],$changes[$rel]['before']);}catch(Throwable $err){error_log('POLANA V20 AUTO ROLLBACK: '.$err->getMessage());}}
$manifest['rolled_back']=true;
@file_put_contents($manifestFile,json_encode($manifest,JSON_PRETTY_PRINT),LOCK_EX);
throw $ex;
}finally{ @flock($lock,LOCK_UN);@fclose($lock); }
return 'Zainstalowano V20. Godziny pobierane są z Ustawień; nie zmieniono bazy rezerwacji, SMTP ani Google Calendar. Kopia: '.basename($dir).'.';
}
function pv20_rollback():string {
$manifests=glob(pv20_backup_base().'/*/manifest.json')?:[];
rsort($manifests,SORT_STRING);
foreach($manifests as $mf){
$m=json_decode((string)@file_get_contents($mf),true);
if(!is_array($m)||($m['version']??'')!=='20'||!empty($m['rolled_back']))continue;
$restore=[];$paths=pv20_target_paths();
foreach($m['files'] as $rel=>$info){
if(!isset($paths[$rel]))throw new RuntimeException('Nieznany plik w kopii.');
$current=@file_get_contents($paths[$rel]);
if(!is_string($current)||!hash_equals((string)$info['after_sha256'],hash('sha256',$current)))throw new RuntimeException('Od instalacji zmienił się '.$rel.' — zatrzymano rollback, aby nie utracić nowszych zmian.');
$previous=@file_get_contents(dirname($mf).'/'.basename((string)$info['backup']));
if(!is_string($previous)||!hash_equals((string)$info['before_sha256'],hash('sha256',$previous)))throw new RuntimeException('Uszkodzona kopia '.$rel);
$restore[$rel]=$previous;
}
foreach($restore as $rel=>$data)pv20_write_atomic($paths[$rel],$data);
$m['rolled_back']=true;
@file_put_contents($mf,json_encode($m,JSON_PRETTY_PRINT|JSON_UNESCAPED_UNICODE|JSON_THROW_ON_ERROR),LOCK_EX);
return 'Przywrócono poprzednie pliki. Dane rezerwacji i ustawienia pozostały nietknięte.';
}
throw new RuntimeException('Brak aktywnej kopii V20 do przywrócenia.');
}
if(($_SERVER['REQUEST_METHOD']??'')==='POST'){
verify_csrf();
try{
$action=(string)($_POST['action']??'');
if($action==='install')$msg=pv20_install();
elseif($action==='rollback' && ($_POST['confirm_rollback']??'')==='1')$msg=pv20_rollback();
else throw new RuntimeException('Nieprawidłowa operacja.');
flash('success',$msg);
}catch(Throwable $e){flash('error',$e->getMessage());}
redirect('/admin/polana-v20-godziny-instalator.php');
}
$plan=pv20_opening_status();
try {$changes=pv20_changes();$installStatus=$changes?'Możliwa instalacja — '.count($changes).' pliki do zmiany':'Zainstalowana';}
catch(Throwable $e){$installStatus='Wymaga uwagi: '.$e->getMessage();}
$canInstall=$plan['active'] && empty($plan['errors']) && !str_starts_with($installStatus,'Wymaga uwagi');
$dayLabels=['Poniedziałek','Wtorek','Środa','Czwartek','Piątek','Sobota','Niedziela'];
render_admin_header('Polana V20 — godziny rezerwacji');
?>
<div style="max-width:900px;margin:28px auto;padding:0 14px">
<div class="form-card" style="padding:25px;border-radius:16px;margin-bottom:20px">
<h1>Polana Smaków · Godziny rezerwacji V20</h1>
<p>Synchronizacja z godzinami wpisanymi w <a href="/admin/settings.php">Ustawieniach strony</a>. Po zmianie godzin rezerwacje aktualizują się automatycznie, bez instalowania ponownie. Zachowujemy Google Calendar, pocztę i istniejące rezerwacje.</p>
<p><strong>Stan:</strong> <?= e($installStatus) ?></p>
<?php if (!$plan['active']): ?><p style="color:#a83228">Brak godzin w głównych Ustawieniach strony. Wpisz je przed instalacją.</p><?php endif; ?>
<?php if ($plan['errors']): ?><div style="background:#fff3ed;padding:14px;border-radius:10px"><strong>Nie można odczytać godzin:</strong><ul><?php foreach($plan['errors'] as $err): ?><li><?= e($err) ?></li><?php endforeach; ?></ul><p>Zalecany format: <code>Pon: nieczynne</code>, <code>Wt–Czw: 15:00–21:00</code>, <code>Pt–Sob: 13:00–22:00</code>, <code>Nd: 13:00–20:00</code>.</p></div><?php endif; ?>
<h2>Podgląd godzin otwarcia</h2>
<div style="display:grid;gap:7px;margin:14px 0 20px">
<?php foreach($dayLabels as $idx=>$day):$window=$plan['days'][(string)($idx+1)]??null; ?>
<div style="padding:9px 13px;background:#f4f5f0;border-radius:8px;display:flex;justify-content:space-between"><strong><?=e($day)?></strong><span><?=is_array($window)?e($window['open'].'–'.$window['close']):'Nieczynne / brak godzin'?></span></div>
<?php endforeach; ?>
</div>
<form method="post"><?=csrf_field()?><input type="hidden" name="action" value="install"><button class="btn btn-primary" type="submit" <?= $canInstall?'':'disabled' ?>>Zainstaluj synchronizację z kopią i rollbackiem</button></form>
<p style="font-size:13px;margin-top:12px">Uwaga: ostatni termin zależy od czasu zajęcia stolika (domyślnie 90 minut), który można zmienić w ustawieniach rezerwacji.</p>
</div>
<div class="form-card" style="padding:24px;border-radius:16px;margin-bottom:20px">
<h2>Przywróć poprzednią wersję</h2><p>Rollback przywraca tylko pliki zmienione podczas instalacji i jest blokowany, jeśli pliki edytowano później.</p>
<form method="post"><?=csrf_field()?><input type="hidden" name="action" value="rollback"><label style="display:block;margin:12px 0"><input type="checkbox" name="confirm_rollback" value="1" required> Potwierdzam cofnięcie V20</label><button class="btn" type="submit">Przywróć poprzednie pliki</button></form>
<p><a href="/admin/polana-godziny-rezerwacji.php">Wyjątki, dni świąteczne i czas stolika</a> · <a href="/rezerwacja" target="_blank" rel="noopener">Sprawdź formularz rezerwacji</a></p>
</div>
</div>
<?php render_admin_footer(); ?>