PluginProbe ʕ •ᴥ•ʔ
VikAppointments Services Booking Calendar / 1.2.21
VikAppointments Services Booking Calendar v1.2.21
1.2.21 1.2.20 trunk 1.2.17 1.2.18 1.2.19
vikappointments / site / helpers / libraries / language / translator.php
vikappointments / site / helpers / libraries / language Last commit date
tables 2 days ago index.html 2 days ago table.php 2 days ago translator.php 2 days ago
translator.php
489 lines
1 <?php
2 /**
3 * @package VikAppointments
4 * @subpackage core
5 * @author E4J s.r.l.
6 * @copyright Copyright (C) 2021 E4J s.r.l. All Rights Reserved.
7 * @license http://www.gnu.org/licenses/gpl-2.0.html GNU/GPL
8 * @link https://vikwp.com
9 */
10
11 // No direct access
12 defined('ABSPATH') or die('No script kiddies please!');
13
14 VAPLoader::import('libraries.language.table');
15
16 /**
17 * Helper class used to handle component translations.
18 *
19 * @since 1.7
20 */
21 class VAPLanguageTranslator
22 {
23 /**
24 * The translator instance.
25 *
26 * @var VAPLanguageTranslator
27 */
28 protected static $instance = null;
29
30 /**
31 * A list of tables with the related columns.
32 *
33 * @var array
34 */
35 protected $tables = null;
36
37 /**
38 * Returns the instance of the translator object.
39 * A new instance is created only if it is not yet available.
40 *
41 * @return VAPLanguageTranslator The instance of the translator.
42 */
43 public static function getInstance()
44 {
45 if (static::$instance === null)
46 {
47 static::$instance = new static();
48 }
49
50 return static::$instance;
51 }
52
53 /**
54 * Loads the translations for the given records.
55 *
56 * @param string $table The translation table ID.
57 * @param mixed $id Either the ID or an array of IDS of the
58 * records to translate.
59 * @param mixed $tag The language tag to use. If not specified,
60 * the current one will be used.
61 *
62 * @return VAPLanguageTable The object containing the translations.
63 *
64 * @throws Exception
65 */
66 public function load($table, $id, $tag = null)
67 {
68 // make sure the table is supported
69 $table = $this->getTable($table);
70
71 if (!$tag)
72 {
73 // get current language tag if not specified
74 $tag = JFactory::getLanguage()->getTag();
75 }
76
77 // always treat the ID as an array
78 // $id = array_map('intval', (array) $id);
79 $id = (array) $id;
80
81 foreach ($id as $k => $tmp)
82 {
83 // check if we already have a loaded translation for the
84 // given product in the specified language
85 if ($table->hasTranslation($tmp, $tag))
86 {
87 // translation available, unset ID
88 unset($id[$k]);
89 }
90 }
91
92 if (!$id)
93 {
94 return $table;
95 }
96
97 // restore linear array
98 $id = array_values($id);
99
100 $dbo = JFactory::getDbo();
101
102 // Get all the table columns that can be translated.
103 // Use the reverse lookup to obtain both the columns of
104 // the translation table and the original one.
105 $contents = $table->getContentColumns($lookup = true);
106
107 // get lang tag column
108 $tagcol = $table->getLangColumn();
109
110 $q = $dbo->getQuery(true);
111
112 // select translation columns
113 foreach ($contents as $lang => $link)
114 {
115 // select language translation
116 $q->select($dbo->qn('t.' . $lang));
117 // select original content
118 $q->select($dbo->qn('o.' . $link, 'original_' . $link));
119 }
120
121 // select link primary key
122 $q->select($dbo->qn('o.' . $table->getLinkedPrimaryKey()));
123 // select language tag
124 $q->select($dbo->qn('t.' . $tagcol));
125
126 // load original table
127 $q->from($dbo->qn($table->getLinkedTable(), 'o'));
128 // join translations with original records
129 $q->leftjoin(
130 $dbo->qn($table->getTableName(), 't') .
131 ' ON ' . $dbo->qn('o.' . $table->getLinkedPrimaryKey()) . ' = ' . $dbo->qn('t.' . $table->getForeignKey()) .
132 ' AND ' . $dbo->qn('t.' . $tagcol) . ' = ' . $dbo->q($tag)
133 );
134
135 // retrieve only the translations for the given ID(s)
136 if (count($id) == 1)
137 {
138 $q->where($dbo->qn('o.' . $table->getLinkedPrimaryKey()) . ' = ' . $dbo->q($id[0]));
139 }
140 else
141 {
142 $q->where($dbo->qn('o.' . $table->getLinkedPrimaryKey()) . ' IN (' . implode(',', array_map(array($dbo, 'q'), $id)) . ')');
143 }
144
145 $dbo->setQuery($q);
146 $original = $dbo->loadObjectList();
147
148 if (!$original)
149 {
150 return $table;
151 }
152
153 $locales = array();
154
155 // load translations and original records
156 foreach ($original as $result)
157 {
158 // get linked primary key
159 $fk = $result->{$table->getLinkedPrimaryKey()};
160
161 if (!isset($locales[$fk]))
162 {
163 $locales[$fk] = new stdClass;
164 $locales[$fk]->id = $fk;
165 $locales[$fk]->langtag = $tag;
166 }
167
168 // iterate language contents
169 foreach ($contents as $lang => $link)
170 {
171 $k = 'original_' . $link;
172
173 // pre-fill locale with default column value
174 if (empty($locales[$fk]->{$link}))
175 {
176 $locales[$fk]->{$link} = $result->{$k};
177 }
178
179 // in case of translation available use it
180 if (!empty($result->{$lang}))
181 {
182 $locales[$fk]->{$link} = $result->{$lang};
183 $locales[$fk]->langtag = $result->{$tagcol};
184 }
185 }
186 }
187
188 foreach ($locales as $locale)
189 {
190 // register translation within language table
191 $table->setTranslation($locale);
192 }
193
194 return $table;
195 }
196
197 /**
198 * Returns the translation of the given record.
199 *
200 * @param string $table The translation table ID.
201 * @param integer $id The product ID to translate.
202 * @param mixed $tag The language tag to use. If not specified,
203 * the current one will be used.
204 *
205 * @return mixed The translated object if exists, false otherwise.
206 *
207 * @throws Exception
208 */
209 public function translate($table, $id, $tag = null)
210 {
211 if (!$tag)
212 {
213 // get current language tag if not specified
214 $tag = JFactory::getLanguage()->getTag();
215 }
216
217 // make sure the translation is already loaded
218 $table = $this->load($table, $id, $tag);
219
220 // return translated object
221 return $table->getTranslation($id, $tag);
222 }
223
224 /**
225 * Returns a list of suggestions based on the translations
226 * that have been already made for the given language tag.
227 *
228 * @param string $str The original string to search for.
229 * @param mixed $tag The language tag to use. If not specified,
230 * the current one will be used.
231 *
232 * @return mixed A list of suggestions on success, false otherwise.
233 */
234 public function getSuggestions($str, $tag = null)
235 {
236 // Abort in case the string is not valid.
237 // Do not use /[a-zA-Z]/ regex because languages based
238 // on unicode (e.g. Chinese) will never go ahead.
239 if (empty($str) || is_numeric($str))
240 {
241 return false;
242 }
243
244 if (!$tag)
245 {
246 // get current language tag if not specified
247 $tag = JFactory::getLanguage()->getTag();
248 }
249
250 $dbo = JFactory::getDbo();
251
252 // get all supported language database tables
253 $tables = $this->getTables();
254
255 if (!$tables)
256 {
257 // no available tables, abort
258 return false;
259 }
260
261 $suggestions = array();
262
263 // iterate all the tables to start searching for a matching translation
264 foreach ($tables as $table)
265 {
266 // Get all the table columns that can be translated.
267 // Use the reverse lookup to obtain both the columns of
268 // the translation table and the original one.
269 $contents = $table->getContentColumns($lookup = true);
270
271 $q = $dbo->getQuery(true);
272
273 // select translation columns
274 foreach ($contents as $lang => $link)
275 {
276 // select language translation
277 $q->select($dbo->qn('t.' . $lang));
278 // select original content
279 $q->select($dbo->qn('o.' . $link, 'original_' . $link));
280 }
281
282 // select language primary key
283 $q->select($dbo->qn('t.' . $table->getPrimaryKey()));
284
285 // load translation table
286 $q->from($dbo->qn($table->getTableName(), 't'));
287 // load original table
288 $q->from($dbo->qn($table->getLinkedTable(), 'o'));
289
290 // filter the translations by language
291 $q->where($dbo->qn('t.' . $table->getLangColumn()) . ' = ' . $dbo->q($tag));
292 // join translations with original records
293 $q->where($dbo->qn('o.' . $table->getLinkedPrimaryKey()) . ' = ' . $dbo->qn('t.' . $table->getForeignKey()));
294
295 $where = array();
296
297 // join content columns
298 foreach ($contents as $link)
299 {
300 $where[] = $dbo->qn('o.' . $link) . ' = ' . $dbo->q($str);
301 }
302
303 if ($where)
304 {
305 // search for suggested translations
306 $q->andWhere($where, 'OR');
307 }
308
309 $dbo->setQuery($q);
310
311 // iterate records fetched
312 foreach ($dbo->loadObjectList() as $record)
313 {
314 // iterate translatable columns
315 foreach ($contents as $lang => $link)
316 {
317 // define original assoc key
318 $k = 'original_' . $link;
319
320 // check if the term is equals to the original column and the
321 // related translation is not empty
322 if (!strcasecmp($record->{$k}, $str) && !empty($record->{$lang}))
323 {
324 // push the related translation within the suggestions list
325 $suggestions[] = $record->{$lang};
326 }
327 }
328 }
329 }
330
331 // do not return duplicate values (array_values doesn't preserve assoc keys)
332 return array_values(array_unique($suggestions));
333 }
334
335 /**
336 * Returns all the languages that owns a translation for the specified record.
337 * The default language will be always included at the beginning of the list.
338 *
339 * @param string $table The translation table ID.
340 * @param mixed $id Either the product ID or an array.
341 * @param mixed $def The default language tag. Accepts 3 different values:
342 * - null uses the default system language;
343 * - false do not include the default language;
344 * - string include the specified language as default.
345 *
346 * @return array A list of supported languages.
347 *
348 * @throws Exception
349 */
350 public function getAvailableLang($table, $id, $def = null)
351 {
352 // make sure the table is supported
353 $table = $this->getTable($table);
354
355 if (!$id)
356 {
357 return $id;
358 }
359
360 $was_array = is_array($id);
361
362 // always treat ID as an array
363 $id = (array) $id;
364
365 $dbo = JFactory::getDbo();
366
367 $q = $dbo->getQuery(true);
368
369 $fk = $table->getForeignKey();
370
371 // select FK and language tag
372 $q->select($dbo->qn('t.' . $fk, 'id'));
373 $q->select('GROUP_CONCAT(' . $dbo->qn('t.' . $table->getLangColumn()) . ') AS ' . $dbo->qn('tag'));
374
375 // load translations table
376 $q->from($dbo->qn($table->getTableName(), 't'));
377
378 // retrieve only the translations for the given ID(s)
379 if (count($id) == 1)
380 {
381 $q->where($dbo->qn('t.' . $fk) . ' = ' . $dbo->q($id[0]));
382 }
383 else
384 {
385 $q->where($dbo->qn('t.' . $fk) . ' IN (' . implode(',', array_map(array($dbo, 'q'), $id)) . ')');
386 }
387
388 $q->group($dbo->qn('t.' . $fk));
389
390 $dbo->setQuery($q);
391
392 $rows = [];
393
394 // create a lookup of ID/languages
395 foreach ($dbo->loadObjectList() as $row)
396 {
397 $rows[$row->id] = explode(',', $row->tag);
398 }
399
400 $map = array();
401
402 if (is_null($def))
403 {
404 // get default language
405 $def = VikAppointments::getDefaultLanguage();
406 }
407
408 // create a lookup of ID/languages starting from
409 // the specified list of IDs
410 foreach ($id as $v)
411 {
412 // assign the languages found to the ID, if any
413 $map[$v] = isset($rows[$v]) ? $rows[$v] : array();
414
415 // check whether the list already contains the default language
416 $index = array_search($def, $map[$v]);
417
418 if ($index !== false)
419 {
420 // always remove the translation
421 array_splice($map[$v], $index, 1);
422 }
423
424 // add the default language at the beginning of the list
425 array_unshift($map[$v], $def);
426 }
427
428 if (!$was_array)
429 {
430 // immediately return the available languages in case
431 // the method received only one ID
432 return reset($map);
433 }
434
435 return $map;
436 }
437
438 /**
439 * Returns all the supported language tables with the related colums.
440 *
441 * @return array
442 *
443 * @throws Exception
444 */
445 public function getTables()
446 {
447 if ($this->tables === null)
448 {
449 $this->tables = array();
450
451 // get all XML drivers
452 $drivers = glob(dirname(__FILE__) . DIRECTORY_SEPARATOR . 'tables' . DIRECTORY_SEPARATOR . '*.xml');
453
454 foreach ($drivers as $driver)
455 {
456 // create language table
457 $table = new VAPLanguageTable($driver);
458 // push table within the list
459 $this->tables[$table->getID()] = $table;
460 }
461 }
462
463 return $this->tables;
464 }
465
466 /**
467 * Returns the requested language table.
468 *
469 * @param string The language table to get.
470 *
471 * @return VAPLanguageTable
472 *
473 * @throws Exception
474 */
475 public function getTable($id)
476 {
477 // first of all get tables
478 $tables = $this->getTables();
479
480 if (!isset($tables[$id]))
481 {
482 // table not found, raise error
483 throw new Exception(sprintf('Language table [%s] is not supported', $id), 500);
484 }
485
486 return $tables[$id];
487 }
488 }
489