multilingual.class.php 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518
  1. <?php
  2. if (!defined('IN_ONLINE')) {
  3. exit('Access Denied');
  4. }
  5. /**
  6. * Description of multilingual
  7. *
  8. * @author Administrator
  9. */
  10. class multilingual{
  11. private static $_multilingual;
  12. function __construct() {
  13. }
  14. public static function getInstance() {
  15. global $memory_limit;
  16. $memory_limit = ini_get("memory_limit");
  17. ini_set("memory_limit", '2048M');
  18. if (!self::$_multilingual) {
  19. $c = __CLASS__;
  20. self::$_multilingual = new $c;
  21. }
  22. return self::$_multilingual;
  23. }
  24. public function multilingual_config() {
  25. $operate = utils::_get('operate');
  26. $operate = strtolower($operate);
  27. /*
  28. * multilingual_page_load
  29. */
  30. if ($operate == "multilingual_page") {
  31. //查询指定语言
  32. $langIds = [];
  33. $langkey = $_POST['langkey'];
  34. // 1. 统一处理 $langs 数组
  35. if (empty($langkey) || strtoupper($langkey) == "ALL") {
  36. // 如果没传或传 ALL,直接查询所有激活语言的 ID
  37. // 优化点:直接查 id,不需要先查 key 再查 id
  38. $langSql = "SELECT id FROM kln_i18n_languages";
  39. $langRows = common::excuteListSql($langSql);
  40. if ($langRows) {
  41. $langIds = array_column($langRows, 'id');
  42. }
  43. } else {
  44. // 2. 如果指定了语言,使用 IN 查询一次性获取
  45. $langs = explode(',', $langkey);
  46. // 安全过滤:防止 SQL 注入,并确保只处理非空字符串
  47. $safeLangs = array();
  48. foreach ($langs as $l) {
  49. $l = trim($l);
  50. if (!empty($l)) {
  51. $safeLangs[] = "'" . common::check_input($l) . "'";
  52. }
  53. }
  54. if (!empty($safeLangs)) {
  55. // 拼接 IN 条件:('english', 'french')
  56. $inClause = implode(',', $safeLangs);
  57. $langSql = "SELECT id FROM kln_i18n_languages WHERE lang_key IN ($inClause)";
  58. $langRows = common::excuteListSql($langSql);
  59. if ($langRows) {
  60. $langIds = array_column($langRows, 'id');
  61. }
  62. }
  63. }
  64. $pageData = $this->_loadPageInfo($langIds);
  65. $langData = $this->_loadLangInfo($langIds);
  66. common::echo_json_encode(200,array("pageData" =>$pageData,"langData" =>$langData));
  67. }
  68. /*
  69. * multilingual_load
  70. */
  71. if ($operate == "multilingual_init") {
  72. $this->_multilingual_init();
  73. }
  74. if ($operate == "multilingual_search") {
  75. $result = $this->_multilingual_search();
  76. common::echo_json_encode(200,$result);
  77. }
  78. /*
  79. * multilingual_save
  80. */
  81. if ($operate == "multilingual_save") {
  82. $multilingual_param = $_POST['multilingual_param'];
  83. $this->_multilingual_save($multilingual_param);
  84. }
  85. }
  86. private function _multilingual_init() {
  87. // 1. 获取并解析 JSON 数据
  88. $sql = "select * from public.kln_i18n_pages_init where page = 'login'";
  89. $data = common::excuteObjectSql($sql);
  90. $rawData = $data['data'];
  91. $mode = "update";
  92. $json = json_decode($rawData, true);
  93. if (!$json) {
  94. common::echo_json_encode(500, "Invalid JSON format");
  95. exit();
  96. }
  97. // 获取控制模式,默认为 'update'
  98. $mode = $mode == 'replace' ? 'replace' : 'update';
  99. $pageKey = isset($json['page']) ? common::check_input($json['page']) : '';
  100. $unverifiedNumber = isset($json['unverifiedNumber']) ? intval($json['unverifiedNumber']) : 0;
  101. $dataList = isset($json['data']) ? $json['data'] : [];
  102. //tracking report booking destinationDelivery
  103. if (empty($pageKey)) {
  104. common::echo_json_encode(500, "Missing page key");
  105. exit();
  106. }
  107. // 2. 定义语言映射配置
  108. $langMap = [
  109. 'traditionalChinese' => ['name' => '繁体中文', 'code' => 'zh-TW'],
  110. 'simplifiedChinese' => ['name' => '简体中文', 'code' => 'zh-CN'],
  111. 'english' => ['name' => '英语', 'code' => 'en-US'],
  112. 'french' => ['name' => '法语', 'code' => 'fr-FR'],
  113. 'spanish' => ['name' => '西班牙语', 'code' => 'es-ES'],
  114. 'portuguese' => ['name' => '葡萄牙语', 'code' => 'pt-BR']
  115. ];
  116. global $db;
  117. $db->StartTrans();
  118. try {
  119. // 第一步:处理页面表 (kln_i18n_pages)
  120. $pageId = null;
  121. // --- 模式判断:如果是覆盖模式,先删除旧数据 ---
  122. if ($mode === 'replace') {
  123. // 1. 先删子表 (kln_i18n_keys)
  124. $kikSqlDelete = "DELETE FROM kln_i18n_keys WHERE page_id IN (SELECT id FROM kln_i18n_pages WHERE page_key = '$pageKey')";
  125. $db->Execute($kikSqlDelete) or ( (!$db->ErrorMsg()) or error_log(common::dbLog($db, $kikSqlDelete), 0));
  126. // 2. 再删主表 (kln_i18n_pages) - 虽然上面删了子表,这里为了保险或者重置页面信息也可以删,
  127. $delPageSql = "DELETE FROM kln_i18n_pages WHERE page_key = '$pageKey'";
  128. $db->Execute($delPageSql) or ( (!$db->ErrorMsg()) or error_log(common::dbLog($db, $delPageSql), 0));
  129. }
  130. // --- 查询页面是否存在 ---
  131. $checkPageSql = "SELECT id FROM kln_i18n_pages WHERE page_key = '$pageKey'";
  132. $checkPageRes = $db->GetRow($checkPageSql) or ( (!$db->ErrorMsg()) or error_log(common::dbLog($db, $checkPageSql), 0));
  133. if (!empty($checkPageRes)) {
  134. // 存在 -> 更新
  135. $pageId = $checkPageRes['id'];
  136. $updatePageSql = "UPDATE kln_i18n_pages SET unverified_number = $unverifiedNumber WHERE id = $pageId";
  137. $db->Execute($updatePageSql) or ( (!$db->ErrorMsg()) or error_log(common::dbLog($db, $updatePageSql), 0));
  138. } else {
  139. // 不存在 -> 插入
  140. $insertPageSql = "INSERT INTO kln_i18n_pages (page_key, unverified_number, description) VALUES ('$pageKey', $unverifiedNumber, '')";
  141. $db->Execute($insertPageSql) or ( (!$db->ErrorMsg()) or error_log(common::dbLog($db, $insertPageSql), 0));
  142. // 获取新生成的 ID (PostgreSQL)
  143. $seqSql = "SELECT currval('kln_i18n_pages_id_seq')";
  144. $pageIdRow = $db->GetRow($seqSql) or ( (!$db->ErrorMsg()) or error_log(common::dbLog($db, $seqSql), 0));
  145. $pageId = $pageIdRow['currval'];
  146. }
  147. // 第二步:循环处理数据
  148. foreach ($dataList as $item) {
  149. $transKey = $item['key'];
  150. $originValue = isset($item['orginEnglish']) ? $item['orginEnglish'] : '';
  151. foreach ($langMap as $jsonKey => $langInfo) {
  152. // 只处理 JSON 中存在的数据
  153. if (isset($item[$jsonKey])) {
  154. $transValue = common::check_input($item[$jsonKey]);
  155. $statusKey = $jsonKey . 'Status';
  156. $status = isset($item[$statusKey]) ? intval($item[$statusKey]) : 0;
  157. // --- 1. 处理语言表 (kln_i18n_languages) ---
  158. $langKey = common::check_input($jsonKey);
  159. $langName = common::check_input($langInfo['name']);
  160. $localeCode = common::check_input($langInfo['code']);
  161. // 查询语言是否存在
  162. $checkLangSql = "SELECT id FROM kln_i18n_languages WHERE lang_key = '$langKey'";
  163. $checkLangRes = $db->GetRow($checkLangSql) or ( (!$db->ErrorMsg()) or error_log(common::dbLog($db, $checkLangSql), 0));
  164. if (!empty($checkLangRes)) {
  165. // 存在 -> 更新
  166. $langId = $checkLangRes['id'];
  167. $updateLangSql = "UPDATE kln_i18n_languages SET lang_name = '$langName', locale_code = '$localeCode' WHERE id = $langId";
  168. $db->Execute($updateLangSql) or ( (!$db->ErrorMsg()) or error_log(common::dbLog($db, $updateLangSql), 0));
  169. } else {
  170. // 不存在 -> 插入
  171. $insertLangSql = "INSERT INTO kln_i18n_languages (lang_key, lang_name, locale_code, is_active) VALUES ('$langKey', '$langName', '$localeCode', true)";
  172. $db->Execute($insertLangSql) or ( (!$db->ErrorMsg()) or error_log(common::dbLog($db, $updateLangSql), 0));
  173. // 获取 ID
  174. $seqLangSql = "SELECT currval('kln_i18n_languages_id_seq')";
  175. $langIdRow = $db->GetRow($seqLangSql) or ( (!$db->ErrorMsg()) or error_log(common::dbLog($db, $seqLangSql), 0));
  176. $langId = $langIdRow['currval'];
  177. }
  178. // --- 2. 处理词条表 (kln_i18n_keys) ---
  179. // 查询词条是否存在
  180. $checkKeySql = "SELECT id FROM kln_i18n_keys WHERE page_id = $pageId AND lang_id = $langId AND trans_key = '$transKey'";
  181. $checkKeyRes = $db->GetRow($checkKeySql) or ( (!$db->ErrorMsg()) or error_log(common::dbLog($db, $checkKeySql), 0));
  182. if (!empty($checkKeyRes)) {
  183. // 存在 -> 更新
  184. $updateKeySql = "UPDATE kln_i18n_keys SET trans_value = '$transValue', orgin_value = '$originValue', status = $status WHERE id = " . $checkKeyRes['id'];
  185. $db->Execute($updateKeySql) or ( (!$db->ErrorMsg()) or error_log(common::dbLog($db, $updateKeySql), 0));
  186. } else {
  187. // 不存在 -> 插入
  188. $insertKeySql = "INSERT INTO kln_i18n_keys (page_id, lang_id, trans_key, trans_value, orgin_value, status) VALUES ($pageId, $langId, '$transKey', '$transValue', '$originValue', $status)";
  189. $db->Execute($insertKeySql) or ( (!$db->ErrorMsg()) or error_log(common::dbLog($db, $insertKeySql), 0));
  190. }
  191. }
  192. }
  193. }
  194. } catch (Exception $e) {
  195. // 捕获异常,标记错误,以便回滚
  196. error_log("Exception in multilingual_init: " . $e->getMessage());
  197. }
  198. // 结束事务
  199. if ($db->CompleteTrans() === FALSE) {
  200. $msg = 'Save Error';
  201. } else {
  202. $msg = 'Save Success';
  203. }
  204. common::echo_json_encode(200, $msg);
  205. exit();
  206. }
  207. /**
  208. * 根据传入的 page_key 查询多语言信息并组装成 JSON 格式
  209. * 支持多个页面查询,将所有数据整合到一个 data 数组中
  210. *
  211. * @param string|array $pageKey 页面标识 (例如 'login' 或 ['login', 'register'])
  212. * @return array 返回组装好的数组 (通常最后会 json_encode 输出)
  213. */
  214. public function _multilingual_search() {
  215. $pageKey = $_POST['pagekey'];
  216. $pageIds = [];
  217. // 1. 统一处理 $pageIds 数组
  218. if (empty($pageKey) || strtoupper($pageKey) == "ALL") {
  219. // 如果没传或传 ALL,直接查询所有页面的 ID
  220. $pageSql = "SELECT id FROM kln_i18n_pages";
  221. $pageRows = common::excuteListSql($pageSql);
  222. if ($pageRows) {
  223. $pageIds = array_column($pageRows, 'id');
  224. }
  225. } else {
  226. // 2. 如果指定了页面,使用 IN 查询一次性获取
  227. $pages = explode(',', $pageKey);
  228. // 安全过滤:防止 SQL 注入,并确保只处理非空字符串
  229. $safePages = array();
  230. foreach ($pages as $p) {
  231. $p = trim($p);
  232. if (!empty($p)) {
  233. $safePages[] = "'" . common::check_input($p) . "'";
  234. }
  235. }
  236. if (!empty($safePages)) {
  237. $inClause = implode(',', $safePages);
  238. // 优化点:一次查询获取所有匹配的 ID 和 unverified_number
  239. $pageSql = "SELECT id, unverified_number FROM kln_i18n_pages WHERE page_key IN ($inClause)";
  240. $pageRows = common::excuteListSql($pageSql);
  241. if ($pageRows) {
  242. $pageIds = array_column($pageRows, 'id');
  243. }
  244. }
  245. }
  246. // 3. 如果没有找到任何页面,返回空数组
  247. if (empty($pageIds)) {
  248. return array();
  249. }
  250. //查询指定语言
  251. $langIds = [];
  252. $langkey = $_POST['langKey'];
  253. // 1. 统一处理 $langs 数组
  254. if (empty($langkey) || strtoupper($langkey) == "ALL") {
  255. // 如果没传或传 ALL,直接查询所有激活语言的 ID
  256. // 优化点:直接查 id,不需要先查 key 再查 id
  257. $langSql = "SELECT id FROM kln_i18n_languages";
  258. $langRows = common::excuteListSql($langSql);
  259. if ($langRows) {
  260. $langIds = array_column($langRows, 'id');
  261. }
  262. } else {
  263. // 2. 如果指定了语言,使用 IN 查询一次性获取
  264. $langs = explode(',', $langkey);
  265. // 安全过滤:防止 SQL 注入,并确保只处理非空字符串
  266. $safeLangs = array();
  267. foreach ($langs as $l) {
  268. $l = trim($l);
  269. if (!empty($l)) {
  270. $safeLangs[] = "'" . common::check_input($l) . "'";
  271. }
  272. }
  273. if (!empty($safeLangs)) {
  274. // 拼接 IN 条件:('english', 'french')
  275. $inClause = implode(',', $safeLangs);
  276. $langSql = "SELECT id FROM kln_i18n_languages WHERE lang_key IN ($inClause)";
  277. $langRows = common::excuteListSql($langSql);
  278. if ($langRows) {
  279. $langIds = array_column($langRows, 'id');
  280. }
  281. }
  282. }
  283. // 4. 查询所有指定页面的翻译词条(一次查询获取所有数据)
  284. $sql = "SELECT
  285. k.trans_key,
  286. k.trans_value,
  287. k.status,
  288. k.orgin_value,
  289. l.lang_key,
  290. p.page_key AS key_page
  291. FROM kln_i18n_keys k
  292. JOIN kln_i18n_languages l ON k.lang_id = l.id
  293. JOIN kln_i18n_pages p ON k.page_id = p.id
  294. WHERE k.page_id IN (" . implode(',', $pageIds) . ")
  295. AND k.lang_id IN (" . implode(',', $langIds) . ")
  296. ORDER BY k.trans_key, p.page_key";
  297. $rows = common::excuteListSql($sql);
  298. if ($rows === FALSE) {
  299. return array();
  300. }
  301. // 5. 数据重组(核心逻辑)
  302. $dataMap = array();
  303. foreach ($rows as $row) {
  304. $key = $row['trans_key'];
  305. $langKey = $row['lang_key'];
  306. // 如果这个 key 还没初始化,先初始化
  307. if (!isset($dataMap[$key])) {
  308. $dataMap[$key] = array(
  309. 'key' => $key,
  310. 'key_page' => $row['key_page'] // 记录所属页面
  311. );
  312. }
  313. // 动态填充语言字段
  314. $dataMap[$key][$langKey] = $row['trans_value'];
  315. // 填充状态字段
  316. $dataMap[$key][$langKey . 'Status'] = intval($row['status']);
  317. // 填充原始值(如果有)
  318. //if (!empty($row['orgin_value'])) {
  319. $dataMap[$key]['orginEnglish'] = $row['orgin_value'];
  320. //}
  321. }
  322. // 6. 将 Map 转换为数组列表
  323. $dataList = array_values($dataMap);
  324. // 7. 组装最终结果
  325. $result = array(
  326. 'page' => $pageKey,
  327. 'data' => $dataList
  328. );
  329. //重新查询一下page load
  330. $pageData = $this->_loadPageInfo($langIds);
  331. $result['pageData'] = $pageData;
  332. //langIds传空,意思是查全部语言
  333. $langData = $this->_loadLangInfo(null,$pageIds);
  334. $result['langData'] = $langData;
  335. return $result;
  336. }
  337. /**
  338. * 保存多语言数据
  339. *
  340. * @param array $multilingual_param 页面未审核数量参数,格式:[{unverifiedNumber: 3, page: 'login'}, ...]
  341. * @param array $multilingual_trans_param 翻译状态参数,格式:[{page: 'login', lang: 'traditionalChinese', trans_key: 'username', status: 1}, ...]
  342. * @return bool 是否保存成功
  343. */
  344. public function _multilingual_save($multilingual_param) {
  345. global $db;
  346. try {
  347. // 开启事务确保数据一致性
  348. $db->StartTrans();
  349. //2. 处理翻译词条状态更新
  350. foreach ($multilingual_param as $param) {
  351. $pageKey = common::check_input($param['page']);
  352. $langKey = common::check_input($param['lang']);
  353. $transKey = common::check_input($param['trans_key']);
  354. $trans_value = common::check_input($param['trans_value']);
  355. $status = $param['status'];
  356. // 查询页面ID
  357. $pageSql = "SELECT id FROM kln_i18n_pages WHERE page_key = '".$pageKey."'";
  358. $pageId = $db->GetOne($pageSql);
  359. if (!$pageId) {
  360. throw new Exception("Page not found: " . $pageKey);
  361. }
  362. // 查询语言ID
  363. $langSql = "SELECT id FROM kln_i18n_languages WHERE lang_key = '".$langKey."'";
  364. $langId = $db->GetOne($langSql);
  365. if (!$langId) {
  366. throw new Exception("Language not found: " . $langKey);
  367. }
  368. // 更新翻译状态
  369. $updateTransSql = "UPDATE kln_i18n_keys SET status = $status, trans_value = '$trans_value' ".
  370. " WHERE page_id = " . $pageId .
  371. " AND lang_id = " . $langId .
  372. " AND trans_key = '".$transKey."'";
  373. $db->Execute($updateTransSql);
  374. }
  375. } catch (Exception $e) {
  376. // 捕获异常,标记错误,以便回滚
  377. error_log("Exception in multilingual_save: " . $e->getMessage());
  378. }
  379. // 结束事务
  380. if ($db->CompleteTrans() === FALSE) {
  381. $msg = 'Save Error';
  382. } else {
  383. $msg = 'Save Success';
  384. }
  385. common::echo_json_encode(200, $msg);
  386. exit();
  387. }
  388. /**
  389. * load page info
  390. */
  391. public function _loadPageInfo($langIds){
  392. $pageSql = "SELECT
  393. (select page_key from public.kln_i18n_pages where id = page_id) as page_key,
  394. COUNT(*) AS unverified_count
  395. FROM (
  396. SELECT
  397. page_id,
  398. trans_key,
  399. BOOL_OR(status = 0) AS all_unverified
  400. FROM kln_i18n_keys
  401. WHERE lang_id IN (" . implode(',', $langIds) . ")
  402. GROUP BY page_id, trans_key
  403. ) AS subquery
  404. WHERE all_unverified = TRUE GROUP BY page_id;";
  405. $pageData = common::excuteListSql($pageSql);
  406. $unverified_count = 0;
  407. foreach($pageData as $_pageData){
  408. $unverified_count += $_pageData['unverified_count'];
  409. }
  410. $newItem = [
  411. 'page_key' => 'all',
  412. 'unverified_count' =>$unverified_count
  413. ];
  414. // 将新数组插入到 $pageDate 的开头
  415. array_unshift($pageData, $newItem);
  416. return $pageData;
  417. }
  418. /**
  419. * load lang info
  420. */
  421. public function _loadLangInfo($langIds,$pageIds = array()){
  422. $page_id_sql = "";
  423. if(!empty($pageIds)){
  424. $page_id_sql = "AND k.page_id IN (" . implode(',', $pageIds) . ")";
  425. }
  426. $lang_id_sql = "";
  427. if(!empty($langIds)){
  428. $lang_id_sql = "AND l.id IN (" . implode(',', $langIds) . ")";
  429. }
  430. $langSql = "SELECT
  431. l.lang_key,
  432. COUNT(k.id) AS unverified_count
  433. FROM kln_i18n_languages l
  434. LEFT JOIN kln_i18n_keys k ON l.id = k.lang_id
  435. $page_id_sql
  436. AND k.status = 0
  437. WHERE
  438. l.is_active = TRUE
  439. $lang_id_sql
  440. GROUP BY
  441. l.id, l.lang_key, l.lang_name;";
  442. $langData = common::excuteListSql($langSql);
  443. $unverified_count = 0;
  444. $retLangData = array('all'=>0);
  445. foreach($langData as $_langData){
  446. $unverified_count += $_langData['unverified_count'];
  447. $retLangData[$_langData['lang_key']] = intval($_langData['unverified_count']);
  448. }
  449. $retLangData['all'] =$unverified_count;
  450. return $retLangData;
  451. }
  452. }
  453. ?>