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

/libraries/joomla/language/language.php

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