Current File : /home/pacjaorg/public_html/km/libraries/src/Language/LanguageHelper.php |
<?php
/**
* Joomla! Content Management System
*
* @copyright (C) 2007 Open Source Matters, Inc. <https://www.joomla.org>
* @license GNU General Public License version 2 or later; see LICENSE.txt
*/
namespace Joomla\CMS\Language;
use Joomla\CMS\Cache\CacheControllerFactoryInterface;
use Joomla\CMS\Cache\Controller\OutputController;
use Joomla\CMS\Factory;
use Joomla\CMS\Installer\Installer;
use Joomla\CMS\Log\Log;
use Joomla\Filesystem\File;
use Joomla\Registry\Registry;
use Joomla\Utilities\ArrayHelper;
// phpcs:disable PSR1.Files.SideEffects
\defined('JPATH_PLATFORM') or die;
// phpcs:enable PSR1.Files.SideEffects
/**
* Language helper class
*
* @since 1.5
*/
class LanguageHelper
{
/**
* Builds a list of the system languages which can be used in a select option
*
* @param string $actualLanguage Client key for the area
* @param string $basePath Base path to use
* @param boolean $caching True if caching is used
* @param boolean $installed Get only installed languages
*
* @return array List of system languages
*
* @since 1.5
*/
public static function createLanguageList($actualLanguage, $basePath = JPATH_BASE, $caching = false, $installed = false)
{
$list = [];
$clientId = $basePath === JPATH_ADMINISTRATOR ? 1 : 0;
$languages = $installed ? static::getInstalledLanguages($clientId, true) : self::getKnownLanguages($basePath);
foreach ($languages as $languageCode => $language) {
$metadata = $installed ? $language->metadata : $language;
$list[] = [
'text' => $metadata['nativeName'] ?? $metadata['name'],
'value' => $languageCode,
'selected' => $languageCode === $actualLanguage ? 'selected="selected"' : null,
];
}
return $list;
}
/**
* Tries to detect the language.
*
* @return string locale or null if not found
*
* @since 1.5
*/
public static function detectLanguage()
{
if (isset($_SERVER['HTTP_ACCEPT_LANGUAGE'])) {
$browserLangs = explode(',', $_SERVER['HTTP_ACCEPT_LANGUAGE']);
$systemLangs = self::getLanguages();
foreach ($browserLangs as $browserLang) {
// Slice out the part before ; on first step, the part before - on second, place into array
$browserLang = substr($browserLang, 0, strcspn($browserLang, ';'));
$primary_browserLang = substr($browserLang, 0, 2);
foreach ($systemLangs as $systemLang) {
// Take off 3 letters iso code languages as they can't match browsers' languages and default them to en
$Jinstall_lang = $systemLang->lang_code;
if (\strlen($Jinstall_lang) < 6) {
if (strtolower($browserLang) == strtolower(substr($systemLang->lang_code, 0, \strlen($browserLang)))) {
return $systemLang->lang_code;
} elseif ($primary_browserLang == substr($systemLang->lang_code, 0, 2)) {
$primaryDetectedLang = $systemLang->lang_code;
}
}
}
if (isset($primaryDetectedLang)) {
return $primaryDetectedLang;
}
}
}
}
/**
* Get available languages
*
* @param string $key Array key
*
* @return array An array of published languages
*
* @since 1.6
*/
public static function getLanguages($key = 'default')
{
static $languages = [];
if (!count($languages)) {
// Installation uses available languages
if (Factory::getApplication()->isClient('installation')) {
$languages[$key] = [];
$knownLangs = self::getKnownLanguages(JPATH_BASE);
foreach ($knownLangs as $metadata) {
// Take off 3 letters iso code languages as they can't match browsers' languages and default them to en
$obj = new \stdClass();
$obj->lang_code = $metadata['tag'];
$languages[$key][] = $obj;
}
} else {
/** @var OutputController $cache */
$cache = Factory::getContainer()->get(CacheControllerFactoryInterface::class)
->createCacheController('output', ['defaultgroup' => 'com_languages']);
if ($cache->contains('languages')) {
$languages = $cache->get('languages');
} else {
$db = Factory::getDbo();
$query = $db->getQuery(true)
->select('*')
->from($db->quoteName('#__languages'))
->where($db->quoteName('published') . ' = 1')
->order($db->quoteName('ordering') . ' ASC');
$db->setQuery($query);
$languages['default'] = $db->loadObjectList();
$languages['sef'] = [];
$languages['lang_code'] = [];
if (isset($languages['default'][0])) {
foreach ($languages['default'] as $lang) {
$languages['sef'][$lang->sef] = $lang;
$languages['lang_code'][$lang->lang_code] = $lang;
}
}
$cache->store($languages, 'languages');
}
}
}
return $languages[$key];
}
/**
* Get a list of installed languages.
*
* @param integer $clientId The client app id.
* @param boolean $processMetaData Fetch Language metadata.
* @param boolean $processManifest Fetch Language manifest.
* @param string $pivot The pivot of the returning array.
* @param string $orderField Field to order the results.
* @param string $orderDirection Direction to order the results.
*
* @return array Array with the installed languages.
*
* @since 3.7.0
*/
public static function getInstalledLanguages(
$clientId = null,
$processMetaData = false,
$processManifest = false,
$pivot = 'element',
$orderField = null,
$orderDirection = null
) {
static $installedLanguages = null;
if ($installedLanguages === null) {
/** @var OutputController $cache */
$cache = Factory::getContainer()->get(CacheControllerFactoryInterface::class)
->createCacheController('output', ['defaultgroup' => 'com_languages']);
if ($cache->contains('installedlanguages')) {
$installedLanguages = $cache->get('installedlanguages');
} else {
$db = Factory::getDbo();
$query = $db->getQuery(true)
->select(
[
$db->quoteName('element'),
$db->quoteName('name'),
$db->quoteName('client_id'),
$db->quoteName('extension_id'),
]
)
->from($db->quoteName('#__extensions'))
->where(
[
$db->quoteName('type') . ' = ' . $db->quote('language'),
$db->quoteName('state') . ' = 0',
$db->quoteName('enabled') . ' = 1',
]
);
$installedLanguages = $db->setQuery($query)->loadObjectList();
$cache->store($installedLanguages, 'installedlanguages');
}
}
$clients = $clientId === null ? [0, 1] : [(int) $clientId];
$languages = [
0 => [],
1 => [],
];
foreach ($installedLanguages as $language) {
// If the language client is not needed continue cycle. Drop for performance.
if (!\in_array((int) $language->client_id, $clients)) {
continue;
}
$lang = $language;
if ($processMetaData || $processManifest) {
$clientPath = (int) $language->client_id === 0 ? JPATH_SITE : JPATH_ADMINISTRATOR;
$metafile = self::getLanguagePath($clientPath, $language->element) . '/langmetadata.xml';
if (!is_file($metafile)) {
$metafile = self::getLanguagePath($clientPath, $language->element) . '/' . $language->element . '.xml';
}
// Process the language metadata.
if ($processMetaData) {
try {
$lang->metadata = self::parseXMLLanguageFile($metafile);
} catch (\Exception $e) {
// Not able to process xml language file. Fail silently.
Log::add(Text::sprintf('JLIB_LANGUAGE_ERROR_CANNOT_LOAD_METAFILE', $language->element, $metafile), Log::WARNING, 'language');
continue;
}
// No metadata found, not a valid language. Fail silently.
if (!\is_array($lang->metadata)) {
Log::add(Text::sprintf('JLIB_LANGUAGE_ERROR_CANNOT_LOAD_METADATA', $language->element, $metafile), Log::WARNING, 'language');
continue;
}
}
// Process the language manifest.
if ($processManifest) {
try {
$lang->manifest = Installer::parseXMLInstallFile($metafile);
} catch (\Exception $e) {
// Not able to process xml language file. Fail silently.
Log::add(Text::sprintf('JLIB_LANGUAGE_ERROR_CANNOT_LOAD_METAFILE', $language->element, $metafile), Log::WARNING, 'language');
continue;
}
// No metadata found, not a valid language. Fail silently.
if (!\is_array($lang->manifest)) {
Log::add(Text::sprintf('JLIB_LANGUAGE_ERROR_CANNOT_LOAD_METADATA', $language->element, $metafile), Log::WARNING, 'language');
continue;
}
}
}
$languages[$language->client_id][] = $lang;
}
// Order the list, if needed.
if ($orderField !== null && $orderDirection !== null) {
$orderDirection = strtolower($orderDirection) === 'desc' ? -1 : 1;
foreach ($languages as $cId => $language) {
// If the language client is not needed continue cycle. Drop for performance.
if (!\in_array($cId, $clients)) {
continue;
}
$languages[$cId] = ArrayHelper::sortObjects($languages[$cId], $orderField, $orderDirection, true, true);
}
}
// Add the pivot, if needed.
if ($pivot !== null) {
foreach ($languages as $cId => $language) {
// If the language client is not needed continue cycle. Drop for performance.
if (!\in_array($cId, $clients)) {
continue;
}
$languages[$cId] = ArrayHelper::pivot($languages[$cId], $pivot);
}
}
return $clientId !== null ? $languages[$clientId] : $languages;
}
/**
* Get a list of content languages.
*
* @param array $publishedStates Array with the content language published states. Empty array for all.
* @param boolean $checkInstalled Check if the content language is installed.
* @param string $pivot The pivot of the returning array.
* @param string $orderField Field to order the results.
* @param string $orderDirection Direction to order the results.
*
* @return array Array of the content languages.
*
* @since 3.7.0
*/
public static function getContentLanguages(
$publishedStates = [1],
$checkInstalled = true,
$pivot = 'lang_code',
$orderField = null,
$orderDirection = null
) {
static $contentLanguages = null;
if ($contentLanguages === null) {
/** @var OutputController $cache */
$cache = Factory::getContainer()->get(CacheControllerFactoryInterface::class)
->createCacheController('output', ['defaultgroup' => 'com_languages']);
if ($cache->contains('contentlanguages')) {
$contentLanguages = $cache->get('contentlanguages');
} else {
$db = Factory::getDbo();
$query = $db->getQuery(true)
->select('*')
->from($db->quoteName('#__languages'));
$contentLanguages = $db->setQuery($query)->loadObjectList();
$cache->store($contentLanguages, 'contentlanguages');
}
}
$languages = $contentLanguages;
// B/C layer. Before 3.8.3.
if ($publishedStates === true) {
$publishedStates = [1];
} elseif ($publishedStates === false) {
$publishedStates = [];
}
// Check the language published state, if needed.
if (\count($publishedStates) > 0) {
foreach ($languages as $key => $language) {
if (!\in_array((int) $language->published, $publishedStates, true)) {
unset($languages[$key]);
}
}
}
// Check if the language is installed, if needed.
if ($checkInstalled) {
$languages = array_values(array_intersect_key(ArrayHelper::pivot($languages, 'lang_code'), static::getInstalledLanguages(0)));
}
// Order the list, if needed.
if ($orderField !== null && $orderDirection !== null) {
$languages = ArrayHelper::sortObjects($languages, $orderField, strtolower($orderDirection) === 'desc' ? -1 : 1, true, true);
}
// Add the pivot, if needed.
if ($pivot !== null) {
$languages = ArrayHelper::pivot($languages, $pivot);
}
return $languages;
}
/**
* Parse strings from a language file.
*
* @param string $fileName The language ini file path.
* @param boolean $debug If set to true debug language ini file.
*
* @return array The strings parsed.
*
* @since 3.9.0
*/
public static function parseIniFile($fileName, $debug = false)
{
// Check if file exists.
if (!is_file($fileName)) {
return [];
}
// Capture hidden PHP errors from the parsing.
if ($debug === true) {
// See https://www.php.net/manual/en/reserved.variables.phperrormsg.php
$php_errormsg = null;
$trackErrors = ini_get('track_errors');
ini_set('track_errors', true);
}
// This was required for https://github.com/joomla/joomla-cms/issues/17198 but not sure what server setup
// issue it is solving
$disabledFunctions = explode(',', ini_get('disable_functions'));
$isParseIniFileDisabled = \in_array('parse_ini_file', array_map('trim', $disabledFunctions));
if (!\function_exists('parse_ini_file') || $isParseIniFileDisabled) {
$contents = file_get_contents($fileName);
$strings = @parse_ini_string($contents, false, INI_SCANNER_RAW);
} else {
$strings = @parse_ini_file($fileName, false, INI_SCANNER_RAW);
}
// Ini files are processed in the "RAW" mode of parse_ini_string, leaving escaped quotes untouched - lets postprocess them
$strings = str_replace('\"', '"', $strings);
// Restore error tracking to what it was before.
if ($debug === true) {
ini_set('track_errors', $trackErrors);
}
return \is_array($strings) ? $strings : [];
}
/**
* Save strings to a language file.
*
* @param string $fileName The language ini file path.
* @param array $strings The array of strings.
*
* @return boolean True if saved, false otherwise.
*
* @since 3.7.0
*/
public static function saveToIniFile($fileName, array $strings)
{
// Escape double quotes.
foreach ($strings as $key => $string) {
$strings[$key] = addcslashes($string, '"');
}
// Write override.ini file with the strings.
$registry = new Registry($strings);
return File::write($fileName, $registry->toString('INI'));
}
/**
* 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 3.7.0
*/
public static function exists($lang, $basePath = JPATH_BASE)
{
static $paths = [];
// 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];
}
/**
* Returns an 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 3.7.0
*/
public static function getMetadata($lang)
{
$file = self::getLanguagePath(JPATH_BASE, $lang) . '/langmetadata.xml';
if (!is_file($file)) {
$file = self::getLanguagePath(JPATH_BASE, $lang) . '/' . $lang . '.xml';
}
$result = null;
if (is_file($file)) {
$result = self::parseXMLLanguageFile($file);
}
if (empty($result)) {
return;
}
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 3.7.0
*/
public static function getKnownLanguages($basePath = JPATH_BASE)
{
return self::parseLanguageFiles(self::getLanguagePath($basePath));
}
/**
* 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 3.7.0
*/
public static function getLanguagePath($basePath = JPATH_BASE, $language = null)
{
return $basePath . '/language' . (!empty($language) ? '/' . $language : '');
}
/**
* 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 3.7.0
*/
public static function parseLanguageFiles($dir = null)
{
$languages = [];
// Search main language directory for subdirectories
foreach (glob($dir . '/*', GLOB_NOSORT | GLOB_ONLYDIR) as $directory) {
// But only directories with lang code format
if (preg_match('#/[a-z]{2,3}-[A-Z]{2}$#', $directory)) {
$dirPathParts = pathinfo($directory);
$file = $directory . '/langmetadata.xml';
if (!is_file($file)) {
$file = $directory . '/' . $dirPathParts['filename'] . '.xml';
}
if (!is_file($file)) {
continue;
}
try {
// Get installed language metadata from xml file and merge it with lang array
if ($metadata = self::parseXMLLanguageFile($file)) {
$languages = array_replace($languages, [$dirPathParts['filename'] => $metadata]);
}
} catch (\RuntimeException $e) {
// Ignore it
}
}
}
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 3.7.0
* @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;
}
// Check that it's a metadata file
if ((string) $xml->getName() !== 'metafile') {
return;
}
$metadata = [];
foreach ($xml->metadata->children() as $child) {
$metadata[$child->getName()] = (string) $child;
}
return $metadata;
}
}