[ Index ]

PHP Cross Reference of Joomla 4.2.2 documentation

title

Body

[close]

/libraries/src/Language/ -> LanguageHelper.php (source)

   1  <?php
   2  
   3  /**
   4   * Joomla! Content Management System
   5   *
   6   * @copyright  (C) 2007 Open Source Matters, Inc. <https://www.joomla.org>
   7   * @license    GNU General Public License version 2 or later; see LICENSE.txt
   8   */
   9  
  10  namespace Joomla\CMS\Language;
  11  
  12  use Joomla\CMS\Cache\CacheControllerFactoryInterface;
  13  use Joomla\CMS\Cache\Controller\OutputController;
  14  use Joomla\CMS\Factory;
  15  use Joomla\CMS\Filesystem\File;
  16  use Joomla\CMS\Installer\Installer;
  17  use Joomla\CMS\Log\Log;
  18  use Joomla\Registry\Registry;
  19  use Joomla\Utilities\ArrayHelper;
  20  
  21  // phpcs:disable PSR1.Files.SideEffects
  22  \defined('JPATH_PLATFORM') or die;
  23  // phpcs:enable PSR1.Files.SideEffects
  24  
  25  /**
  26   * Language helper class
  27   *
  28   * @since  1.5
  29   */
  30  class LanguageHelper
  31  {
  32      /**
  33       * Builds a list of the system languages which can be used in a select option
  34       *
  35       * @param   string   $actualLanguage  Client key for the area
  36       * @param   string   $basePath        Base path to use
  37       * @param   boolean  $caching         True if caching is used
  38       * @param   boolean  $installed       Get only installed languages
  39       *
  40       * @return  array  List of system languages
  41       *
  42       * @since   1.5
  43       */
  44      public static function createLanguageList($actualLanguage, $basePath = JPATH_BASE, $caching = false, $installed = false)
  45      {
  46          $list      = array();
  47          $clientId  = $basePath === JPATH_ADMINISTRATOR ? 1 : 0;
  48          $languages = $installed ? static::getInstalledLanguages($clientId, true) : self::getKnownLanguages($basePath);
  49  
  50          foreach ($languages as $languageCode => $language) {
  51              $metadata = $installed ? $language->metadata : $language;
  52  
  53              $list[] = array(
  54                  'text'     => $metadata['nativeName'] ?? $metadata['name'],
  55                  'value'    => $languageCode,
  56                  'selected' => $languageCode === $actualLanguage ? 'selected="selected"' : null,
  57              );
  58          }
  59  
  60          return $list;
  61      }
  62  
  63      /**
  64       * Tries to detect the language.
  65       *
  66       * @return  string  locale or null if not found
  67       *
  68       * @since   1.5
  69       */
  70      public static function detectLanguage()
  71      {
  72          if (isset($_SERVER['HTTP_ACCEPT_LANGUAGE'])) {
  73              $browserLangs = explode(',', $_SERVER['HTTP_ACCEPT_LANGUAGE']);
  74              $systemLangs = self::getLanguages();
  75  
  76              foreach ($browserLangs as $browserLang) {
  77                  // Slice out the part before ; on first step, the part before - on second, place into array
  78                  $browserLang = substr($browserLang, 0, strcspn($browserLang, ';'));
  79                  $primary_browserLang = substr($browserLang, 0, 2);
  80  
  81                  foreach ($systemLangs as $systemLang) {
  82                      // Take off 3 letters iso code languages as they can't match browsers' languages and default them to en
  83                      $Jinstall_lang = $systemLang->lang_code;
  84  
  85                      if (\strlen($Jinstall_lang) < 6) {
  86                          if (strtolower($browserLang) == strtolower(substr($systemLang->lang_code, 0, \strlen($browserLang)))) {
  87                              return $systemLang->lang_code;
  88                          } elseif ($primary_browserLang == substr($systemLang->lang_code, 0, 2)) {
  89                              $primaryDetectedLang = $systemLang->lang_code;
  90                          }
  91                      }
  92                  }
  93  
  94                  if (isset($primaryDetectedLang)) {
  95                      return $primaryDetectedLang;
  96                  }
  97              }
  98          }
  99      }
 100  
 101      /**
 102       * Get available languages
 103       *
 104       * @param   string  $key  Array key
 105       *
 106       * @return  array  An array of published languages
 107       *
 108       * @since   1.6
 109       */
 110      public static function getLanguages($key = 'default')
 111      {
 112          static $languages;
 113  
 114          if (empty($languages)) {
 115              // Installation uses available languages
 116              if (Factory::getApplication()->isClient('installation')) {
 117                  $languages[$key] = array();
 118                  $knownLangs = self::getKnownLanguages(JPATH_BASE);
 119  
 120                  foreach ($knownLangs as $metadata) {
 121                      // Take off 3 letters iso code languages as they can't match browsers' languages and default them to en
 122                      $obj = new \stdClass();
 123                      $obj->lang_code = $metadata['tag'];
 124                      $languages[$key][] = $obj;
 125                  }
 126              } else {
 127                  /** @var OutputController $cache */
 128                  $cache = Factory::getContainer()->get(CacheControllerFactoryInterface::class)
 129                      ->createCacheController('output', ['defaultgroup' => 'com_languages']);
 130  
 131                  if ($cache->contains('languages')) {
 132                      $languages = $cache->get('languages');
 133                  } else {
 134                      $db = Factory::getDbo();
 135                      $query = $db->getQuery(true)
 136                          ->select('*')
 137                          ->from($db->quoteName('#__languages'))
 138                          ->where($db->quoteName('published') . ' = 1')
 139                          ->order($db->quoteName('ordering') . ' ASC');
 140                      $db->setQuery($query);
 141  
 142                      $languages['default'] = $db->loadObjectList();
 143                      $languages['sef'] = array();
 144                      $languages['lang_code'] = array();
 145  
 146                      if (isset($languages['default'][0])) {
 147                          foreach ($languages['default'] as $lang) {
 148                              $languages['sef'][$lang->sef] = $lang;
 149                              $languages['lang_code'][$lang->lang_code] = $lang;
 150                          }
 151                      }
 152  
 153                      $cache->store($languages, 'languages');
 154                  }
 155              }
 156          }
 157  
 158          return $languages[$key];
 159      }
 160  
 161      /**
 162       * Get a list of installed languages.
 163       *
 164       * @param   integer  $clientId         The client app id.
 165       * @param   boolean  $processMetaData  Fetch Language metadata.
 166       * @param   boolean  $processManifest  Fetch Language manifest.
 167       * @param   string   $pivot            The pivot of the returning array.
 168       * @param   string   $orderField       Field to order the results.
 169       * @param   string   $orderDirection   Direction to order the results.
 170       *
 171       * @return  array  Array with the installed languages.
 172       *
 173       * @since   3.7.0
 174       */
 175      public static function getInstalledLanguages(
 176          $clientId = null,
 177          $processMetaData = false,
 178          $processManifest = false,
 179          $pivot = 'element',
 180          $orderField = null,
 181          $orderDirection = null
 182      ) {
 183          static $installedLanguages = null;
 184  
 185          if ($installedLanguages === null) {
 186              /** @var OutputController $cache */
 187              $cache = Factory::getContainer()->get(CacheControllerFactoryInterface::class)
 188                  ->createCacheController('output', ['defaultgroup' => 'com_languages']);
 189  
 190              if ($cache->contains('installedlanguages')) {
 191                  $installedLanguages = $cache->get('installedlanguages');
 192              } else {
 193                  $db = Factory::getDbo();
 194  
 195                  $query = $db->getQuery(true)
 196                      ->select(
 197                          [
 198                              $db->quoteName('element'),
 199                              $db->quoteName('name'),
 200                              $db->quoteName('client_id'),
 201                              $db->quoteName('extension_id'),
 202                          ]
 203                      )
 204                      ->from($db->quoteName('#__extensions'))
 205                      ->where(
 206                          [
 207                              $db->quoteName('type') . ' = ' . $db->quote('language'),
 208                              $db->quoteName('state') . ' = 0',
 209                              $db->quoteName('enabled') . ' = 1',
 210                          ]
 211                      );
 212  
 213                  $installedLanguages = $db->setQuery($query)->loadObjectList();
 214  
 215                  $cache->store($installedLanguages, 'installedlanguages');
 216              }
 217          }
 218  
 219          $clients   = $clientId === null ? array(0, 1) : array((int) $clientId);
 220          $languages = array(
 221              0 => array(),
 222              1 => array(),
 223          );
 224  
 225          foreach ($installedLanguages as $language) {
 226              // If the language client is not needed continue cycle. Drop for performance.
 227              if (!\in_array((int) $language->client_id, $clients)) {
 228                  continue;
 229              }
 230  
 231              $lang = $language;
 232  
 233              if ($processMetaData || $processManifest) {
 234                  $clientPath = (int) $language->client_id === 0 ? JPATH_SITE : JPATH_ADMINISTRATOR;
 235                  $metafile   = self::getLanguagePath($clientPath, $language->element) . '/langmetadata.xml';
 236  
 237                  if (!is_file($metafile)) {
 238                      $metafile = self::getLanguagePath($clientPath, $language->element) . '/' . $language->element . '.xml';
 239                  }
 240  
 241                  // Process the language metadata.
 242                  if ($processMetaData) {
 243                      try {
 244                          $lang->metadata = self::parseXMLLanguageFile($metafile);
 245                      } catch (\Exception $e) {
 246                          // Not able to process xml language file. Fail silently.
 247                          Log::add(Text::sprintf('JLIB_LANGUAGE_ERROR_CANNOT_LOAD_METAFILE', $language->element, $metafile), Log::WARNING, 'language');
 248  
 249                          continue;
 250                      }
 251  
 252                      // No metadata found, not a valid language. Fail silently.
 253                      if (!\is_array($lang->metadata)) {
 254                          Log::add(Text::sprintf('JLIB_LANGUAGE_ERROR_CANNOT_LOAD_METADATA', $language->element, $metafile), Log::WARNING, 'language');
 255  
 256                          continue;
 257                      }
 258                  }
 259  
 260                  // Process the language manifest.
 261                  if ($processManifest) {
 262                      try {
 263                          $lang->manifest = Installer::parseXMLInstallFile($metafile);
 264                      } catch (\Exception $e) {
 265                          // Not able to process xml language file. Fail silently.
 266                          Log::add(Text::sprintf('JLIB_LANGUAGE_ERROR_CANNOT_LOAD_METAFILE', $language->element, $metafile), Log::WARNING, 'language');
 267  
 268                          continue;
 269                      }
 270  
 271                      // No metadata found, not a valid language. Fail silently.
 272                      if (!\is_array($lang->manifest)) {
 273                          Log::add(Text::sprintf('JLIB_LANGUAGE_ERROR_CANNOT_LOAD_METADATA', $language->element, $metafile), Log::WARNING, 'language');
 274  
 275                          continue;
 276                      }
 277                  }
 278              }
 279  
 280              $languages[$language->client_id][] = $lang;
 281          }
 282  
 283          // Order the list, if needed.
 284          if ($orderField !== null && $orderDirection !== null) {
 285              $orderDirection = strtolower($orderDirection) === 'desc' ? -1 : 1;
 286  
 287              foreach ($languages as $cId => $language) {
 288                  // If the language client is not needed continue cycle. Drop for performance.
 289                  if (!\in_array($cId, $clients)) {
 290                      continue;
 291                  }
 292  
 293                  $languages[$cId] = ArrayHelper::sortObjects($languages[$cId], $orderField, $orderDirection, true, true);
 294              }
 295          }
 296  
 297          // Add the pivot, if needed.
 298          if ($pivot !== null) {
 299              foreach ($languages as $cId => $language) {
 300                  // If the language client is not needed continue cycle. Drop for performance.
 301                  if (!\in_array($cId, $clients)) {
 302                      continue;
 303                  }
 304  
 305                  $languages[$cId] = ArrayHelper::pivot($languages[$cId], $pivot);
 306              }
 307          }
 308  
 309          return $clientId !== null ? $languages[$clientId] : $languages;
 310      }
 311  
 312      /**
 313       * Get a list of content languages.
 314       *
 315       * @param   array    $publishedStates  Array with the content language published states. Empty array for all.
 316       * @param   boolean  $checkInstalled   Check if the content language is installed.
 317       * @param   string   $pivot            The pivot of the returning array.
 318       * @param   string   $orderField       Field to order the results.
 319       * @param   string   $orderDirection   Direction to order the results.
 320       *
 321       * @return  array  Array of the content languages.
 322       *
 323       * @since   3.7.0
 324       */
 325      public static function getContentLanguages(
 326          $publishedStates = array(1),
 327          $checkInstalled = true,
 328          $pivot = 'lang_code',
 329          $orderField = null,
 330          $orderDirection = null
 331      ) {
 332          static $contentLanguages = null;
 333  
 334          if ($contentLanguages === null) {
 335              /** @var OutputController $cache */
 336              $cache = Factory::getContainer()->get(CacheControllerFactoryInterface::class)
 337                  ->createCacheController('output', ['defaultgroup' => 'com_languages']);
 338  
 339              if ($cache->contains('contentlanguages')) {
 340                  $contentLanguages = $cache->get('contentlanguages');
 341              } else {
 342                  $db = Factory::getDbo();
 343  
 344                  $query = $db->getQuery(true)
 345                      ->select('*')
 346                      ->from($db->quoteName('#__languages'));
 347  
 348                  $contentLanguages = $db->setQuery($query)->loadObjectList();
 349  
 350                  $cache->store($contentLanguages, 'contentlanguages');
 351              }
 352          }
 353  
 354          $languages = $contentLanguages;
 355  
 356          // B/C layer. Before 3.8.3.
 357          if ($publishedStates === true) {
 358              $publishedStates = array(1);
 359          } elseif ($publishedStates === false) {
 360              $publishedStates = array();
 361          }
 362  
 363          // Check the language published state, if needed.
 364          if (\count($publishedStates) > 0) {
 365              foreach ($languages as $key => $language) {
 366                  if (!\in_array((int) $language->published, $publishedStates, true)) {
 367                      unset($languages[$key]);
 368                  }
 369              }
 370          }
 371  
 372          // Check if the language is installed, if needed.
 373          if ($checkInstalled) {
 374              $languages = array_values(array_intersect_key(ArrayHelper::pivot($languages, 'lang_code'), static::getInstalledLanguages(0)));
 375          }
 376  
 377          // Order the list, if needed.
 378          if ($orderField !== null && $orderDirection !== null) {
 379              $languages = ArrayHelper::sortObjects($languages, $orderField, strtolower($orderDirection) === 'desc' ? -1 : 1, true, true);
 380          }
 381  
 382          // Add the pivot, if needed.
 383          if ($pivot !== null) {
 384              $languages = ArrayHelper::pivot($languages, $pivot);
 385          }
 386  
 387          return $languages;
 388      }
 389  
 390      /**
 391       * Parse strings from a language file.
 392       *
 393       * @param   string   $fileName  The language ini file path.
 394       * @param   boolean  $debug     If set to true debug language ini file.
 395       *
 396       * @return  array  The strings parsed.
 397       *
 398       * @since   3.9.0
 399       */
 400      public static function parseIniFile($fileName, $debug = false)
 401      {
 402          // Check if file exists.
 403          if (!is_file($fileName)) {
 404              return array();
 405          }
 406  
 407          // Capture hidden PHP errors from the parsing.
 408          if ($debug === true) {
 409              // See https://www.php.net/manual/en/reserved.variables.phperrormsg.php
 410              $php_errormsg = null;
 411  
 412              $trackErrors = ini_get('track_errors');
 413              ini_set('track_errors', true);
 414          }
 415  
 416          // This was required for https://github.com/joomla/joomla-cms/issues/17198 but not sure what server setup
 417          // issue it is solving
 418          $disabledFunctions = explode(',', ini_get('disable_functions'));
 419          $isParseIniFileDisabled = \in_array('parse_ini_file', array_map('trim', $disabledFunctions));
 420  
 421          if (!\function_exists('parse_ini_file') || $isParseIniFileDisabled) {
 422              $contents = file_get_contents($fileName);
 423              $strings = @parse_ini_string($contents);
 424          } else {
 425              $strings = @parse_ini_file($fileName);
 426          }
 427  
 428          // Restore error tracking to what it was before.
 429          if ($debug === true) {
 430              ini_set('track_errors', $trackErrors);
 431          }
 432  
 433          return \is_array($strings) ? $strings : array();
 434      }
 435  
 436      /**
 437       * Save strings to a language file.
 438       *
 439       * @param   string  $fileName  The language ini file path.
 440       * @param   array   $strings   The array of strings.
 441       *
 442       * @return  boolean  True if saved, false otherwise.
 443       *
 444       * @since   3.7.0
 445       */
 446      public static function saveToIniFile($fileName, array $strings)
 447      {
 448          // Escape double quotes.
 449          foreach ($strings as $key => $string) {
 450              $strings[$key] = addcslashes($string, '"');
 451          }
 452  
 453          // Write override.ini file with the strings.
 454          $registry = new Registry($strings);
 455  
 456          return File::write($fileName, $registry->toString('INI'));
 457      }
 458  
 459      /**
 460       * Checks if a language exists.
 461       *
 462       * This is a simple, quick check for the directory that should contain language files for the given user.
 463       *
 464       * @param   string  $lang      Language to check.
 465       * @param   string  $basePath  Optional path to check.
 466       *
 467       * @return  boolean  True if the language exists.
 468       *
 469       * @since   3.7.0
 470       */
 471      public static function exists($lang, $basePath = JPATH_BASE)
 472      {
 473          static $paths = array();
 474  
 475          // Return false if no language was specified
 476          if (!$lang) {
 477              return false;
 478          }
 479  
 480          $path = $basePath . '/language/' . $lang;
 481  
 482          // Return previous check results if it exists
 483          if (isset($paths[$path])) {
 484              return $paths[$path];
 485          }
 486  
 487          // Check if the language exists
 488          $paths[$path] = is_dir($path);
 489  
 490          return $paths[$path];
 491      }
 492  
 493      /**
 494       * Returns an associative array holding the metadata.
 495       *
 496       * @param   string  $lang  The name of the language.
 497       *
 498       * @return  mixed  If $lang exists return key/value pair with the language metadata, otherwise return NULL.
 499       *
 500       * @since   3.7.0
 501       */
 502      public static function getMetadata($lang)
 503      {
 504          $file = self::getLanguagePath(JPATH_BASE, $lang) . '/langmetadata.xml';
 505  
 506          if (!is_file($file)) {
 507              $file = self::getLanguagePath(JPATH_BASE, $lang) . '/' . $lang . '.xml';
 508          }
 509  
 510          $result = null;
 511  
 512          if (is_file($file)) {
 513              $result = self::parseXMLLanguageFile($file);
 514          }
 515  
 516          if (empty($result)) {
 517              return;
 518          }
 519  
 520          return $result;
 521      }
 522  
 523      /**
 524       * Returns a list of known languages for an area
 525       *
 526       * @param   string  $basePath  The basepath to use
 527       *
 528       * @return  array  key/value pair with the language file and real name.
 529       *
 530       * @since   3.7.0
 531       */
 532      public static function getKnownLanguages($basePath = JPATH_BASE)
 533      {
 534          return self::parseLanguageFiles(self::getLanguagePath($basePath));
 535      }
 536  
 537      /**
 538       * Get the path to a language
 539       *
 540       * @param   string  $basePath  The basepath to use.
 541       * @param   string  $language  The language tag.
 542       *
 543       * @return  string  language related path or null.
 544       *
 545       * @since   3.7.0
 546       */
 547      public static function getLanguagePath($basePath = JPATH_BASE, $language = null)
 548      {
 549          return $basePath . '/language' . (!empty($language) ? '/' . $language : '');
 550      }
 551  
 552      /**
 553       * Searches for language directories within a certain base dir.
 554       *
 555       * @param   string  $dir  directory of files.
 556       *
 557       * @return  array  Array holding the found languages as filename => real name pairs.
 558       *
 559       * @since   3.7.0
 560       */
 561      public static function parseLanguageFiles($dir = null)
 562      {
 563          $languages = array();
 564  
 565          // Search main language directory for subdirectories
 566          foreach (glob($dir . '/*', GLOB_NOSORT | GLOB_ONLYDIR) as $directory) {
 567              // But only directories with lang code format
 568              if (preg_match('#/[a-z]{2,3}-[A-Z]{2}$#', $directory)) {
 569                  $dirPathParts = pathinfo($directory);
 570                  $file         = $directory . '/langmetadata.xml';
 571  
 572                  if (!is_file($file)) {
 573                      $file = $directory . '/' . $dirPathParts['filename'] . '.xml';
 574                  }
 575  
 576                  if (!is_file($file)) {
 577                      continue;
 578                  }
 579  
 580                  try {
 581                      // Get installed language metadata from xml file and merge it with lang array
 582                      if ($metadata = self::parseXMLLanguageFile($file)) {
 583                          $languages = array_replace($languages, array($dirPathParts['filename'] => $metadata));
 584                      }
 585                  } catch (\RuntimeException $e) {
 586                      // Ignore it
 587                  }
 588              }
 589          }
 590  
 591          return $languages;
 592      }
 593  
 594      /**
 595       * Parse XML file for language information.
 596       *
 597       * @param   string  $path  Path to the XML files.
 598       *
 599       * @return  array  Array holding the found metadata as a key => value pair.
 600       *
 601       * @since   3.7.0
 602       * @throws  \RuntimeException
 603       */
 604      public static function parseXMLLanguageFile($path)
 605      {
 606          if (!is_readable($path)) {
 607              throw new \RuntimeException('File not found or not readable');
 608          }
 609  
 610          // Try to load the file
 611          $xml = simplexml_load_file($path);
 612  
 613          if (!$xml) {
 614              return;
 615          }
 616  
 617          // Check that it's a metadata file
 618          if ((string) $xml->getName() !== 'metafile') {
 619              return;
 620          }
 621  
 622          $metadata = array();
 623  
 624          foreach ($xml->metadata->children() as $child) {
 625              $metadata[$child->getName()] = (string) $child;
 626          }
 627  
 628          return $metadata;
 629      }
 630  }


Generated: Wed Sep 7 05:41:13 2022 Chilli.vc Blog - For Webmaster,Blog-Writer,System Admin and Domainer