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/includes/

Upload File :
current_dir [ Writeable ] document_root [ Writeable ]

 

Command :


[ Back ]     

Current File : /polana/includes/polana_v13_blog.php
<?php
declare(strict_types=1);
/** V13 — business blog translation; uses V9 jobs and existing blog_posts language columns. */
require_once __DIR__.'/tklm_v9.php';
if (is_file(__DIR__.'/polana_v14_names.php')) require_once __DIR__.'/polana_v14_names.php';
function p13_blog_stats(): array {
    v9_init();
    $out=['pending'=>0,'review'=>0,'approved'=>0,'errors'=>0,'processing'=>0,'total'=>0];
    foreach(db()->query("SELECT status,attempts,COUNT(*) c FROM tklm_v9_jobs WHERE entity='blog_posts' GROUP BY status,attempts") as $row){
        $status=(string)$row['status'];$n=(int)$row['c'];$out['total']+=$n;
        if($status==='pending'&&(int)$row['attempts']>=5)$out['errors']+=$n;
        elseif(isset($out[$status]))$out[$status]+=$n;
    }
    return $out;
}
function p13_blog_scan(int $offset=0,int $limit=20): array {
    v9_init();
    $limit=max(1,min(40,$limit));$offset=max(0,$offset);
    $q=db()->query('SELECT id,title,excerpt,content,title_en,title_de,title_es,excerpt_en,excerpt_de,excerpt_es,content_en,content_de,content_es FROM blog_posts ORDER BY id LIMIT '.$limit.' OFFSET '.$offset);
    $count=0;$added=0;
    $find=db()->prepare("SELECT source_hash,status FROM tklm_v9_jobs WHERE entity='blog_posts' AND entity_id=? AND field=? AND lang=?");
    foreach($q->fetchAll(PDO::FETCH_ASSOC) as $post){
        $count++;
        foreach(['title','excerpt','content'] as $field){
            $text=trim((string)($post[$field]??''));if($text==='')continue;
            $hash=hash('sha256',$text);
            foreach(['en','de','es'] as $lang){
                $find->execute([(int)$post['id'],$field,$lang]);$current=$find->fetch(PDO::FETCH_ASSOC);
                // Respect manually translated fields without a V9 job.
                if(!$current&&trim((string)($post[$field.'_'.$lang]??''))!=='')continue;
                if($current&&hash_equals((string)$current['source_hash'],$hash)&&($current['status']??'')!=='')continue;
                v9_enqueue('blog_posts',(int)$post['id'],$field,$lang,$text);$added++;
            }
        }
    }
    return ['scanned'=>$count,'added'=>$added,'next'=>$count===$limit?$offset+$count:null];
}
function p13_split_text(string $s,int $max=1600): array {
    if(strlen($s)<=$max)return [$s];
    $parts=[];$buf='';
    // Keep paragraph boundaries. For exceptionally long paragraphs, split on UTF-8 boundaries.
    $paragraphs=preg_split('/\n\s*\n/u',$s)?:[$s];
    foreach($paragraphs as $para){
        if($para==='')continue;
        if(strlen($para)>$max){
            if($buf!==''){$parts[]=$buf;$buf='';}
            while(strlen($para)>$max){
                $head=mb_strcut($para,0,$max,'UTF-8');
                $space=mb_strrpos($head,' ',0,'UTF-8');
                if($space!==false&&$space>250)$head=mb_substr($head,0,$space,'UTF-8');
                $parts[]=$head;
                $para=trim(mb_substr($para,mb_strlen($head,'UTF-8'),null,'UTF-8'));
            }
            $buf=$para;
        }else{
            if($buf!==''&&strlen($buf)+strlen($para)+2>$max){$parts[]=$buf;$buf='';}
            $buf=$buf===''?$para:$buf."\n\n".$para;
        }
    }
    if($buf!=='')$parts[]=$buf;
    return $parts;
}
/**
 * V16.2: deterministic, UTF-8-safe fragments, normally <= 420 bytes.
 * Preserve original whitespace between fragments to avoid flattening Markdown.
 * The API never receives the whole article, and one request handles ONE fragment.
 */
function p13_v162_segments(string $text, int $maxBytes=420): array {
    $maxBytes=max(180,min(750,$maxBytes));
    // Keep whole protected names and street addresses in ONE fragment.
    $named=[];
    if(function_exists('p14_terms')){
        $named=array_map(static fn($v)=>preg_quote((string)$v,'~'),p14_terms());
    }
    $street='(?:ul\.|ulica|ulicy|al\.|aleja|alei)\s+[A-ZĄĆĘŁŃÓŚŹŻ][\p{L}\p{M}.\x27-]{1,}(?:\s+[A-ZĄĆĘŁŃÓŚŹŻ][\p{L}\p{M}.\x27-]{1,}){0,3}(?:\s+\d+[A-Za-z]?)?';
    $protected='(?<![\p{L}\p{N}])(?:'.$street.($named?'|'.implode('|',$named):'').')(?![\p{L}\p{N}])';
    $pattern='~https?://[^\s<>]+|'.$protected.'|\s+|[^\s]+~iu';
    $found=preg_match_all($pattern,$text,$matches);
    if($found===false)throw new RuntimeException('Niepoprawne UTF-8 artykułu');
    $pieces=$matches[0];
    if(implode('',$pieces)!==$text)throw new RuntimeException('Błąd dzielenia nazw chronionych');
    $segments=[];$buffer='';
    foreach($pieces as $piece){
        if($piece==='')continue;
        if(preg_match('/^\s+$/u',$piece)){
            $buffer.=$piece;
            continue;
        }
        if(strlen($piece)>$maxBytes){
            throw new RuntimeException('Zbyt długi wyraz lub fragment HTML bez spacji; sprawdź treść artykułu');
        }
        if(strlen($buffer.$piece)>$maxBytes && trim($buffer)!==''){
            preg_match('/(\s*)$/u',$buffer,$m);
            $separator=(string)($m[1]??'');
            $body=substr($buffer,0,strlen($buffer)-strlen($separator));
            if(trim($body)!=='')$segments[]=['text'=>$body,'after'=>$separator];
            $buffer=$piece;
        }else{$buffer.=$piece;}
    }
    if(trim($buffer)!==''){
        preg_match('/(\s*)$/u',$buffer,$m);
        $separator=(string)($m[1]??'');
        $body=substr($buffer,0,strlen($buffer)-strlen($separator));
        if(trim($body)!=='')$segments[]=['text'=>$body,'after'=>$separator];
    }elseif($segments && $buffer!==''){
        $segments[count($segments)-1]['after'].=$buffer;
    }
    if(!$segments)throw new RuntimeException('Pusty tekst do tłumaczenia');
    // Guard against malformed splits that could silently omit source content.
    $check='';foreach($segments as $part)$check.=$part['text'].$part['after'];
    if($check!==$text)throw new RuntimeException('Kontrola dzielenia tekstu nie powiodła się');
    return $segments;
}

/** Progress survives PHP timeout, reload, logout and transient HTTP 502. */
function p13_v162_progress_schema():void {
    db()->exec("CREATE TABLE IF NOT EXISTS polana_v16_blog_progress (
      job_id BIGINT UNSIGNED NOT NULL PRIMARY KEY,
      source_hash CHAR(64) NOT NULL,
      next_segment INT UNSIGNED NOT NULL DEFAULT 0,
      segment_total INT UNSIGNED NOT NULL,
      translations_json MEDIUMTEXT NOT NULL,
      updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci");
}

function p13_blog_work(): array {
    v9_init();
    p13_v162_progress_schema();
    $db=db();
    // The existing claim scheme also recovers requests killed by a PHP timeout.
    $db->exec("UPDATE tklm_v9_jobs SET status='pending' WHERE entity='blog_posts' AND status='processing' AND updated_at<DATE_SUB(NOW(),INTERVAL 15 MINUTE)");
    // HTTP 502 can mean a temporarily unavailable proxy/NAS. Wait 90 seconds before retry.
    $q=$db->query("SELECT j.* FROM tklm_v9_jobs j
        WHERE j.entity='blog_posts' AND j.status='pending' AND j.attempts<5
          AND NOT (j.last_error LIKE '%HTTP 502%' AND j.updated_at>DATE_SUB(NOW(), INTERVAL 90 SECOND))
        ORDER BY j.updated_at,j.id LIMIT 1");
    $job=$q->fetch(PDO::FETCH_ASSOC);
    if(!$job)return ['attempted'=>0,'translated'=>0,'failed'=>0,'waiting'=>true];
    $id=(int)$job['id'];$field=(string)$job['field'];$lang=(string)$job['lang'];
    if(!in_array($field,['title','excerpt','content'],true)||!in_array($lang,['en','de','es'],true)){
        $db->prepare("UPDATE tklm_v9_jobs SET attempts=5,last_error='Nieznane pole lub język' WHERE id=?")->execute([$id]);
        return ['attempted'=>1,'translated'=>0,'failed'=>1];
    }
    $claim=$db->prepare("UPDATE tklm_v9_jobs SET status='processing',updated_at=NOW() WHERE id=? AND status='pending' AND attempts<5");
    $claim->execute([$id]);
    if(!$claim->rowCount())return ['attempted'=>0,'translated'=>0,'failed'=>0,'busy'=>true];
    try{
        $q=$db->prepare("SELECT `$field` FROM blog_posts WHERE id=?");
        $q->execute([(int)$job['entity_id']]);$value=$q->fetchColumn();
        if(!is_string($value)||hash('sha256',trim($value))!==(string)$job['source_hash']){
            if(is_string($value)&&trim($value)!=='')v9_enqueue('blog_posts',(int)$job['entity_id'],$field,$lang,$value);
            else $db->prepare("UPDATE tklm_v9_jobs SET status='pending',attempts=5,last_error='Brak oryginału' WHERE id=?")->execute([$id]);
            return ['attempted'=>1,'translated'=>0,'failed'=>0,'skipped'=>true];
        }
        $source=trim($value);
        $segments=p13_v162_segments($source);
        $total=count($segments);
        $q=$db->prepare('SELECT * FROM polana_v16_blog_progress WHERE job_id=?');$q->execute([$id]);
        $progress=$q->fetch(PDO::FETCH_ASSOC);
        if(!$progress || !hash_equals((string)$progress['source_hash'],(string)$job['source_hash']) || (int)$progress['segment_total']!==$total){
            $db->prepare('REPLACE INTO polana_v16_blog_progress(job_id,source_hash,next_segment,segment_total,translations_json) VALUES(?,?,0,?,?)')
               ->execute([$id,$job['source_hash'],$total,'[]']);
            $index=0;$translations=[];
        }else{
            $index=(int)$progress['next_segment'];
            $translations=json_decode((string)$progress['translations_json'],true);
            if(!is_array($translations)||count($translations)!==$index||$index>$total){
                throw new RuntimeException('Nieprawidłowy zapis postępu: skontroluj tabelę postępu');
            }
        }
        if($index<$total){
            $text=(string)$segments[$index]['text'];
            $api=new TklmTranslateClient(v9_resolve_translate_key());
            // The glossary is optional; preserve named places / streets if available.
            $translated=function_exists('p14_translate')
                ?p14_translate($api,$text,$lang)
                :trim((string)($api->translate($text,'pl',$lang)['translatedText']??''));
            $translated=trim($translated);
            if($translated==='')throw new RuntimeException('API nie zwróciło tekstu fragmentu');
            $translations[]=$translated;
            $index++;
            // Save the successfully translated fragment BEFORE releasing the job.
            $q=$db->prepare('UPDATE polana_v16_blog_progress SET next_segment=?,translations_json=? WHERE job_id=? AND source_hash=?');
            $q->execute([$index,json_encode($translations,JSON_UNESCAPED_UNICODE|JSON_INVALID_UTF8_SUBSTITUTE),$id,$job['source_hash']]);
            if(!$q->rowCount())throw new RuntimeException('Nie zapisano postępu fragmentu');
        }
        if($index<$total){
            $q=$db->prepare("UPDATE tklm_v9_jobs SET status='pending',attempts=0,last_error=NULL,updated_at=NOW() WHERE id=? AND source_hash=? AND status='processing'");
            $q->execute([$id,$job['source_hash']]);
            return ['attempted'=>1,'translated'=>0,'failed'=>0,'partial'=>true,'job_id'=>$id,
                    'lang'=>$lang,'field'=>$field,'entity_id'=>(int)$job['entity_id'],
                    'segment'=>$index,'segments'=>$total];
        }
        $target='';
        foreach($segments as $i=>$segment)$target.=$translations[$i].$segment['after'];
        $target=trim($target);
        if($target==='')throw new RuntimeException('Puste tłumaczenie po połączeniu fragmentów');
        if($field==='title'&&mb_strlen($target,'UTF-8')>255)throw new RuntimeException('Przekroczono 255 znaków w tytule');
        $db->beginTransaction();
        try{
            $q=$db->prepare("UPDATE tklm_v9_jobs SET status='review',translated_text=?,last_error=NULL,updated_at=NOW() WHERE id=? AND source_hash=? AND status='processing'");
            $q->execute([$target,$id,$job['source_hash']]);
            if($q->rowCount()!==1)throw new RuntimeException('Zadanie zmienione podczas zapisu');
            $db->prepare('DELETE FROM polana_v16_blog_progress WHERE job_id=?')->execute([$id]);
            $db->commit();
        }catch(Throwable $e){$db->rollBack();throw $e;}
        return ['attempted'=>1,'translated'=>1,'failed'=>0,'job_id'=>$id,'lang'=>$lang,
                'field'=>$field,'entity_id'=>(int)$job['entity_id'],'segments'=>$total];
    }catch(Throwable $e){
        if($db->inTransaction())$db->rollBack();
        error_log('POLANA V16.2 blog job='.$id.' '.preg_replace('/[a-f0-9]{48}/i','[key]',$e->getMessage()));
        $q=$db->prepare("UPDATE tklm_v9_jobs SET status='pending',attempts=attempts+1,last_error=?,updated_at=NOW() WHERE id=? AND status='processing'");
        $q->execute([mb_substr($e->getMessage(),0,500),$id]);
        return ['attempted'=>1,'translated'=>0,'failed'=>1,'job_id'=>$id,'lang'=>$lang];
    }
}
function p13_blog_approve(array $ids): array {
    $ids=array_values(array_unique(array_filter(array_map('intval',$ids),static fn($v)=>$v>0)));
    if(count($ids)>150)throw new RuntimeException('Limit 150 pozycji jednorazowo');
    $s=db()->prepare("SELECT id FROM tklm_v9_jobs WHERE entity='blog_posts' AND status='review' AND id=?");
    $n=0;$errors=[];
    foreach($ids as $id){
        $s->execute([$id]);if(!$s->fetchColumn())continue;
        try{v9_approve($id);$n++;}catch(Throwable $e){$errors[]=$id.': '.$e->getMessage();}
    }
    return ['approved'=>$n,'errors'=>$errors];
}
function p13_blog_enqueue_one(int $id): int {
    if($id<=0)return 0;
    $q=db()->prepare('SELECT id,title,excerpt,content,title_en,title_de,title_es,excerpt_en,excerpt_de,excerpt_es,content_en,content_de,content_es FROM blog_posts WHERE id=?');
    $q->execute([$id]);$post=$q->fetch(PDO::FETCH_ASSOC);if(!$post)return 0;
    $count=0;
    $existing=db()->prepare("SELECT source_hash,status FROM tklm_v9_jobs WHERE entity='blog_posts' AND entity_id=? AND field=? AND lang=?");
    foreach(['title','excerpt','content'] as $field){
        $source=trim((string)($post[$field]??''));if($source==='')continue;
        $hash=hash('sha256',$source);
        foreach(['en','de','es'] as $lang){
            $existing->execute([$id,$field,$lang]);$old=$existing->fetch(PDO::FETCH_ASSOC);
            if(!$old&&trim((string)($post[$field.'_'.$lang]??''))!=='')continue;
            if($old&&hash_equals((string)$old['source_hash'],$hash))continue;
            v9_enqueue('blog_posts',$id,$field,$lang,$source);$count++;
        }
    }
    return $count;
}

Youez - 2016 - github.com/yon3zu
LinuXploit