PageRenderTime 53ms CodeModel.GetById 17ms RepoModel.GetById 0ms app.codeStats 0ms

/src/Joomla/Language/Language.php

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