PageRenderTime 123ms CodeModel.GetById 26ms RepoModel.GetById 1ms app.codeStats 0ms

/libraries/joomla/language/language.php

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