1327 lines
28 KiB
PHP
1327 lines
28 KiB
PHP
|
<?php
|
||
|
/**
|
||
|
* @package Joomla.Platform
|
||
|
* @subpackage Language
|
||
|
*
|
||
|
* @copyright Copyright (C) 2005 - 2013 Open Source Matters, Inc. All rights reserved.
|
||
|
* @license GNU General Public License version 2 or later; see LICENSE
|
||
|
*/
|
||
|
|
||
|
defined('JPATH_PLATFORM') or die;
|
||
|
|
||
|
/**
|
||
|
* Allows for quoting in language .ini files.
|
||
|
*/
|
||
|
define('_QQ_', '"');
|
||
|
|
||
|
/**
|
||
|
* Languages/translation handler class
|
||
|
*
|
||
|
* @package Joomla.Platform
|
||
|
* @subpackage Language
|
||
|
* @since 11.1
|
||
|
*/
|
||
|
class JLanguage
|
||
|
{
|
||
|
protected static $languages = array();
|
||
|
|
||
|
/**
|
||
|
* Debug language, If true, highlights if string isn't found.
|
||
|
* @var boolean
|
||
|
* @since 11.1
|
||
|
*/
|
||
|
protected $debug = false;
|
||
|
|
||
|
/**
|
||
|
* The default language, used when a language file in the requested language does not exist.
|
||
|
* @var string
|
||
|
* @since 11.1
|
||
|
*/
|
||
|
protected $default = 'en-GB';
|
||
|
|
||
|
/**
|
||
|
* An array of orphaned text.
|
||
|
* @var array
|
||
|
* @since 11.1
|
||
|
*/
|
||
|
protected $orphans = array();
|
||
|
|
||
|
/**
|
||
|
* Array holding the language metadata.
|
||
|
* @var array
|
||
|
* @since 11.1
|
||
|
*/
|
||
|
protected $metadata = null;
|
||
|
|
||
|
/**
|
||
|
* Array holding the language locale or boolean null if none.
|
||
|
* @var array|boolean
|
||
|
* @since 11.1
|
||
|
*/
|
||
|
protected $locale = null;
|
||
|
|
||
|
/**
|
||
|
* The language to load.
|
||
|
* @var string
|
||
|
* @since 11.1
|
||
|
*/
|
||
|
protected $lang = null;
|
||
|
|
||
|
/**
|
||
|
* A nested array of language files that have been loaded
|
||
|
* @var array
|
||
|
* @since 11.1
|
||
|
*/
|
||
|
protected $paths = array();
|
||
|
|
||
|
/**
|
||
|
* List of language files that are in error state
|
||
|
* @var array
|
||
|
* @since 11.1
|
||
|
*/
|
||
|
protected $errorfiles = array();
|
||
|
|
||
|
/**
|
||
|
* Translations
|
||
|
* @var array
|
||
|
* @since 11.1
|
||
|
*/
|
||
|
protected $strings = null;
|
||
|
|
||
|
/**
|
||
|
* An array of used text, used during debugging.
|
||
|
* @var array
|
||
|
* @since 11.1
|
||
|
*/
|
||
|
protected $used = array();
|
||
|
|
||
|
/**
|
||
|
* Counter for number of loads.
|
||
|
* @var integer
|
||
|
* @since 11.1
|
||
|
*/
|
||
|
protected $counter = 0;
|
||
|
|
||
|
/**
|
||
|
* An array used to store overrides.
|
||
|
* @var array
|
||
|
* @since 11.1
|
||
|
*/
|
||
|
protected $override = array();
|
||
|
|
||
|
/**
|
||
|
* Name of the transliterator function for this language.
|
||
|
* @var string
|
||
|
* @since 11.1
|
||
|
*/
|
||
|
protected $transliterator = null;
|
||
|
|
||
|
/**
|
||
|
* Name of the pluralSuffixesCallback function for this language.
|
||
|
* @var string
|
||
|
* @since 11.1
|
||
|
*/
|
||
|
protected $pluralSuffixesCallback = null;
|
||
|
|
||
|
/**
|
||
|
* Name of the ignoredSearchWordsCallback function for this language.
|
||
|
* @var string
|
||
|
* @since 11.1
|
||
|
*/
|
||
|
protected $ignoredSearchWordsCallback = null;
|
||
|
|
||
|
/**
|
||
|
* Name of the lowerLimitSearchWordCallback function for this language.
|
||
|
* @var string
|
||
|
* @since 11.1
|
||
|
*/
|
||
|
protected $lowerLimitSearchWordCallback = null;
|
||
|
|
||
|
/**
|
||
|
* Name of the uppperLimitSearchWordCallback function for this language
|
||
|
* @var string
|
||
|
* @since 11.1
|
||
|
*/
|
||
|
protected $upperLimitSearchWordCallback = null;
|
||
|
|
||
|
/**
|
||
|
* Name of the searchDisplayedCharactersNumberCallback function for this language.
|
||
|
* @var string
|
||
|
* @since 11.1
|
||
|
*/
|
||
|
protected $searchDisplayedCharactersNumberCallback = null;
|
||
|
|
||
|
/**
|
||
|
* Constructor activating the default information of the language.
|
||
|
*
|
||
|
* @param string $lang The language
|
||
|
* @param boolean $debug Indicates if language debugging is enabled.
|
||
|
*
|
||
|
* @since 11.1
|
||
|
*/
|
||
|
public function __construct($lang = null, $debug = false)
|
||
|
{
|
||
|
$this->strings = array();
|
||
|
|
||
|
if ($lang == null)
|
||
|
{
|
||
|
$lang = $this->default;
|
||
|
}
|
||
|
|
||
|
$this->setLanguage($lang);
|
||
|
$this->setDebug($debug);
|
||
|
|
||
|
$filename = JPATH_BASE . "/language/overrides/$lang.override.ini";
|
||
|
|
||
|
if (file_exists($filename) && $contents = $this->parse($filename))
|
||
|
{
|
||
|
if (is_array($contents))
|
||
|
{
|
||
|
// Sort the underlying heap by key values to optimize merging
|
||
|
ksort($contents, SORT_STRING);
|
||
|
$this->override = $contents;
|
||
|
}
|
||
|
unset($contents);
|
||
|
}
|
||
|
|
||
|
// Look for a language specific localise class
|
||
|
$class = str_replace('-', '_', $lang . 'Localise');
|
||
|
$paths = array();
|
||
|
if (defined('JPATH_SITE'))
|
||
|
{
|
||
|
// Note: Manual indexing to enforce load order.
|
||
|
$paths[0] = JPATH_SITE . "/language/overrides/$lang.localise.php";
|
||
|
$paths[2] = JPATH_SITE . "/language/$lang/$lang.localise.php";
|
||
|
}
|
||
|
|
||
|
if (defined('JPATH_ADMINISTRATOR'))
|
||
|
{
|
||
|
// Note: Manual indexing to enforce load order.
|
||
|
$paths[1] = JPATH_ADMINISTRATOR . "/language/overrides/$lang.localise.php";
|
||
|
$paths[3] = JPATH_ADMINISTRATOR . "/language/$lang/$lang.localise.php";
|
||
|
}
|
||
|
|
||
|
ksort($paths);
|
||
|
$path = reset($paths);
|
||
|
|
||
|
while (!class_exists($class) && $path)
|
||
|
{
|
||
|
if (file_exists($path))
|
||
|
{
|
||
|
require_once $path;
|
||
|
}
|
||
|
$path = next($paths);
|
||
|
}
|
||
|
|
||
|
if (class_exists($class))
|
||
|
{
|
||
|
/* Class exists. Try to find
|
||
|
* -a transliterate method,
|
||
|
* -a getPluralSuffixes method,
|
||
|
* -a getIgnoredSearchWords method
|
||
|
* -a getLowerLimitSearchWord method
|
||
|
* -a getUpperLimitSearchWord method
|
||
|
* -a getSearchDisplayCharactersNumber method
|
||
|
*/
|
||
|
if (method_exists($class, 'transliterate'))
|
||
|
{
|
||
|
$this->transliterator = array($class, 'transliterate');
|
||
|
}
|
||
|
|
||
|
if (method_exists($class, 'getPluralSuffixes'))
|
||
|
{
|
||
|
$this->pluralSuffixesCallback = array($class, 'getPluralSuffixes');
|
||
|
}
|
||
|
|
||
|
if (method_exists($class, 'getIgnoredSearchWords'))
|
||
|
{
|
||
|
$this->ignoredSearchWordsCallback = array($class, 'getIgnoredSearchWords');
|
||
|
}
|
||
|
|
||
|
if (method_exists($class, 'getLowerLimitSearchWord'))
|
||
|
{
|
||
|
$this->lowerLimitSearchWordCallback = array($class, 'getLowerLimitSearchWord');
|
||
|
}
|
||
|
|
||
|
if (method_exists($class, 'getUpperLimitSearchWord'))
|
||
|
{
|
||
|
$this->upperLimitSearchWordCallback = array($class, 'getUpperLimitSearchWord');
|
||
|
}
|
||
|
|
||
|
if (method_exists($class, 'getSearchDisplayedCharactersNumber'))
|
||
|
{
|
||
|
$this->searchDisplayedCharactersNumberCallback = array($class, 'getSearchDisplayedCharactersNumber');
|
||
|
}
|
||
|
}
|
||
|
|
||
|
$this->load();
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Returns a language object.
|
||
|
*
|
||
|
* @param string $lang The language to use.
|
||
|
* @param boolean $debug The debug mode.
|
||
|
*
|
||
|
* @return JLanguage The Language object.
|
||
|
*
|
||
|
* @since 11.1
|
||
|
*/
|
||
|
public static function getInstance($lang, $debug = false)
|
||
|
{
|
||
|
if (!isset(self::$languages[$lang . $debug]))
|
||
|
{
|
||
|
self::$languages[$lang . $debug] = new JLanguage($lang, $debug);
|
||
|
}
|
||
|
|
||
|
return self::$languages[$lang . $debug];
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Translate function, mimics the php gettext (alias _) function.
|
||
|
*
|
||
|
* The function checks if $jsSafe is true, then if $interpretBackslashes is true.
|
||
|
*
|
||
|
* @param string $string The string to translate
|
||
|
* @param boolean $jsSafe Make the result javascript safe
|
||
|
* @param boolean $interpretBackSlashes Interpret \t and \n
|
||
|
*
|
||
|
* @return string The translation of the string
|
||
|
*
|
||
|
* @since 11.1
|
||
|
*/
|
||
|
public function _($string, $jsSafe = false, $interpretBackSlashes = true)
|
||
|
{
|
||
|
// Detect empty string
|
||
|
if ($string == '')
|
||
|
{
|
||
|
return '';
|
||
|
}
|
||
|
|
||
|
$key = strtoupper($string);
|
||
|
|
||
|
if (isset($this->strings[$key]))
|
||
|
{
|
||
|
$string = $this->debug ? '**' . $this->strings[$key] . '**' : $this->strings[$key];
|
||
|
|
||
|
// Store debug information
|
||
|
if ($this->debug)
|
||
|
{
|
||
|
$caller = $this->getCallerInfo();
|
||
|
|
||
|
if (!array_key_exists($key, $this->used))
|
||
|
{
|
||
|
$this->used[$key] = array();
|
||
|
}
|
||
|
|
||
|
$this->used[$key][] = $caller;
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
if ($this->debug)
|
||
|
{
|
||
|
$caller = $this->getCallerInfo();
|
||
|
$caller['string'] = $string;
|
||
|
|
||
|
if (!array_key_exists($key, $this->orphans))
|
||
|
{
|
||
|
$this->orphans[$key] = array();
|
||
|
}
|
||
|
|
||
|
$this->orphans[$key][] = $caller;
|
||
|
|
||
|
$string = '??' . $string . '??';
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if ($jsSafe)
|
||
|
{
|
||
|
// Javascript filter
|
||
|
$string = addslashes($string);
|
||
|
}
|
||
|
elseif ($interpretBackSlashes)
|
||
|
{
|
||
|
// Interpret \n and \t characters
|
||
|
$string = str_replace(array('\\\\', '\t', '\n'), array("\\", "\t", "\n"), $string);
|
||
|
}
|
||
|
|
||
|
return $string;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Transliterate function
|
||
|
*
|
||
|
* This method processes a string and replaces all accented UTF-8 characters by unaccented
|
||
|
* ASCII-7 "equivalents".
|
||
|
*
|
||
|
* @param string $string The string to transliterate.
|
||
|
*
|
||
|
* @return string The transliteration of the string.
|
||
|
*
|
||
|
* @since 11.1
|
||
|
*/
|
||
|
public function transliterate($string)
|
||
|
{
|
||
|
if ($this->transliterator !== null)
|
||
|
{
|
||
|
return call_user_func($this->transliterator, $string);
|
||
|
}
|
||
|
|
||
|
$string = JLanguageTransliterate::utf8_latin_to_ascii($string);
|
||
|
$string = JString::strtolower($string);
|
||
|
|
||
|
return $string;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Getter for transliteration function
|
||
|
*
|
||
|
* @return callable The transliterator function
|
||
|
*
|
||
|
* @since 11.1
|
||
|
*/
|
||
|
public function getTransliterator()
|
||
|
{
|
||
|
return $this->transliterator;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Set the transliteration function.
|
||
|
*
|
||
|
* @param callable $function Function name or the actual function.
|
||
|
*
|
||
|
* @return callable The previous function.
|
||
|
*
|
||
|
* @since 11.1
|
||
|
*/
|
||
|
public function setTransliterator($function)
|
||
|
{
|
||
|
$previous = $this->transliterator;
|
||
|
$this->transliterator = $function;
|
||
|
|
||
|
return $previous;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Returns an array of suffixes for plural rules.
|
||
|
*
|
||
|
* @param integer $count The count number the rule is for.
|
||
|
*
|
||
|
* @return array The array of suffixes.
|
||
|
*
|
||
|
* @since 11.1
|
||
|
*/
|
||
|
public function getPluralSuffixes($count)
|
||
|
{
|
||
|
if ($this->pluralSuffixesCallback !== null)
|
||
|
{
|
||
|
return call_user_func($this->pluralSuffixesCallback, $count);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
return array((string) $count);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Getter for pluralSuffixesCallback function.
|
||
|
*
|
||
|
* @return callable Function name or the actual function.
|
||
|
*
|
||
|
* @since 11.1
|
||
|
*/
|
||
|
public function getPluralSuffixesCallback()
|
||
|
{
|
||
|
return $this->pluralSuffixesCallback;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Set the pluralSuffixes function.
|
||
|
*
|
||
|
* @param callable $function Function name or actual function.
|
||
|
*
|
||
|
* @return callable The previous function.
|
||
|
*
|
||
|
* @since 11.1
|
||
|
*/
|
||
|
public function setPluralSuffixesCallback($function)
|
||
|
{
|
||
|
$previous = $this->pluralSuffixesCallback;
|
||
|
$this->pluralSuffixesCallback = $function;
|
||
|
|
||
|
return $previous;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Returns an array of ignored search words
|
||
|
*
|
||
|
* @return array The array of ignored search words.
|
||
|
*
|
||
|
* @since 11.1
|
||
|
*/
|
||
|
public function getIgnoredSearchWords()
|
||
|
{
|
||
|
if ($this->ignoredSearchWordsCallback !== null)
|
||
|
{
|
||
|
return call_user_func($this->ignoredSearchWordsCallback);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
return array();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Getter for ignoredSearchWordsCallback function.
|
||
|
*
|
||
|
* @return callable Function name or the actual function.
|
||
|
*
|
||
|
* @since 11.1
|
||
|
*/
|
||
|
public function getIgnoredSearchWordsCallback()
|
||
|
{
|
||
|
return $this->ignoredSearchWordsCallback;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Setter for the ignoredSearchWordsCallback function
|
||
|
*
|
||
|
* @param callable $function Function name or actual function.
|
||
|
*
|
||
|
* @return callable The previous function.
|
||
|
*
|
||
|
* @since 11.1
|
||
|
*/
|
||
|
public function setIgnoredSearchWordsCallback($function)
|
||
|
{
|
||
|
$previous = $this->ignoredSearchWordsCallback;
|
||
|
$this->ignoredSearchWordsCallback = $function;
|
||
|
|
||
|
return $previous;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Returns a lower limit integer for length of search words
|
||
|
*
|
||
|
* @return integer The lower limit integer for length of search words (3 if no value was set for a specific language).
|
||
|
*
|
||
|
* @since 11.1
|
||
|
*/
|
||
|
public function getLowerLimitSearchWord()
|
||
|
{
|
||
|
if ($this->lowerLimitSearchWordCallback !== null)
|
||
|
{
|
||
|
return call_user_func($this->lowerLimitSearchWordCallback);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
return 3;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Getter for lowerLimitSearchWordCallback function
|
||
|
*
|
||
|
* @return callable Function name or the actual function.
|
||
|
*
|
||
|
* @since 11.1
|
||
|
*/
|
||
|
public function getLowerLimitSearchWordCallback()
|
||
|
{
|
||
|
return $this->lowerLimitSearchWordCallback;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Setter for the lowerLimitSearchWordCallback function.
|
||
|
*
|
||
|
* @param callable $function Function name or actual function.
|
||
|
*
|
||
|
* @return callable The previous function.
|
||
|
*
|
||
|
* @since 11.1
|
||
|
*/
|
||
|
public function setLowerLimitSearchWordCallback($function)
|
||
|
{
|
||
|
$previous = $this->lowerLimitSearchWordCallback;
|
||
|
$this->lowerLimitSearchWordCallback = $function;
|
||
|
|
||
|
return $previous;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Returns an upper limit integer for length of search words
|
||
|
*
|
||
|
* @return integer The upper limit integer for length of search words (20 if no value was set for a specific language).
|
||
|
*
|
||
|
* @since 11.1
|
||
|
*/
|
||
|
public function getUpperLimitSearchWord()
|
||
|
{
|
||
|
if ($this->upperLimitSearchWordCallback !== null)
|
||
|
{
|
||
|
return call_user_func($this->upperLimitSearchWordCallback);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
return 20;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Getter for upperLimitSearchWordCallback function
|
||
|
*
|
||
|
* @return callable Function name or the actual function.
|
||
|
*
|
||
|
* @since 11.1
|
||
|
*/
|
||
|
public function getUpperLimitSearchWordCallback()
|
||
|
{
|
||
|
return $this->upperLimitSearchWordCallback;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Setter for the upperLimitSearchWordCallback function
|
||
|
*
|
||
|
* @param callable $function Function name or the actual function.
|
||
|
*
|
||
|
* @return callable The previous function.
|
||
|
*
|
||
|
* @since 11.1
|
||
|
*/
|
||
|
public function setUpperLimitSearchWordCallback($function)
|
||
|
{
|
||
|
$previous = $this->upperLimitSearchWordCallback;
|
||
|
$this->upperLimitSearchWordCallback = $function;
|
||
|
|
||
|
return $previous;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Returns the number of characters displayed in search results.
|
||
|
*
|
||
|
* @return integer The number of characters displayed (200 if no value was set for a specific language).
|
||
|
*
|
||
|
* @since 11.1
|
||
|
*/
|
||
|
public function getSearchDisplayedCharactersNumber()
|
||
|
{
|
||
|
if ($this->searchDisplayedCharactersNumberCallback !== null)
|
||
|
{
|
||
|
return call_user_func($this->searchDisplayedCharactersNumberCallback);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
return 200;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Getter for searchDisplayedCharactersNumberCallback function
|
||
|
*
|
||
|
* @return callable Function name or the actual function.
|
||
|
*
|
||
|
* @since 11.1
|
||
|
*/
|
||
|
public function getSearchDisplayedCharactersNumberCallback()
|
||
|
{
|
||
|
return $this->searchDisplayedCharactersNumberCallback;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Setter for the searchDisplayedCharactersNumberCallback function.
|
||
|
*
|
||
|
* @param callable $function Function name or the actual function.
|
||
|
*
|
||
|
* @return callable The previous function.
|
||
|
*
|
||
|
* @since 11.1
|
||
|
*/
|
||
|
public function setSearchDisplayedCharactersNumberCallback($function)
|
||
|
{
|
||
|
$previous = $this->searchDisplayedCharactersNumberCallback;
|
||
|
$this->searchDisplayedCharactersNumberCallback = $function;
|
||
|
|
||
|
return $previous;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Checks if a language exists.
|
||
|
*
|
||
|
* This is a simple, quick check for the directory that should contain language files for the given user.
|
||
|
*
|
||
|
* @param string $lang Language to check.
|
||
|
* @param string $basePath Optional path to check.
|
||
|
*
|
||
|
* @return boolean True if the language exists.
|
||
|
*
|
||
|
* @since 11.1
|
||
|
*/
|
||
|
public static function exists($lang, $basePath = JPATH_BASE)
|
||
|
{
|
||
|
static $paths = array();
|
||
|
|
||
|
// Return false if no language was specified
|
||
|
if (!$lang)
|
||
|
{
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
$path = $basePath . '/language/' . $lang;
|
||
|
|
||
|
// Return previous check results if it exists
|
||
|
if (isset($paths[$path]))
|
||
|
{
|
||
|
return $paths[$path];
|
||
|
}
|
||
|
|
||
|
// Check if the language exists
|
||
|
$paths[$path] = is_dir($path);
|
||
|
|
||
|
return $paths[$path];
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Loads a single language file and appends the results to the existing strings
|
||
|
*
|
||
|
* @param string $extension The extension for which a language file should be loaded.
|
||
|
* @param string $basePath The basepath to use.
|
||
|
* @param string $lang The language to load, default null for the current language.
|
||
|
* @param boolean $reload Flag that will force a language to be reloaded if set to true.
|
||
|
* @param boolean $default Flag that force the default language to be loaded if the current does not exist.
|
||
|
*
|
||
|
* @return boolean True if the file has successfully loaded.
|
||
|
*
|
||
|
* @since 11.1
|
||
|
*/
|
||
|
public function load($extension = 'joomla', $basePath = JPATH_BASE, $lang = null, $reload = false, $default = true)
|
||
|
{
|
||
|
if (!$lang)
|
||
|
{
|
||
|
$lang = $this->lang;
|
||
|
}
|
||
|
|
||
|
$path = self::getLanguagePath($basePath, $lang);
|
||
|
|
||
|
$internal = $extension == 'joomla' || $extension == '';
|
||
|
$filename = $internal ? $lang : $lang . '.' . $extension;
|
||
|
$filename = "$path/$filename.ini";
|
||
|
|
||
|
if (isset($this->paths[$extension][$filename]) && !$reload)
|
||
|
{
|
||
|
// This file has already been tested for loading.
|
||
|
$result = $this->paths[$extension][$filename];
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
// Load the language file
|
||
|
$result = $this->loadLanguage($filename, $extension);
|
||
|
|
||
|
// Check whether there was a problem with loading the file
|
||
|
if ($result === false && $default)
|
||
|
{
|
||
|
// No strings, so either file doesn't exist or the file is invalid
|
||
|
$oldFilename = $filename;
|
||
|
|
||
|
// Check the standard file name
|
||
|
$path = self::getLanguagePath($basePath, $this->default);
|
||
|
$filename = $internal ? $this->default : $this->default . '.' . $extension;
|
||
|
$filename = "$path/$filename.ini";
|
||
|
|
||
|
// If the one we tried is different than the new name, try again
|
||
|
if ($oldFilename != $filename)
|
||
|
{
|
||
|
$result = $this->loadLanguage($filename, $extension, false);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return $result;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Loads a language file.
|
||
|
*
|
||
|
* This method will not note the successful loading of a file - use load() instead.
|
||
|
*
|
||
|
* @param string $filename The name of the file.
|
||
|
* @param string $extension The name of the extension.
|
||
|
*
|
||
|
* @return boolean True if new strings have been added to the language
|
||
|
*
|
||
|
* @see JLanguage::load()
|
||
|
* @since 11.1
|
||
|
*/
|
||
|
protected function loadLanguage($filename, $extension = 'unknown')
|
||
|
{
|
||
|
$this->counter++;
|
||
|
|
||
|
$result = false;
|
||
|
$strings = false;
|
||
|
|
||
|
if (file_exists($filename))
|
||
|
{
|
||
|
$strings = $this->parse($filename);
|
||
|
}
|
||
|
|
||
|
if ($strings)
|
||
|
{
|
||
|
if (is_array($strings))
|
||
|
{
|
||
|
// Sort the underlying heap by key values to optimize merging
|
||
|
ksort($strings, SORT_STRING);
|
||
|
$this->strings = array_merge($this->strings, $strings);
|
||
|
}
|
||
|
|
||
|
if (is_array($strings) && count($strings))
|
||
|
{
|
||
|
// Do not bother with ksort here. Since the originals were sorted, PHP will already have chosen the best heap.
|
||
|
$this->strings = array_merge($this->strings, $this->override);
|
||
|
$result = true;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Record the result of loading the extension's file.
|
||
|
if (!isset($this->paths[$extension]))
|
||
|
{
|
||
|
$this->paths[$extension] = array();
|
||
|
}
|
||
|
|
||
|
$this->paths[$extension][$filename] = $result;
|
||
|
|
||
|
return $result;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Parses a language file.
|
||
|
*
|
||
|
* @param string $filename The name of the file.
|
||
|
*
|
||
|
* @return array The array of parsed strings.
|
||
|
*
|
||
|
* @since 11.1
|
||
|
*/
|
||
|
protected function parse($filename)
|
||
|
{
|
||
|
if ($this->debug)
|
||
|
{
|
||
|
// Capture hidden PHP errors from the parsing.
|
||
|
$php_errormsg = null;
|
||
|
$track_errors = ini_get('track_errors');
|
||
|
ini_set('track_errors', true);
|
||
|
}
|
||
|
|
||
|
$contents = file_get_contents($filename);
|
||
|
$contents = str_replace('_QQ_', '"\""', $contents);
|
||
|
$strings = @parse_ini_string($contents);
|
||
|
|
||
|
if (!is_array($strings))
|
||
|
{
|
||
|
$strings = array();
|
||
|
}
|
||
|
|
||
|
if ($this->debug)
|
||
|
{
|
||
|
// Restore error tracking to what it was before.
|
||
|
ini_set('track_errors', $track_errors);
|
||
|
|
||
|
// Initialise variables for manually parsing the file for common errors.
|
||
|
$blacklist = array('YES', 'NO', 'NULL', 'FALSE', 'ON', 'OFF', 'NONE', 'TRUE');
|
||
|
$regex = '/^(|(\[[^\]]*\])|([A-Z][A-Z0-9_\-\.]*\s*=(\s*(("[^"]*")|(_QQ_)))+))\s*(;.*)?$/';
|
||
|
$this->debug = false;
|
||
|
$errors = array();
|
||
|
|
||
|
// Open the file as a stream.
|
||
|
$file = new SplFileObject($filename);
|
||
|
|
||
|
foreach ($file as $lineNumber => $line)
|
||
|
{
|
||
|
// Avoid BOM error as BOM is OK when using parse_ini
|
||
|
if ($lineNumber == 0)
|
||
|
{
|
||
|
$line = str_replace("\xEF\xBB\xBF", '', $line);
|
||
|
}
|
||
|
|
||
|
// Check that the key is not in the blacklist and that the line format passes the regex.
|
||
|
$key = strtoupper(trim(substr($line, 0, strpos($line, '='))));
|
||
|
|
||
|
// Workaround to reduce regex complexity when matching escaped quotes
|
||
|
$line = str_replace('\"', '_QQ_', $line);
|
||
|
|
||
|
if (!preg_match($regex, $line) || in_array($key, $blacklist))
|
||
|
{
|
||
|
$errors[] = $lineNumber;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Check if we encountered any errors.
|
||
|
if (count($errors))
|
||
|
{
|
||
|
if (basename($filename) != $this->lang . '.ini')
|
||
|
{
|
||
|
$this->errorfiles[$filename] = $filename . JText::sprintf('JERROR_PARSING_LANGUAGE_FILE', implode(', ', $errors));
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
$this->errorfiles[$filename] = $filename . ' : error(s) in line(s) ' . implode(', ', $errors);
|
||
|
}
|
||
|
}
|
||
|
elseif ($php_errormsg)
|
||
|
{
|
||
|
// We didn't find any errors but there's probably a parse notice.
|
||
|
$this->errorfiles['PHP' . $filename] = 'PHP parser errors :' . $php_errormsg;
|
||
|
}
|
||
|
|
||
|
$this->debug = true;
|
||
|
}
|
||
|
|
||
|
return $strings;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Get a metadata language property.
|
||
|
*
|
||
|
* @param string $property The name of the property.
|
||
|
* @param mixed $default The default value.
|
||
|
*
|
||
|
* @return mixed The value of the property.
|
||
|
*
|
||
|
* @since 11.1
|
||
|
*/
|
||
|
public function get($property, $default = null)
|
||
|
{
|
||
|
if (isset($this->metadata[$property]))
|
||
|
{
|
||
|
return $this->metadata[$property];
|
||
|
}
|
||
|
|
||
|
return $default;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Determine who called JLanguage or JText.
|
||
|
*
|
||
|
* @return array Caller information.
|
||
|
*
|
||
|
* @since 11.1
|
||
|
*/
|
||
|
protected function getCallerInfo()
|
||
|
{
|
||
|
// Try to determine the source if none was provided
|
||
|
if (!function_exists('debug_backtrace'))
|
||
|
{
|
||
|
return null;
|
||
|
}
|
||
|
|
||
|
$backtrace = debug_backtrace();
|
||
|
$info = array();
|
||
|
|
||
|
// Search through the backtrace to our caller
|
||
|
$continue = true;
|
||
|
while ($continue && next($backtrace))
|
||
|
{
|
||
|
$step = current($backtrace);
|
||
|
$class = @ $step['class'];
|
||
|
|
||
|
// We're looking for something outside of language.php
|
||
|
if ($class != 'JLanguage' && $class != 'JText')
|
||
|
{
|
||
|
$info['function'] = @ $step['function'];
|
||
|
$info['class'] = $class;
|
||
|
$info['step'] = prev($backtrace);
|
||
|
|
||
|
// Determine the file and name of the file
|
||
|
$info['file'] = @ $step['file'];
|
||
|
$info['line'] = @ $step['line'];
|
||
|
|
||
|
$continue = false;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return $info;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Getter for Name.
|
||
|
*
|
||
|
* @return string Official name element of the language.
|
||
|
*
|
||
|
* @since 11.1
|
||
|
*/
|
||
|
public function getName()
|
||
|
{
|
||
|
return $this->metadata['name'];
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Get a list of language files that have been loaded.
|
||
|
*
|
||
|
* @param string $extension An optional extension name.
|
||
|
*
|
||
|
* @return array
|
||
|
*
|
||
|
* @since 11.1
|
||
|
*/
|
||
|
public function getPaths($extension = null)
|
||
|
{
|
||
|
if (isset($extension))
|
||
|
{
|
||
|
if (isset($this->paths[$extension]))
|
||
|
{
|
||
|
return $this->paths[$extension];
|
||
|
}
|
||
|
|
||
|
return null;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
return $this->paths;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Get a list of language files that are in error state.
|
||
|
*
|
||
|
* @return array
|
||
|
*
|
||
|
* @since 11.1
|
||
|
*/
|
||
|
public function getErrorFiles()
|
||
|
{
|
||
|
return $this->errorfiles;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Getter for the language tag (as defined in RFC 3066)
|
||
|
*
|
||
|
* @return string The language tag.
|
||
|
*
|
||
|
* @since 11.1
|
||
|
*/
|
||
|
public function getTag()
|
||
|
{
|
||
|
return $this->metadata['tag'];
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Get the RTL property.
|
||
|
*
|
||
|
* @return boolean True is it an RTL language.
|
||
|
*
|
||
|
* @since 11.1
|
||
|
*/
|
||
|
public function isRTL()
|
||
|
{
|
||
|
return (bool) $this->metadata['rtl'];
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Set the Debug property.
|
||
|
*
|
||
|
* @param boolean $debug The debug setting.
|
||
|
*
|
||
|
* @return boolean Previous value.
|
||
|
*
|
||
|
* @since 11.1
|
||
|
*/
|
||
|
public function setDebug($debug)
|
||
|
{
|
||
|
$previous = $this->debug;
|
||
|
$this->debug = (boolean) $debug;
|
||
|
|
||
|
return $previous;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Get the Debug property.
|
||
|
*
|
||
|
* @return boolean True is in debug mode.
|
||
|
*
|
||
|
* @since 11.1
|
||
|
*/
|
||
|
public function getDebug()
|
||
|
{
|
||
|
return $this->debug;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Get the default language code.
|
||
|
*
|
||
|
* @return string Language code.
|
||
|
*
|
||
|
* @since 11.1
|
||
|
*/
|
||
|
public function getDefault()
|
||
|
{
|
||
|
return $this->default;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Set the default language code.
|
||
|
*
|
||
|
* @param string $lang The language code.
|
||
|
*
|
||
|
* @return string Previous value.
|
||
|
*
|
||
|
* @since 11.1
|
||
|
*/
|
||
|
public function setDefault($lang)
|
||
|
{
|
||
|
$previous = $this->default;
|
||
|
$this->default = $lang;
|
||
|
|
||
|
return $previous;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Get the list of orphaned strings if being tracked.
|
||
|
*
|
||
|
* @return array Orphaned text.
|
||
|
*
|
||
|
* @since 11.1
|
||
|
*/
|
||
|
public function getOrphans()
|
||
|
{
|
||
|
return $this->orphans;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Get the list of used strings.
|
||
|
*
|
||
|
* Used strings are those strings requested and found either as a string or a constant.
|
||
|
*
|
||
|
* @return array Used strings.
|
||
|
*
|
||
|
* @since 11.1
|
||
|
*/
|
||
|
public function getUsed()
|
||
|
{
|
||
|
return $this->used;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Determines is a key exists.
|
||
|
*
|
||
|
* @param string $string The key to check.
|
||
|
*
|
||
|
* @return boolean True, if the key exists.
|
||
|
*
|
||
|
* @since 11.1
|
||
|
*/
|
||
|
public function hasKey($string)
|
||
|
{
|
||
|
$key = strtoupper($string);
|
||
|
|
||
|
return isset($this->strings[$key]);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Returns a associative array holding the metadata.
|
||
|
*
|
||
|
* @param string $lang The name of the language.
|
||
|
*
|
||
|
* @return mixed If $lang exists return key/value pair with the language metadata, otherwise return NULL.
|
||
|
*
|
||
|
* @since 11.1
|
||
|
*/
|
||
|
public static function getMetadata($lang)
|
||
|
{
|
||
|
$path = self::getLanguagePath(JPATH_BASE, $lang);
|
||
|
$file = $lang . '.xml';
|
||
|
|
||
|
$result = null;
|
||
|
|
||
|
if (is_file("$path/$file"))
|
||
|
{
|
||
|
$result = self::parseXMLLanguageFile("$path/$file");
|
||
|
}
|
||
|
|
||
|
if (empty($result))
|
||
|
{
|
||
|
return null;
|
||
|
}
|
||
|
|
||
|
return $result;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Returns a list of known languages for an area
|
||
|
*
|
||
|
* @param string $basePath The basepath to use
|
||
|
*
|
||
|
* @return array key/value pair with the language file and real name.
|
||
|
*
|
||
|
* @since 11.1
|
||
|
*/
|
||
|
public static function getKnownLanguages($basePath = JPATH_BASE)
|
||
|
{
|
||
|
$dir = self::getLanguagePath($basePath);
|
||
|
$knownLanguages = self::parseLanguageFiles($dir);
|
||
|
|
||
|
return $knownLanguages;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Get the path to a language
|
||
|
*
|
||
|
* @param string $basePath The basepath to use.
|
||
|
* @param string $language The language tag.
|
||
|
*
|
||
|
* @return string language related path or null.
|
||
|
*
|
||
|
* @since 11.1
|
||
|
*/
|
||
|
public static function getLanguagePath($basePath = JPATH_BASE, $language = null)
|
||
|
{
|
||
|
$dir = $basePath . '/language';
|
||
|
|
||
|
if (!empty($language))
|
||
|
{
|
||
|
$dir .= '/' . $language;
|
||
|
}
|
||
|
|
||
|
return $dir;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Set the language attributes to the given language.
|
||
|
*
|
||
|
* Once called, the language still needs to be loaded using JLanguage::load().
|
||
|
*
|
||
|
* @param string $lang Language code.
|
||
|
*
|
||
|
* @return string Previous value.
|
||
|
*
|
||
|
* @since 11.1
|
||
|
*/
|
||
|
public function setLanguage($lang)
|
||
|
{
|
||
|
$previous = $this->lang;
|
||
|
$this->lang = $lang;
|
||
|
$this->metadata = $this->getMetadata($this->lang);
|
||
|
|
||
|
return $previous;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Get the language locale based on current language.
|
||
|
*
|
||
|
* @return array The locale according to the language.
|
||
|
*
|
||
|
* @since 11.1
|
||
|
*/
|
||
|
public function getLocale()
|
||
|
{
|
||
|
if (!isset($this->locale))
|
||
|
{
|
||
|
$locale = str_replace(' ', '', isset($this->metadata['locale']) ? $this->metadata['locale'] : '');
|
||
|
|
||
|
if ($locale)
|
||
|
{
|
||
|
$this->locale = explode(',', $locale);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
$this->locale = false;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return $this->locale;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Get the first day of the week for this language.
|
||
|
*
|
||
|
* @return integer The first day of the week according to the language
|
||
|
*
|
||
|
* @since 11.1
|
||
|
*/
|
||
|
public function getFirstDay()
|
||
|
{
|
||
|
return (int) (isset($this->metadata['firstDay']) ? $this->metadata['firstDay'] : 0);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Searches for language directories within a certain base dir.
|
||
|
*
|
||
|
* @param string $dir directory of files.
|
||
|
*
|
||
|
* @return array Array holding the found languages as filename => real name pairs.
|
||
|
*
|
||
|
* @since 11.1
|
||
|
*/
|
||
|
public static function parseLanguageFiles($dir = null)
|
||
|
{
|
||
|
$languages = array();
|
||
|
|
||
|
$iterator = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($dir));
|
||
|
|
||
|
foreach ($iterator as $file)
|
||
|
{
|
||
|
$langs = array();
|
||
|
$fileName = $file->getFilename();
|
||
|
|
||
|
if (!$file->isFile() || !preg_match("/^([-_A-Za-z]*)\.xml$/", $fileName))
|
||
|
{
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
try
|
||
|
{
|
||
|
$metadata = self::parseXMLLanguageFile($file->getRealPath());
|
||
|
if ($metadata)
|
||
|
{
|
||
|
$lang = str_replace('.xml', '', $fileName);
|
||
|
$langs[$lang] = $metadata;
|
||
|
}
|
||
|
$languages = array_merge($languages, $langs);
|
||
|
}
|
||
|
catch (RuntimeException $e)
|
||
|
{
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return $languages;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Parse XML file for language information.
|
||
|
*
|
||
|
* @param string $path Path to the XML files.
|
||
|
*
|
||
|
* @return array Array holding the found metadata as a key => value pair.
|
||
|
*
|
||
|
* @since 11.1
|
||
|
* @throws RuntimeException
|
||
|
*/
|
||
|
public static function parseXMLLanguageFile($path)
|
||
|
{
|
||
|
if (!is_readable($path))
|
||
|
{
|
||
|
throw new RuntimeException('File not found or not readable');
|
||
|
}
|
||
|
|
||
|
// Try to load the file
|
||
|
$xml = simplexml_load_file($path);
|
||
|
if (!$xml)
|
||
|
{
|
||
|
return null;
|
||
|
}
|
||
|
|
||
|
// Check that it's a metadata file
|
||
|
if ((string) $xml->getName() != 'metafile')
|
||
|
{
|
||
|
return null;
|
||
|
}
|
||
|
|
||
|
$metadata = array();
|
||
|
|
||
|
foreach ($xml->metadata->children() as $child)
|
||
|
{
|
||
|
$metadata[$child->getName()] = (string) $child;
|
||
|
}
|
||
|
|
||
|
return $metadata;
|
||
|
}
|
||
|
}
|