| 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 : |
<?php
declare(strict_types=1);
/** TKLM Polana Menu EN V2 - safe additive module */
require_once __DIR__ . '/functions.php';
function tm_host(): string {
$host = strtolower((string)($_SERVER['HTTP_HOST'] ?? ''));
$host = preg_replace('/:\d+$/', '', $host) ?? '';
if (str_starts_with($host, 'www.')) $host = substr($host, 4);
if (!in_array($host, ['polanasmakow.pl', 'mojapolanasmakow.pl'], true)) {
throw new RuntimeException('Nieznany host Polany: sprawdź domenę.');
}
return $host;
}
function tm_api_key(): string {
$root = dirname(__DIR__);
$places = [
$root . '/tklm-translate-private/keys.php',
$root . '/_tklm_translate/keys.php',
dirname($root) . '/tklm-translate-private/keys.php',
];
$host = tm_host();
foreach ($places as $place) {
if (!is_file($place) || !is_readable($place)) continue;
$keys = require $place;
if (!is_array($keys)) continue;
$key = $keys[$host] ?? null;
if (!is_string($key) && $host === 'mojapolanasmakow.pl') $key = $keys['MOJA_POLANA_KEY'] ?? null;
if (!is_string($key) && $host === 'polanasmakow.pl') $key = $keys['POLANA_KEY'] ?? null;
if (is_string($key) && preg_match('/^[a-f0-9]{48}$/', $key)) return $key;
}
throw new RuntimeException('Brak poprawnego klucza tej domeny w keys.php. Sprawdź instrukcję lokalizacji.');
}
function tm_translate_text(string $source): string {
$source = trim($source);
if ($source === '' || strlen($source) > 4500) throw new RuntimeException('Tekst pusty lub za długi do automatycznego tłumaczenia.');
if ($source !== strip_tags($source)) throw new RuntimeException('Automatyczne tłumaczenie HTML wymaga przeglądu ręcznego.');
if (!extension_loaded('curl')) throw new RuntimeException('PHP cURL nie jest aktywne.');
$ch = curl_init('https://translate.tklm.pl/translate');
if ($ch === false) throw new RuntimeException('Nie można uruchomić cURL.');
$opts = [
CURLOPT_RETURNTRANSFER => true, CURLOPT_POST => true,
CURLOPT_POSTFIELDS => http_build_query(['q'=>$source,'source'=>'pl','target'=>'en','format'=>'text'],'','&',PHP_QUERY_RFC3986),
CURLOPT_HTTPHEADER => ['X-TKLM-Translate-Key: ' . tm_api_key(), 'Accept: application/json', 'Content-Type: application/x-www-form-urlencoded'],
CURLOPT_CONNECTTIMEOUT => 8, CURLOPT_TIMEOUT => 65,
CURLOPT_FOLLOWLOCATION => false, CURLOPT_SSL_VERIFYPEER => true, CURLOPT_SSL_VERIFYHOST => 2,
];
if (defined('CURLOPT_PROTOCOLS_STR')) $opts[CURLOPT_PROTOCOLS_STR]='https';
elseif (defined('CURLOPT_PROTOCOLS')) $opts[CURLOPT_PROTOCOLS]=CURLPROTO_HTTPS;
curl_setopt_array($ch,$opts);
$raw=curl_exec($ch); $status=(int)curl_getinfo($ch,CURLINFO_RESPONSE_CODE);
$err=$raw===false?curl_error($ch):''; curl_close($ch);
if ($raw === false) throw new RuntimeException('Połączenie API nieudane: ' . $err);
if ($status !== 200) throw new RuntimeException('API zwróciło HTTP ' . $status . ($status===429?' — poczekaj 2 sekundy.':''));
$obj=json_decode($raw,true);
if (!is_array($obj) || !is_string($obj['translatedText'] ?? null) || trim($obj['translatedText'])==='') {
throw new RuntimeException('API nie zwróciło poprawnego translatedText.');
}
return trim($obj['translatedText']);
}
function tm_schema(): void {
db()->exec("CREATE TABLE IF NOT EXISTS tklm_menu_i18n (
entity VARCHAR(32) NOT NULL, entity_id BIGINT UNSIGNED NOT NULL,
field_name VARCHAR(40) NOT NULL, lang CHAR(2) NOT NULL DEFAULT 'en',
source_sha CHAR(64) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
translated_text MEDIUMTEXT NOT NULL,
origin ENUM('auto','manual') NOT NULL DEFAULT 'auto',
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY(entity,entity_id,field_name,lang)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci");
}
function tm_entities(): array {
return [
'menu_items' => ['name'=>'name_en','description'=>'description_en','allergens'=>'allergens_en'],
'menu_categories' => ['name'=>'name_en'],
'menus' => ['intro'=>null,'seo_title'=>null,'seo_description'=>null],
'pages' => ['title'=>null,'heading'=>null,'seo_title'=>null,'seo_description'=>null],
'blog_posts' => ['title'=>'title_en','excerpt'=>'excerpt_en'],
];
}
function tm_one(string $entity,int $id): array {
if ($id < 1 || !array_key_exists($entity,tm_entities())) throw new RuntimeException('Nieprawidłowy rekord.');
// Entity and table names are server-side whitelisted, never interpolated from unchecked input.
$sql = "SELECT * FROM `$entity` WHERE id=? LIMIT 1";
$st=db()->prepare($sql); $st->execute([$id]); $row=$st->fetch();
if (!$row) throw new RuntimeException('Nie znaleziono rekordu.');
return $row;
}
function tm_i18n_row(string $entity,int $id,string $field): ?array {
$q=db()->prepare('SELECT * FROM tklm_menu_i18n WHERE entity=? AND entity_id=? AND field_name=? AND lang="en"');
$q->execute([$entity,$id,$field]);return $q->fetch() ?: null;
}
function tm_task(string $entity,int $id,string $field): array {
$schema=tm_entities();
if (!isset($schema[$entity]) || !array_key_exists($field,$schema[$entity])) throw new RuntimeException('Nieznane pole tłumaczenia.');
$row=tm_one($entity,$id); $dest=$schema[$entity][$field];
if ($dest!==null && !array_key_exists($dest,$row)) throw new RuntimeException('Brak kolumny EN w aktualnej bazie: '.$dest);
$source=trim((string)($row[$field] ?? ''));
$audit=tm_i18n_row($entity,$id,$field);
$translated=$dest!==null?trim((string)($row[$dest]??'')):trim((string)($audit['translated_text']??''));
$stale=$audit && $audit['source_sha']!==hash('sha256',$source);
return compact('entity','id','field','dest','source','translated','stale','audit');
}
function tm_track(string $entity,int $id,string $field,string $source,string $translated,string $origin): void {
$q=db()->prepare('INSERT INTO tklm_menu_i18n(entity,entity_id,field_name,lang,source_sha,translated_text,origin) VALUES(?,?,?,"en",?,?,?) ON DUPLICATE KEY UPDATE source_sha=VALUES(source_sha),translated_text=VALUES(translated_text),origin=VALUES(origin)');
$q->execute([$entity,$id,$field,hash('sha256',$source),$translated,$origin]);
}
function tm_auto_one(string $entity,int $id,string $field): array {
$t=tm_task($entity,$id,$field);
if ($t['source']==='') return ['skipped'=>true,'why'=>'Puste pole PL'];
if ($t['translated']!=='') return ['skipped'=>true,'why'=>'Tłumaczenie EN istnieje — nie nadpisuję'];
$trans=tm_translate_text($t['source']);
$now=tm_task($entity,$id,$field);
if ($now['source']!==$t['source'] || $now['translated']!=='') return ['skipped'=>true,'why'=>'Dane zmieniły się podczas tłumaczenia'];
$dest=$t['dest'];
db()->beginTransaction();
try {
if ($dest!==null) {
$u=db()->prepare("UPDATE `$entity` SET `$dest`=? WHERE id=? AND `$field`=? AND (`$dest` IS NULL OR TRIM(`$dest` )='')");
$u->execute([$trans,$id,$t['source']]);
if ($u->rowCount() !== 1) { db()->rollBack(); return ['skipped'=>true,'why'=>'Zapis anulowany — pole już edytowane']; }
}
tm_track($entity,$id,$field,$t['source'],$trans,'auto');
db()->commit();
} catch(Throwable $e) {if(db()->inTransaction())db()->rollBack();throw $e;}
return ['ok'=>true,'translation'=>$trans];
}
function tm_save_en(string $entity,int $id,string $field,string $english): void {
$t=tm_task($entity,$id,$field);
$english=trim($english);
if (strlen($english)>5000) throw new RuntimeException('Wpis jest za długi.');
if ($t['dest']!==null) {
$dest=$t['dest'];$u=db()->prepare("UPDATE `$entity` SET `$dest`=? WHERE id=?");$u->execute([$english,$id]);
}
tm_track($entity,$id,$field,$t['source'],$english,'manual');
}
function tm_active_menus(): array {
return db()->query("SELECT id,name,status,menu_type,valid_from,valid_to FROM menus ORDER BY (status='published') DESC,id DESC LIMIT 45")->fetchAll();
}
function tm_menu_rows(int $menuId): array {
$one=tm_one('menus',$menuId);
$q=db()->prepare('SELECT * FROM menu_categories WHERE menu_id=? ORDER BY sort_order,id');$q->execute([$menuId]);$categories=$q->fetchAll();
$q=db()->prepare('SELECT i.* FROM menu_items i JOIN menu_categories c ON c.id=i.category_id WHERE c.menu_id=? ORDER BY c.sort_order,c.id,i.sort_order,i.id');$q->execute([$menuId]);
$by=[];foreach($q->fetchAll() as $item){$by[(int)$item['category_id']][]=$item;}
foreach($categories as &$cat) $cat['items']=$by[(int)$cat['id']]??[];
unset($cat);
return [$one,$categories];
}
function tm_json(array $data,int $code=200): never {
http_response_code($code);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;
}