PageRenderTime 50ms CodeModel.GetById 11ms RepoModel.GetById 0ms app.codeStats 1ms

/wp-content/plugins/codestyling-localization/includes/class-translationfile.php

https://gitlab.com/oxidigitaluser/liguelista
PHP | 1075 lines | 877 code | 103 blank | 95 comment | 186 complexity | 290b10716e41affa1fda55e12db0e1cc MD5 | raw file
  1. <?php
  2. /*
  3. License:
  4. ==============================================================================
  5. Copyright 2008 Heiko Rabe (email : info@code-styling.de)
  6. This program is free software; you can redistribute it and/or modify
  7. it under the terms of the GNU General Public License as published by
  8. the Free Software Foundation; either version 2 of the License, or
  9. (at your option) any later version.
  10. This program is distributed in the hope that it will be useful,
  11. but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13. GNU General Public License for more details.
  14. You should have received a copy of the GNU General Public License
  15. along with this program; if not, write to the Free Software
  16. Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
  17. ==============================================================================
  18. contribution for performant mo-file reading: Thomas Urban (www.toxa.de)
  19. fixed arround PHP preg_match bug
  20. references and possible explainations:
  21. Bug #37793 child pid xxx exit signal Segmentation fault (11) => http://bugs.php.net/bug.php?id=37793
  22. Bugzilla Bug 841 => http://bugs.exim.org/show_bug.cgi?id=841
  23. http://mobile-website.mobi/php-utf8-vs-iso-8859-1-59
  24. */
  25. class CspStringsAreAscii {
  26. function _strlen($string) { return strlen($string); }
  27. function _strpos($haystack, $needle, $offset = null) { return strpos($haystack, $needle, $offset); }
  28. function _substr($string, $offset, $length = null) { return (is_null($length) ? substr($string, $offset) : substr($string, $offset, $length)); }
  29. function _str_split($string, $chunkSize) { return str_split($string, $chunkSize); }
  30. function _substr_count($haystack, $needle) { return substr_count($haystack, $needle); }
  31. function _seems_utf8($string) { return seems_utf8($string); }
  32. function _utf8_encode($string) { return utf8_encode($string); }
  33. }
  34. class CspStringsAreMultibyte {
  35. function _strlen($string) { return mb_strlen($string, 'ascii'); }
  36. function _strpos($haystack, $needle, $offset = null) { return mb_strpos($haystack, $needle, $offset, 'ascii'); }
  37. function _substr($string, $offset, $length = null) { return (is_null($length) ? mb_substr($string, $offset, 1073741824, 'ascii') : mb_substr($string, $offset, $length, 'ascii')); }
  38. function _str_split($string, $chunkSize) {
  39. //do not! break unicode / uft8 character in the middle of encoding, just at char border
  40. $length = $this->_strlen($string);
  41. $out = array();
  42. for ($i=0;$i<$length;$i+=$chunkSize) {
  43. $out[] = $this->_substr($string, $i, $chunkSize);
  44. }
  45. return $out;
  46. }
  47. function _substr_count($haystack, $needle) { return mb_substr_count($haystack, $needle, 'ascii'); }
  48. function _seems_utf8($string) { return mb_check_encoding($string, 'UTF-8'); }
  49. function _utf8_encode($string) { return mb_convert_encoding($string, 'UTF-8'); }
  50. }
  51. class CspTranslationFile {
  52. function CspTranslationFile($type = 'unknown') {
  53. $this->__construct($type);
  54. }
  55. function __construct($type = 'unknown') {
  56. //now lets check whether overloaded functions been used and provide the correct str_* functions as usual
  57. if(( ini_get( 'mbstring.func_overload' ) & 0x02) === 0x02 && extension_loaded('mbstring') && is_callable( 'mb_substr' )) {
  58. $this->strings = new CspStringsAreMultibyte();
  59. }
  60. else{
  61. $this->strings = new CspStringsAreAscii();
  62. }
  63. $this->component_type = $type;
  64. $this->header = array(
  65. 'Project-Id-Version' => '',
  66. 'Report-Msgid-Bugs-To' => '',
  67. 'POT-Creation-Date' => '',
  68. 'PO-Revision-Date' => '',
  69. 'Last-Translator' => '',
  70. 'Language-Team' => '',
  71. 'MIME-Version' => '1.0',
  72. 'Content-Type' => 'text/plain; charset=UTF-8',
  73. 'Content-Transfer-Encoding' => '8bit',
  74. 'Plural-Forms' => 'nplurals=2; plural=n != 1;',
  75. 'X-Generator' => 'CSL v1.x',
  76. 'X-Poedit-Language' => '',
  77. 'X-Poedit-Country' => '',
  78. 'X-Poedit-SourceCharset' => 'utf-8',
  79. 'X-Poedit-KeywordsList' => '__;_e;__ngettext:1,2;_n:1,2;__ngettext_noop:1,2;_n_noop:1,2;_c,_nc:4c,1,2;_x:1,2c;_ex:1,2c;_nx:4c,1,2;_nx_noop:4c,1,2;',
  80. 'X-Poedit-Basepath' => '',
  81. 'X-Poedit-Bookmarks' => '',
  82. 'X-Poedit-SearchPath-0' => '.',
  83. 'X-Textdomain-Support' => 'no'
  84. );
  85. $this->header_vars = array_keys($this->header);
  86. $this->mo_header_vars = array(
  87. 'PO-Revision-Date',
  88. 'MIME-Version',
  89. 'Content-Type',
  90. 'Content-Transfer-Encoding',
  91. 'Plural-Forms',
  92. 'X-Generator',
  93. 'Project-Id-Version'
  94. );
  95. array_splice($this->header_vars, array_search('X-Poedit-KeywordsList', $this->header_vars), 1 );
  96. $this->plural_definitions = array(
  97. 'nplurals=1; plural=0;' => array('hu', 'ja', 'ko', 'tr'),
  98. 'nplurals=2; plural=1;' => array('zh'),
  99. 'nplurals=2; plural=n>1;' => array('fr'),
  100. 'nplurals=2; plural=n != 1;' => array('af','be','bg','ca','da','de','el','en','es','et','eo','eu','fi','fo','fy','he','id','in','is','it','kk','ky','lb','nk','nb','nl','no','pt','ro','sr','sv','th','tl','vi','xh','zu'),
  101. 'nplurals=3; plural=(n==1) ? 1 : (n>=2 && n<=4) ? 2 : 0;' => array('sk'),
  102. 'nplurals=3; plural=n%10==1 && n%100!=11 ? 0 : n != 0 ? 1 : 2;' => array('lv'),
  103. 'nplurals=3; plural=n%100/10==1 ? 2 : n%10==1 ? 0 : (n+9)%10>3 ? 2 : 1;' => array('cs', 'hr', 'ru', 'uk'),
  104. 'nplurals=3; plural=n==1 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2;' => array('pl'),
  105. 'nplurals=3; plural=n%10==1 && n%100!=11 ? 0 : n%10>=2 && (n%100<10 || n%100>=20) ? 1 : 2;' => array('lt'),
  106. 'nplurals=4; plural=n%100==1 ? 0 : n%100==2 ? 1 : n%100==3 || n%100==4 ? 2 : 3;' => array('sl'),
  107. 'nplurals=5; plural=n==1 ? 0 : n==2 ? 1 : n<7 ? 2 : n<11 ? 3 : 4;' => array('ga'),
  108. 'nplurals=6; plural=n==0 ? 0 : n==1 ? 1 : n==2 ? 2 : n%100>=3 && n%100<=10 ? 3 : n%100>=11 ? 4 : 5;' => array('ar')
  109. );
  110. $this->map = array();
  111. $this->nplurals = 2;
  112. $this->plural_func = 'n != 1;';
  113. $this->reg_comment = '/^#\s(.*)/';
  114. $this->reg_comment_ex = '/^#\.\s+(.*)/';
  115. $this->reg_reference = '/^#:\s+(.*)/';
  116. $this->reg_flags = '/^#,\s+(.*)/';
  117. $this->reg_textdomain = '/^#\s*@\s*(.*)/';
  118. $this->reg_msgctxt = '/^msgctxt\s+(".*")/';
  119. $this->reg_msgid = '/^msgid\s+(".*")/';
  120. $this->reg_msgstr = '/^msgstr\s+(".*")/';
  121. $this->reg_msgid_plural = '/^msgid_plural\s+(".*")/';
  122. $this->reg_msgstr_plural = '/^msgstr\[\d+\]\s+(".*")/';
  123. $this->reg_multi_line = "/^(\".*\")/s";
  124. }
  125. function _set_header_from_string($head, $lang='', $mo_hdr_vars = false) {
  126. if (!is_string($head)) return;
  127. $hdr = explode("\n", $head);
  128. $hdr_vars = $mo_hdr_vars ? $this->mo_header_vars : $this->header_vars;
  129. foreach($hdr as $e) {
  130. if ($this->strings->_strpos($e, ':') === false) continue;
  131. list($key, $val) = explode(':', $e, 2);
  132. $key = trim($key);$val = str_replace("\\","/", trim($val));
  133. if (in_array($key, $hdr_vars)) {
  134. $this->header[$key] = $val;
  135. //ensure qualified pluralization forms now
  136. if ($key == 'Plural-Forms' && !$mo_hdr_vars) {
  137. $func = '';
  138. foreach($this->plural_definitions as $f => $langs) {
  139. if (in_array($lang, $langs)) $func = $f;
  140. }
  141. if (empty($func)) { $func = 'nplurals=2; plural=n != 1;'; }
  142. $this->header[$key] = $func;
  143. }
  144. }
  145. }
  146. $msgstr = array();
  147. foreach($this->header as $key => $value) {
  148. $msgstr[] = $key.": ".$value;
  149. }
  150. $msgstr = implode("\n", $msgstr);
  151. $this->map[''] = $this->_new_entry('', $msgstr);
  152. if (preg_match("/nplurals\s*=\s*(\d+)\s*\;\s*plural\s*=\s*([^\n]+)\;/", $this->header['Plural-Forms'], $matches)) {
  153. $this->nplurals = (int)$matches[1];
  154. $this->plural_func = $matches[2];
  155. }
  156. }
  157. function _new_entry($org, $trans, $reference=false, $flags=false, $tcomment=false, $ccomment=false, $ltd = false) {
  158. // T ... translation data, contains embed \00 if plurals
  159. // X ... true, if org contains \04 context in front of
  160. // P ... true, if is a pluralization,
  161. // CT ... remark (comment) translator
  162. // CC ... remark (code) - hard code translations required
  163. // F ... flags like 'php-format'
  164. // R ... reference
  165. // LTD ... loaded text domain
  166. //Bugfix: illegal line separators contained
  167. if ($trans !== false)
  168. $trans = preg_replace('/
/', '', $trans); //LINE SEPARATOR decimal: &#8232; UTF-8 (e2, 80, a8)
  169. return array(
  170. 'T' => $trans,
  171. 'X' => ($this->strings->_strpos( $org, "\04" ) !== false),
  172. 'P' => ($this->strings->_strpos( $org, "\00" ) !== false),
  173. 'CT' => (is_string($tcomment) ? array($tcomment) : (is_array($tcomment) ? $tcomment : array())),
  174. 'CC' => (is_string($ccomment) ? array($ccomment) : (is_array($ccomment) ? $ccomment : array())),
  175. 'F' => (is_string($flags) ? array($flags) : (is_array($flags) ? $flags : array())),
  176. 'R' => (is_string($reference) ? array($reference) : (is_array($reference) ? $reference : array())),
  177. 'LTD' => (is_string($ltd) ? array($ltd) : (is_array($ltd) ? $ltd : array()))
  178. );
  179. }
  180. function trim_quotes($s) {
  181. if ( $this->strings->_substr($s, 0, 1) == '"') $s = $this->strings->_substr($s, 1);
  182. if ( $this->strings->_substr($s, -1, 1) == '"') $s = $this->strings->_substr($s, 0, -1);
  183. return $s;
  184. }
  185. function _clean_import($string) {
  186. $escapes = array('t' => "\t", 'n' => "\n", '\\' => '\\');
  187. $lines = array_map('trim', explode("\n", $string));
  188. $lines = array_map(array('CspTranslationFile', 'trim_quotes'), $lines);
  189. $unpoified = '';
  190. $previous_is_backslash = false;
  191. foreach($lines as $line) {
  192. preg_match_all('/./u', $line, $chars);
  193. $chars = $chars[0];
  194. foreach($chars as $char) {
  195. if (!$previous_is_backslash) {
  196. if ('\\' == $char)
  197. $previous_is_backslash = true;
  198. else
  199. $unpoified .= $char;
  200. } else {
  201. $previous_is_backslash = false;
  202. $unpoified .= isset($escapes[$char])? $escapes[$char] : $char;
  203. }
  204. }
  205. }
  206. unset($lines);
  207. return $unpoified;
  208. }
  209. function _clean_export($string) {
  210. $quote = '"';
  211. $slash = '\\';
  212. $newline = "\n";
  213. $replaces = array(
  214. "$slash" => "$slash$slash",
  215. "$quote" => "$slash$quote",
  216. "\t" => '\t',
  217. );
  218. $string = str_replace(array_keys($replaces), array_values($replaces), $string);
  219. $po = $quote.implode("${slash}n$quote$newline$quote", explode($newline, $string)).$quote;
  220. // add empty string on first line for readbility
  221. if (false !== $this->strings->_strpos($string, $newline) &&
  222. ($this->strings->_substr_count($string, $newline) > 1 || !($newline === $this->strings->_substr($string, -$this->strings->_strlen($newline))))) {
  223. $po = "$quote$quote$newline$po";
  224. }
  225. // remove empty strings
  226. $po = str_replace("$newline$quote$quote", '', $po);
  227. return $po;
  228. }
  229. function _build_rel_path($base_file) {
  230. $a = explode('/', $base_file);
  231. $rel = '';
  232. for ($i=0; $i<count($a)-1; $i++) { $rel.="../"; }
  233. return $rel;
  234. }
  235. function supports_textdomain_extension() {
  236. return ($this->header['X-Textdomain-Support'] == 'yes');
  237. }
  238. function new_pofile($pofile, $base_file, $proj_id, $timestamp, $translator, $pluralforms, $language, $country) {
  239. $rel = $this->_build_rel_path($base_file);
  240. preg_match("/([a-z][a-z]_[A-Z][A-Z]).(mo|po|pot)$/", $pofile, $hits);
  241. $po_lang = $this->strings->_substr($hits[1],0,2);
  242. $country = strtoupper($country);
  243. $this->_set_header_from_string(
  244. "Project-Id-Version: $proj_id\nPO-Revision-Date: $timestamp\nLast-Translator: $translator\nX-Poedit-Language: $language\nX-Poedit-Country: $country\nX-Poedit-Basepath: $rel\nPlural-Forms: \nX-Textdomain-Support: yes\n",
  245. $po_lang
  246. );
  247. return true;
  248. }
  249. function read_pofile($pofile, $check_plurals=false, $base_file=false) {
  250. if (!empty($pofile) && file_exists($pofile) && is_readable($pofile)) {
  251. $handle = fopen($pofile,'rb');
  252. $msgid = false;
  253. $cur_entry = $this->_new_entry('', false); //empty
  254. while (!feof($handle)) {
  255. $line = trim(fgets($handle));
  256. if (!$this->strings->_seems_utf8($line)) $line = $this->strings->_utf8_encode($line);
  257. if (empty($line)) {
  258. if ($msgid !== false) {
  259. $temp = ($cur_entry['X'] !== false ? $cur_entry['X']."\04".$msgid : $msgid);
  260. //merge test: existing do not kill by empty!
  261. if(!isset($this->map[$temp]) || !(!empty($this->map[$temp]['T']) && empty($cur_entry['T']))) {
  262. $this->map[$temp] = $this->_new_entry(
  263. $temp,
  264. $cur_entry['T'],
  265. $cur_entry['R'],
  266. $cur_entry['F'],
  267. $cur_entry['CT'],
  268. $cur_entry['CC'],
  269. $cur_entry['LTD']
  270. );
  271. }
  272. }
  273. $msgid = false;
  274. unset($cur_entry);
  275. $cur_entry = $this->_new_entry('',false);
  276. continue;
  277. }
  278. if (preg_match($this->reg_multi_line, $line, $hits)) {
  279. if ($cur_entry['T'] === false) { $msgid .= $this->_clean_import($line); }
  280. else { $cur_entry['T'] .= $this->_clean_import($line); }
  281. continue;
  282. }
  283. if (preg_match($this->reg_msgctxt, $line, $hits)) { $cur_entry['X'] = $this->_clean_import($hits[1]); };
  284. if (preg_match($this->reg_textdomain, $line, $hits)) { $cur_entry['LTD'][] = $hits[1]; }
  285. elseif (preg_match($this->reg_comment, $line, $hits)) { $cur_entry['CT'][] = $this->_clean_import($hits[1]); }
  286. if (preg_match($this->reg_comment_ex, $line, $hits)) { $cur_entry['CC'][] = $this->_clean_import($hits[1]); }
  287. if (preg_match($this->reg_reference, $line, $hits)) { $cur_entry['R'][] = $hits[1]; }
  288. if (preg_match($this->reg_flags, $line, $hits)) { $cur_entry['F'][] = $hits[1]; }
  289. if (preg_match($this->reg_msgid, $line, $hits)) { $msgid = $this->_clean_import($hits[1]); }
  290. if (preg_match($this->reg_msgstr, $line, $hits)) { $cur_entry['T'] = $this->_clean_import($hits[1]); }
  291. if (preg_match($this->reg_msgid_plural, $line, $hits)) { $msgid .= "\0"; $msgid .= $this->_clean_import($hits[1]); }
  292. if (preg_match($this->reg_msgstr_plural, $line, $hits)) {
  293. if ($cur_entry['T'] === false) $cur_entry['T'] = $this->_clean_import($hits[1]);
  294. else {
  295. $cur_entry['T'] .= (preg_match("/[^\\0*]*/", $cur_entry['T']) ? "\0" : '');
  296. $cur_entry['T'] .= $this->_clean_import($hits[1]);
  297. }
  298. }
  299. }
  300. fclose ($handle);
  301. //BUGFIX: language not possible if it's a template file
  302. $po_lang = 'en';
  303. if (preg_match("/([a-z][a-z]_[A-Z][A-Z]).(mo|po)$/", $pofile, $hits)) {
  304. $po_lang = $this->strings->_substr($hits[1],0,2);
  305. }else{
  306. $po_lang = $this->strings->_substr($_POST['language'],0,2);
  307. }
  308. $this->_set_header_from_string($this->map[""]['T'], $po_lang);
  309. $this->_set_header_from_string('Plural-Forms: ', $po_lang); //for safetly the plural forms!
  310. if ($base_file) {
  311. $rel = $this->_build_rel_path($base_file);
  312. $this->_set_header_from_string("X-Poedit-Basepath: $rel\nX-Poedit-SearchPath-0: .", $po_lang);
  313. }
  314. return true;
  315. }
  316. return false;
  317. }
  318. //extension made to stamp pot files to textdomain entirely
  319. function write_pofile($pofile, $last = false, $textdomain = false, $tds = 'yes') {
  320. if (file_exists($pofile) && !is_writable($pofile)) return false;
  321. $handle = @fopen($pofile, "wb");
  322. if ($handle === false) return false;
  323. //set the plurals and multi textdomain support
  324. //update the revision date
  325. $stamp = date("Y-m-d H:i:sO");
  326. //BUGFIX: language not possible if it's a template file
  327. $po_lang = 'en';
  328. if (preg_match("/([a-z][a-z]_[A-Z][A-Z]).(mo|po)$/", $pofile, $hits)) {
  329. $po_lang = $this->strings->_substr($hits[1],0,2);
  330. }else{
  331. $po_lang = $this->strings->_substr($_POST['language'],0,2);
  332. }
  333. $this->_set_header_from_string("PO-Revision-Date: $stamp\nPlural-Forms: \nX-Textdomain-Support: $tds\n", $po_lang);
  334. //write header if last because it has no code ref anyway
  335. if ($last === true) {
  336. fwrite($handle, 'msgid ""'."\n");
  337. fwrite($handle, 'msgstr '.$this->_clean_export($this->map['']['T'])."\n\n");
  338. }
  339. foreach($this->map as $key => $entry) {
  340. if ((is_array($entry['R']) && (count($entry['R']) > 0)) || ($last === false)) {
  341. if (is_array($entry['CT'])) {
  342. foreach($entry['CT'] as $comt) {
  343. fwrite($handle, '# '.$comt."\n");
  344. }
  345. }
  346. if (is_array($entry['CC'])) {
  347. foreach($entry['CC'] as $comc) {
  348. fwrite($handle, '#. '.$comc."\n");
  349. }
  350. }
  351. if (is_array($entry['R'])) {
  352. foreach($entry['R'] as $ref) {
  353. fwrite($handle, '#: '.$ref."\n");
  354. }
  355. }
  356. if (is_array($entry['F']) && count($entry['F'])) {
  357. fwrite($handle, '#, '.implode(', ', $entry['F'])."\n");
  358. }
  359. if (is_array($entry['LTD']) && count($entry['LTD'])) {
  360. foreach($entry['LTD'] as $domain) {
  361. if(!empty($domain)) fwrite($handle, '#@ '.$domain."\n");
  362. }
  363. }elseif($textdomain) {
  364. fwrite($handle, '#@ '.$textdomain."\n");
  365. }
  366. if($entry['P'] !== false) {
  367. list($msgid, $msgid_plural) = explode("\0", $key);
  368. if ($entry['X'] !== false) {
  369. list($ctx, $msgid) = explode("\04", $msgid);
  370. fwrite($handle, 'msgctxt '.$this->_clean_export($ctx)."\n");
  371. }
  372. fwrite($handle, 'msgid '.$this->_clean_export($msgid)."\n");
  373. fwrite($handle, 'msgid_plural '.$this->_clean_export($msgid_plural)."\n");
  374. $msgstr_arr = explode("\0", $entry['T']);
  375. for ($i=0; $i<count($msgstr_arr); $i++) {
  376. fwrite($handle, 'msgstr['.$i.'] '.$this->_clean_export($msgstr_arr[$i])."\n");
  377. }
  378. }
  379. else{
  380. $msgid = $key;
  381. if ($entry['X'] !== false) {
  382. list($ctx, $msgid) = explode("\04", $key);
  383. fwrite($handle, 'msgctxt '.$this->_clean_export($ctx)."\n");
  384. }
  385. fwrite($handle, 'msgid '.$this->_clean_export($msgid)."\n");
  386. fwrite($handle, 'msgstr '.$this->_clean_export($entry['T'])."\n");
  387. }
  388. fwrite($handle, "\n");
  389. }
  390. }
  391. fclose($handle);
  392. return true;
  393. }
  394. function ftp_get_pofile_content($pofile, $last = false, $textdomain = false, $tds = 'yes') {
  395. $content = '';
  396. //set the plurals and multi textdomain support
  397. //update the revision date
  398. $stamp = date("Y-m-d H:i:sO");
  399. $this->_set_header_from_string("PO-Revision-Date: $stamp\nPlural-Forms: \nX-Textdomain-Support: $tds\n");
  400. //write header if last because it has no code ref anyway
  401. if ($last === true) {
  402. $content .= 'msgid ""'."\n";
  403. $content .= 'msgstr '.$this->_clean_export($this->map['']['T'])."\n\n";
  404. }
  405. foreach($this->map as $key => $entry) {
  406. if ((is_array($entry['R']) && (count($entry['R']) > 0)) || ($last === false)) {
  407. if (is_array($entry['CT'])) {
  408. foreach($entry['CT'] as $comt) {
  409. $content .= '# '.$comt."\n";
  410. }
  411. }
  412. if (is_array($entry['CC'])) {
  413. foreach($entry['CC'] as $comc) {
  414. $content .= '#. '.$comc."\n";
  415. }
  416. }
  417. if (is_array($entry['R'])) {
  418. foreach($entry['R'] as $ref) {
  419. $content .= '#: '.$ref."\n";
  420. }
  421. }
  422. if (is_array($entry['F']) && count($entry['F'])) {
  423. $content .= '#, '.implode(', ', $entry['F'])."\n";
  424. }
  425. if (is_array($entry['LTD']) && count($entry['LTD'])) {
  426. foreach($entry['LTD'] as $domain) {
  427. if(!empty($domain)) $content .= '#@ '.$domain."\n";
  428. }
  429. }elseif($textdomain) {
  430. $content .= '#@ '.$textdomain."\n";
  431. }
  432. if($entry['P'] !== false) {
  433. list($msgid, $msgid_plural) = explode("\0", $key);
  434. if ($entry['X'] !== false) {
  435. list($ctx, $msgid) = explode("\04", $msgid);
  436. $content .= 'msgctxt '.$this->_clean_export($ctx)."\n";
  437. }
  438. $content .= 'msgid '.$this->_clean_export($msgid)."\n";
  439. $content .= 'msgid_plural '.$this->_clean_export($msgid_plural)."\n";
  440. $msgstr_arr = explode("\0", $entry['T']);
  441. for ($i=0; $i<count($msgstr_arr); $i++) {
  442. $content .= 'msgstr['.$i.'] '.$this->_clean_export($msgstr_arr[$i])."\n";
  443. }
  444. }
  445. else{
  446. $msgid = $key;
  447. if ($entry['X'] !== false) {
  448. list($ctx, $msgid) = explode("\04", $key);
  449. $content .= 'msgctxt '.$this->_clean_export($ctx)."\n";
  450. }
  451. $content .= 'msgid '.$this->_clean_export($msgid)."\n";
  452. $content .= 'msgstr '.$this->_clean_export($entry['T'])."\n";
  453. }
  454. $content .= "\n";
  455. }
  456. }
  457. return $content;
  458. }
  459. function read_mofile($mofile, $check_plurals, $base_file=false, $default_textdomain='') {
  460. //mo file reading without need of further WP introduced classes !
  461. if (file_exists($mofile)) {
  462. if (is_readable($mofile)) {
  463. $file = fopen( $mofile, 'rb' );
  464. if ( !$file )
  465. return false;
  466. $header = fread( $file, 28 );
  467. if ( $this->strings->_strlen( $header ) != 28 )
  468. return false;
  469. // detect endianess
  470. $endian = unpack( 'Nendian', $this->strings->_substr( $header, 0, 4 ) );
  471. if ( $endian['endian'] == intval( hexdec( '950412de' ) ) )
  472. $endian = 'N';
  473. else if ( $endian['endian'] == intval( hexdec( 'de120495' ) ) )
  474. $endian = 'V';
  475. else
  476. return false;
  477. // parse header
  478. $header = unpack( "{$endian}Hrevision/{$endian}Hcount/{$endian}HposOriginals/{$endian}HposTranslations/{$endian}HsizeHash/{$endian}HposHash", $this->strings->_substr( $header, 4 ) );
  479. if ( !is_array( $header ) )
  480. return false;
  481. extract( $header );
  482. // support revision 0 of MO format specs, only
  483. if ( $Hrevision != 0 )
  484. return false;
  485. // read originals' index
  486. fseek( $file, $HposOriginals, SEEK_SET );
  487. $originals = fread( $file, $Hcount * 8 );
  488. if ( $this->strings->_strlen( $originals ) != $Hcount * 8 )
  489. return false;
  490. // read translations index
  491. fseek( $file, $HposTranslations, SEEK_SET );
  492. $translations = fread( $file, $Hcount * 8 );
  493. if ( $this->strings->_strlen( $translations ) != $Hcount * 8 )
  494. return false;
  495. // transform raw data into set of indices
  496. $originals = $this->strings->_str_split( $originals, 8 );
  497. $translations = $this->strings->_str_split( $translations, 8 );
  498. // find position of first string in file
  499. $HposStrings = 0x7FFFFFFF;
  500. for ( $i = 0; $i < $Hcount; $i++ )
  501. {
  502. // parse index records on original and related translation
  503. $o = unpack( "{$endian}length/{$endian}pos", $originals[$i] );
  504. $t = unpack( "{$endian}length/{$endian}pos", $translations[$i] );
  505. if ( !$o || !$t )
  506. return false;
  507. $originals[$i] = $o;
  508. $translations[$i] = $t;
  509. $HposStrings = min( $HposStrings, $o['pos'], $t['pos'] );
  510. }
  511. // read strings expected in rest of file
  512. fseek( $file, $HposStrings, SEEK_SET );
  513. $strings = '';
  514. while ( !feof( $file ) )
  515. $strings .= fread( $file, 4096 );
  516. fclose( $file );
  517. //now reading the contents
  518. $this->map = array();
  519. for ( $i = 0; $i < $Hcount; $i++ )
  520. {
  521. // adjust offset due to reading strings to separate space before
  522. $originals[$i]['pos'] -= $HposStrings;
  523. $translations[$i]['pos'] -= $HposStrings;
  524. // extract original and translations
  525. $original = $this->strings->_substr( $strings, $originals[$i]['pos'], $originals[$i]['length'] );
  526. $translation = $this->strings->_substr( $strings, $translations[$i]['pos'], $translations[$i]['length'] );
  527. //Bugfix: 1.9.19 - trailing nul were occuring somehow, removed now.
  528. $translation = trim($translation, "\0");
  529. $this->map[$original] = $this->_new_entry($original, $translation, false, false, false, false, $default_textdomain);
  530. }
  531. preg_match("/([a-z][a-z]_[A-Z][A-Z]).(mo|po)$/", $mofile, $hits);
  532. $po_lang = $this->strings->_substr($hits[1],0,2);
  533. $this->_set_header_from_string((isset($this->map['']) ? $this->map['']['T'] : ''), $po_lang);
  534. $this->_set_header_from_string('Plural-Forms: ',$po_lang); //for safetly the plural forms!
  535. if ($base_file) {
  536. $rel = $this->_build_rel_path($base_file);
  537. $this->_set_header_from_string("X-Poedit-Basepath: $rel\nX-Poedit-SearchPath-0: .", $po_lang);
  538. }
  539. return true;
  540. }
  541. }
  542. return false;
  543. }
  544. function _is_valid_entry(&$entry, &$textdomain) {
  545. return (
  546. ($this->strings->_strlen(str_replace("\0", "", $entry['T'])) > 0)
  547. &&
  548. in_array($textdomain, $entry['LTD'])
  549. )
  550. ||
  551. (stripos($entry['T'], 'Plural-Forms') !== false);
  552. }
  553. function is_illegal_empty_mofile($textdomain) {
  554. $entries = 0;
  555. foreach($this->map as $key => $value) {
  556. if($this->_is_valid_entry($value, $textdomain)) { $entries++; }
  557. }
  558. return ($entries == 0);
  559. }
  560. function _reduce_header_for_mofile() {
  561. if (isset($this->map[''])) {
  562. $header = $this->map[''];
  563. $this->map[''] = $header;
  564. }
  565. }
  566. function write_mofile($mofile, $textdomain) {
  567. //handle WordPress continent cities patch to separate "Center" for UI and Continent/City use
  568. if (isset($this->map["continents-cities\04Center"])) {
  569. $trans = $this->map["continents-cities\04Center"];
  570. unset($this->map["continents-cities\04Center"]);
  571. $this->map["Center"] = $trans;
  572. }
  573. //reduce the mofile header
  574. $mohdr = $this->map['']['T'];
  575. $mohdr_h = $this->header;
  576. $this->header = array();
  577. $this->_set_header_from_string($mohdr, '', true);
  578. $handle = @fopen($mofile, "wb");
  579. if ($handle === false){
  580. $this->header = $mohdr_h;
  581. $this->_set_header_from_string($mohdr, '', false);
  582. return false;
  583. }
  584. ksort($this->map, SORT_REGULAR);
  585. //let's calculate none empty values
  586. $entries = 0;
  587. foreach($this->map as $key => $value) {
  588. if($this->_is_valid_entry($value, $textdomain)) { $entries++; }
  589. }
  590. $tab_size = $entries * 8;
  591. //header: little endian magic|revision|entries|offset originals|offset translations|hashing table size|hashing table ofs
  592. $header = pack('NVVVVVV@'.(28+$tab_size*2),0xDE120495,0x00000000,$entries,28,28+$tab_size,0x00000000,28+$tab_size*2);
  593. $org_table = '';
  594. $trans_table = '';
  595. fwrite($handle, $header);
  596. foreach($this->map as $key => $value) {
  597. if ($this->_is_valid_entry($value, $textdomain)) {
  598. $l=$this->strings->_strlen($key);
  599. $org_table .= pack('VV', $l, ftell($handle));
  600. $res = pack('A'.$l.'x',$key);
  601. fwrite($handle, $res);
  602. }
  603. }
  604. foreach($this->map as $key => $value) {
  605. if ($this->_is_valid_entry($value, $textdomain)) {
  606. $l=$this->strings->_strlen($value['T']);
  607. $trans_table .= pack('VV', $l, ftell($handle));
  608. $res = pack('A'.$l.'x',$value['T']);
  609. fwrite($handle, $res);
  610. }
  611. }
  612. fseek($handle, 28, SEEK_SET);
  613. fwrite($handle,$org_table);
  614. fwrite($handle,$trans_table);
  615. fclose($handle);
  616. $this->header = $mohdr_h;
  617. $this->_set_header_from_string($mohdr, '', false);
  618. return true;
  619. }
  620. function ftp_get_mofile_content($mofile, $textdomain) {
  621. //handle WordPress continent cities patch to separate "Center" for UI and Continent/City use
  622. if (isset($this->map["continents-cities\04Center"])) {
  623. $trans = $this->map["continents-cities\04Center"];
  624. unset($this->map["continents-cities\04Center"]);
  625. $this->map["Center"] = $trans;
  626. }
  627. //reduce the mofile header
  628. $mohdr = $this->map['']['T'];
  629. $mohdr_h = $this->header;
  630. $this->header = array();
  631. $this->_set_header_from_string($mohdr, '', true);
  632. $content = '';
  633. ksort($this->map, SORT_REGULAR);
  634. //let's calculate none empty values
  635. $entries = 0;
  636. foreach($this->map as $key => $value) {
  637. if($this->_is_valid_entry($value, $textdomain)) { $entries++; }
  638. }
  639. $tab_size = $entries * 8;
  640. //header: little endian magic|revision|entries|offset originals|offset translations|hashing table size|hashing table ofs
  641. $header = pack('NVVVVVV@'.(28+$tab_size*2),0xDE120495,0x00000000,$entries,28,28+$tab_size,0x00000000,28+$tab_size*2);
  642. $org_table = '';
  643. $trans_table = '';
  644. $content .= $header;
  645. foreach($this->map as $key => $value) {
  646. if ($this->_is_valid_entry($value, $textdomain)) {
  647. $l=$this->strings->_strlen($key);
  648. $org_table .= pack('VV', $l, strlen($content));
  649. $res = pack('A'.$l.'x',$key);
  650. $content .= $res;
  651. }
  652. }
  653. foreach($this->map as $key => $value) {
  654. if ($this->_is_valid_entry($value, $textdomain)) {
  655. $l=$this->strings->_strlen($value['T']);
  656. $trans_table .= pack('VV', $l, strlen($content));
  657. $res = pack('A'.$l.'x',$value['T']);
  658. $content .= $res;
  659. }
  660. }
  661. $content = substr_replace($content, $org_table, 28, strlen($org_table));
  662. $content = substr_replace($content, $trans_table, 28 + strlen($org_table), strlen($trans_table));
  663. $this->header = $mohdr_h;
  664. $this->_set_header_from_string($mohdr, '', false);
  665. return $content;
  666. }
  667. function _reset_data(&$val, $key) {
  668. unset($val['R']); $val['R'] = array();
  669. unset($val['CC']); $val['CC'] = array();
  670. unset($val['LTD']); $val['LTD'] = array();
  671. }
  672. function parsing_init() {
  673. //reset the references and textdomains
  674. $func = array(&$this, '_reset_data');
  675. array_walk($this->map, $func);
  676. $this->_set_header_from_string("X-Textdomain-Support: yes\n");
  677. }
  678. function add_messages($r = false) {
  679. if ($r === false) return; //file doesn't exist
  680. $gettext = $r['gettext'];
  681. $not_gettext = $r['not_gettext'];
  682. if (count($gettext)) {
  683. foreach($gettext as $match) {
  684. $entry = null;
  685. if (isset($this->map[$match['msgid']]))
  686. $entry = $this->map[$match['msgid']];
  687. if (!is_array($entry)) {
  688. $entry = $this->_new_entry(
  689. $match['msgid'],
  690. str_pad('', (isset($match['P']) ? $this->nplurals -1 : 0), "\0"),
  691. $match['R'],
  692. false, false,false,
  693. $match['LTD']
  694. );
  695. }
  696. else{
  697. if (!in_array($match['R'], $entry['R']))
  698. $entry['R'][] = $match['R'];
  699. }
  700. if (!in_array($match['LTD'], $entry['LTD'])) {
  701. $entry['LTD'][] = $match['LTD'];
  702. }
  703. foreach($match['CC'] as $cc) {
  704. if (!in_array($cc, $entry['CC'])) {
  705. $entry['CC'][] = $cc;
  706. }
  707. }
  708. if(preg_match("/(%[A-Za-z0-9])/", $match['msgid']) > 0) {
  709. if (!is_array($entry['F'])||(!in_array('php-format', $entry['F']))) {
  710. $entry['F'][] = 'php-format';
  711. }
  712. }
  713. $this->map[$match['msgid']] = $entry;
  714. }
  715. }
  716. if (count($not_gettext)) {
  717. foreach($not_gettext as $match) {
  718. $entry = null;
  719. if (isset($this->map[$match['msgid']]))
  720. $entry = $this->map[$match['msgid']];
  721. if (!is_array($entry)) {
  722. $entry = $this->_new_entry(
  723. $match['msgid'],
  724. '',
  725. $match['R'],
  726. false,
  727. false,
  728. $match['CC'],
  729. $match['LTD']
  730. );
  731. }
  732. else{
  733. if (!in_array($match['R'], $entry['R'])) {
  734. $entry['R'][] = $match['R'];
  735. }
  736. foreach($match['CC'] as $cc) {
  737. if (!in_array($cc, $entry['CC'])) {
  738. $entry['CC'][] = $cc;
  739. }
  740. }
  741. }
  742. if (!in_array($match['LTD'], $entry['LTD'])) {
  743. $entry['LTD'][] = $match['LTD'];
  744. }
  745. $this->map[$match['msgid']] = $entry;
  746. }
  747. }
  748. }
  749. function parsing_add_messages($path, $sourcefile, $textdomain='') {
  750. require_once('class.parser.php');
  751. $parser = new csp_l10n_parser($path, $textdomain, true, false);
  752. $r = $parser->parseFile($sourcefile, $this->component_type);
  753. $this->add_messages($r);
  754. }
  755. function repair_illegal_single_multi_utilization() {
  756. //find a solution for this crude examples (duplications po edit warnings):
  757. // ...e('All', 'texdomain'); ...n('All', 'All', 5, 'textdomain'); ...n('All', 'Garbage', 5, 'textdomain');
  758. //plural string have to get precedence over single string!
  759. //plural string with identical singual strings have to be joined too
  760. $single_to_remap = array();
  761. $plural_to_join = array();
  762. foreach($this->map as $key => $entry) {
  763. $test = explode("\0", $key, 2);
  764. if (is_array($test) && count($test) == 2) {
  765. //check if we have that singular as single and remap it
  766. if(isset($this->map[$test[0]])) {
  767. $single_to_remap[$test[0]] = $key;
  768. }
  769. //collect potential duplicated plurals
  770. if (!isset($plural_to_join[$test[0]])) $plural_to_join[$test[0]] = array();
  771. $plural_to_join[$test[0]][] = $key;
  772. }
  773. }
  774. foreach($single_to_remap as $single => $plural){
  775. $e_single = $this->map[$single];
  776. $e_plural = $this->map[$plural];
  777. //re-base code comments
  778. foreach($e_single['CC'] as $cc) {
  779. if (!in_array($cc, $e_plural['CC'])) {
  780. $this->map[$plural]['CC'][] = $cc;
  781. }
  782. }
  783. //re-base lines found
  784. foreach($e_single['R'] as $r) {
  785. if (!in_array($r, $e_plural['R'])) {
  786. $this->map[$plural]['R'][] = $r;
  787. }
  788. }
  789. //re-base textdomains
  790. foreach($e_single['LTD'] as $ltd) {
  791. if (!in_array($ltd, $e_plural['LTD'])) {
  792. $this->map[$plural]['LTD'][] = $ltd;
  793. }
  794. }
  795. //additional place a warning at code comment
  796. $this->map[$plural]['CC'][] = 'gettext fix: identical singular and plural forms found, that may be ambiguous! Please check the code!';
  797. //remove that single now
  798. unset($this->map[$single]);
  799. }
  800. foreach($plural_to_join as $key => $plurals) {
  801. if (count($plurals) < 2) continue;
  802. $target = array_shift($plurals);
  803. foreach($plurals as $plural) {
  804. $e_single = $this->map[$plural];
  805. $e_plural = $this->map[$target];
  806. //re-base code comments
  807. foreach($e_single['CC'] as $cc) {
  808. if (!in_array($cc, $e_plural['CC'])) {
  809. $this->map[$target]['CC'][] = $cc;
  810. }
  811. }
  812. //re-base lines found
  813. foreach($e_single['R'] as $r) {
  814. if (!in_array($r, $e_plural['R'])) {
  815. $this->map[$target]['R'][] = $r;
  816. }
  817. }
  818. //re-base textdomains
  819. foreach($e_single['LTD'] as $ltd) {
  820. if (!in_array($ltd, $e_plural['LTD'])) {
  821. $this->map[$target]['LTD'][] = $ltd;
  822. }
  823. }
  824. //additional place a warning at code comment
  825. $this->map[$target]['CC'][] = 'gettext fix: duplicate plural forms found, that may be ambiguous! Please check the code!';
  826. }
  827. //remove the duplicated plural form now
  828. unset($this->map[$plural]);
  829. }
  830. }
  831. function parsing_finalize($textdomain, $prjidver) {
  832. //if there is only one textdomain included and this is '' (empty string) replace all with the given textdomain
  833. $ltd = array();
  834. foreach($this->map as $key => $entry) {
  835. if (is_array($entry['R']) && (count($entry['R']) > 0)) {
  836. if (count($entry['LTD']) == 0) {
  837. $this->map[$key]['LTD'] = array($textdomain);
  838. $entry['LTD'] = array($textdomain);
  839. }
  840. foreach($entry['LTD'] as $domain) {
  841. if (!in_array($domain, $ltd)) $ltd[] = $domain;
  842. }
  843. }
  844. }
  845. if ((count($ltd) == 1) && ($ltd[0] == '')) {
  846. $keys = array_keys($this->map);
  847. foreach($keys as $key) {
  848. $this->map[$key]['LTD'] = array($textdomain);
  849. }
  850. }
  851. $this->repair_illegal_single_multi_utilization();
  852. $this->_set_header_from_string("Project-Id-Version: $prjidver");
  853. }
  854. function _convert_for_js($str) {
  855. $search = array( '"\"', "\\", "\n", "\r", "\t", "\"");
  856. $replace = array( '"\\\\"', '\\\\', '\\\\n', '\\\\r', '\\\\t', '\\\\\"');
  857. $str = str_replace( $search, $replace, $str );
  858. return $str;
  859. }
  860. function _convert_js_input($str) {
  861. $search = array('\\\\\\\"', '\\\\\"','\\\\n', '\\\\t','\\0', "\\'", '\\\\');
  862. $replace = array('\"', '"', "\n", "\\t", "\0", "'", "\\");
  863. $str = str_replace( $search, $replace, $str );
  864. return $str;
  865. }
  866. function echo_as_json($path, $file, $sys_locales, $api_type) {
  867. $loc = $this->strings->_substr($file,strlen($file)-8,-3);
  868. header('Content-Type: application/json; charset=utf-8');
  869. ?>
  870. {
  871. header : "<table id=\"po-hdr\" style=\"display:none;\"><?php
  872. foreach($this->header as $key => $value) {
  873. echo "<tr><td class=\\\"po-hdr-key\\\">".$key."</td><td class=\\\"po-hdr-val\\\">".htmlspecialchars($value)."</td></tr>";
  874. }?>",
  875. destlang: "<?php echo ( isset($sys_locales[$loc]) && !empty($api_type) && $api_type != 'none' ? $sys_locales[$loc][$api_type.'-api'] : ''); ?>",
  876. api_type: "<?php echo $api_type; ?>",
  877. last_saved : "<?php $mo = $this->strings->_substr($path.$file,0,-2)."mo"; if (file_exists($mo)) { echo date (__('m/d/Y H:i:s',CSP_PO_TEXTDOMAIN), filemtime($mo)); } else { _e('unknown',CSP_PO_TEXTDOMAIN); } ?>",
  878. plurals_num : <?php echo $this->nplurals; ?>,
  879. plurals_func : "<?php echo $this->plural_func; ?>",
  880. path : "<?php echo $path; ?>",
  881. file : "<?php echo $file; ?>",
  882. index : {
  883. 'total' : [],
  884. 'plurals' : [],
  885. 'open' : [],
  886. 'rem' : [],
  887. 'code' : [],
  888. 'ctx' : [],
  889. 'cur' : [],
  890. 'ltd' : [],
  891. 'trail' : []
  892. },
  893. content : [
  894. <?php
  895. $num = count($this->map);
  896. $c = 0;
  897. $ltd = array();
  898. foreach($this->map as $key => $entry) {
  899. $c++;
  900. if (!strlen($key)) { continue; }
  901. if ($this->strings->_strpos($key, "\04") > 0) {
  902. list($ctx, $key) = explode("\04", $key);
  903. echo "{ \"ctx\" : \"".$this->_convert_for_js($ctx)."\",";
  904. }else {
  905. echo "{ ";
  906. }
  907. if (is_array($entry['LTD']) && count($entry['LTD'])) { echo " \"ltd\" : [\"".implode("\",\"",$entry['LTD'])."\"],"; }
  908. else { echo " \"ltd\" : [\"\"],"; }
  909. if ($entry['P'] !== false) {
  910. $parts = explode("\0", $key);
  911. for($i=0; $i<count($parts); $i++) {
  912. $parts[$i] = $this->_convert_for_js($parts[$i]);
  913. }
  914. echo " \"key\" : [\"".implode("\",\"",$parts)."\"],";
  915. } else{ echo " \"key\" : \"".$this->_convert_for_js($key)."\","; }
  916. if ($this->strings->_strpos($entry['T'], "\0") !== false) {
  917. $parts = explode("\0", $entry['T']);
  918. for($i=0; $i<count($parts); $i++) {
  919. $parts[$i] = $this->_convert_for_js($parts[$i]);
  920. }
  921. //BUGFIX: extend template plurals
  922. if(count($parts) < $this->nplurals) {
  923. for($i=count($parts); $i<$this->nplurals; $i++) {
  924. $parts[] = '';
  925. }
  926. }
  927. echo " \"val\" : [\"".implode("\",\"",$parts)."\"]";
  928. } else { echo " \"val\" : \"".$this->_convert_for_js($entry['T'])."\""; }
  929. if (is_array($entry['CT']) && count($entry['CT'])) { echo ", \"rem\" : \"".implode('\n',$this->_convert_for_js($entry['CT']))."\""; }
  930. else { echo ", \"rem\" : \"\""; }
  931. if (is_array($entry['CC']) && count($entry['CC'])) {
  932. echo ", \"code\" : \"".implode('\n',$this->_convert_for_js($entry['CC']))."\"";
  933. }
  934. if (is_array($entry['R']) && count($entry['R'])) { echo ", \"ref\" : [\"".implode("\",\"",$entry['R'])."\"]"; }
  935. else { echo ", \"ref\" : []"; }
  936. echo "}".($c != $num ? ',' : '')."\n";
  937. foreach($entry['LTD'] as $d) {
  938. if (!in_array($d, $ltd)) $ltd[] = esc_js($d);
  939. }
  940. }
  941. ?>
  942. ],
  943. textdomains : ["<?php sort($ltd); echo implode('","', array_reverse($ltd)); ?>"]
  944. }
  945. <?php
  946. }
  947. function update_entry($msgid, $msgstr) {
  948. $msgid = $this->_convert_js_input($msgid);
  949. if (array_key_exists($msgid, $this->map)) {
  950. $this->map[$msgid]['T'] = $this->_convert_js_input($msgstr);
  951. return true;
  952. }
  953. //the \t issue must be handled carefully
  954. $msgid = str_replace('\\t', "\t", $msgid);
  955. $msgstr = str_replace('\\t', "\t", $msgstr);
  956. if (array_key_exists($msgid, $this->map)) {
  957. $this->map[$msgid]['T'] = $this->_convert_js_input($msgstr);
  958. return true;
  959. }
  960. return false;
  961. }
  962. }
  963. ?>