| Server IP : 188.114.97.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_v1801_rollback/ |
Upload File : |
<?php
declare(strict_types=1);
/* POLANA_V11_LANG_INSTALL */
require_once __DIR__.'/polana_v11_lang.php';
/* POLANA_OPINIE_SIMPLE_INCLUDE_V2 */
require_once __DIR__ . '/opinie_simple.php';
/* POLANA_NEWSLETTER_INCLUDE_V1_1 */
require_once __DIR__ . '/newsletter.php';
require_once __DIR__ . '/db.php';
require_once __DIR__ . '/global_splash.php';
require_once __DIR__ . '/pot_visual.php';
require_once __DIR__ . '/seasonal_visual.php';
require_once __DIR__ . '/seasonal_illustration.php';
require_once __DIR__ . '/social.php';
require_once __DIR__ . '/smtp.php';
require_once __DIR__ . '/reservations.php';
require_once __DIR__ . '/schedule.php';
require_once __DIR__ . '/operations.php';
require_once __DIR__ . '/blog.php';
require_once __DIR__ . '/staff_portal.php';
require_once __DIR__ . '/tips.php';
require_once __DIR__ . '/tips_kiosk.php';
require_once __DIR__ . '/tips_import.php';
require_once __DIR__ . '/polana_domain.php';
require_once __DIR__ . '/polana_i18n.php';
function e(?string $value): string
{
return htmlspecialchars((string)$value, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
}
function session_lifetime_seconds(): int
{
return 30 * 24 * 60 * 60; // 30 dni
}
function start_session(): void
{
if (session_status() === PHP_SESSION_ACTIVE) {
return;
}
$lifetime = session_lifetime_seconds();
// Własny katalog sesji jest stabilniejszy na hostingu współdzielonym.
$sessionDir = dirname(__DIR__) . '/storage/sessions';
if (!is_dir($sessionDir)) {
@mkdir($sessionDir, 0700, true);
}
if (is_dir($sessionDir) && is_writable($sessionDir)) {
session_save_path($sessionDir);
}
// Trzymamy sesję także po zamknięciu przeglądarki.
// Ustawienia są przed session_start(), żeby hosting nie skrócił sesji do domyślnego czasu.
@ini_set('session.gc_maxlifetime', (string)$lifetime);
@ini_set('session.cookie_lifetime', (string)$lifetime);
@ini_set('session.use_strict_mode', '1');
session_name('POLANA_ADMIN');
$secure = (
(!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off') ||
(($_SERVER['HTTP_X_FORWARDED_PROTO'] ?? '') === 'https') ||
(($_SERVER['HTTP_X_FORWARDED_SSL'] ?? '') === 'on')
);
session_set_cookie_params([
'lifetime' => $lifetime,
'path' => '/',
'secure' => $secure,
'httponly' => true,
'samesite' => 'Lax',
]);
session_start();
// Sliding session: każde normalne użycie panelu przesuwa ważność o kolejne 30 dni.
if (session_status() === PHP_SESSION_ACTIVE) {
$now = time();
$lastRefresh = (int)($_SESSION['_cookie_refreshed_at'] ?? 0);
if ($lastRefresh < ($now - 3600)) {
setcookie(session_name(), session_id(), [
'expires' => $now + $lifetime,
'path' => '/',
'secure' => $secure,
'httponly' => true,
'samesite' => 'Lax',
]);
$_SESSION['_cookie_refreshed_at'] = $now;
}
}
}
function setting(string $key, string $default = ''): string
{
static $cache = null;
if ($cache === null) {
$cache = [];
try {
$rows = db()->query('SELECT setting_key, setting_value FROM settings')->fetchAll();
foreach ($rows as $row) {
$cache[$row['setting_key']] = (string)($row['setting_value'] ?? '');
}
} catch (Throwable $e) {
return $default;
}
}
return array_key_exists($key, $cache) ? $cache[$key] : $default;
}
function clear_settings_cache(): void
{
// Cache is per-request, so nothing is required here.
}
function current_scheme(): string
{
if (
(!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off') ||
(($_SERVER['HTTP_X_FORWARDED_PROTO'] ?? '') === 'https')
) {
return 'https';
}
return 'http';
}
function site_url(string $path = ''): string
{
// One explicit deployment setting prevents stale DB site_url values from
// leaking the previous domain into canonical, hreflang, RSS, emails or assets.
$forced = defined('POLANA_PUBLIC_ORIGIN') ? rtrim(trim((string)POLANA_PUBLIC_ORIGIN), '/') : '';
if ($forced !== '' && filter_var($forced, FILTER_VALIDATE_URL)) {
$base = $forced;
} else {
$configured = trim(setting('site_url', ''));
if ($configured !== '') {
$base = rtrim($configured, '/');
} else {
$host = $_SERVER['HTTP_HOST'] ?? 'localhost';
$base = current_scheme() . '://' . $host;
}
}
if ($path === '') {
return $base;
}
return $base . '/' . ltrim($path, '/');
}
function asset(string $path): string
{
return site_url('assets/' . ltrim($path, '/'));
}
function asset_v(string $path): string
{
$clean = ltrim($path, '/');
$absolute = dirname(__DIR__) . '/assets/' . $clean;
$version = is_file($absolute) ? (string)filemtime($absolute) : (string)time();
return asset($clean) . '?v=' . rawurlencode($version);
}
function redirect(string $url): never
{
header('Location: ' . $url);
exit;
}
function csrf_token(): string
{
start_session();
if (empty($_SESSION['csrf_token'])) {
$_SESSION['csrf_token'] = bin2hex(random_bytes(32));
}
return $_SESSION['csrf_token'];
}
function csrf_field(): string
{
return '<input type="hidden" name="csrf_token" value="' . e(csrf_token()) . '">';
}
function verify_csrf(): void
{
start_session();
$sent = $_POST['csrf_token'] ?? '';
if (!is_string($sent) || !hash_equals($_SESSION['csrf_token'] ?? '', $sent)) {
http_response_code(419);
exit('Sesja formularza wygasła. Wróć do poprzedniej strony i spróbuj ponownie.');
}
}
function flash(string $type, string $message): void
{
start_session();
$_SESSION['flash'][] = ['type' => $type, 'message' => $message];
}
function consume_flashes(): array
{
start_session();
$flashes = $_SESSION['flash'] ?? [];
unset($_SESSION['flash']);
return is_array($flashes) ? $flashes : [];
}
function is_logged_in(): bool
{
start_session();
return !empty($_SESSION['user_id']);
}
function session_mark_login(): void
{
start_session();
$now = time();
$_SESSION['_login_at'] = $now;
$_SESSION['_cookie_refreshed_at'] = $now;
}
function session_forget_cookie(): void
{
$secure = (
(!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off') ||
(($_SERVER['HTTP_X_FORWARDED_PROTO'] ?? '') === 'https') ||
(($_SERVER['HTTP_X_FORWARDED_SSL'] ?? '') === 'on')
);
setcookie('POLANA_ADMIN', '', [
'expires' => time() - 3600,
'path' => '/',
'secure' => $secure,
'httponly' => true,
'samesite' => 'Lax',
]);
}
function site_public_mode(): string
{
return setting('site_public_mode', 'normal');
}
function public_preview_is_logged_in(): bool
{
// Dla zwykłego gościa nie uruchamiamy sesji PHP. Dopiero jeśli istnieje
// cookie panelu, sprawdzamy prawdziwą sesję użytkownika.
if (empty($_COOKIE['POLANA_ADMIN'])) {
return false;
}
start_session();
return !empty($_SESSION['user_id']);
}
function render_global_site_splash_if_needed(): void
{
if (site_public_mode() !== 'splash') {
return;
}
// Zachowanie jak maintenance mode w WordPressie:
// zalogowany widzi stronę, niezalogowany widzi zaślepkę.
if (public_preview_is_logged_in()) {
return;
}
render_global_splash_page();
}
function public_splash_preview_active(): bool
{
return site_public_mode() === 'splash' && public_preview_is_logged_in();
}
function require_login(): void
{
if (!is_logged_in()) {
redirect('/admin/login.php');
}
$user = admin_user();
$role = (string)($user['role'] ?? '');
$scriptName = (string)($_SERVER['SCRIPT_NAME'] ?? '');
if ($role === 'manager' && str_starts_with($scriptName, '/admin/')) {
$allowed = [
'index.php', 'schedule.php', 'schedule-day.php',
'reservations.php', 'reservation.php', 'reservations-day.php',
'tips.php', 'tips-export.php', 'tips-import.php', 'tips-template.php',
'tips-photo.php', 'tips-photo-file.php', 'tips-kiosk.php', 'today.php', 'manager-report.php', 'logout.php'
];
$script = basename($scriptName);
if ($script !== '' && !in_array($script, $allowed, true)) {
http_response_code(403);
exit('Konto managera ma dostęp do grafiku, rezerwacji i napiwków.');
}
}
if (in_array($role, ['waiter', 'kitchen'], true) && str_starts_with($scriptName, '/admin/')) {
$allowed = $role === 'waiter'
? ['index.php', 'reservations.php', 'reservation.php', 'reservations-day.php', 'tips.php', 'tips-export.php', 'schedule.php', 'logout.php']
: ['index.php', 'tips.php', 'tips-export.php', 'schedule.php', 'logout.php'];
$script = basename($scriptName);
if ($script !== '' && !in_array($script, $allowed, true)) {
http_response_code(403);
exit(
$role === 'waiter'
? 'Konto kelnera ma dostęp tylko do rezerwacji, grafiku i napiwków.'
: 'Konto kuchni ma dostęp tylko do grafiku i napiwków.'
);
}
}
}
function require_admin_role(): void
{
require_login();
$user = admin_user();
if (!$user || ($user['role'] ?? '') !== 'admin') {
http_response_code(403);
exit('Brak uprawnień administratora.');
}
}
function admin_user(): ?array
{
if (!is_logged_in()) {
return null;
}
static $user = null;
if ($user === null) {
$stmt = db()->prepare('SELECT id, username, name, role FROM users WHERE id = ? AND active = 1 LIMIT 1');
$stmt->execute([(int)$_SESSION['user_id']]);
$user = $stmt->fetch() ?: false;
}
return $user ?: null;
}
function admin_log(string $action, string $description = ''): void
{
$user = admin_user();
$stmt = db()->prepare(
'INSERT INTO admin_logs (user_id, action, description, ip_address) VALUES (?, ?, ?, ?)'
);
$stmt->execute([
$user['id'] ?? null,
$action,
$description,
$_SERVER['REMOTE_ADDR'] ?? null,
]);
}
function active_menu(string $type = 'food'): ?array
{
$type = polana_menu_type($type);
$today = date('Y-m-d');
$typeSql = polana_schema_ready() ? ' AND menu_type = ?' : '';
if (!polana_schema_ready() && $type !== 'food') {
return null;
}
$stmt = db()->prepare(
"SELECT * FROM menus WHERE status = 'published'"
. $typeSql . " AND (valid_from IS NULL OR valid_from <= ?)"
. " AND (valid_to IS NULL OR valid_to >= ?)"
. " ORDER BY COALESCE(valid_from, '1900-01-01') DESC, id DESC LIMIT 1"
);
$stmt->execute(array_merge($typeSql ? [$type] : [], [$today, $today]));
$menu = $stmt->fetch();
if ($menu) return $menu;
$stmt = db()->prepare(
"SELECT * FROM menus WHERE status='published'" . $typeSql . ' ORDER BY id DESC LIMIT 1'
);
$stmt->execute($typeSql ? [$type] : []);
return $stmt->fetch() ?: null;
}
function menu_categories_with_items(int $menuId, bool $admin = false): array
{
$sql = 'SELECT * FROM menu_categories WHERE menu_id = ?';
if (!$admin) {
$sql .= ' AND active = 1';
}
$sql .= ' ORDER BY sort_order ASC, id ASC';
$stmt = db()->prepare($sql);
$stmt->execute([$menuId]);
$categories = $stmt->fetchAll();
foreach ($categories as &$category) {
$sqlItems = 'SELECT * FROM menu_items WHERE category_id = ?';
if (!$admin) {
$sqlItems .= ' AND active = 1 AND available = 1';
}
$sqlItems .= ' ORDER BY sort_order ASC, id ASC';
$itemStmt = db()->prepare($sqlItems);
$itemStmt->execute([(int)$category['id']]);
$category['items'] = $itemStmt->fetchAll();
}
unset($category);
return $categories;
}
function restaurant_schema(): array
{
$schema = [
'@context' => 'https://schema.org',
'@type' => 'Restaurant',
'name' => setting('site_name', APP_NAME),
'url' => site_url('/'),
'image' => asset('img/logo.png'),
'menu' => site_url(polana_local_path('/menu-seo')),
];
if (setting('phone') !== '') {
$schema['telephone'] = setting('phone');
}
if (setting('email') !== '') {
$schema['email'] = setting('email');
}
$address = array_filter([
'streetAddress' => setting('address'),
'postalCode' => setting('postal_code'),
'addressLocality' => setting('city'),
'addressCountry' => 'PL',
]);
if (count($address) > 1) {
$schema['address'] = array_merge(['@type' => 'PostalAddress'], $address);
}
$sameAs = array_values(array_filter(array_map(
static fn(array $item): string => $item['url'],
social_links()
)));
if ($sameAs) {
$schema['sameAs'] = $sameAs;
}
if (setting('price_range') !== '') {
$schema['priceRange'] = setting('price_range');
}
return $schema;
}
function render_public_head(
string $title,
string $description,
string $canonicalPath,
?array $extraSchema = null,
string $robots = 'index,follow'
): void {
polana_start_translation_buffer();
render_global_site_splash_if_needed();
/* POLANA_V12_SERVER_I18N */
require_once __DIR__.'/polana_v12_pages.php';
p12_boot();
$v11CurrentLang = p11_lang();
$canonical = site_url(p11_path($canonicalPath,$v11CurrentLang));
if ($v11CurrentLang !== 'pl') {
$v11Meta=p11_meta($canonicalPath,$v11CurrentLang);
if ($v11Meta) { $title=$v11Meta['title']; $description=$v11Meta['description']; }
if (!p11_page_ready($canonicalPath,$v11CurrentLang)) $robots = 'noindex,follow';
}
$schemas = [restaurant_schema()];
if ($extraSchema) {
$schemas[] = $extraSchema;
}
?>
<!doctype html>
<html lang="<?= polana_lang() ?>">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title><?= e($title) ?></title>
<meta name="description" content="<?= e($description) ?>">
<meta name="robots" content="<?= e($robots) ?>">
<link rel="canonical" href="<?= e($canonical) ?>">
<?php foreach(p11_hreflang($canonicalPath) as $v11Code=>$v11Url): ?><link rel="alternate" hreflang="<?= e($v11Code) ?>" href="<?= e($v11Url) ?>"><?php endforeach; ?>
<link rel="alternate" hreflang="pl" href="<?= e(site_url(polana_local_path($canonicalPath, 'pl'))) ?>">
<link rel="alternate" hreflang="en" href="<?= e(site_url(polana_local_path($canonicalPath, 'en'))) ?>">
<link rel="alternate" hreflang="x-default" href="<?= e(site_url(polana_local_path($canonicalPath, 'pl'))) ?>">
<meta property="og:locale" content="<?= polana_lang() === 'en' ? 'en_GB' : 'pl_PL' ?>">
<meta property="og:type" content="website">
<?php
$ogImage = trim(setting('default_og_image', ''));
if ($ogImage === '') {
$ogImage = asset('img/logo.png');
}
$ogTitle = polana_lang() === 'en' ? $title : (trim(setting('og_title_override', '')) !== '' ? setting('og_title_override') : $title);
$ogDescription = polana_lang() === 'en' ? $description : (trim(setting('og_description_override', '')) !== '' ? setting('og_description_override') : $description);
?>
<meta property="og:title" content="<?= e($ogTitle) ?>">
<meta property="og:description" content="<?= e($ogDescription) ?>">
<meta property="og:url" content="<?= e($canonical) ?>">
<meta property="og:image" content="<?= e($ogImage) ?>">
<meta property="og:site_name" content="<?= e(setting('site_name', APP_NAME)) ?>">
<meta name="twitter:card" content="<?= e(setting('twitter_card_type', 'summary_large_image')) ?>">
<meta name="twitter:title" content="<?= e($ogTitle) ?>">
<meta name="twitter:description" content="<?= e($ogDescription) ?>">
<meta name="twitter:image" content="<?= e($ogImage) ?>">
<?php if (setting('google_verification') !== ''): ?>
<meta name="google-site-verification" content="<?= e(setting('google_verification')) ?>">
<?php endif; ?>
<?php if (setting('facebook_app_id') !== ''): ?>
<meta property="fb:app_id" content="<?= e(setting('facebook_app_id')) ?>">
<?php endif; ?>
<link rel="icon" type="image/png" href="<?= e(asset('img/favicon.png')) ?>">
<link rel="apple-touch-icon" href="<?= e(asset('img/favicon.png')) ?>">
<link rel="manifest" href="/manifest.webmanifest">
<?php if (blog_public_enabled()): ?><link rel="alternate" type="application/rss+xml" title="Blog – <?= e(setting('site_name', APP_NAME)) ?>" href="/blog/rss.xml"><?php endif; ?>
<meta name="theme-color" content="#111111">
<link rel="stylesheet" href="<?= e(asset_v('css/style.css')) ?>">
<!-- POLANA_HERITAGE_CSS_V2 -->
<link rel="stylesheet" href="<?= e(asset_v('css/polana-heritage.css')) ?>">
<link rel="stylesheet" href="/assets/css/polana-v11-flags.css?v=11">
<script type="application/ld+json"><?= json_encode(
count($schemas) === 1 ? $schemas[0] : $schemas,
JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES | JSON_HEX_TAG
) ?></script>
<?php /* POLANA_TRACKING_HEAD_V174 */ require_once __DIR__ . '/polana_tracking.php'; polana_tracking_head(); ?>
</head>
<body>
<?php
}
function render_public_header(string $active = ''): void
{
?>
<?php if (public_splash_preview_active()): ?>
<div class="splash-preview-bar">
<div class="container splash-preview-bar-inner">
<strong>Podgląd zalogowanego</strong>
<span>Dla gości aktywna jest zaślepka.</span>
<a href="/admin/seo.php">Ustawienia zaślepki</a>
</div>
</div>
<?php endif; ?>
<header class="site-header">
<?php if (setting('social_top_enabled', '1') === '1' && has_social_links()): ?>
<div class="social-topbar">
<div class="container social-topbar-inner">
<span>Polana Smaków · Restauracja Andrzeja Polana</span>
<?php render_social_links('social-links-top'); ?>
</div>
</div>
<?php endif; ?>
<div class="container header-inner">
<a href="<?= e(p11_path('/',p11_lang())) ?>" class="brand" aria-label="Moja Polana Smaków – strona główna">
<img src="<?= e(asset('img/logo.png')) ?>" alt="Moja Polana Smaków – Andrzej Polan">
</a>
<button class="nav-toggle" type="button" aria-expanded="false" aria-controls="main-nav">
Menu
</button>
<nav id="main-nav" class="main-nav" aria-label="Nawigacja główna">
<a class="<?= $active === 'home' ? 'active' : '' ?>" href="<?= e(p11_path('/',p11_lang())) ?>">Start</a>
<a class="<?= $active === 'andrzej' ? 'active' : '' ?>" href="<?= e(p11_path('/andrzej-polan',p11_lang())) ?>">Andrzej Polan</a>
<a class="<?= $active === 'menu' ? 'active' : '' ?>" href="<?= e(p11_path('/menu',p11_lang())) ?>">Menu</a>
<?php if (reservations_enabled()): ?>
<a class="<?= $active === 'reservation' ? 'active' : '' ?>" href="<?= e(p11_path('/rezerwacja',p11_lang())) ?>">Rezerwacja</a>
<?php endif; ?>
<?php if (setting('nav_gallery_enabled', '0') === '1'): ?>
<a class="<?= $active === 'gallery' ? 'active' : '' ?>" href="<?= e(p11_path('/galeria',p11_lang())) ?>">Galeria</a>
<?php endif; ?>
<?php if (setting('nav_blog_enabled', '0') === '1'): ?>
<a class="<?= $active === 'blog' ? 'active' : '' ?>" href="<?= e(p11_path('/blog',p11_lang())) ?>">Blog</a>
<?php endif; ?>
<?php if (setting('nav_collaboration_enabled', '0') === '1'): ?>
<a class="<?= $active === 'collaboration' ? 'active' : '' ?>" href="<?= e(p11_path('/wspolpraca',p11_lang())) ?>">Współpraca</a>
<?php endif; ?>
<a class="<?= $active === 'contact' ? 'active' : '' ?>" href="<?= e(p11_path('/kontakt',p11_lang())) ?>">Kontakt</a>
<?php p11_render_switcher(); ?>
</nav>
<div class="polana-lang-switch" aria-label="Language / Język">
<a href="<?= e(polana_switch_url('pl')) ?>" hreflang="pl" lang="pl"
class="<?= polana_lang() === 'pl' ? 'selected' : '' ?>" aria-label="Polski" title="Polski">🇵🇱 <span>PL</span></a>
<a href="<?= e(polana_switch_url('en')) ?>" hreflang="en" lang="en"
class="<?= polana_lang() === 'en' ? 'selected' : '' ?>" aria-label="English" title="English">🇬🇧 <span>EN</span></a>
</div>
</div>
</header>
<?php
}
function render_public_footer(): void
{
/* POLANA_GOOGLE_REVIEWS_FOOTER_V1 */
if (function_exists('polana_google_reviews_render')) polana_google_reviews_render();
/* POLANA_NEWSLETTER_FOOTER_V1_1 */
if (function_exists('newsletter_render_footer')) newsletter_render_footer();
?>
<footer class="site-footer">
<div class="container footer-grid">
<div>
<img class="footer-logo" src="<?= e(asset('img/logo.png')) ?>" alt="Moja Polana Smaków">
<p><?= e(setting('owner_name', 'Andrzej Polan')) ?></p>
<?php render_social_links('social-links-footer'); ?>
</div>
<div>
<strong>Na skróty</strong>
<a href="<?= e(p11_path('/andrzej-polan',p11_lang())) ?>">Andrzej Polan</a>
<a href="<?= e(p11_path('/menu',p11_lang())) ?>">Aktualne menu</a>
<?php if (reservations_enabled()): ?>
<a href="<?= e(p11_path('/rezerwacja',p11_lang())) ?>">Rezerwacja</a>
<?php endif; ?>
<?php if (setting('nav_gallery_enabled', '0') === '1'): ?>
<a href="<?= e(p11_path('/galeria',p11_lang())) ?>">Galeria</a>
<?php endif; ?>
<?php if (setting('nav_blog_enabled', '0') === '1'): ?>
<a href="<?= e(p11_path('/blog',p11_lang())) ?>">Blog</a>
<?php endif; ?>
<?php if (setting('nav_collaboration_enabled', '0') === '1'): ?>
<a href="<?= e(p11_path('/wspolpraca',p11_lang())) ?>">Współpraca</a>
<?php endif; ?>
<a href="<?= e(p11_path('/kontakt',p11_lang())) ?>">Kontakt</a>
<a href="<?= e(p11_path('/alergeny',p11_lang())) ?>">Składniki i alergeny</a>
<a href="<?= e(p11_path('/polityka-prywatnosci',p11_lang())) ?>">Polityka prywatności</a>
</div>
<div>
<strong>Kontakt</strong>
<?php if (setting('phone') !== ''): ?>
<a href="tel:<?= e(preg_replace('/\s+/', '', setting('phone'))) ?>"><?= e(setting('phone')) ?></a>
<?php endif; ?>
<?php if (setting('email') !== ''): ?>
<a href="mailto:<?= e(setting('email')) ?>"><?= e(setting('email')) ?></a>
<?php endif; ?>
<?php if (setting('city') !== ''): ?>
<span><?= e(trim(setting('address') . ', ' . setting('postal_code') . ' ' . setting('city'), ', ')) ?></span>
<?php endif; ?>
</div>
</div>
<div class="container footer-bottom footer-bottom-legal">
<span>© <?= date('Y') ?> <?= e(setting('site_name', APP_NAME)) ?></span>
<span>ANDRZEJ POLAN · NIP 7361000321 · REGON 490451846</span>
<span>ul. Bożego Ciała 15, 31-059 Kraków</span>
</div>
</footer>
<script src="/assets/js/polana-v11-links.js?v=11" defer></script>
<!-- POLANA_V13_FLAGS -->
<script src="/assets/js/polana-v13-flags-clean.js?v=13" defer></script>
<!-- POLANA_HERITAGE_JS_V2 -->
<script src="<?= e(asset_v('js/polana-heritage.js')) ?>" defer></script>
<script src="<?= e(asset_v('js/app.js')) ?>" defer></script>
<script src="<?= e(asset_v('js/pwa.js')) ?>" defer></script>
<!-- POLANA_I18N_UI_V96 -->
<script src="/assets/js/polana-i18n-map-v96.js?v=96" defer></script>
<script src="/assets/js/polana-i18n-ui-v96.js?v=96" defer></script>
<?php /* POLANA_TRACKING_FOOTER_V174 */ if (function_exists('polana_tracking_footer')) polana_tracking_footer(); ?>
</body>
</html>
<?php
}
function render_admin_header(string $title = 'Panel'): void
{
require_login();
$user = admin_user();
$flashes = consume_flashes();
$role = (string)($user['role'] ?? '');
$roleLabel = match ($role) {
'waiter' => 'Kelner',
'kitchen' => 'Kuchnia',
'manager' => 'Manager',
'editor' => 'Redaktor',
default => 'Administrator',
};
?>
<!doctype html>
<html lang="pl">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="robots" content="noindex,nofollow">
<title><?= e($title) ?> – Panel</title>
<link rel="icon" type="image/png" href="/assets/img/favicon.png">
<link rel="manifest" href="/admin/manifest.webmanifest">
<meta name="theme-color" content="#111111">
<link rel="stylesheet" href="<?= e(asset_v('css/admin.css')) ?>">
</head>
<body class="admin-body">
<header class="admin-top">
<a class="admin-brand" href="/admin/">
<img src="/assets/img/favicon.png" alt="">
<span>Polana</span>
</a>
<button
class="admin-nav-toggle"
type="button"
aria-expanded="false"
aria-controls="admin-main-nav"
data-admin-nav-toggle
>
<span>Menu</span>
<i aria-hidden="true"></i>
</button>
<nav id="admin-main-nav" class="admin-nav" data-admin-nav>
<a href="/admin/">Pulpit</a>
<!-- POLANA_V175_NAV -->
<?php if ($role === 'admin'): ?><a href="/admin/polana-godziny-rezerwacji.php">Godziny rezerwacji</a><?php endif; ?>
<!-- POLANA_V167_MANAGEMENT_NAV -->
<?php if (in_array($role, ['admin','editor'], true)): ?>
<a href="/admin/polana-zarzadzanie.php">Zarządzanie</a>
<a href="/admin/polana-menu-centrum.php">Menu</a>
<?php endif; ?>
<?php if ($role === 'admin'): ?>
<a href="/admin/polana-seo-centrum.php">SEO i Google</a>
<?php endif; ?>
<?php if (in_array($role, ['admin','editor','manager'], true)): ?>
<a href="/admin/today.php">Dzisiaj</a>
<?php endif; ?>
<a href="/admin/schedule.php">Grafik</a>
<?php if ($role !== 'kitchen'): ?>
<a href="/admin/reservations.php">Rezerwacje</a>
<?php endif; ?>
<?php if (in_array($role, ['admin','editor','manager','waiter','kitchen'], true)): ?>
<a href="/admin/tips.php">Napiwki</a>
<?php endif; ?>
<?php if (in_array($role, ['admin','manager'], true)): ?>
<details class="admin-nav-group">
<summary>Operacje</summary>
<div class="admin-nav-dropdown">
<a href="/admin/tips-kiosk.php">Terminal tipów</a>
<a href="/admin/manager-report.php">Raport</a>
</div>
</details>
<?php endif; ?>
<?php if (!in_array($role, ['waiter','kitchen','manager'], true)): ?>
<details class="admin-nav-group">
<summary>Treści</summary>
<div class="admin-nav-dropdown">
<!-- POLANA_V163_SIMPLE_NAV -->
<a href="/admin/polana-centrum-tresci.php">Centrum treści</a>
<a href="/admin/media.php">Zdjęcia</a>
</div>
</details>
<details class="admin-nav-group">
<summary>System</summary>
<div class="admin-nav-dropdown admin-nav-dropdown-right">
<a href="/admin/settings.php">Ustawienia</a>
<?php if ($role === 'admin'): ?>
<a href="/admin/users.php">Użytkownicy</a>
<a href="/admin/database-backup.php">Backup SQL</a>
<?php endif; ?>
<?php if ($role === 'admin'): ?><a href="/admin/polana-analityka.php">Analityka i piksele</a><?php endif; ?>
<a href="/admin/seo.php">SEO / zaślepka</a>
<a href="/admin/qr.php">QR</a>
</div>
</details>
<?php endif; ?>
</nav>
<details class="admin-account">
<summary>
<span class="admin-account-name"><?= e($user['name'] ?: $user['username']) ?></span>
<small><?= e($roleLabel) ?></small>
</summary>
<div class="admin-account-menu">
<button
type="button"
class="admin-account-action pwa-install-btn"
data-pwa-install
hidden
>
Zainstaluj aplikację
</button>
<a href="/" target="_blank" rel="noopener">Otwórz stronę ↗</a>
<a href="/admin/logout.php" class="admin-account-logout">Wyloguj</a>
</div>
</details>
</header>
<main class="admin-main">
<?php foreach ($flashes as $flash): ?>
<div class="flash flash-<?= e($flash['type']) ?>"><?= e($flash['message']) ?></div>
<?php endforeach; ?>
<?php
}
function render_admin_footer(): void
{
?>
</main>
<script src="<?= e(asset_v('js/admin.js')) ?>" defer></script>
<script src="<?= e(asset_v('js/pwa.js')) ?>"></script>
</body>
</html>
<?php
}
function upload_file(
array $file,
string $targetDir,
array $allowedMimes,
int $maxBytes
): ?string {
if (($file['error'] ?? UPLOAD_ERR_NO_FILE) === UPLOAD_ERR_NO_FILE) {
return null;
}
if (($file['error'] ?? UPLOAD_ERR_OK) !== UPLOAD_ERR_OK) {
throw new RuntimeException('Błąd podczas wysyłania pliku.');
}
if (($file['size'] ?? 0) > $maxBytes) {
throw new RuntimeException('Plik jest za duży.');
}
$tmp = $file['tmp_name'] ?? '';
if (!is_uploaded_file($tmp)) {
throw new RuntimeException('Nieprawidłowy upload.');
}
$mime = (new finfo(FILEINFO_MIME_TYPE))->file($tmp);
if (!in_array($mime, $allowedMimes, true)) {
throw new RuntimeException('Niedozwolony typ pliku.');
}
$extMap = [
'application/pdf' => 'pdf',
'image/jpeg' => 'jpg',
'image/png' => 'png',
'image/webp' => 'webp',
];
$ext = $extMap[$mime] ?? 'bin';
$name = bin2hex(random_bytes(16)) . '.' . $ext;
$absolute = dirname(__DIR__) . '/' . trim($targetDir, '/') . '/';
if (!is_dir($absolute) && !mkdir($absolute, 0775, true) && !is_dir($absolute)) {
throw new RuntimeException('Nie można utworzyć katalogu uploadu.');
}
if (!move_uploaded_file($tmp, $absolute . $name)) {
throw new RuntimeException('Nie udało się zapisać pliku.');
}
return '/' . trim($targetDir, '/') . '/' . $name;
}