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 / libraries / adapter / language / language.php
vikappointments / libraries / adapter / language Last commit date
handler.php 3 days ago language.php 3 days ago text.php 3 days ago transliterate.php 3 days ago
language.php
567 lines
1 <?php
2 /**
3 * @package VikWP - Libraries
4 * @subpackage adapter.language
5 * @author E4J s.r.l.
6 * @copyright Copyright (C) 2023 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 JLoader::import('adapter.language.handler');
15
16 /**
17 * Languages/translation adapter class.
18 *
19 * @since 10.0
20 */
21 #[\AllowDynamicProperties]
22 class JLanguage
23 {
24 /**
25 * Array of Language objects.
26 *
27 * @var array
28 */
29 protected static $instances = array();
30
31 /**
32 * The current language tag.
33 *
34 * @var string
35 */
36 private $tag = null;
37
38 /**
39 * A list of handlers to switch the lang keys.
40 *
41 * @var array
42 */
43 private $handlers = array();
44
45 /**
46 * Name of the transliterator function for this language.
47 *
48 * @var Callable
49 */
50 private $transliterator = null;
51
52 /**
53 * Class constructor.
54 *
55 * @param string $lang The language to use.
56 *
57 * @uses load()
58 */
59 public function __construct($lang = null)
60 {
61 $this->tag = $lang;
62 }
63
64 /**
65 * Returns a language object.
66 *
67 * @param string $lang The language to use.
68 *
69 * @return self The Language object.
70 */
71 public static function getInstance($lang = null)
72 {
73 if (!isset(static::$instances[$lang]))
74 {
75 static::$instances[$lang] = new static($lang);
76 }
77
78 return static::$instances[$lang];
79 }
80
81 /**
82 * Loads a single language file. This method doesn't update the
83 * current user locale.
84 *
85 * @param string $extension The extension for which a language file should be loaded.
86 * @param string $basePath The basepath to use.
87 * @param string $lang The language to load, default null for the current language.
88 *
89 * @return boolean True if the file has been successfully loaded.
90 *
91 * @uses getLocaleFilter()
92 */
93 public function load($extension, $basePath = '', $lang = null)
94 {
95 // make sure we are not using the Joomla tag standards
96 $lang = is_null($lang) ? null : str_replace('-', '_', $lang);
97
98 // make sure the name of the plugin is correct
99 $extension = preg_replace("/^com_/", '', $extension);
100
101 $this->localeFilter = $lang;
102
103 /**
104 * Action triggered before loading the text domain.
105 *
106 * @param string $extension The plugin text domain to look for.
107 * @param string $basePath The base path containing the languages.
108 * @param mixed $lang An optional language tag to use.
109 *
110 * @return void
111 *
112 * @since 10.1.30
113 */
114 do_action('vik_plugin_before_load_language', $extension, $basePath, $lang);
115
116 /**
117 * In case the base path was not provided, or a default Joomla constant was passed,
118 * we need to try to auto-detect the standard folder where the language files
119 * should be placed.
120 *
121 * @since 10.1.24
122 */
123 if (!$basePath || $basePath == 'JPATH_ADMINISTRATOR' || $basePath == 'JPATH_SITE')
124 {
125 // create constant name to retrieve standard languages folder
126 $const = strtoupper($extension) . '_LANG';
127
128 // try to retrieve language path from constant, if defined
129 $basePath = defined($const) ? constant($const) : '';
130 }
131
132 // check if the lang tag has been specified,
133 // otherwise use the default locale
134 if (!is_null($lang))
135 {
136 // create a filter to override the 'plugin_locale'
137 add_filter('plugin_locale', array($this, 'getLocaleFilter'));
138 }
139
140 // we need to attach an action to 'load_textdomain' to unset the
141 // cache related to the existing language, if any
142 add_action('load_textdomain', array($this, 'refreshDomain'));
143
144 /**
145 * Register 2 hooks to prevent the hack defined by Polylang to support the
146 * lazy loading of the translations. Since our plugin might run before
147 * Polylang, we need to by pass this limitation and always load the translations
148 * without waiting the latter is ready.
149 *
150 * @since 10.1.33
151 */
152 add_filter('load_textdomain_mofile', array($this, 'storeDefaultMofile'), 1, 2);
153 add_filter('load_textdomain_mofile', array($this, 'preventPolylangHack'), 100, 2);
154
155 // init language
156 $loaded = load_plugin_textdomain($extension, false, $basePath);
157
158 /**
159 * Hook used to load plugin translations from different folders.
160 *
161 * @param boolean $loaded True if a language translation has been already loaded.
162 * @param string $extension The plugin text domain to look for.
163 *
164 * @return boolean True if a new translation is loaded.
165 *
166 * @since 10.1.28
167 */
168 $loaded = apply_filters('vik_plugin_load_language', $loaded, $extension);
169
170 // remove the action to avoid affecting other plugins
171 remove_action('load_textdomain', array($this, 'refreshDomain'));
172
173 // remove the filter to avoid affecting other plugins
174 remove_filter('plugin_locale', array($this, 'getLocaleFilter'));
175
176 return $loaded;
177 }
178
179 /**
180 * Returns the lang tag to load for 'locale' filter.
181 *
182 * @return string The locale.
183 */
184 public function getLocaleFilter()
185 {
186 /**
187 * @var $localeFilter is declared in self::load()
188 */
189 return isset($this->localeFilter) ? $this->localeFilter : null;
190 }
191
192 /**
193 * Used to unset the cache related to a language already loaded.
194 *
195 * @param string $domain The language domain to unset.
196 *
197 * @return void
198 */
199 public function refreshDomain()
200 {
201 $args = func_get_args();
202 $domain = array_shift($args);
203
204 if (!$domain)
205 {
206 return;
207 }
208
209 // the global $l10n var contains all the cached languages
210 global $l10n;
211
212 // if the domain is set in the cache, unset it
213 if (isset($l10n[$domain]))
214 {
215 unset($l10n[$domain]);
216 }
217 }
218
219 /**
220 * Every time Gettext tries to fetch the path of a MO file,
221 * we need to internally save the default given path.
222 *
223 * This method should be executed as soon as possible.
224 *
225 * @param string $mofile Path to the MO file.
226 * @param string $domain Unique identifier for retrieving translated strings.
227 *
228 * @return string The updated MO file.
229 *
230 * @since 10.1.33
231 */
232 public function storeDefaultMofile($mofile, $domain)
233 {
234 // always track the default MO file path
235 $this->defaultMoFile = $mofile;
236
237 return $mofile;
238 }
239
240 /**
241 * Polylang always unset the given MO files to load them all together when
242 * this latter already loaded all the resources. In order to prevent this
243 * behavior/hack, we need to reset the empty path with the previously
244 * registered one (@see storeDefaultMofile).
245 *
246 * This method should be executed as late as possible.
247 *
248 * @param string $mofile Path to the MO file.
249 * @param string $domain Unique identifier for retrieving translated strings.
250 *
251 * @return string The updated MO file.
252 *
253 * @since 10.1.33
254 */
255 public function preventPolylangHack($mofile, $domain)
256 {
257 if (!$mofile)
258 {
259 // the path has been probably emptied by Polylang, reset it
260 $mofile = $this->defaultMoFile;
261 }
262
263 return $mofile;
264 }
265
266 /**
267 * Translates a string into the current language.
268 *
269 * @param string $string The string to translate.
270 * @param boolean $jsSafe Make the result javascript safe.
271 * @param boolean $interpretBackSlashes To interpret backslashes (\\=\, \n=carriage return, \t=tabulation)
272 *
273 * @return string The translated string.
274 *
275 * @uses findTranslation()
276 */
277 public function _($string, $jsSafe = false, $interpretBackSlashes = true)
278 {
279 $return = $this->findTranslation($string);
280
281 if (empty($return))
282 {
283 return $string;
284 }
285
286 if ($jsSafe)
287 {
288 // javascript filter
289 $return = addslashes($return);
290 }
291 else if ($interpretBackSlashes)
292 {
293 if (strpos($return, '\\') !== false)
294 {
295 // interpret \n and \t characters
296 $return = str_replace(array('\\\\', '\t', '\n'), array("\\", "\t", "\n"), $return);
297 }
298 }
299
300 return $return;
301 }
302
303 /**
304 * Dispatches the attached handlers to find the specified string.
305 *
306 * @param string $string The string to translate.
307 *
308 * @return string The translated string, otherwise null.
309 */
310 protected function findTranslation($string)
311 {
312 foreach ($this->handlers as $handler)
313 {
314 $result = $handler->translate($string);
315
316 if ($result !== null)
317 {
318 return $result;
319 }
320 }
321
322 return null;
323 }
324
325 /**
326 * Getter for the language tag (as defined in RFC 3066).
327 *
328 * @return string The language tag.
329 */
330 public function getTag()
331 {
332 $tag = $this->tag;
333
334 // if no tag set, return the current one
335 if (is_null($tag))
336 {
337 /**
338 * Take the locale specified by the user.
339 * In case of missing locale, the function
340 * always fallback to the default one.
341 *
342 * @since 10.1.31
343 */
344 $tag = get_user_locale();
345 }
346
347 // replace the underscore with an hyphen
348 return str_replace('_', '-', $tag);
349 }
350
351 /**
352 * Determines is a key exists.
353 *
354 * @param string $string The key to check.
355 *
356 * @return boolean True if the key exists, otherwise false.
357 *
358 * @uses _()
359 */
360 public function hasKey($string)
361 {
362 /**
363 * Fixed return value, which was exactly a negation of
364 * the expected boolean.
365 *
366 * @since 10.1.35
367 */
368 return strcmp($this->_($string), $string) === 0 ? false : true;
369 }
370
371 /**
372 * Attaches an handler to evaluate the key to translate.
373 *
374 * @param mixed $handler Either a file path or an handler (@since 10.1.46 added support to JLanguageHandler).
375 * @param string $domain The plugin domain name.
376 *
377 * @return self This object to support chaining.
378 */
379 public function attachHandler($handler, $domain)
380 {
381 /**
382 * Strip any unexpected characters from domain.
383 *
384 * @since 10.1.29
385 */
386 $domain = preg_replace("/[^a-z0-9_]+/i", '', $domain);
387
388 $sign = serialize([$handler, $domain]);
389
390 if (!isset($this->handlers[$sign]))
391 {
392 /**
393 * Auto-load the class handler only in case a file path is provided.
394 *
395 * @since 10.1.46
396 */
397 if (is_string($handler))
398 {
399 require_once $handler;
400
401 $name = basename($handler);
402 $name = substr($name, 0, strrpos($name, '.'));
403
404 $classname = ucwords($domain) . 'Language' . ucwords($name);
405 $handler = new $classname();
406 }
407
408 if ($handler instanceof JLanguageHandler)
409 {
410 $this->handlers[$sign] = $handler;
411 }
412 }
413
414 return $this;
415 }
416
417 /**
418 * Transliterate function.
419 * This method processes a string and replaces all accented UTF-8 characters by unaccented
420 * ASCII-7 "equivalents".
421 *
422 * @param string $string The string to transliterate.
423 *
424 * @return string The transliteration of the string.
425 */
426 public function transliterate($string)
427 {
428 if ($this->transliterator !== null)
429 {
430 return call_user_func($this->transliterator, $string);
431 }
432
433 JLoader::import('adapter.language.transliterate');
434
435 $string = Transliterate::utf8_latin_to_ascii($string);
436
437 return $string;
438 }
439
440 /**
441 * Getter for transliteration function.
442 *
443 * @return callable The transliterator function.
444 */
445 public function getTransliterator()
446 {
447 return $this->transliterator;
448 }
449
450 /**
451 * Set the transliteration function.
452 *
453 * @param callable $function Function name or the actual function.
454 *
455 * @return callable The previous function.
456 */
457 public function setTransliterator($function)
458 {
459 $previous = $this->transliterator;
460 $this->transliterator = $function;
461
462 return $previous;
463 }
464
465 /**
466 * Get the first day of the week for this language.
467 *
468 * @return integer The first day of the week according to the language
469 *
470 * @since 10.1.28
471 */
472 public function getFirstDay()
473 {
474 // since we have not enough information to know
475 // the first day of the week for this region,
476 // we should rely on the global configuration
477 return get_option('start_of_week', 0);
478 }
479
480 /**
481 * Get the RTL property.
482 *
483 * @return boolean True is it an RTL language.
484 *
485 * @since 10.1.28
486 */
487 public function isRtl()
488 {
489 // checks whether the current locale is RTL
490 return is_rtl();
491 }
492
493 /**
494 * Returns a list of known languages.
495 *
496 * @return array Key/value pair with the language file and related metadata.
497 *
498 * @since 10.1.9
499 */
500 public static function getKnownLanguages()
501 {
502 /**
503 * Get installed languages.
504 *
505 * @since 10.1.55 Get rid of the keys because third-party plugins might attach
506 * custom languages by using non-integer keys.
507 */
508 $list = array_values(get_available_languages());
509
510 // if the default US lang is in the array, remove it
511 if ($index = array_search('en_US', $list))
512 {
513 array_splice($list, $index, 1);
514 }
515
516 // insert en_US default lang at the beginning of the list
517 array_unshift($list, 'en_US');
518
519 // replace all the underscores with an hyphen
520 $list = array_map(function($elem)
521 {
522 return str_replace('_', '-', $elem);
523 }, $list);
524
525 // obtain WP available translations
526 require_once ABSPATH . 'wp-admin/includes/translation-install.php';
527 $translations = wp_get_available_translations();
528
529 $map = array();
530
531 foreach ($list as $lang)
532 {
533 // get the original locale
534 $key = str_replace('-', '_', $lang);
535
536 if (isset($translations[$key]))
537 {
538 $name = $translations[$key]['english_name'];
539 $native = $translations[$key]['native_name'];
540 $locale = implode(', ', $translations[$key]['iso']);
541
542 unset($translations[$key]);
543 }
544 else
545 {
546 $name = $native = $lang;
547 $locale = substr($lang, 0, 2);
548 }
549
550 $map[$lang] = array(
551 'name' => $name,
552 'nativeName' => $native,
553 'tag' => $lang,
554 'locale' => $locale,
555 'rtl' => 0,
556 'firstDay' => 0,
557 );
558 }
559
560 // fix en_US property because its details don't exist
561 $map['en-US']['name'] = 'English (United States)';
562 $map['en-US']['nativeName'] = 'English (United States)';
563
564 return $map;
565 }
566 }
567