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; } }