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/polana-v13-blog.php
<?php
declare(strict_types=1);
require_once dirname(__DIR__).'/includes/functions.php';
require_admin_role();
require_once dirname(__DIR__).'/includes/polana_v13_blog.php';
require_once dirname(__DIR__).'/includes/polana_v12_pages.php';
require_once dirname(__DIR__).'/includes/polana_v14_names.php';
$error='';$rows=[];
try{
 v9_init();$lang=(string)($_GET['lang']??'all');$status=(string)($_GET['status']??'review');
 if(!in_array($lang,['all','en','de','es'],true))$lang='all';
 if(!in_array($status,['review','pending','approved','all','errors'],true))$status='review';
 $page=max(1,(int)($_GET['page']??1));$limit=50;$offset=($page-1)*$limit;
 $where=["j.entity='blog_posts'"];$params=[];
 if($lang!=='all'){$where[]='j.lang=?';$params[]=$lang;}
 if($status==='errors'){$where[]="j.status='pending' AND j.attempts>=5";}
 elseif($status!=='all'){$where[]='j.status=?';$params[]=$status;}
 $sql="SELECT j.*,b.title AS blog_title,b.slug FROM tklm_v9_jobs j LEFT JOIN blog_posts b ON b.id=j.entity_id WHERE ".implode(' AND ',$where).' ORDER BY j.id ASC LIMIT '.$limit.' OFFSET '.$offset;
 $q=db()->prepare($sql);$q->execute($params);$rows=$q->fetchAll(PDO::FETCH_ASSOC);
 $stats=p13_blog_stats();
 $coverage=db()->query("SELECT COUNT(*) all_posts, SUM(CASE WHEN COALESCE(title_en,'')<>'' AND COALESCE(content_en,'')<>'' THEN 1 ELSE 0 END) en_posts, SUM(CASE WHEN COALESCE(title_de,'')<>'' AND COALESCE(content_de,'')<>'' THEN 1 ELSE 0 END) de_posts, SUM(CASE WHEN COALESCE(title_es,'')<>'' AND COALESCE(content_es,'')<>'' THEN 1 ELSE 0 END) es_posts FROM blog_posts")->fetch(PDO::FETCH_ASSOC);
}catch(Throwable $e){$error=$e->getMessage();$stats=['pending'=>0,'review'=>0,'approved'=>0,'errors'=>0,'processing'=>0,'total'=>0];$lang='all';$status='review';$page=1;$coverage=[];}
$p13BlogExample='';
try{
 $q=db()->query("SELECT slug FROM blog_posts WHERE status='published' AND publish_at<=NOW() AND publish_at IS NOT NULL ORDER BY id DESC LIMIT 1");
 $slug=(string)$q->fetchColumn();if(preg_match('/^[a-z0-9-]+$/',$slug))$p13BlogExample='/blog/'.$slug;
}catch(Throwable $e){error_log('V13 sample blog: '.$e->getMessage());}
render_admin_header('V13 — Blog w 4 językach');
?>
<style>
.p13-main{max-width:1280px;margin:22px auto;padding:16px}.p13-box{border-radius:13px;background:#fff;border:1px solid #dadfda;padding:18px;margin:14px 0;color:#243024}.p13-bar{display:flex;align-items:center;gap:12px;flex-wrap:wrap}.p13-btn{padding:11px 15px;background:#2a492c;color:#fff;border:0;border-radius:9px;cursor:pointer}.p13-btn:disabled{opacity:.45}.p13-muted{font-size:13px;color:#647066}.p13-box textarea{width:100%;min-height:110px;padding:9px;font:inherit;border:1px solid #d3d8d2;border-radius:8px;background:#fafcfb;color:#222}.p13-pill{display:inline-block;background:#f0f5e9;border-radius:6px;padding:5px 10px}.p13-top{position:sticky;top:8px;z-index:4;background:#fff}.p13-box details{margin:8px 0}.p13-box summary{cursor:pointer;font-weight:600}
</style>
<main class="p13-main">
<h1>POLANA — Blog PL / EN / DE / ES</h1>
<p><a href="/admin/polana-v14-slownik.php">Słownik chronionych nazw ulic i nazw własnych →</a></p>
<p>Wszystkie wpisy z MariaDB, także szkice i 40 artykułów z V10. Synology tłumaczy przez API, a zatwierdzenie zapisuje gotowe języki w dotychczasowych kolumnach bloga. Trzymaj kartę otwartą podczas tłumaczenia.</p>
<p class="p13-pill">Wpisy: <?= (int)($coverage['all_posts']??0) ?> · EN z tytułem i treścią: <?= (int)($coverage['en_posts']??0) ?> · DE: <?= (int)($coverage['de_posts']??0) ?> · ES: <?= (int)($coverage['es_posts']??0) ?></p>
<?php if($error):?><div class="p13-box" style="color:#b32">Błąd: <?=e($error)?></div><?php endif; ?>
<section class="p13-box">
<h2>1. Zbierz wszystkie wpisy i przetłumacz</h2>
<div class="p13-bar"><button type="button" class="p13-btn" id="p13-scan">Skanuj i tłumacz wszystkie wpisy</button><button type="button" class="p13-btn" id="p13-run">▶ Tłumacz wszystko</button><button type="button" class="p13-btn" id="p13-stop" style="background:#777">■ Stop</button></div>
<div id="p13-stats" class="p13-pill" style="margin-top:10px">Oczekujące: <?=$stats['pending']?> · Gotowe: <?=$stats['review']?> · Zatwierdzone: <?=$stats['approved']?> · Błędy: <?=$stats['errors']?></div>
<p id="p13-message" class="p13-muted" aria-live="polite">Możesz przerwać i wznowić. Przetłumaczone wyniki pozostają w bazie.</p>
</section>
<section class="p13-box">
<h2>2. Sprawdź i zatwierdź zbiorczo</h2>
<form method="get" class="p13-bar" style="margin:12px 0"><label>Język <select name="lang"><option value="all">Wszystkie</option><?php foreach(['en'=>'Angielski','de'=>'Niemiecki','es'=>'Hiszpański'] as $k=>$n):?><option value="<?=$k?>" <?=$lang===$k?'selected':''?>><?=$n?></option><?php endforeach;?></select></label>
<label>Status <select name="status"><?php foreach(['review'=>'Do zatwierdzenia','pending'=>'Oczekujące','errors'=>'Błędy','approved'=>'Zatwierdzone','all'=>'Wszystkie'] as $v=>$n):?><option value="<?=$v?>" <?=$status===$v?'selected':''?>><?=$n?></option><?php endforeach;?></select></label><button class="p13-btn" type="submit">Filtruj</button></form>
<div class="p13-bar p13-top" style="padding:10px;border:1px solid #ddd;border-radius:9px"><label><input type="checkbox" id="p13-all"> Zaznacz gotowe na tej stronie</label><span id="p13-chosen">0 wybranych</span><button class="p13-btn" id="p13-approve" type="button">Zatwierdź zaznaczone</button><button class="p13-btn" id="p13-approve-ready" type="button" style="background:#63822d">Zatwierdź gotowe z filtra (partiami)</button></div>
<p class="p13-muted">Przy długich tekstach najpierw otwórz podgląd i sprawdź wynik. Zatwierdzone wersje nowych wpisów pojawią się publicznie dopiero zgodnie ze statusem publikacji PL.</p>
<?php foreach($rows as $job): ?>
<div class="p13-box" data-job="<?=(int)$job['id']?>">
 <div class="p13-bar">
 <?php if($job['status']==='review'):?><label><input type="checkbox" class="p13-pick" value="<?=(int)$job['id']?>"> Wybierz</label><?php endif;?>
 <strong>#<?=(int)$job['entity_id']?> — <?=e((string)($job['blog_title']??''))?></strong>
 <span class="p13-pill"><?=e(strtoupper($job['lang']))?> · <?=e($job['field'])?> · <?=e($job['status'])?></span>
 <?php if(!empty($job['slug'])): ?><a href="/blog/<?=e($job['slug'])?>?preview=1" target="_blank" rel="noopener">Podgląd PL ↗</a><?php endif;?>
 </div>
 <details><summary>Oryginał PL</summary><pre style="white-space:pre-wrap;overflow-wrap:anywhere"><?=e((string)$job['source_text'])?></pre></details>
 <?php if($job['status']==='review'):?>
  <label>Tekst tłumaczenia — można poprawić przed zatwierdzeniem<textarea class="p13-value" rows="5"><?=e((string)$job['translated_text'])?></textarea></label>
  <?php $missing=p14_missing_terms((string)$job['source_text'],(string)$job['translated_text']);if($missing):?><p style="color:#ab4e08"><strong>Do sprawdzenia — zmieniono nazwę własną:</strong> <?=e(implode(', ',$missing))?>. Możesz poprawić tekst albo przetłumaczyć ponownie po zainstalowaniu V14.</p><?php endif;?>
  <button class="p13-btn p13-save" type="button">Zapisz korektę</button> <button class="p13-btn p13-retry" type="button" style="background:#777">Przetłumacz ponownie z ochroną nazw</button>
 <?php endif;?>
 <?php if($job['status']==='pending'):?><button class="p13-btn p13-retry" type="button">Ponów tłumaczenie</button><?php endif;?>
 <?php if($job['last_error']):?><p class="p13-muted">Błąd: <?=e($job['last_error'])?></p><?php endif;?>
</div>
<?php endforeach;?>
<?php if(!$rows):?><p>Brak wpisów w tym filtrze. Uruchom skanowanie.</p><?php endif;?>
<div class="p13-bar"><a href="?lang=<?=e($lang)?>&status=<?=e($status)?>&page=<?=max(1,$page-1)?>">← Poprzednia</a><b>Strona <?=$page?></b><a href="?lang=<?=e($lang)?>&status=<?=e($status)?>&page=<?=$page+1?>">Następna →</a></div>
</section>
<section class="p13-box"><h2>3. Podstrony i SEO</h2>
<p>Ten sam serwer tłumaczeń obsługuje również strony główną, menu, opinie, galerię, kontakt, rezerwacje i stałe teksty bloga. Zatwierdzanie podstron jest w istniejącym centrum V12.</p>
<div class="p13-bar"><button class="p13-btn" type="button" id="p13-all-pages">Skanuj i tłumacz wszystkie podstrony</button><button class="p13-btn" type="button" id="p13-stop-pages" style="background:#777">Zatrzymaj</button></div>
<p id="p13-pages-status" class="p13-muted" aria-live="polite">Stałe teksty, etykiety i nagłówki stron. Wpisy bloga tłumaczymy oddzielnie w sekcji wyżej.</p>
<div class="p13-bar"><a href="/admin/polana-v12-podstrony.php">Zatwierdź tłumaczenia podstron →</a><a href="/admin/polana-v11-podstrony.php">Gotowość SEO / hreflang →</a><a href="/admin/polana-v11-harmonogram.php">Harmonogram bloga →</a><a href="/admin/polana-v13-integracja.php">Automatyczne tłumaczenia nowych wpisów →</a></div>
</section>
</main>
<script>
(()=>{'use strict';
const apiUrl='/admin/polana-v13-api.php',token=<?=json_encode(csrf_token())?>;
const msg=document.querySelector('#p13-message'),stats=document.querySelector('#p13-stats'),lang=<?=json_encode($lang)?>;
const paths=<?=json_encode(array_keys(p12_paths()),JSON_UNESCAPED_UNICODE)?>,samplePost=<?=json_encode($p13BlogExample)?>;
let run=false;
const wait=ms=>new Promise(r=>setTimeout(r,ms));
async function call(action,props={}){
 const form=new FormData();form.append('csrf_token',token);form.append('action',action);
 for(const [k,v] of Object.entries(props)){if(Array.isArray(v))v.forEach(x=>form.append(k+'[]',x));else form.append(k,String(v));}
 const resp=await fetch(apiUrl,{method:'POST',body:form,credentials:'same-origin',cache:'no-store'});
 let data;try{data=await resp.json()}catch{throw new Error('Serwer zwrócił HTML lub błąd PHP (HTTP '+resp.status+').')}
 if(!resp.ok||!data.ok)throw new Error(data.error||('HTTP '+resp.status));return data;
}
function display(c){if(c)stats.textContent=`Oczekujące: ${c.pending} · Gotowe: ${c.review} · Zatwierdzone: ${c.approved} · Błędy: ${c.errors}`}
let pagesRunning=false;
document.querySelector('#p13-stop-pages').onclick=()=>{pagesRunning=false;document.querySelector('#p13-pages-status').textContent='Zatrzymuję po bieżącym żądaniu…'};
document.querySelector('#p13-all-pages').onclick=async()=>{
 if(pagesRunning)return;
 pagesRunning=true;const status=document.querySelector('#p13-pages-status');let scanned=0,failed=[];
 const fetchPage=async path=>{
  const url=path+(path.includes('?')?'&':'?')+'p12_scan='+encodeURIComponent(token);
  const res=await fetch(url,{credentials:'same-origin',cache:'no-store',redirect:'follow'});
  if(!res.ok||res.url.includes('/admin/login'))throw Error('HTTP '+res.status);
  await res.text();
 };
 const pagesApi=async action=>{
  const form=new FormData();form.append('csrf_token',token);form.append('action',action);
  const res=await fetch('/admin/polana-v12-api.php',{method:'POST',body:form,credentials:'same-origin',cache:'no-store'});
  const json=await res.json();if(!res.ok||!json.ok)throw Error(json.error||('HTTP '+res.status));return json;
 };
 try{
  for(const key of paths){
   if(!pagesRunning)break;
   const real=key==='/blog-post'?samplePost:key;
   if(!real){failed.push(key+' (brak opublikowanego wpisu)');continue;}
   status.textContent=`Skanowanie ${scanned+1}/${paths.length}: ${key}`;
   try{await fetchPage(real);scanned++;}catch(e){failed.push(key+' ('+e.message+')');}
  }
  let done=0,errors=0;
  while(pagesRunning){
   const r=await pagesApi('translate');done+=r.batch?.translated||0;errors+=r.batch?.errors||0;
   status.textContent=`Zeskanowano: ${scanned}; przetłumaczono: ${done}; oczekujące: ${r.counts?.pending||0}; błędy: ${errors}`;
   if(!r.counts?.pending||r.batch?.attempted===0)break;
   await wait(450);
  }
  if(failed.length)status.textContent+='; pominięte: '+failed.join(', ');
  status.textContent+=' — przejdź do centrum V12, aby zatwierdzić języki.';
 }catch(e){status.textContent='Błąd: '+e.message;}
 finally{pagesRunning=false;}
};
document.querySelector('#p13-scan').onclick=async()=>{
 let offset=0,added=0,seen=0;msg.textContent='Skanowanie bazy blogów…';
 try{for(let round=0;round<100;round++){
  const r=await call('scan',{offset});seen+=r.result.scanned;added+=r.result.added;
  msg.textContent=`Wpisy: ${seen}, zadania dodane/odświeżone: ${added}`;display(r.stats);
  if(r.result.next===null)break;offset=r.result.next;
 }msg.textContent+=' — skanowanie zakończone. Uruchamiam tłumaczenia…';document.querySelector('#p13-run').click();
 }catch(e){msg.textContent='Błąd skanowania: '+e.message;}
};
document.querySelector('#p13-run').onclick=async()=>{
 if(run)return;run=true;let done=0,errors=0;
 try{while(run){
  const r=await call('translate');display(r.stats);done+=r.result.translated;errors+=r.result.failed;
  msg.textContent=`Przetłumaczone pola: ${done}; błędy prób: ${errors}; oczekujące: ${r.stats.pending}`;
  if(r.stats.pending===0||r.result.attempted===0)break;
  await wait(450);
 }}catch(e){msg.textContent='Błąd: '+e.message;}finally{run=false;msg.textContent+=' — gotowe lub zatrzymane. Odśwież listę do zatwierdzenia.'}
};
document.querySelector('#p13-stop').onclick=()=>{run=false;msg.textContent='Zatrzymuję po bieżącym żądaniu…'};
const pick=[...document.querySelectorAll('.p13-pick')],all=document.querySelector('#p13-all'),chosen=document.querySelector('#p13-chosen');
function update(){const n=pick.filter(x=>x.checked).length;chosen.textContent=n+' wybranych';all.checked=n===pick.length&&n>0;all.indeterminate=n>0&&n<pick.length;}
all.onchange=()=>{pick.forEach(x=>x.checked=all.checked);update()};pick.forEach(x=>x.onchange=update);update();
document.querySelector('#p13-approve').onclick=async()=>{
 const ids=pick.filter(x=>x.checked).map(x=>x.value);if(!ids.length)return alert('Zaznacz gotowe tłumaczenia');
 if(!confirm('Zapisać korekty i zatwierdzić '+ids.length+' zweryfikowanych pól bloga?'))return;
 try{
  // Save changes in textarea before approval; never silently discard an unsaved correction.
  for(const id of ids){const card=document.querySelector('[data-job="'+id+'"]');const field=card?.querySelector('.p13-value');
   if(field&&field.value!==field.defaultValue){await call('edit',{id,text:field.value});field.defaultValue=field.value;}
  }
  const r=await call('approve',{ids});alert('Zatwierdzono: '+r.result.approved+'; błędy: '+r.result.errors.length);location.reload()}catch(e){alert(e.message)}
};
document.querySelector('#p13-approve-ready').onclick=async()=>{
 if([...document.querySelectorAll('.p13-value')].some(t=>t.value!==t.defaultValue))return alert('Masz niezapisane korekty na tej stronie. Zapisz je lub użyj „Zatwierdź zaznaczone” (zapis automatyczny).');
 if(!confirm('Potwierdzasz sprawdzenie wszystkich GOTOWYCH tłumaczeń w filtrze '+lang.toUpperCase()+'?'))return;
 let count=0;for(let i=0;i<100;i++){
  try{const r=await call('approve_ready',{lang});count+=r.result.approved;display(r.stats);
   msg.textContent='Zatwierdzono zbiorczo: '+count;
   if(!r.result.approved||r.stats.review===0)break;
  }catch(e){alert(e.message);break;}
 }location.reload();
};
document.querySelectorAll('.p13-save').forEach(btn=>btn.onclick=async()=>{
 const card=btn.closest('[data-job]');try{await call('edit',{id:card.dataset.job,text:card.querySelector('textarea').value});card.querySelector('textarea').defaultValue=card.querySelector('textarea').value;btn.textContent='Zapisano'}catch(e){alert(e.message)}
});
document.querySelectorAll('.p13-retry').forEach(btn=>btn.onclick=async()=>{
 const card=btn.closest('[data-job]');try{await call('retry',{id:card.dataset.job});location.reload()}catch(e){alert(e.message)}
});
})();
</script>
<?php render_admin_footer(); ?>

Youez - 2016 - github.com/yon3zu
LinuXploit