PageRenderTime 50ms CodeModel.GetById 16ms RepoModel.GetById 0ms app.codeStats 0ms

/src/Joomla/Language/Language.php

https://github.com/piotr-cz/joomla-framework
PHP | 1364 lines | 817 code | 137 blank | 410 comment | 63 complexity | 7e71af053e902201dcbf67e337f708e0 MD5 | raw file
Possible License(s): GPL-2.0, LGPL-2.1
  1. <?php
  2. /**
  3. * Part of the Joomla Framework Language Package
  4. *
  5. * @copyright Copyright (C) 2005 - 2013 Open Source Matters, Inc. All rights reserved.
  6. * @license GNU General Public License version 2 or later; see LICENSE
  7. */
  8. namespace Joomla\Language;
  9. use Joomla\String\String;
  10. /**
  11. * Allows for quoting in language .ini files.
  12. */
  13. define('_QQ_', '"');
  14. /**
  15. * Languages/translation handler class
  16. *
  17. * @since 1.0
  18. */
  19. class Language
  20. {
  21. /**
  22. * Language instance container
  23. *
  24. * @var array
  25. * @since 1.0
  26. */
  27. protected static $languages = array();
  28. /**
  29. * Debug language, If true, highlights if string isn't found.
  30. *
  31. * @var boolean
  32. * @since 1.0
  33. */
  34. protected $debug = false;
  35. /**
  36. * The default language, used when a language file in the requested language does not exist.
  37. *
  38. * @var string
  39. * @since 1.0
  40. */
  41. protected $default = 'en-GB';
  42. /**
  43. * An array of orphaned text.
  44. *
  45. * @var array
  46. * @since 1.0
  47. */
  48. protected $orphans = array();
  49. /**
  50. * Array holding the language metadata.
  51. *
  52. * @var array
  53. * @since 1.0
  54. */
  55. protected $metadata = null;
  56. /**
  57. * Array holding the language locale or boolean null if none.
  58. *
  59. * @var array|boolean
  60. * @since 1.0
  61. */
  62. protected $locale = null;
  63. /**
  64. * The language to load.
  65. *
  66. * @var string
  67. * @since 1.0
  68. */
  69. protected $lang = null;
  70. /**
  71. * A nested array of language files that have been loaded
  72. *
  73. * @var array
  74. * @since 1.0
  75. */
  76. protected $paths = array();
  77. /**
  78. * List of language files that are in error state
  79. *
  80. * @var array
  81. * @since 1.0
  82. */
  83. protected $errorfiles = array();
  84. /**
  85. * Translations
  86. *
  87. * @var array
  88. * @since 1.0
  89. */
  90. protected $strings = null;
  91. /**
  92. * An array of used text, used during debugging.
  93. *
  94. * @var array
  95. * @since 1.0
  96. */
  97. protected $used = array();
  98. /**
  99. * Counter for number of loads.
  100. *
  101. * @var integer
  102. * @since 1.0
  103. */
  104. protected $counter = 0;
  105. /**
  106. * An array used to store overrides.
  107. *
  108. * @var array
  109. * @since 1.0
  110. */
  111. protected $override = array();
  112. /**
  113. * Name of the transliterator function for this language.
  114. *
  115. * @var string
  116. * @since 1.0
  117. */
  118. protected $transliterator = null;
  119. /**
  120. * Name of the pluralSuffixesCallback function for this language.
  121. *
  122. * @var callable
  123. * @since 1.0
  124. */
  125. protected $pluralSuffixesCallback = null;
  126. /**
  127. * Name of the ignoredSearchWordsCallback function for this language.
  128. *
  129. * @var callable
  130. * @since 1.0
  131. */
  132. protected $ignoredSearchWordsCallback = null;
  133. /**
  134. * Name of the lowerLimitSearchWordCallback function for this language.
  135. *
  136. * @var callable
  137. * @since 1.0
  138. */
  139. protected $lowerLimitSearchWordCallback = null;
  140. /**
  141. * Name of the uppperLimitSearchWordCallback function for this language
  142. *
  143. * @var callable
  144. * @since 1.0
  145. */
  146. protected $upperLimitSearchWordCallback = null;
  147. /**
  148. * Name of the searchDisplayedCharactersNumberCallback function for this language.
  149. *
  150. * @var callable
  151. * @since 1.0
  152. */
  153. protected $searchDisplayedCharactersNumberCallback = null;
  154. /**
  155. * Constructor activating the default information of the language.
  156. *
  157. * @param string $lang The language
  158. * @param boolean $debug Indicates if language debugging is enabled.
  159. *
  160. * @since 1.0
  161. */
  162. public function __construct($lang = null, $debug = false)
  163. {
  164. $this->strings = array();
  165. if ($lang == null)
  166. {
  167. $lang = $this->default;
  168. }
  169. $this->setLanguage($lang);
  170. $this->setDebug($debug);
  171. $filename = JPATH_ROOT . "/language/overrides/$lang.override.ini";
  172. if (file_exists($filename) && $contents = $this->parse($filename))
  173. {
  174. if (is_array($contents))
  175. {
  176. // Sort the underlying heap by key values to optimize merging
  177. ksort($contents, SORT_STRING);
  178. $this->override = $contents;
  179. }
  180. unset($contents);
  181. }
  182. // Look for a language specific localise class
  183. $class = str_replace('-', '_', $lang . 'Localise');
  184. $paths = array();
  185. $basePath = self::getLanguagePath(JPATH_ROOT);
  186. $paths[0] = $basePath . "/language/overrides/$lang.localise.php";
  187. $paths[1] = $basePath . "/language/$lang/$lang.localise.php";
  188. ksort($paths);
  189. $path = reset($paths);
  190. while (!class_exists($class) && $path)
  191. {
  192. if (file_exists($path))
  193. {
  194. require_once $path;
  195. }
  196. $path = next($paths);
  197. }
  198. if (class_exists($class))
  199. {
  200. /* Class exists. Try to find
  201. * -a transliterate method,
  202. * -a getPluralSuffixes method,
  203. * -a getIgnoredSearchWords method
  204. * -a getLowerLimitSearchWord method
  205. * -a getUpperLimitSearchWord method
  206. * -a getSearchDisplayCharactersNumber method
  207. */
  208. if (method_exists($class, 'transliterate'))
  209. {
  210. $this->transliterator = array($class, 'transliterate');
  211. }
  212. if (method_exists($class, 'getPluralSuffixes'))
  213. {
  214. $this->pluralSuffixesCallback = array($class, 'getPluralSuffixes');
  215. }
  216. if (method_exists($class, 'getIgnoredSearchWords'))
  217. {
  218. $this->ignoredSearchWordsCallback = array($class, 'getIgnoredSearchWords');
  219. }
  220. if (method_exists($class, 'getLowerLimitSearchWord'))
  221. {
  222. $this->lowerLimitSearchWordCallback = array($class, 'getLowerLimitSearchWord');
  223. }
  224. if (method_exists($class, 'getUpperLimitSearchWord'))
  225. {
  226. $this->upperLimitSearchWordCallback = array($class, 'getUpperLimitSearchWord');
  227. }
  228. if (method_exists($class, 'getSearchDisplayedCharactersNumber'))
  229. {
  230. $this->searchDisplayedCharactersNumberCallback = array($class, 'getSearchDisplayedCharactersNumber');
  231. }
  232. }
  233. $this->load();
  234. }
  235. /**
  236. * Returns a language object.
  237. *
  238. * @param string $lang The language to use.
  239. * @param boolean $debug The debug mode.
  240. *
  241. * @return Language The Language object.
  242. *
  243. * @since 1.0
  244. */
  245. public static function getInstance($lang = null, $debug = false)
  246. {
  247. if (!isset(self::$languages[$lang . $debug]))
  248. {
  249. $language = new self($lang, $debug);
  250. self::$languages[$lang . $debug] = $language;
  251. /*
  252. * Check if Language was instantiated with a null $lang param;
  253. * if so, retrieve the language code from the object and store
  254. * the instance with the language code as well
  255. */
  256. if (is_null($lang))
  257. {
  258. self::$languages[$language->getLanguage() . $debug] = $language;
  259. }
  260. }
  261. return self::$languages[$lang . $debug];
  262. }
  263. /**
  264. * Translate function, mimics the php gettext (alias _) function.
  265. *
  266. * The function checks if $jsSafe is true, then if $interpretBackslashes is true.
  267. *
  268. * @param string $string The string to translate
  269. * @param boolean $jsSafe Make the result javascript safe
  270. * @param boolean $interpretBackSlashes Interpret \t and \n
  271. *
  272. * @return string The translation of the string
  273. *
  274. * @since 1.0
  275. */
  276. public function _($string, $jsSafe = false, $interpretBackSlashes = true)
  277. {
  278. // Detect empty string
  279. if ($string == '')
  280. {
  281. return '';
  282. }
  283. $key = strtoupper($string);
  284. if (isset($this->strings[$key]))
  285. {
  286. $string = $this->debug ? '**' . $this->strings[$key] . '**' : $this->strings[$key];
  287. // Store debug information
  288. if ($this->debug)
  289. {
  290. $caller = $this->getCallerInfo();
  291. if (!array_key_exists($key, $this->used))
  292. {
  293. $this->used[$key] = array();
  294. }
  295. $this->used[$key][] = $caller;
  296. }
  297. }
  298. else
  299. {
  300. if ($this->debug)
  301. {
  302. $caller = $this->getCallerInfo();
  303. $caller['string'] = $string;
  304. if (!array_key_exists($key, $this->orphans))
  305. {
  306. $this->orphans[$key] = array();
  307. }
  308. $this->orphans[$key][] = $caller;
  309. $string = '??' . $string . '??';
  310. }
  311. }
  312. if ($jsSafe)
  313. {
  314. // Javascript filter
  315. $string = addslashes($string);
  316. }
  317. elseif ($interpretBackSlashes)
  318. {
  319. // Interpret \n and \t characters
  320. $string = str_replace(array('\\\\', '\t', '\n'), array("\\", "\t", "\n"), $string);
  321. }
  322. return $string;
  323. }
  324. /**
  325. * Transliterate function
  326. *
  327. * This method processes a string and replaces all accented UTF-8 characters by unaccented
  328. * ASCII-7 "equivalents".
  329. *
  330. * @param string $string The string to transliterate.
  331. *
  332. * @return string The transliteration of the string.
  333. *
  334. * @since 1.0
  335. */
  336. public function transliterate($string)
  337. {
  338. if ($this->transliterator !== null)
  339. {
  340. return call_user_func($this->transliterator, $string);
  341. }
  342. $string = Transliterate::utf8_latin_to_ascii($string);
  343. $string = String::strtolower($string);
  344. return $string;
  345. }
  346. /**
  347. * Getter for transliteration function
  348. *
  349. * @return callable The transliterator function
  350. *
  351. * @since 1.0
  352. */
  353. public function getTransliterator()
  354. {
  355. return $this->transliterator;
  356. }
  357. /**
  358. * Set the transliteration function.
  359. *
  360. * @param callable $function Function name or the actual function.
  361. *
  362. * @return callable The previous function.
  363. *
  364. * @since 1.0
  365. */
  366. public function setTransliterator($function)
  367. {
  368. $previous = $this->transliterator;
  369. $this->transliterator = $function;
  370. return $previous;
  371. }
  372. /**
  373. * Returns an array of suffixes for plural rules.
  374. *
  375. * @param integer $count The count number the rule is for.
  376. *
  377. * @return array The array of suffixes.
  378. *
  379. * @since 1.0
  380. */
  381. public function getPluralSuffixes($count)
  382. {
  383. if ($this->pluralSuffixesCallback !== null)
  384. {
  385. return call_user_func($this->pluralSuffixesCallback, $count);
  386. }
  387. else
  388. {
  389. return array((string) $count);
  390. }
  391. }
  392. /**
  393. * Getter for pluralSuffixesCallback function.
  394. *
  395. * @return callable Function name or the actual function.
  396. *
  397. * @since 1.0
  398. */
  399. public function getPluralSuffixesCallback()
  400. {
  401. return $this->pluralSuffixesCallback;
  402. }
  403. /**
  404. * Set the pluralSuffixes function.
  405. *
  406. * @param callable $function Function name or actual function.
  407. *
  408. * @return callable The previous function.
  409. *
  410. * @since 1.0
  411. */
  412. public function setPluralSuffixesCallback($function)
  413. {
  414. $previous = $this->pluralSuffixesCallback;
  415. $this->pluralSuffixesCallback = $function;
  416. return $previous;
  417. }
  418. /**
  419. * Returns an array of ignored search words
  420. *
  421. * @return array The array of ignored search words.
  422. *
  423. * @since 1.0
  424. */
  425. public function getIgnoredSearchWords()
  426. {
  427. if ($this->ignoredSearchWordsCallback !== null)
  428. {
  429. return call_user_func($this->ignoredSearchWordsCallback);
  430. }
  431. else
  432. {
  433. return array();
  434. }
  435. }
  436. /**
  437. * Getter for ignoredSearchWordsCallback function.
  438. *
  439. * @return callable Function name or the actual function.
  440. *
  441. * @since 1.0
  442. */
  443. public function getIgnoredSearchWordsCallback()
  444. {
  445. return $this->ignoredSearchWordsCallback;
  446. }
  447. /**
  448. * Setter for the ignoredSearchWordsCallback function
  449. *
  450. * @param callable $function Function name or actual function.
  451. *
  452. * @return callable The previous function.
  453. *
  454. * @since 1.0
  455. */
  456. public function setIgnoredSearchWordsCallback($function)
  457. {
  458. $previous = $this->ignoredSearchWordsCallback;
  459. $this->ignoredSearchWordsCallback = $function;
  460. return $previous;
  461. }
  462. /**
  463. * Returns a lower limit integer for length of search words
  464. *
  465. * @return integer The lower limit integer for length of search words (3 if no value was set for a specific language).
  466. *
  467. * @since 1.0
  468. */
  469. public function getLowerLimitSearchWord()
  470. {
  471. if ($this->lowerLimitSearchWordCallback !== null)
  472. {
  473. return call_user_func($this->lowerLimitSearchWordCallback);
  474. }
  475. else
  476. {
  477. return 3;
  478. }
  479. }
  480. /**
  481. * Getter for lowerLimitSearchWordCallback function
  482. *
  483. * @return callable Function name or the actual function.
  484. *
  485. * @since 1.0
  486. */
  487. public function getLowerLimitSearchWordCallback()
  488. {
  489. return $this->lowerLimitSearchWordCallback;
  490. }
  491. /**
  492. * Setter for the lowerLimitSearchWordCallback function.
  493. *
  494. * @param callable $function Function name or actual function.
  495. *
  496. * @return callable The previous function.
  497. *
  498. * @since 1.0
  499. */
  500. public function setLowerLimitSearchWordCallback($function)
  501. {
  502. $previous = $this->lowerLimitSearchWordCallback;
  503. $this->lowerLimitSearchWordCallback = $function;
  504. return $previous;
  505. }
  506. /**
  507. * Returns an upper limit integer for length of search words
  508. *
  509. * @return integer The upper limit integer for length of search words (20 if no value was set for a specific language).
  510. *
  511. * @since 1.0
  512. */
  513. public function getUpperLimitSearchWord()
  514. {
  515. if ($this->upperLimitSearchWordCallback !== null)
  516. {
  517. return call_user_func($this->upperLimitSearchWordCallback);
  518. }
  519. else
  520. {
  521. return 20;
  522. }
  523. }
  524. /**
  525. * Getter for upperLimitSearchWordCallback function
  526. *
  527. * @return callable Function name or the actual function.
  528. *
  529. * @since 1.0
  530. */
  531. public function getUpperLimitSearchWordCallback()
  532. {
  533. return $this->upperLimitSearchWordCallback;
  534. }
  535. /**
  536. * Setter for the upperLimitSearchWordCallback function
  537. *
  538. * @param callable $function Function name or the actual function.
  539. *
  540. * @return callable The previous function.
  541. *
  542. * @since 1.0
  543. */
  544. public function setUpperLimitSearchWordCallback($function)
  545. {
  546. $previous = $this->upperLimitSearchWordCallback;
  547. $this->upperLimitSearchWordCallback = $function;
  548. return $previous;
  549. }
  550. /**
  551. * Returns the number of characters displayed in search results.
  552. *
  553. * @return integer The number of characters displayed (200 if no value was set for a specific language).
  554. *
  555. * @since 1.0
  556. */
  557. public function getSearchDisplayedCharactersNumber()
  558. {
  559. if ($this->searchDisplayedCharactersNumberCallback !== null)
  560. {
  561. return call_user_func($this->searchDisplayedCharactersNumberCallback);
  562. }
  563. else
  564. {
  565. return 200;
  566. }
  567. }
  568. /**
  569. * Getter for searchDisplayedCharactersNumberCallback function
  570. *
  571. * @return callable Function name or the actual function.
  572. *
  573. * @since 1.0
  574. */
  575. public function getSearchDisplayedCharactersNumberCallback()
  576. {
  577. return $this->searchDisplayedCharactersNumberCallback;
  578. }
  579. /**
  580. * Setter for the searchDisplayedCharactersNumberCallback function.
  581. *
  582. * @param callable $function Function name or the actual function.
  583. *
  584. * @return callable The previous function.
  585. *
  586. * @since 1.0
  587. */
  588. public function setSearchDisplayedCharactersNumberCallback($function)
  589. {
  590. $previous = $this->searchDisplayedCharactersNumberCallback;
  591. $this->searchDisplayedCharactersNumberCallback = $function;
  592. return $previous;
  593. }
  594. /**
  595. * Checks if a language exists.
  596. *
  597. * This is a simple, quick check for the directory that should contain language files for the given user.
  598. *
  599. * @param string $lang Language to check.
  600. * @param string $basePath Optional path to check.
  601. *
  602. * @return boolean True if the language exists.
  603. *
  604. * @since 1.0
  605. */
  606. public static function exists($lang, $basePath = JPATH_ROOT)
  607. {
  608. static $paths = array();
  609. // Return false if no language was specified
  610. if (!$lang)
  611. {
  612. return false;
  613. }
  614. $path = $basePath . '/language/' . $lang;
  615. // Return previous check results if it exists
  616. if (isset($paths[$path]))
  617. {
  618. return $paths[$path];
  619. }
  620. // Check if the language exists
  621. $paths[$path] = is_dir($path);
  622. return $paths[$path];
  623. }
  624. /**
  625. * Loads a single language file and appends the results to the existing strings
  626. *
  627. * @param string $extension The extension for which a language file should be loaded.
  628. * @param string $basePath The basepath to use.
  629. * @param string $lang The language to load, default null for the current language.
  630. * @param boolean $reload Flag that will force a language to be reloaded if set to true.
  631. * @param boolean $default Flag that force the default language to be loaded if the current does not exist.
  632. *
  633. * @return boolean True if the file has successfully loaded.
  634. *
  635. * @since 1.0
  636. */
  637. public function load($extension = 'joomla', $basePath = JPATH_ROOT, $lang = null, $reload = false, $default = true)
  638. {
  639. if (!$lang)
  640. {
  641. $lang = $this->lang;
  642. }
  643. $path = self::getLanguagePath($basePath, $lang);
  644. $internal = $extension == 'joomla' || $extension == '';
  645. $filename = $internal ? $lang : $lang . '.' . $extension;
  646. $filename = "$path/$filename.ini";
  647. if (isset($this->paths[$extension][$filename]) && !$reload)
  648. {
  649. // This file has already been tested for loading.
  650. $result = $this->paths[$extension][$filename];
  651. }
  652. else
  653. {
  654. // Load the language file
  655. $result = $this->loadLanguage($filename, $extension);
  656. // Check whether there was a problem with loading the file
  657. if ($result === false && $default)
  658. {
  659. // No strings, so either file doesn't exist or the file is invalid
  660. $oldFilename = $filename;
  661. // Check the standard file name
  662. $path = self::getLanguagePath($basePath, $this->default);
  663. $filename = $internal ? $this->default : $this->default . '.' . $extension;
  664. $filename = "$path/$filename.ini";
  665. // If the one we tried is different than the new name, try again
  666. if ($oldFilename != $filename)
  667. {
  668. $result = $this->loadLanguage($filename, $extension, false);
  669. }
  670. }
  671. }
  672. return $result;
  673. }
  674. /**
  675. * Loads a language file.
  676. *
  677. * This method will not note the successful loading of a file - use load() instead.
  678. *
  679. * @param string $filename The name of the file.
  680. * @param string $extension The name of the extension.
  681. *
  682. * @return boolean True if new strings have been added to the language
  683. *
  684. * @see Language::load()
  685. * @since 1.0
  686. */
  687. protected function loadLanguage($filename, $extension = 'unknown')
  688. {
  689. $this->counter++;
  690. $result = false;
  691. $strings = false;
  692. if (file_exists($filename))
  693. {
  694. $strings = $this->parse($filename);
  695. }
  696. if ($strings)
  697. {
  698. if (is_array($strings))
  699. {
  700. // Sort the underlying heap by key values to optimize merging
  701. ksort($strings, SORT_STRING);
  702. $this->strings = array_merge($this->strings, $strings);
  703. }
  704. if (is_array($strings) && count($strings))
  705. {
  706. // Do not bother with ksort here. Since the originals were sorted, PHP will already have chosen the best heap.
  707. $this->strings = array_merge($this->strings, $this->override);
  708. $result = true;
  709. }
  710. }
  711. // Record the result of loading the extension's file.
  712. if (!isset($this->paths[$extension]))
  713. {
  714. $this->paths[$extension] = array();
  715. }
  716. $this->paths[$extension][$filename] = $result;
  717. return $result;
  718. }
  719. /**
  720. * Parses a language file.
  721. *
  722. * @param string $filename The name of the file.
  723. *
  724. * @return array The array of parsed strings.
  725. *
  726. * @since 1.0
  727. */
  728. protected function parse($filename)
  729. {
  730. if ($this->debug)
  731. {
  732. // Capture hidden PHP errors from the parsing.
  733. $php_errormsg = null;
  734. $track_errors = ini_get('track_errors');
  735. ini_set('track_errors', true);
  736. }
  737. $contents = file_get_contents($filename);
  738. $contents = str_replace('_QQ_', '"\""', $contents);
  739. $strings = @parse_ini_string($contents);
  740. if (!is_array($strings))
  741. {
  742. $strings = array();
  743. }
  744. if ($this->debug)
  745. {
  746. // Restore error tracking to what it was before.
  747. ini_set('track_errors', $track_errors);
  748. // Initialise variables for manually parsing the file for common errors.
  749. $blacklist = array('YES', 'NO', 'NULL', 'FALSE', 'ON', 'OFF', 'NONE', 'TRUE');
  750. $regex = '/^(|(\[[^\]]*\])|([A-Z][A-Z0-9_\-\.]*\s*=(\s*(("[^"]*")|(_QQ_)))+))\s*(;.*)?$/';
  751. $this->debug = false;
  752. $errors = array();
  753. // Open the file as a stream.
  754. $file = new \SplFileObject($filename);
  755. foreach ($file as $lineNumber => $line)
  756. {
  757. // Avoid BOM error as BOM is OK when using parse_ini
  758. if ($lineNumber == 0)
  759. {
  760. $line = str_replace("\xEF\xBB\xBF", '', $line);
  761. }
  762. // Check that the key is not in the blacklist and that the line format passes the regex.
  763. $key = strtoupper(trim(substr($line, 0, strpos($line, '='))));
  764. // Workaround to reduce regex complexity when matching escaped quotes
  765. $line = str_replace('\"', '_QQ_', $line);
  766. if (!preg_match($regex, $line) || in_array($key, $blacklist))
  767. {
  768. $errors[] = $lineNumber;
  769. }
  770. }
  771. // Check if we encountered any errors.
  772. if (count($errors))
  773. {
  774. $this->errorfiles[$filename] = $filename . '&#160;: error(s) in line(s) ' . implode(', ', $errors);
  775. }
  776. elseif ($php_errormsg)
  777. {
  778. // We didn't find any errors but there's probably a parse notice.
  779. $this->errorfiles['PHP' . $filename] = 'PHP parser errors :' . $php_errormsg;
  780. }
  781. $this->debug = true;
  782. }
  783. return $strings;
  784. }
  785. /**
  786. * Get a metadata language property.
  787. *
  788. * @param string $property The name of the property.
  789. * @param mixed $default The default value.
  790. *
  791. * @return mixed The value of the property.
  792. *
  793. * @since 1.0
  794. */
  795. public function get($property, $default = null)
  796. {
  797. if (isset($this->metadata[$property]))
  798. {
  799. return $this->metadata[$property];
  800. }
  801. return $default;
  802. }
  803. /**
  804. * Determine who called Language or Text.
  805. *
  806. * @return array Caller information.
  807. *
  808. * @since 1.0
  809. */
  810. protected function getCallerInfo()
  811. {
  812. // Try to determine the source if none was provided
  813. if (!function_exists('debug_backtrace'))
  814. {
  815. return null;
  816. }
  817. $backtrace = debug_backtrace();
  818. $info = array();
  819. // Search through the backtrace to our caller
  820. $continue = true;
  821. while ($continue && next($backtrace))
  822. {
  823. $step = current($backtrace);
  824. $class = @ $step['class'];
  825. // We're looking for something outside of language.php
  826. if ($class != '\\Joomla\\Language\\Language' && $class != '\\Joomla\\Language\\Text')
  827. {
  828. $info['function'] = @ $step['function'];
  829. $info['class'] = $class;
  830. $info['step'] = prev($backtrace);
  831. // Determine the file and name of the file
  832. $info['file'] = @ $step['file'];
  833. $info['line'] = @ $step['line'];
  834. $continue = false;
  835. }
  836. }
  837. return $info;
  838. }
  839. /**
  840. * Getter for Name.
  841. *
  842. * @return string Official name element of the language.
  843. *
  844. * @since 1.0
  845. */
  846. public function getName()
  847. {
  848. return $this->metadata['name'];
  849. }
  850. /**
  851. * Get a list of language files that have been loaded.
  852. *
  853. * @param string $extension An optional extension name.
  854. *
  855. * @return array
  856. *
  857. * @since 1.0
  858. */
  859. public function getPaths($extension = null)
  860. {
  861. if (isset($extension))
  862. {
  863. if (isset($this->paths[$extension]))
  864. {
  865. return $this->paths[$extension];
  866. }
  867. return null;
  868. }
  869. else
  870. {
  871. return $this->paths;
  872. }
  873. }
  874. /**
  875. * Get a list of language files that are in error state.
  876. *
  877. * @return array
  878. *
  879. * @since 1.0
  880. */
  881. public function getErrorFiles()
  882. {
  883. return $this->errorfiles;
  884. }
  885. /**
  886. * Getter for the language tag (as defined in RFC 3066)
  887. *
  888. * @return string The language tag.
  889. *
  890. * @since 1.0
  891. */
  892. public function getTag()
  893. {
  894. return $this->metadata['tag'];
  895. }
  896. /**
  897. * Get the RTL property.
  898. *
  899. * @return boolean True is it an RTL language.
  900. *
  901. * @since 1.0
  902. */
  903. public function isRTL()
  904. {
  905. return (bool) $this->metadata['rtl'];
  906. }
  907. /**
  908. * Set the Debug property.
  909. *
  910. * @param boolean $debug The debug setting.
  911. *
  912. * @return boolean Previous value.
  913. *
  914. * @since 1.0
  915. */
  916. public function setDebug($debug)
  917. {
  918. $previous = $this->debug;
  919. $this->debug = (boolean) $debug;
  920. return $previous;
  921. }
  922. /**
  923. * Get the Debug property.
  924. *
  925. * @return boolean True is in debug mode.
  926. *
  927. * @since 1.0
  928. */
  929. public function getDebug()
  930. {
  931. return $this->debug;
  932. }
  933. /**
  934. * Get the default language code.
  935. *
  936. * @return string Language code.
  937. *
  938. * @since 1.0
  939. */
  940. public function getDefault()
  941. {
  942. return $this->default;
  943. }
  944. /**
  945. * Set the default language code.
  946. *
  947. * @param string $lang The language code.
  948. *
  949. * @return string Previous value.
  950. *
  951. * @since 1.0
  952. */
  953. public function setDefault($lang)
  954. {
  955. $previous = $this->default;
  956. $this->default = $lang;
  957. return $previous;
  958. }
  959. /**
  960. * Get the list of orphaned strings if being tracked.
  961. *
  962. * @return array Orphaned text.
  963. *
  964. * @since 1.0
  965. */
  966. public function getOrphans()
  967. {
  968. return $this->orphans;
  969. }
  970. /**
  971. * Get the list of used strings.
  972. *
  973. * Used strings are those strings requested and found either as a string or a constant.
  974. *
  975. * @return array Used strings.
  976. *
  977. * @since 1.0
  978. */
  979. public function getUsed()
  980. {
  981. return $this->used;
  982. }
  983. /**
  984. * Determines is a key exists.
  985. *
  986. * @param string $string The key to check.
  987. *
  988. * @return boolean True, if the key exists.
  989. *
  990. * @since 1.0
  991. */
  992. public function hasKey($string)
  993. {
  994. $key = strtoupper($string);
  995. return isset($this->strings[$key]);
  996. }
  997. /**
  998. * Returns a associative array holding the metadata.
  999. *
  1000. * @param string $lang The name of the language.
  1001. *
  1002. * @return mixed If $lang exists return key/value pair with the language metadata, otherwise return NULL.
  1003. *
  1004. * @since 1.0
  1005. */
  1006. public static function getMetadata($lang)
  1007. {
  1008. $path = self::getLanguagePath(JPATH_ROOT, $lang);
  1009. $file = $lang . '.xml';
  1010. $result = null;
  1011. if (is_file("$path/$file"))
  1012. {
  1013. $result = self::parseXMLLanguageFile("$path/$file");
  1014. }
  1015. if (empty($result))
  1016. {
  1017. return null;
  1018. }
  1019. return $result;
  1020. }
  1021. /**
  1022. * Returns a list of known languages for an area
  1023. *
  1024. * @param string $basePath The basepath to use
  1025. *
  1026. * @return array key/value pair with the language file and real name.
  1027. *
  1028. * @since 1.0
  1029. */
  1030. public static function getKnownLanguages($basePath = JPATH_ROOT)
  1031. {
  1032. $dir = self::getLanguagePath($basePath);
  1033. $knownLanguages = self::parseLanguageFiles($dir);
  1034. return $knownLanguages;
  1035. }
  1036. /**
  1037. * Get the path to a language
  1038. *
  1039. * @param string $basePath The basepath to use.
  1040. * @param string $language The language tag.
  1041. *
  1042. * @return string language related path or null.
  1043. *
  1044. * @since 1.0
  1045. */
  1046. public static function getLanguagePath($basePath = JPATH_ROOT, $language = null)
  1047. {
  1048. $dir = $basePath . '/language';
  1049. if (!empty($language))
  1050. {
  1051. $dir .= '/' . $language;
  1052. }
  1053. return $dir;
  1054. }
  1055. /**
  1056. * Get the current language code.
  1057. *
  1058. * @return string The language code
  1059. *
  1060. * @since 1.0
  1061. */
  1062. public function getLanguage()
  1063. {
  1064. return $this->lang;
  1065. }
  1066. /**
  1067. * Set the language attributes to the given language.
  1068. *
  1069. * Once called, the language still needs to be loaded using JLanguage::load().
  1070. *
  1071. * @param string $lang Language code.
  1072. *
  1073. * @return string Previous value.
  1074. *
  1075. * @since 1.0
  1076. */
  1077. public function setLanguage($lang)
  1078. {
  1079. $previous = $this->lang;
  1080. $this->lang = $lang;
  1081. $this->metadata = $this->getMetadata($this->lang);
  1082. return $previous;
  1083. }
  1084. /**
  1085. * Get the language locale based on current language.
  1086. *
  1087. * @return array The locale according to the language.
  1088. *
  1089. * @since 1.0
  1090. */
  1091. public function getLocale()
  1092. {
  1093. if (!isset($this->locale))
  1094. {
  1095. $locale = str_replace(' ', '', isset($this->metadata['locale']) ? $this->metadata['locale'] : '');
  1096. if ($locale)
  1097. {
  1098. $this->locale = explode(',', $locale);
  1099. }
  1100. else
  1101. {
  1102. $this->locale = false;
  1103. }
  1104. }
  1105. return $this->locale;
  1106. }
  1107. /**
  1108. * Get the first day of the week for this language.
  1109. *
  1110. * @return integer The first day of the week according to the language
  1111. *
  1112. * @since 1.0
  1113. */
  1114. public function getFirstDay()
  1115. {
  1116. return (int) (isset($this->metadata['firstDay']) ? $this->metadata['firstDay'] : 0);
  1117. }
  1118. /**
  1119. * Searches for language directories within a certain base dir.
  1120. *
  1121. * @param string $dir directory of files.
  1122. *
  1123. * @return array Array holding the found languages as filename => real name pairs.
  1124. *
  1125. * @since 1.0
  1126. */
  1127. public static function parseLanguageFiles($dir = null)
  1128. {
  1129. $languages = array();
  1130. $iterator = new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($dir));
  1131. foreach ($iterator as $file)
  1132. {
  1133. $langs = array();
  1134. $fileName = $file->getFilename();
  1135. if (!$file->isFile() || !preg_match("/^([-_A-Za-z]*)\.xml$/", $fileName))
  1136. {
  1137. continue;
  1138. }
  1139. try
  1140. {
  1141. $metadata = self::parseXMLLanguageFile($file->getRealPath());
  1142. if ($metadata)
  1143. {
  1144. $lang = str_replace('.xml', '', $fileName);
  1145. $langs[$lang] = $metadata;
  1146. }
  1147. $languages = array_merge($languages, $langs);
  1148. }
  1149. catch (\RuntimeException $e)
  1150. {
  1151. }
  1152. }
  1153. return $languages;
  1154. }
  1155. /**
  1156. * Parse XML file for language information.
  1157. *
  1158. * @param string $path Path to the XML files.
  1159. *
  1160. * @return array Array holding the found metadata as a key => value pair.
  1161. *
  1162. * @since 1.0
  1163. * @throws \RuntimeException
  1164. */
  1165. public static function parseXMLLanguageFile($path)
  1166. {
  1167. if (!is_readable($path))
  1168. {
  1169. throw new \RuntimeException('File not found or not readable');
  1170. }
  1171. // Try to load the file
  1172. $xml = simplexml_load_file($path);
  1173. if (!$xml)
  1174. {
  1175. return null;
  1176. }
  1177. // Check that it's a metadata file
  1178. if ((string) $xml->getName() != 'metafile')
  1179. {
  1180. return null;
  1181. }
  1182. $metadata = array();
  1183. foreach ($xml->metadata->children() as $child)
  1184. {
  1185. $metadata[$child->getName()] = (string) $child;
  1186. }
  1187. return $metadata;
  1188. }
  1189. }