PluginProbe
VikBooking Hotel Booking Engine & PMS / 1.6.5
VikBooking Hotel Booking Engine & PMS v1.6.5
1.8.16 1.8.15 1.8.14 1.8.13 1.8.12 1.8.11 1.8.10 1.8.9 1.8.6 1.8.7 1.8.8 trunk 1.6.0 1.6.1 1.6.2 1.6.3 1.6.4 1.6.5 1.6.6 1.6.7 1.6.8 1.6.9 1.7.0 1.7.1 1.7.2 All 37 releases
vikbooking / site / helpers / translator.php

translator.php in VikBooking Hotel Booking Engine & PMS 1.6.5, at site/helpers/translator.php

609 lines 18.3 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * @package VikBooking
4 * @subpackage com_vikbooking
5 * @author Alessio Gaggii - e4j - Extensionsforjoomla.com
6 * @copyright Copyright (C) 2018 e4j - Extensionsforjoomla.com. All rights reserved.
7 * @license GNU General Public License version 2 or later; see LICENSE
8 * @link https://vikwp.com
9 */
10
11 defined('ABSPATH') or die('No script kiddies please!');
12
13 class VikBookingTranslator
14 {
15 public $current_lang = null;
16 public $default_lang = null;
17 public $lim;
18 public $lim0;
19 public $navigation;
20 public $error;
21 private $xml;
22 private $all_langs;
23 private $dbo;
24 private $translations_path_file;
25 private $translations_buffer;
26
27 public static $force_tolang = null;
28
29 public function __construct()
30 {
31 $app = JFactory::getApplication();
32 $this->current_lang = $this->getCurrentLang();
33 $this->default_lang = $this->getDefaultLang();
34 $this->lim = $app->input->getInt('limit', 5);
35 $this->lim0 = $app->input->getInt('limitstart', 0);
36 $this->navigation = '';
37 $this->error = '';
38 $this->xml = '';
39 $this->all_langs = array();
40 $this->dbo = JFactory::getDbo();
41 $this->translations_path_file = VBO_ADMIN_PATH . DIRECTORY_SEPARATOR . 'fields' . DIRECTORY_SEPARATOR . 'translations.xml';
42 $this->translations_buffer = array();
43 }
44
45 public function getCurrentLang()
46 {
47 return !is_null($this->current_lang) ? $this->current_lang : JFactory::getLanguage()->getTag();
48 }
49
50 public function getDefaultLang($section = 'site')
51 {
52 if (VBOPlatformDetection::isWordPress()) {
53 /**
54 * @wponly import the JComponentHelper class
55 */
56 jimport('joomla.application.component.helper');
57 }
58
59 return !is_null($this->default_lang) && $section == 'site' ? $this->default_lang : JComponentHelper::getParams('com_languages')->get($section);
60 }
61
62 public function getIniFiles()
63 {
64 // Keys = Lang Def composed as VBINIEXPL.strtoupper(Key)
65 // Values = Paths to INI Files
66
67 if (VBOPlatformDetection::isWordPress()) {
68 /**
69 * @wponly nothing to return
70 */
71 return [];
72 }
73
74 return [
75 'com_vikbooking_front' => ['path' => JPATH_SITE . DIRECTORY_SEPARATOR . 'language' . DIRECTORY_SEPARATOR . 'en-GB' . DIRECTORY_SEPARATOR . 'en-GB.com_vikbooking.ini'],
76 'com_vikbooking_admin' => ['path' => JPATH_ADMINISTRATOR . DIRECTORY_SEPARATOR . 'language' . DIRECTORY_SEPARATOR . 'en-GB' . DIRECTORY_SEPARATOR . 'en-GB.com_vikbooking.ini'],
77 'com_vikbooking_admin_sys' => ['path' => JPATH_ADMINISTRATOR . DIRECTORY_SEPARATOR . 'language' . DIRECTORY_SEPARATOR . 'en-GB' . DIRECTORY_SEPARATOR . 'en-GB.com_vikbooking.sys.ini'],
78 'mod_vikbooking_search' => ['path' => JPATH_SITE . DIRECTORY_SEPARATOR . 'language' . DIRECTORY_SEPARATOR . 'en-GB' . DIRECTORY_SEPARATOR . 'en-GB.mod_vikbooking_search.ini'],
79 'mod_vikbooking_horizontalsearch' => ['path' => JPATH_SITE . DIRECTORY_SEPARATOR . 'language' . DIRECTORY_SEPARATOR . 'en-GB' . DIRECTORY_SEPARATOR . 'en-GB.mod_vikbooking_horizontalsearch.ini'],
80 ];
81 }
82
83 public function getLanguagesList()
84 {
85 $known_langs = VikBooking::getVboApplication()->getKnownLanguages();
86 $langs = array();
87 foreach ($known_langs as $ltag => $ldet) {
88 if ($ltag == $this->default_lang) {
89 $langs = array($ltag => $ldet) + $langs;
90 } else {
91 $langs[$ltag] = $ldet;
92 }
93 }
94 $this->all_langs = $langs;
95 return $this->all_langs;
96 }
97
98 public function getLanguagesTags()
99 {
100 return array_keys($this->all_langs);
101 }
102
103 public function replacePrefix($str)
104 {
105 return $this->dbo->replacePrefix($str);
106 }
107
108 /**
109 * Helper method that makes sure the table name starts with the prefix placeholder.
110 * In order to avoid issues with queries containing the prefix placeholder ("#__"),
111 * the name of the table for the translated record is removed from the placeholder prefix.
112 *
113 * @param string $table_name the table name to adjust.
114 *
115 * @return string the adjust table name with the prefix placeholder.
116 *
117 * @since 1.16.0 (J) - 1.6.0 (WP)
118 */
119 public function adjustTablePrefix($table_name)
120 {
121 if (empty($table_name) || !is_string($table_name)) {
122 // nothing to do with the given value
123 return $table_name;
124 }
125
126 if (preg_match("/^#__/", $table_name)) {
127 // default prefix placeholder found at the beginning of the string
128 return $table_name;
129 }
130
131 if (strpos($table_name, 'vikbooking_') === false) {
132 // nothing to do with this table name
133 return $table_name;
134 }
135
136 // make the table name start with the prefix placeholder
137 $table_nm_parts = explode('vikbooking_', $table_name);
138 $table_nm_parts[0] = '#__';
139
140 return implode('vikbooking_', $table_nm_parts);
141 }
142
143 /**
144 * Fixer method for BC. The old structure was storing the table names for
145 * the translated records without the default prefix placeholder ('#__'),
146 * but this is invalid for the backup features. This method converts all
147 * table names for the translated records so that they will contain the
148 * default prefix placeholder at the beginning of the string.
149 *
150 * @return boolean
151 *
152 * @since 1.16.0 (J) - 1.6.0 (WP)
153 */
154 public function normalizeTnTableNames()
155 {
156 $query = $this->dbo->getQuery(true);
157
158 $query->select($this->dbo->qn('t.id'));
159 $query->select($this->dbo->qn('t.table'));
160
161 $query->from($this->dbo->qn('#__vikbooking_translations', 't'));
162
163 $this->dbo->setQuery($query);
164 $translations = $this->dbo->loadObjectList();
165
166 if (!$translations) {
167 // no translation records found
168 return false;
169 }
170
171 foreach ($translations as $tn_record) {
172 // normalize table name with prefix
173 $tn_record->table = $this->adjustTablePrefix($tn_record->table);
174
175 // update record on db
176 $this->dbo->updateObject('#__vikbooking_translations', $tn_record, 'id');
177 }
178
179 return true;
180 }
181
182 public function getTranslationTables()
183 {
184 $xml = $this->getTranslationsXML();
185 if ($xml === false) {
186 return false;
187 }
188 $tables = array();
189 foreach ($xml->Translation as $translation) {
190 $attr = $translation->attributes();
191 $tables[(string)$attr->table] = JText::translate((string)$attr->name);
192 }
193 return $tables;
194 }
195
196 /**
197 * Returns the translated name of the table given the prefix
198 *
199 * @param string $table
200 */
201 public function getTranslationTableName($table)
202 {
203 $xml = $this->getTranslationsXML();
204 $table_name = '';
205 foreach ($xml->Translation as $translation) {
206 $attr = $translation->attributes();
207 if ((string)$attr->table == $table) {
208 return JText::translate((string)$attr->name);
209 }
210 }
211 return $table_name;
212 }
213
214 /**
215 * Returns an array with the XML Columns of the given table
216 *
217 * @param string $table
218 */
219 public function getTableColumns($table)
220 {
221 $xml = $this->getTranslationsXML();
222 $cols = array();
223 foreach ($xml->Translation as $translation) {
224 $attr = $translation->attributes();
225 if ((string)$attr->table == $table) {
226 foreach ($translation->Column as $column) {
227 $col_attr = $column->attributes();
228 if (!property_exists($col_attr, 'name')) {
229 continue;
230 }
231 $ind = (string)$col_attr->name;
232 $cols[$ind]['jlang'] = JText::translate((string)$column);
233 foreach ($col_attr as $key => $val) {
234 $cols[$ind][(string)$key] = (string)$val;
235 }
236 }
237 }
238 }
239 return $cols;
240 }
241
242 /**
243 * Returns the db column marked as reference, of the record. Ex. the name of the Room in this record
244 *
245 * @param array $cols
246 * @param array $record
247 */
248 public function getRecordReferenceName($cols, $record)
249 {
250 foreach ($cols as $key => $values) {
251 if (array_key_exists('reference', $values)) {
252 if (array_key_exists($key, $record)) {
253 return $record[$key];
254 }
255 }
256 }
257 //if not found, not present or empty, return first value of the record
258 return $record[key($record)];
259 }
260
261 /**
262 * Returns the current records for the default language and this table
263 *
264 * @param string $table the name of the table.
265 * @param array $cols array containing the db fields to fetch, result of array_keys($this->getTableColumns()).
266 */
267 public function getTableDefaultDbValues($table, $cols = array())
268 {
269 $def_vals = array();
270
271 if (!count($cols)) {
272 $cols = array_keys($this->getTableColumns($table));
273 if (!$cols) {
274 $this->setError("Table $table has no Columns.");
275 }
276 }
277
278 if ($cols) {
279 $q = "SELECT SQL_CALC_FOUND_ROWS `id`," . implode(',', $cols) . " FROM " . $this->dbo->qn($table) . " ORDER BY " . $this->dbo->qn($table) . ".`id` ASC";
280 $this->dbo->setQuery($q, $this->lim0, $this->lim);
281 $records = $this->dbo->loadAssocList();
282 if ($records) {
283 $this->dbo->setQuery('SELECT FOUND_ROWS();');
284 $this->setPagination($this->dbo->loadResult());
285 foreach ($records as $record) {
286 $ref_id = $record['id'];
287 unset($record['id']);
288 $def_vals[$ref_id] = $record;
289 }
290 } else {
291 $this->setError("Table ".$this->getTranslationTableName($table)." has no Records.");
292 }
293 }
294
295 return $def_vals;
296 }
297
298 /**
299 * Sets the pagination HTML value for the current
300 * translation list, by using the Joomla native functions.
301 *
302 * @param int $tot_rows
303 */
304 private function setPagination($tot_rows)
305 {
306 jimport('joomla.html.pagination');
307 $pageNav = new JPagination($tot_rows, $this->lim0, $this->lim);
308 $this->navigation = $pageNav->getListFooter();
309 }
310
311 /**
312 * Returns the current pagination HTML value.
313 */
314 public function getPagination()
315 {
316 return $this->navigation;
317 }
318
319 /**
320 * Returns the translated records for this table and language
321 *
322 * @param string $table
323 * @param string $lang
324 */
325 public function getTranslatedTable($table, $lang)
326 {
327 $translated = array();
328
329 $q = "SELECT * FROM `#__vikbooking_translations` WHERE `table`=" . $this->dbo->quote($this->adjustTablePrefix($table)) . " AND `lang`=" . $this->dbo->quote($lang) . " ORDER BY `#__vikbooking_translations`.`reference_id` ASC;";
330 $this->dbo->setQuery($q);
331 $records = $this->dbo->loadAssocList();
332 foreach ($records as $record) {
333 $record['content'] = json_decode($record['content'], true);
334 $translated[$record['reference_id']] = $record;
335 }
336
337 return $translated;
338 }
339
340 /**
341 * Main function to translate contents saved in the database.
342 *
343 * @param array $content The array taken from the database with the default values to be translated, passed as reference.
344 * @param string $table The name of the table containing the translation values, should match with the XML table name.
345 * @param array $alias_keys Key-Value pairs where Key is the ALIAS used and Value is the original field name. Opposite instead for the ID (reference_id).
346 * The key 'id' is always treated differently than the other keys. Correct usage: array('id' => 'idroom', 'room_name' => 'name')
347 * @param array $ids The reference_IDs to be translated, the IDs of the records. Taken from the content array if empty array passed.
348 * @param string $lang Force the translation to a specific language tag like it-IT.
349 *
350 * @return array The initial array with translated values (if applicable).
351 */
352 public function translateContents(&$content, $table, $alias_keys = array(), $ids = array(), $lang = null)
353 {
354 $to_lang = is_null($lang) ? $this->current_lang : $lang;
355 $to_lang = !is_null(self::$force_tolang) ? self::$force_tolang : $to_lang;
356
357 // multilang may be disabled
358 if (!$this->allowMultiLanguage()) {
359 return $content;
360 }
361
362 // check that requested lang is not the default lang
363 if ($to_lang == $this->default_lang || !$content) {
364 return $content;
365 }
366
367 // get all translatable columns of this table
368 $cols = $this->getTableColumns($table);
369
370 // get the reference ids to be translated
371 if (!count($ids)) {
372 $ids = $this->getReferencesFromContents($content, $alias_keys);
373 }
374
375 // load translations buffer for this table or set the var to an empty array
376 $translated = $this->getTranslationsBuffer($table, $ids, $to_lang);
377
378 if (!count($translated)) {
379 // load translations from db
380 $q = "SELECT * FROM `#__vikbooking_translations` WHERE `table`=" . $this->dbo->quote($this->adjustTablePrefix($table)) . " AND `lang`=" . $this->dbo->quote($to_lang) . (count($ids) ? " AND `reference_id` IN (" . implode(",", $ids) . ")" : "") . ";";
381 $this->dbo->setQuery($q);
382 $records = $this->dbo->loadAssocList();
383 foreach ($records as $record) {
384 $record['content'] = json_decode($record['content'], true);
385 if (is_array($record['content']) && $record['content']) {
386 $translated[$record['reference_id']] = $record['content'];
387 }
388 }
389 }
390
391 if (count($translated)) {
392 // set translations buffer
393 $this->translations_buffer[$table][$to_lang] = $translated;
394
395 // fetch reference_id to be translated and replace default lang values
396 $reference_key = array_key_exists('id', $alias_keys) ? $alias_keys['id'] : 'id';
397 foreach ($content as $ckey => $cvals) {
398 $reference_id = 0;
399 if (is_array($cvals)) {
400 foreach ($cvals as $subckey => $subcvals) {
401 if ($subckey == $reference_key) {
402 $reference_id = (int)$subcvals;
403 break;
404 }
405 }
406 $content[$ckey] = $this->translateArrayValues($cvals, $cols, $reference_id, $alias_keys, $translated);
407 } elseif ($ckey == $reference_key) {
408 $reference_id = (int)$cvals;
409 $content = $this->translateArrayValues($content, $cols, $reference_id, $alias_keys, $translated);
410 break;
411 }
412 }
413 }
414
415 return $content;
416 }
417
418 /**
419 * Compares the array to be translated with the translation and replaces the array values if not empty.
420 *
421 * @param array $content default lang values to be translated.
422 * @param array $alias_keys Key_Values pairs where Key is the ALIAS used and Value is the original
423 * field name. Opposite instead for the ID (reference_id).
424 */
425 private function getReferencesFromContents($content, $alias_keys)
426 {
427 $references = array();
428 $reference_key = array_key_exists('id', $alias_keys) ? $alias_keys['id'] : 'id';
429 foreach ($content as $ckey => $cvals) {
430 if (is_array($cvals)) {
431 foreach ($cvals as $subckey => $subcvals) {
432 if ($subckey == $reference_key) {
433 $references[] = (int)$subcvals;
434 break;
435 }
436 }
437 } elseif ($ckey == $reference_key) {
438 $references[] = (int)$cvals;
439 break;
440 }
441 }
442 if (count($references) > 0) {
443 $references = array_unique($references);
444 }
445
446 return $references;
447 }
448
449 /**
450 * Check whether these reference IDs were already fetched from the db for this table
451 *
452 * @param string $table
453 * @param array $ids
454 * @param string $lang
455 *
456 * @return array
457 */
458 private function getTranslationsBuffer($table, $ids, $lang)
459 {
460 if (!count($this->translations_buffer) || !isset($this->translations_buffer[$table]) || !isset($this->translations_buffer[$table][$lang])) {
461 return array();
462 }
463
464 $missing = false;
465 foreach ($ids as $id) {
466 if (!isset($this->translations_buffer[$table][$lang][$id])) {
467 $missing = true;
468 break;
469 }
470 }
471
472 return $missing === false ? $this->translations_buffer[$table][$lang] : array();
473 }
474
475 /**
476 * Compares the array to be translated with the translation and replaces the array values if not empty.
477 *
478 * @param array $content default lang values to be translated.
479 * @param array $cols the columns of this table.
480 * @param int $reference_id reference_id.
481 * @param array $alias_keys Key_Values pairs where Key is the ALIAS used and Value is the original field name.
482 * Opposite instead for the ID (reference_id).
483 * @param array $translated translated.
484 */
485 private function translateArrayValues($content, $cols, $reference_id, $alias_keys, $translated)
486 {
487 if (empty($reference_id)) {
488 return $content;
489 }
490
491 if (!array_key_exists($reference_id, $translated)) {
492 return $content;
493 }
494
495 foreach ($content as $key => $value) {
496 $native_key = $key;
497 if (count($alias_keys) > 0 && array_key_exists($key, $alias_keys) && $key != 'id') {
498 $key = $alias_keys[$key];
499 }
500 if (!array_key_exists($key, $cols)) {
501 continue;
502 }
503 if (array_key_exists($key, $translated[$reference_id]) && strlen($translated[$reference_id][$key]) > 0) {
504 $type = $cols[$key]['type'];
505 if ($type == 'json') {
506 // only the translated and not empty keys will be taken from the translation
507 $tn_json = json_decode($translated[$reference_id][$key], true);
508 $content_json = json_decode($value, true);
509 $jkeys = !empty($cols[$key]['keys']) ? explode(',', $cols[$key]['keys']) : array();
510 if (is_array($tn_json) && $tn_json && is_array($content_json) && $content_json) {
511 foreach ($content_json as $jk => $jv) {
512 if (array_key_exists($jk, $tn_json) && strlen($tn_json[$jk]) > 0) {
513 $content_json[$jk] = $tn_json[$jk];
514 }
515 }
516 $content[$native_key] = json_encode($content_json);
517 }
518 } else {
519 // field is a text type or a text-derived one
520 $content[$native_key] = $translated[$reference_id][$key];
521 }
522 }
523 }
524
525 return $content;
526 }
527
528 /**
529 * Sets and Returns the SimpleXML object for the translations
530 */
531 public function getTranslationsXML()
532 {
533 if (!is_file($this->translations_path_file)) {
534 $this->setError($this->translations_path_file . ' does not exist or is not readable');
535 return false;
536 }
537 if (!function_exists('simplexml_load_file')) {
538 $this->setError('Function simplexml_load_file is not available on the server.');
539 return false;
540 }
541 if (is_object($this->xml)) {
542 return $this->xml;
543 }
544 libxml_use_internal_errors(true);
545 if (($xml = simplexml_load_file($this->translations_path_file)) === false) {
546 $this->setError("Error reading XML:\n".$this->libxml_display_errors());
547 return false;
548 }
549 $this->xml = $xml;
550 return $xml;
551 }
552
553 private function allowMultiLanguage()
554 {
555 return VikBooking::allowMultiLanguage();
556 }
557
558 /**
559 * Explanation of the XML error
560 *
561 * @param $error
562 */
563 public function libxml_display_error($error)
564 {
565 $return = "\n";
566 switch ($error->level) {
567 case LIBXML_ERR_WARNING :
568 $return .= "Warning ".$error->code.": ";
569 break;
570 case LIBXML_ERR_ERROR :
571 $return .= "Error ".$error->code.": ";
572 break;
573 case LIBXML_ERR_FATAL :
574 $return .= "Fatal Error ".$error->code.": ";
575 break;
576 }
577 $return .= trim($error->message);
578 if ($error->file) {
579 $return .= " in ".$error->file;
580 }
581 $return .= " on line ".$error->line."\n";
582 return $return;
583 }
584
585 /**
586 * Get the XML errors occurred
587 */
588 public function libxml_display_errors()
589 {
590 $errorstr = "";
591 $errors = libxml_get_errors();
592 foreach ($errors as $error) {
593 $errorstr .= $this->libxml_display_error($error);
594 }
595 libxml_clear_errors();
596 return $errorstr;
597 }
598
599 private function setError($str)
600 {
601 $this->error .= $str."\n";
602 }
603
604 public function getError()
605 {
606 return nl2br(rtrim($this->error, "\n"));
607 }
608 }
609