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/translate-site.php
<?php
declare(strict_types=1);
require_once dirname(__DIR__).'/includes/functions.php';
require_once dirname(__DIR__).'/includes/polana_translate_client.php';
require_login();
require_admin_role();
start_session();

function polana_site_candidates(string $scope): array
{
    if ($scope === 'setting') {
        $all = [];
        foreach (polana_site_translation_columns() as $key=>$label) {
            $pl = trim(setting($key));
            if ($pl !== '') $all[polana_site_translation_hash('setting',$key)] = [
                'source'=>$key,'pl'=>$pl,'label'=>$label,'scope'=>'setting',
            ];
        }
        return $all;
    }
    $all = [];
    foreach (polana_english_strings() as $pl=>$en) {
        $pl = preg_replace('/\s+/u', ' ', trim((string)$pl));
        if ($pl === '' || $pl === null) continue;
        $all[polana_site_translation_hash('dictionary',$pl)] = [
            'source'=>$pl, 'pl'=>$pl,'label'=>'Interfejs / tekst strony','scope'=>'dictionary',
        ];
    }
    // Extra phrases can be added by the editor without modifying PHP sources.
    foreach (polana_site_existing() as $stored) {
        if (($stored['scope'] ?? '') !== 'dictionary') continue;
        $pl=(string)$stored['source_text'];
        $hash=polana_site_translation_hash('dictionary',$pl);
        if (!isset($all[$hash])) $all[$hash]=['source'=>$pl,'pl'=>$pl,'label'=>'Własny tekst strony','scope'=>'dictionary'];
    }
    return $all;
}

function polana_site_existing(): array
{
    if (!polana_site_translation_ready()) return [];
    $rows = db()->query('SELECT * FROM polana_site_translations')->fetchAll(PDO::FETCH_ASSOC);
    $indexed = [];
    foreach ($rows as $r) $indexed[$r['translation_id']] = $r;
    return $indexed;
}
function polana_site_json(array $data, int $status=200): never
{
    http_response_code($status);
    header('Content-Type: application/json; charset=utf-8');
    header('Cache-Control: no-store');
    echo json_encode($data,JSON_UNESCAPED_UNICODE|JSON_INVALID_UTF8_SUBSTITUTE);
    exit;
}

$scope = (string)($_POST['scope'] ?? $_GET['scope'] ?? 'setting');
if (!in_array($scope,['setting','dictionary'],true)) $scope = 'setting';
$jobKey='polana_site_machine_'.$scope;
$err='';
$note='';
$existing = polana_site_existing();
$candidates = polana_site_candidates($scope);

if (($_SERVER['REQUEST_METHOD'] ?? '') === 'POST') {
    verify_csrf();
    $action = (string)($_POST['action'] ?? '');
    try {
        if (!polana_site_translation_ready()) {
            throw new RuntimeException('Najpierw uruchom migrację bazy w panelu PL/EN.');
        }
        if ($action === 'begin') {
            if (!polana_mt_configured()) throw new RuntimeException('Skonfiguruj HTTPS i X-TKLM-Translate-Key dla translate.tklm.pl.');
            $rows=[];
            foreach ($candidates as $hash=>$row) {
                if (isset($existing[$hash]) && (string)$existing[$hash]['source_text'] === (string)$row['source'] && $scope==='dictionary' && trim((string)$existing[$hash]['translated_text'])!=='') continue;
                if (isset($existing[$hash]) && $scope==='setting' && (string)$existing[$hash]['source_text'] === (string)$row['pl']) continue;
                // Changed Polish source may be retranslated; never overwrite unchanged reviewed text.
                if (strlen($row['pl']) > 10000) continue;
                $rows[$hash] = $row + ['en'=>''];
            }
            if (!$rows) throw new RuntimeException('Wszystkie dostępne teksty mają już tłumaczenie robocze lub opublikowane. Poniżej możesz je edytować.');
            $_SESSION[$jobKey] = ['created_at'=>time(),'rows'=>$rows];
            redirect('/admin/translate-site.php?scope='.$scope);
        }
        if ($action === 'step') {
            $job=$_SESSION[$jobKey] ?? null;
            if (!is_array($job) || time()-(int)($job['created_at']??0)>7200) throw new RuntimeException('Sesja wygasła. Rozpocznij ponownie.');
            $first=null;
            foreach ($job['rows'] as $hash=>$row) {if ($row['en']==='') {$first=$hash;break;}}
            if ($first !== null) {
                // Translate one site field at a time; long paragraphs are split safely.
                $result=polana_mt_long_text((string)$job['rows'][$first]['pl']);
                $_SESSION[$jobKey]['rows'][$first]['en']=$result;
            }
            $pending=count(array_filter($_SESSION[$jobKey]['rows'], static fn($r)=>$r['en']===''));
            polana_site_json(['ok'=>true,'remaining'=>$pending,'total'=>count($_SESSION[$jobKey]['rows'])]);
        }
        if ($action === 'save_generated') {
            $job=$_SESSION[$jobKey]??null;
            if (!is_array($job) || time()-(int)($job['created_at']??0)>7200) throw new RuntimeException('Sesja wygasła.');
            if (array_filter($job['rows'],static fn($r)=>$r['en']==='')) throw new RuntimeException('Wciąż są teksty do przetłumaczenia.');
            $pdo=db();$pdo->beginTransaction();
            try {
                $select=$pdo->prepare('SELECT source_text FROM polana_site_translations WHERE translation_id=? FOR UPDATE');
                $insert=$pdo->prepare("INSERT INTO polana_site_translations (translation_id,scope,source_text,translated_text,state) VALUES (?,?,?,?,'draft')");
                $update=$pdo->prepare("UPDATE polana_site_translations SET source_text=?, translated_text=?, state='draft',reviewed_at=NULL WHERE translation_id=?");
                foreach ($job['rows'] as $hash=>$row) {
                    $source=$row['scope']==='setting'?$row['pl']:$row['source'];
                    $select->execute([$hash]);
                    $old=$select->fetchColumn();
                    if ($old===false) $insert->execute([$hash,$row['scope'],$source,$row['en']]);
                    elseif ((string)$old !== $source) $update->execute([$source,$row['en'],$hash]);
                    else {
                        $fill=$pdo->prepare("UPDATE polana_site_translations SET translated_text=?,state='draft',reviewed_at=NULL WHERE translation_id=? AND translated_text=''");
                        $fill->execute([$row['en'],$hash]);
                    }
                }
                $pdo->commit();
            } catch (Throwable $e) {if($pdo->inTransaction())$pdo->rollBack();throw $e;}
            unset($_SESSION[$jobKey]);
            admin_log('site_mt_drafts','Utworzono szkice EN dla '.$scope);
            flash('success','Wygenerowano robocze tłumaczenia EN. ŻADNE nie zostało opublikowane. Przejrzyj i opublikuj każde osobno.');
            redirect('/admin/translate-site.php?scope='.$scope);
        }
        if ($action === 'cancel') {unset($_SESSION[$jobKey]);redirect('/admin/translate-site.php?scope='.$scope);}
        if ($action==='add_phrase') {
            if ($scope!=='dictionary') throw new RuntimeException('Nowe zwroty dodaj w zakładce Napisy interfejsu.');
            $pl=trim((string)($_POST['source_pl']??''));
            $pl=preg_replace('/\s+/u',' ',$pl)??'';
            if ($pl==='' || strlen($pl)>1500 || str_contains($pl,'<') || str_contains($pl,'>')) {
                throw new RuntimeException('Podaj polski tekst bez HTML, maksymalnie 1500 bajtów.');
            }
            $hash=polana_site_translation_hash('dictionary',$pl);
            $insert=db()->prepare("INSERT IGNORE INTO polana_site_translations (translation_id,scope,source_text,translated_text,state) VALUES (?,'dictionary',?,'','draft')");
            $insert->execute([$hash,$pl]);
            flash('success','Dodano polski zwrot. Wybierz generowanie brakujących EN lub wpisz własne tłumaczenie.');
            redirect('/admin/translate-site.php?scope=dictionary&q='.rawurlencode(substr($pl,0,80)));
        }
        if (in_array($action,['save','publish','withdraw'],true)) {
            $hash=(string)($_POST['translation_id']??'');
            if (!preg_match('/^[a-f0-9]{64}$/D',$hash) || !isset($candidates[$hash])) throw new RuntimeException('Nieznane tłumaczenie.');
            $row=$candidates[$hash];
            $english=trim((string)($_POST['english']??''));
            if ($action!=='withdraw' && ($english==='' || strlen($english)>12000)) throw new RuntimeException('Tekst EN musi mieć od 1 do 12000 znaków.');
            if ($action==='withdraw') {
                $stmt=db()->prepare("UPDATE polana_site_translations SET state='draft',reviewed_at=NULL WHERE translation_id=? AND scope=?");
                $stmt->execute([$hash,$scope]);
                $note='Cofnięto do szkicu. Goście nie widzą tej wersji.';
            } else {
                // Publishing is only possible from an explicit review checkbox.
                if ($action==='publish' && empty($_POST['verified'])) throw new RuntimeException('Zaznacz ręczne sprawdzenie tłumaczenia przed publikacją.');
                $state=$action==='publish'?'published':'draft';
                $stmt=db()->prepare("INSERT INTO polana_site_translations (translation_id,scope,source_text,translated_text,state,reviewed_at) VALUES (?,?,?,?,?,".($action==='publish'?'NOW()':'NULL').") ON DUPLICATE KEY UPDATE source_text=VALUES(source_text),translated_text=VALUES(translated_text),state=VALUES(state),reviewed_at=VALUES(reviewed_at)");
                $stmt->execute([$hash,$scope,$scope==='setting'?$row['pl']:$row['source'],$english,$state]);
                $note=$action==='publish'?'Zatwierdzono i opublikowano EN.':'Zapisano roboczą wersję EN. Bez publikacji.';
            }
            admin_log('site_mt_'.$action, $scope.' '.$hash);
            $existing=polana_site_existing();
        }
    } catch(Throwable $e) {
        if ($action==='step') polana_site_json(['ok'=>false,'message'=>$e->getMessage()],502);
        $err=$e->getMessage();
    }
}

$job=$_SESSION[$jobKey]??null;
if (is_array($job) && time()-(int)($job['created_at']??0)>7200) {unset($_SESSION[$jobKey]);$job=null;}
$remaining=is_array($job)?count(array_filter($job['rows'],static fn($r)=>$r['en']==='')):0;
$filter=trim((string)($_GET['q']??''));
$filter=substr($filter,0,100);
$filtered=array_filter($candidates,static function($row)use($filter){return $filter===''||stripos($row['pl'].' '.$row['label'],$filter)!==false;});
$page=max(1,(int)($_GET['page']??1));
$perPage=15;
$pages=max(1,(int)ceil(count($filtered)/$perPage));$page=min($page,$pages);
$view=array_slice($filtered,($page-1)*$perPage,$perPage,true);
render_admin_header('Tłumaczenie WWW PL → EN');
?>
<div class="admin-title-row"><div><h1>🇬🇧 Tłumaczenie strony przez Docker</h1><p>translate.tklm.pl · wyłącznie szkice do czasu ręcznego sprawdzenia</p></div><a class="btn btn-light" href="/admin/settings.php">Ustawienia</a></div>
<section class="form-card">
<p style="padding:14px;border:1px solid #dfa31a;border-radius:10px"><strong>⚠️ Tłumaczenie maszynowe — sprawdź przed publikacją.</strong> Zweryfikuj nazwy własne, adresy, informacje dla klientów i zgodność SEO. Samo wygenerowanie tłumaczenia niczego nie publikuje.</p>
<p><strong>Konfiguracja API:</strong> <?= polana_mt_configured()?'URL i klucz ustawione (połączenie testowane przy użyciu)':'brak – skonfiguruj includes/polana_translate_config.local.php' ?>.</p>
<?php if ($err): ?><div class="alert alert-error"><?= e($err) ?></div><?php endif; ?>
<?php if ($note): ?><div class="alert alert-success"><?= e($note) ?></div><?php endif; ?>
<?php if (!polana_site_translation_ready()): ?><p><a href="/admin/polana-bilingual-install.php">Uruchom migrację bazy tłumaczeń →</a></p><?php endif; ?>
<p><a class="btn <?= $scope==='setting'?'btn-green':'btn-light' ?>" href="?scope=setting">Treści strony i SEO</a> <a class="btn <?= $scope==='dictionary'?'btn-green':'btn-light' ?>" href="?scope=dictionary">Napisy interfejsu (<?= count(polana_site_candidates('dictionary')) ?>)</a></p>
<?php if ($job): ?>
  <div id="polana-site-progress" role="status" aria-live="polite">Do przetłumaczenia: <?= $remaining ?> / <?= count($job['rows']) ?></div>
  <?php if ($remaining): ?><button id="polana-site-run" class="btn btn-primary" type="button">Rozpocznij / wznów tłumaczenie</button>
  <?php else: ?><form method="post"><?= csrf_field() ?><input type="hidden" name="scope" value="<?= e($scope) ?>"><input type="hidden" name="action" value="save_generated"><button class="btn btn-green" type="submit">Zapisz wszystkie jako SZKICE (nie publikuj)</button></form><?php endif; ?>
  <form method="post"><?= csrf_field() ?><input type="hidden" name="scope" value="<?= e($scope) ?>"><input type="hidden" name="action" value="cancel"><button class="btn btn-light" type="submit">Anuluj generowanie</button></form>
<?php else: ?>
  <form method="post"><?= csrf_field() ?><input type="hidden" name="scope" value="<?= e($scope) ?>"><input type="hidden" name="action" value="begin"><button class="btn btn-primary" type="submit" <?= !polana_site_translation_ready()?'disabled':'' ?>>Wygeneruj brakujące tłumaczenia PL → EN</button></form>
<?php endif; ?>
<?php if($scope==='dictionary'): ?>
<form method="post" style="margin-top:20px"><?= csrf_field() ?><input type="hidden" name="scope" value="dictionary"><input type="hidden" name="action" value="add_phrase">
<label><strong>Dodaj brakujący polski tekst strony</strong><textarea name="source_pl" rows="2" maxlength="1500" placeholder="Wklej dokładny tekst wyświetlany na stronie" style="width:100%"></textarea></label>
<button type="submit" class="btn btn-light">Dodaj do szkiców EN</button>
<p class="muted">Dotyczy tekstów renderowanych jako zwykłe elementy HTML. Nie dotyczy treści generowanych przez JavaScript ani plików graficznych.</p>
</form>
<?php endif; ?>
<p class="muted">Dotychczasowe teksty EN pozostają dostępne. Maszynowe tłumaczenia zapisujemy tylko w nowych szkicach, nie nadpisujemy ręcznych zmian.</p>
</section>
<?php if (!$job): ?>
<section class="form-card"><h2>Tłumaczenia — edycja i publikacja</h2>
<form method="get"><input type="hidden" name="scope" value="<?= e($scope) ?>"><input name="q" value="<?= e($filter) ?>" placeholder="Szukaj po polskim tekście"><button type="submit" class="btn btn-light">Szukaj</button></form>
<p><?= count($filtered) ?> tekstów · strona <?= $page ?> z <?= $pages ?></p>
<?php foreach ($view as $hash=>$item): $stored=$existing[$hash]??null; $defaultEnglish=$scope==='dictionary'?(polana_english_strings()[$item['source']]??''):setting($item['source'].'_en'); ?>
<form method="post" style="margin:14px 0;padding:14px;border:1px solid #ddd;border-radius:12px">
<?= csrf_field() ?><input type="hidden" name="scope" value="<?= e($scope) ?>"><input type="hidden" name="translation_id" value="<?= e($hash) ?>">
<strong><?= e($item['label']) ?></strong> <?php if ($scope==='setting' && $stored && (string)$stored['source_text'] !== $item['pl']): ?><small style="color:#9b2424">⚠️ Polski tekst zmieniono od poprzedniego tłumaczenia — wymaga ponownego zatwierdzenia.</small><?php endif; ?>
<div style="white-space:pre-wrap;margin:6px 0 10px"><?= e($item['pl']) ?></div>
<label>Angielska wersja <small>· <?= e($stored['state']??'bez szkicu') ?></small>
<textarea name="english" rows="3" maxlength="12000" style="width:100%;display:block"><?= e((string)($stored['translated_text']??$defaultEnglish)) ?></textarea></label>
<label style="display:block;margin:10px 0"><input type="checkbox" name="verified" value="1"> <strong>Sprawdziłem tłumaczenie maszynowe i zatwierdzam publikację.</strong></label>
<button type="submit" name="action" value="save" class="btn btn-light">Zapisz szkic</button> <button type="submit" name="action" value="publish" class="btn btn-green">Zatwierdź i opublikuj EN</button>
<?php if (($stored['state']??'')==='published'): ?><button type="submit" name="action" value="withdraw" class="btn btn-light">Wycofaj EN do szkicu</button><?php endif; ?>
</form>
<?php endforeach; ?>
<?php if ($pages>1): ?><div><a href="?scope=<?= e($scope) ?>&amp;q=<?= urlencode($filter) ?>&amp;page=<?= max(1,$page-1) ?>">← Poprzednia</a> · <?= $page ?>/<?= $pages ?> · <a href="?scope=<?= e($scope) ?>&amp;q=<?= urlencode($filter) ?>&amp;page=<?= min($pages,$page+1) ?>">Następna →</a></div><?php endif; ?>
</section>
<?php endif; ?>
<?php if ($job && $remaining): ?>
<script>
(()=>{const b=document.getElementById('polana-site-run'),p=document.getElementById('polana-site-progress');if(!b||!p)return;b.onclick=async()=>{b.disabled=true;try{for(;;){let form=new URLSearchParams({csrf_token:<?= json_encode(csrf_token()) ?>,scope:<?= json_encode($scope) ?>,action:'step'});let r=await fetch('/admin/translate-site.php?scope=<?= e($scope) ?>',{method:'POST',credentials:'same-origin',headers:{'Content-Type':'application/x-www-form-urlencoded'},body:form});let x=await r.json();if(!r.ok||!x.ok)throw Error(x.message||('HTTP '+r.status));p.textContent='Przetłumaczono '+(x.total-x.remaining)+' z '+x.total+' tekstów';if(x.remaining===0){location.reload();break;}}}catch(e){p.textContent='Błąd: '+e.message+' — możesz wznowić.';b.disabled=false;}};})();
</script>
<?php endif; ?>
<?php render_admin_footer(); ?>

Youez - 2016 - github.com/yon3zu
LinuXploit