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/import-en.php
<?php
declare(strict_types=1);
require_once dirname(__DIR__) . '/includes/import_menu.php';
require_login();
if (!polana_schema_ready()) {
    flash('error', 'Najpierw wykonaj migrację bazy PL/EN.');
    redirect('/admin/polana-bilingual-install.php');
}
$id = (int)($_GET['id'] ?? $_POST['menu_id'] ?? 0);
$stmt = db()->prepare('SELECT * FROM menus WHERE id=? LIMIT 1');
$stmt->execute([$id]);
$menu = $stmt->fetch();
if (!$menu) { http_response_code(404); exit('Nie znaleziono karty.'); }
$error = '';
$preview = null;
$token = '';
$comparison = [];

/** Compare the English file by category and dish order, never by untrusted names. */
function polana_english_matches(int $menuId, array $parsed): array
{
    $original = menu_categories_with_items($menuId, true);
    $incoming = $parsed['categories'] ?? [];
    if (count($original) !== count($incoming)) {
        throw new RuntimeException('Liczba kategorii w EN musi być taka sama jak w polskiej karcie. PL: '.count($original).', EN: '.count($incoming).'.');
    }
    $mapped = [];
    foreach ($original as $i => $cat) {
        $in = $incoming[$i];
        if (count($cat['items']) !== count($in['items'])) {
            throw new RuntimeException('Kategoria '.($i+1).' ('.$cat['name'].'): różna liczba pozycji: PL '.count($cat['items']).', EN '.count($in['items']).'.');
        }
        $parts = [];
        foreach ($cat['items'] as $j => $item) {
            $en = $in['items'][$j];
            // Karta win: porównaj obie ceny z price_text, nie pojedyncze pole price.
            $plPair = [];
            $enPair = [];
            preg_match_all('~(\d+(?:[,.]\d{1,2})?)\s*zł~u', (string)($item['price_text'] ?? ''), $plPair);
            preg_match_all('~(\d+(?:[,.]\d{1,2})?)\s*zł~u', (string)($en['price_text'] ?? ''), $enPair);
            if (!empty($plPair[1]) && array_map(static fn($x) => (float)str_replace(',', '.', $x), $plPair[1])
                !== array_map(static fn($x) => (float)str_replace(',', '.', $x), $enPair[1])) {
                throw new RuntimeException('Różne ceny kieliszka/butelki w kategorii '.($i+1).', pozycja '.($j+1).'.');
            }
            if ($item['price'] !== null && abs((float)$item['price'] - (float)($en['price'] ?? 0)) > 0.009) {
                throw new RuntimeException('Różna cena w kategorii '.($i+1).', pozycja '.($j+1).': PL '.$item['price'].', EN '.($en['price'] ?? '').'. Kolejność i ceny muszą być identyczne.');
            }
            $parts[] = ['id'=>(int)$item['id'], 'pl'=>(string)$item['name'], 'en'=>(string)$en['name'], 'description_en'=>(string)($en['description'] ?? '')];
        }
        $mapped[] = ['id'=>(int)$cat['id'], 'pl'=>(string)$cat['name'], 'en'=>(string)$in['name'], 'items'=>$parts];
    }
    return $mapped;
}

if (($_SERVER['REQUEST_METHOD'] ?? '') === 'POST') {
    verify_csrf();
    try {
        $action = (string)($_POST['action'] ?? 'preview');
        if ($action === 'preview') {
            $uploadId = strtolower((string)($_POST['docx_upload_token'] ?? ''));
            $rawText = trim((string)($_POST['menu_text'] ?? ''));
            if ($uploadId !== '') {
                $stage = $_SESSION['polana_word_uploads'][$uploadId] ?? null;
                if (!preg_match('/^[a-f0-9]{32}$/D', $uploadId) || !is_array($stage)
                    || empty($stage['complete']) || (int)($stage['started_at'] ?? 0) < time() - 1800
                    || !is_readable((string)($stage['path'] ?? ''))
                    || filesize((string)$stage['path']) !== (int)$stage['total_size']) {
                    throw new RuntimeException('Przesłany Word wygasł lub jest niekompletny. Spróbuj ponownie.');
                }
                $lines = docx_to_lines((string)$stage['path']);
                @unlink((string)$stage['path']);
                unset($_SESSION['polana_word_uploads'][$uploadId]);
            } elseif (isset($_FILES['docx']) && ($_FILES['docx']['error'] ?? UPLOAD_ERR_NO_FILE) === UPLOAD_ERR_OK) {
                if (strtolower(pathinfo((string)$_FILES['docx']['name'], PATHINFO_EXTENSION)) !== 'docx') throw new RuntimeException('Wybierz .docx.');
                $lines = docx_to_lines((string)$_FILES['docx']['tmp_name']);
            } elseif ($rawText !== '') {
                $lines = text_to_lines($rawText);
            } else {
                throw new RuntimeException('Wybierz angielski DOCX lub wklej angielskie menu. Kod 6 PHP wymaga przeglądarki z włączonym JavaScript.');
            }
            $parsed = parse_menu_lines($lines);
            if (empty($parsed['categories'])) throw new RuntimeException('Nie rozpoznano angielskich pozycji i cen.');
            $comparison = polana_english_matches($id, $parsed);
            $token = bin2hex(random_bytes(16));
            $_SESSION['polana_english_preview'] = ['token'=>$token,'menu_id'=>$id,'created_at'=>time(),'parsed'=>$parsed];
            $preview = $parsed;
        } elseif ($action === 'apply') {
            $st = $_SESSION['polana_english_preview'] ?? null;
            if (!is_array($st) || (int)($st['menu_id'] ?? 0) !== $id
                || (int)($st['created_at'] ?? 0) < time()-1800
                || !hash_equals((string)($st['token'] ?? ''), (string)($_POST['preview_token'] ?? ''))) {
                throw new RuntimeException('Podgląd EN wygasł. Wczytaj dokument ponownie.');
            }
            // Lock every edited category/item while comparing, so no silent order mismatch.
            db()->beginTransaction();
            try {
                $comparison = polana_english_matches($id, $st['parsed']);
                $catUpdate = db()->prepare('UPDATE menu_categories SET name_en=? WHERE id=? AND menu_id=?');
                $itemUpdate = db()->prepare('UPDATE menu_items SET name_en=?, description_en=? WHERE id=? AND category_id=?');
                foreach ($comparison as $cat) {
                    $catUpdate->execute([$cat['en'], $cat['id'], $id]);
                    foreach ($cat['items'] as $item) {
                        $itemUpdate->execute([$item['en'], $item['description_en'], $item['id'], $cat['id']]);
                    }
                }
                db()->commit();
            } catch (Throwable $e) {
                db()->rollBack();
                throw $e;
            }
            unset($_SESSION['polana_english_preview']);
            admin_log('menu_english_import', 'Wczytano angielskie tłumaczenie menu #'.$id);
            flash('success', 'Angielskie nazwy zostały zapisane. Polskie nazwy i ceny pozostały bez zmian.');
            redirect('/admin/menu.php?id='.$id);
        } else throw new RuntimeException('Nieznana akcja.');
    } catch (Throwable $e) {
        $error = $e->getMessage();
    }
}
render_admin_header('Import tłumaczenia EN');
?>
<div class="admin-title-row"><div><h1>🇬🇧 Import angielskiej wersji</h1><p>Istniejąca karta: <?= e($menu['name']) ?> (<?= e(polana_type_label((string)($menu['menu_type'] ?? 'food'),'pl')) ?>)</p></div><a class="btn btn-light" href="/admin/menu.php?id=<?= $id ?>">← Karta</a></div>
<?php if ($error): ?><div class="form-card" style="border:2px solid #c53535"><strong><?= e($error) ?></strong></div><?php endif; ?>
<section class="form-card"><p>Word EN musi mieć <strong>identyczną kolejność kategorii, dań i te same ceny</strong> co polska karta. System porówna dokumenty i pokaże pary PL ↔ EN; przy rozbieżnościach niczego nie zapisze.</p></section>
<form id="polana-word-import-form" method="post" enctype="multipart/form-data" class="form-card form-grid">
    <?= csrf_field() ?>
    <input type="hidden" name="action" value="preview">
    <input type="hidden" name="menu_id" value="<?= $id ?>">
    <input type="hidden" name="docx_upload_token" id="polana-docx-upload-token" value="">
    <label class="full">English Word (.docx)
        <input type="file" name="docx" id="polana-docx-file" accept=".docx,application/vnd.openxmlformats-officedocument.wordprocessingml.document">
        <span class="muted" id="polana-docx-upload-progress" role="status" aria-live="polite"></span>
    </label>
    <label class="full">Albo wklej tekst po angielsku
        <textarea name="menu_text" rows="12" placeholder="Starters&#10;Beef tartare with fermented vegetables 58&#10;&#10;Soups&#10;Green soup with roast apple 31"><?= e($_POST['menu_text'] ?? '') ?></textarea>
    </label>
    <div class="full"><button class="btn btn-primary" type="submit" id="polana-docx-preview-btn">Sprawdź pary PL / EN</button></div>
</form>
<?php if ($preview): ?>
<section class="form-card"><h2>Podgląd dopasowania – <?= count($comparison) ?> kategorii</h2>
<?php foreach ($comparison as $cat): ?>
    <div class="list-card"><h3><?= e($cat['pl']) ?> → <?= e($cat['en']) ?></h3>
    <?php foreach ($cat['items'] as $item): ?><p><?= e($item['pl']) ?> → <strong><?= e($item['en']) ?></strong></p><?php endforeach; ?>
    </div>
<?php endforeach; ?>
<form method="post"><?= csrf_field() ?><input type="hidden" name="menu_id" value="<?= $id ?>"><input type="hidden" name="action" value="apply"><input type="hidden" name="preview_token" value="<?= e($token) ?>"><button class="btn btn-green" type="submit">Zapisz angielskie nazwy do tej karty</button></form>
</section>
<?php endif; ?>
<script>
(() => {
 'use strict';
 const form = document.getElementById('polana-word-import-form');
 const fileInput = document.getElementById('polana-docx-file');
 const tokenInput = document.getElementById('polana-docx-upload-token');
 const progress = document.getElementById('polana-docx-upload-progress');
 const button = document.getElementById('polana-docx-preview-btn');
 if (!form || !fileInput || !tokenInput || !progress || !window.fetch) return;
 let uploading = false;
 const SIZE = 15 * 1024;
 form.addEventListener('submit', async e => {
    const file = fileInput.files && fileInput.files[0];
    if (!file || uploading) { if (uploading) e.preventDefault(); return; }
    e.preventDefault();
    if (!/\.docx$/i.test(file.name) || file.size < 1 || file.size > 20*1024*1024) { progress.textContent='DOCX musi mieć 1 B – 20 MB.';return; }
    if (!window.crypto?.getRandomValues) { progress.textContent='Przeglądarka nie obsługuje bezpiecznego przesyłania.'; return; }
    uploading=true;button.disabled=true;
    const bytes=new Uint8Array(16);crypto.getRandomValues(bytes);
    const id=Array.from(bytes,b=>b.toString(16).padStart(2,'0')).join('');
    const count=Math.ceil(file.size/SIZE);
    const csrf=form.querySelector('[name="csrf_token"]').value;
    try {
        for(let i=0;i<count;i++) {
            progress.textContent='Przesyłanie Worda EN: '+Math.round(100*i/count)+'%';
            const response=await fetch('/admin/import-upload.php',{
                method:'POST',credentials:'same-origin',cache:'no-store',
                headers:{'Content-Type':'application/octet-stream','X-Polana-Csrf':csrf,'X-Polana-Upload':id,
                    'X-Polana-Index':String(i),'X-Polana-Count':String(count),'X-Polana-Size':String(file.size),
                    'X-Polana-Name':encodeURIComponent(file.name)},
                body:file.slice(i*SIZE,Math.min((i+1)*SIZE,file.size))
            });
            let body;try{body=await response.json();}catch(_){throw new Error('HTTP '+response.status);}
            if(!response.ok||!body.ok)throw new Error(body.message||'HTTP '+response.status);
            if(i===count-1&&!body.complete)throw new Error('Niekompletny plik.');
        }
        progress.textContent='DOCX EN przesłany. Sprawdzam zgodność kategorii i cen…';
        tokenInput.value=id;fileInput.disabled=true;form.submit();
    }catch(err){progress.textContent='Błąd: '+(err?.message||'Nieznany błąd');uploading=false;button.disabled=false;}
 });
})();
</script>
<?php render_admin_footer(); ?>

Youez - 2016 - github.com/yon3zu
LinuXploit