| 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);
require_once dirname(__DIR__).'/includes/tklm_v9.php';
require_admin_role();
v9_init();
$error='';
function v95_h($x): string { return htmlspecialchars((string)$x,ENT_QUOTES|ENT_SUBSTITUTE,'UTF-8'); }
if ($_SERVER['REQUEST_METHOD']==='POST') {
verify_csrf();
try {
$action=(string)($_POST['action']??'');
if($action==='bulk_approve'){
$ids=$_POST['job_ids']??[];
if(!is_array($ids))throw new RuntimeException('Nieprawidłowa lista');
$ids=array_values(array_unique(array_filter(array_map('intval',$ids),static fn($n)=>$n>0)));
if(count($ids)>120)throw new RuntimeException('Limit 120 zaznaczonych pozycji');
$ok=0;$bad=[];
foreach($ids as $id){
try { v9_approve($id);$ok++; }
catch(Throwable $e){$bad[]=$id;error_log('v9.5 approve #'.$id.': '.$e->getMessage());}
}
$_SESSION['v95_note']='Zatwierdzono '.$ok.'; błędy '.count($bad).($bad?' (ID: '.implode(', ',array_slice($bad,0,10)).')':'');
} elseif ($action==='approve') {
v9_approve((int)($_POST['job_id']??0));
$_SESSION['v95_note']='Tłumaczenie zatwierdzone';
} elseif ($action==='retry') {
$q=db()->prepare("UPDATE tklm_v9_jobs SET status='pending',attempts=0,last_error=NULL WHERE id=? AND status='pending'");
$q->execute([(int)($_POST['job_id']??0)]);
} elseif ($action==='work') {
v9_work(1);
}
header('Location: v9-review.php',true,303);exit;
}catch(Throwable $e){$error=$e->getMessage();}
}
// Najpierw gotowe do zatwierdzenia, później oczekujące. Brak automatycznego skanowania historycznych kart.
$stats=db()->query("SELECT status,COUNT(*) n FROM tklm_v9_jobs GROUP BY status")->fetchAll(PDO::FETCH_ASSOC);
$c=['pending'=>0,'review'=>0,'approved'=>0,'skipped'=>0];
foreach($stats as $st){$c[$st['status']]=(int)$st['n'];}
$eligible=(int)db()->query("SELECT COUNT(*) FROM tklm_v9_jobs WHERE status='pending' AND attempts<5")->fetchColumn();
$blocked=(int)db()->query("SELECT COUNT(*) FROM tklm_v9_jobs WHERE status='pending' AND attempts>=5")->fetchColumn();
$jobs=db()->query("SELECT * FROM tklm_v9_jobs WHERE status IN ('review','pending') ORDER BY FIELD(status,'review','pending'),updated_at DESC,id DESC LIMIT 120")->fetchAll(PDO::FETCH_ASSOC);
render_admin_header('Polana — tłumaczenia');
echo '<main style="max-width:1150px;margin:24px auto;padding:16px">';
echo '<h1>Centrum tłumaczeń — automatycznie</h1>';
echo '<p>Jedno kliknięcie: pozostaw tę kartę otwartą. System sam pobiera kolejne teksty i odkłada tłumaczenia do sprawdzenia.</p>';
if(isset($_SESSION['v95_note'])){echo '<p style="padding:12px;background:#edf7ef">'.v95_h($_SESSION['v95_note']).'</p>';unset($_SESSION['v95_note']);}
if($error)echo '<p style="color:#b91c1c">'.v95_h($error).'</p>';
echo '<section style="background:#f4f5f3;border:1px solid #dadeda;border-radius:14px;padding:18px;margin:16px 0">';
echo '<div style="display:flex;gap:10px;align-items:center;flex-wrap:wrap">';
echo '<button type="button" id="v95-start" style="padding:12px 20px;font-weight:bold;background:#27663c;color:#fff;border:0;border-radius:8px;cursor:pointer">▶ Przetłumacz wszystko z kolejki</button>';
echo '<button type="button" id="v95-stop" disabled style="padding:12px 18px;border:1px solid #888;border-radius:8px;cursor:pointer">■ Zatrzymaj</button>';
echo '</div>';
echo '<div id="v95-status" role="status" aria-live="polite" style="margin:12px 0">Gotowe do uruchomienia.</div>';
echo '<progress id="v95-progress" max="100" value="0" style="width:100%;height:14px"></progress>';
echo '<p id="v95-counts" style="font-size:14px">Oczekuje: '. $eligible .' · Do zatwierdzenia: '.($c['review']??0).' · Zatwierdzone: '.($c['approved']??0).' · Błędy: '.$blocked.'</p>';
echo '<small>Przetwarzanie trwa tylko gdy karta jest otwarta i aktywna. Po wylogowaniu lub zamknięciu okna zakończy się po bieżącym żądaniu.</small>';
echo '</section>';
echo '<form method="post" id="v95-bulk">'.csrf_field();
echo '<div style="position:sticky;top:0;background:#fff;padding:12px;border:1px solid #ddd;z-index:3;display:flex;gap:12px;align-items:center;flex-wrap:wrap">';
echo '<label><input type="checkbox" id="v95-all"> Zaznacz wszystkie gotowe na tej stronie</label><b id="v95-picked">0 wybranych</b>';
echo '<button type="submit" name="action" value="bulk_approve" onclick="return confirm(\'Zatwierdzić wybrane tłumaczenia?\')">Zatwierdź zaznaczone</button></div></form>';
foreach($jobs as $j){
echo '<section style="background:#fff;border:1px solid #ddd;padding:16px;margin:12px 0;border-radius:12px">';
if($j['status']==='review') echo '<label><input class="v95-pick" type="checkbox" form="v95-bulk" name="job_ids[]" value="'.(int)$j['id'].'"> Wybierz</label> ';
echo '<strong>'.v95_h($j['entity']).' #'.(int)$j['entity_id'].' — '.v95_h($j['field']).' / '.strtoupper(v95_h($j['lang'])).'</strong> · '.v95_h($j['status']);
echo '<p><b>PL:</b> '.nl2br(v95_h(mb_substr((string)$j['source_text'],0,1000))).'</p>';
if($j['status']==='review')echo '<p><b>Tłumaczenie:</b> '.nl2br(v95_h(mb_substr((string)$j['translated_text'],0,1000))).'</p>';
if($j['last_error'])echo '<p style="color:#a33">'.v95_h($j['last_error']).'</p>';
echo '<form method="post">'.csrf_field().'<input type="hidden" name="job_id" value="'.(int)$j['id'].'">';
if($j['status']==='review')echo '<button name="action" value="approve">Zatwierdź</button> ';
if($j['status']==='pending')echo '<button name="action" value="retry">Ponów</button>';
echo '</form></section>';
}
$token=json_encode(csrf_token(),JSON_HEX_TAG|JSON_HEX_APOS|JSON_HEX_AMP|JSON_HEX_QUOT);
$initial=(int)$eligible;
$script=<<<'JS'
<script>
(()=>{
const all=document.getElementById('v95-all');const pick=[...document.querySelectorAll('.v95-pick')];
const count=document.getElementById('v95-picked');
const update=()=>{const n=pick.filter(x=>x.checked).length;count.textContent=n+' wybranych';all.checked=!!pick.length&&n===pick.length;all.indeterminate=n>0&&n<pick.length;};
all.addEventListener('change',()=>{pick.forEach(x=>x.checked=all.checked);update();});pick.forEach(x=>x.addEventListener('change',update));update();
const start=document.getElementById('v95-start'),stop=document.getElementById('v95-stop'),status=document.getElementById('v95-status'),progress=document.getElementById('v95-progress'),counts=document.getElementById('v95-counts');
const csrf=__TOKEN__,initial=__INITIAL__;let active=false,failures=0,handled=0,total=initial;
const sleep=ms=>new Promise(resolve=>setTimeout(resolve,ms));
function controls(){start.disabled=active;stop.disabled=!active;}
async function run(){
if(active)return;active=true;failures=0;controls();status.textContent='Tłumaczenie trwa — nie zamykaj karty…';
while(active){
try{
const response=await fetch('v9-batch.php',{method:'POST',credentials:'same-origin',cache:'no-store',headers:{'Accept':'application/json','Content-Type':'application/x-www-form-urlencoded'},body:new URLSearchParams({csrf_token:csrf})});
if(response.status===401||response.status===403||response.status===419){throw new Error('Sesja wygasła — zaloguj się ponownie');}
const raw=await response.text();let data;
try{data=JSON.parse(raw);}catch(e){throw new Error('Serwer nie zwrócił JSON (HTTP '+response.status+')');}
if(!response.ok||!data.ok)throw new Error(data.error||'HTTP '+response.status);
failures=0;
if(data.busy){status.textContent='Inne żądanie już tłumaczy. Czekam…';await sleep(1100);continue;}
handled+=Number(data.attempted||0);
const st=data.stats||{};const remaining=Number(st.pending||0);
total=Math.max(total,handled+remaining);
progress.value=total?Math.min(100,Math.round(handled/total*100)):100;
counts.textContent='Oczekuje: '+remaining+' · Do zatwierdzenia: '+(st.review||0)+' · Zatwierdzone: '+(st.approved||0)+' · Błędy: '+(st.failed||0);
status.textContent='Przetworzono w tej sesji: '+handled+' / '+total;
if(remaining===0){active=false;status.textContent='Kolejka ukończona. Odświeżam listę do zatwierdzenia…';await sleep(1200);location.reload();return;}
if(!Number(data.attempted||0)){active=false;status.textContent='Brak postępu; zatrzymano, sprawdź kolejkę.';break;}
await sleep(350);
}catch(e){failures++;status.textContent='Problem: '+e.message+' ('+failures+'/3)';if(failures>=3){active=false;break;}await sleep(2000);}
}
controls();
}
start.addEventListener('click',run);
stop.addEventListener('click',()=>{active=false;status.textContent='Zatrzymuję po bieżącej frazie…';controls();});
window.addEventListener('beforeunload',e=>{if(active){e.preventDefault();e.returnValue='';}});
})();
</script>
JS;
echo str_replace(['__TOKEN__','__INITIAL__'],[$token,(string)$initial],$script);
echo '</main>';
render_admin_footer();