ILIAS  trunk Revision v11.0_alpha-2638-g80c1d007f79
class.ilObjLanguage.php
Go to the documentation of this file.
1 <?php
2 
19 declare(strict_types=1);
20 
29 class ilObjLanguage extends ilObject
30 {
35  public string $separator;
36  public string $comment_separator;
37  public string $lang_default;
38  public string $lang_user;
39  public string $lang_path;
40  public string $key;
41  public string $status;
42  public string $cust_lang_path;
43 
50  public function __construct(int $a_id = 0, bool $a_call_by_reference = false)
51  {
52  global $DIC;
53  $lng = $DIC->language();
54 
55  $this->type = "lng";
56  parent::__construct($a_id, $a_call_by_reference);
57 
58  $this->type = "lng";
59  $this->key = $this->title;
60  $this->status = $this->desc;
61  $this->lang_default = $lng->lang_default;
62  $this->lang_user = $lng->lang_user;
63  $this->lang_path = $lng->lang_path;
64  $this->cust_lang_path = $lng->getCustomLangPath();
65  $this->separator = $lng->separator;
66  $this->comment_separator = $lng->comment_separator;
67  }
68 
69 
73  public static function getInstalledLanguages(): array
74  {
75  $objects = array();
76  $languages = ilObject::_getObjectsByType("lng");
77  foreach ($languages as $lang) {
78  $langObj = new ilObjLanguage((int) $lang["obj_id"], false);
79  if ($langObj->isInstalled()) {
80  $objects[] = $langObj;
81  } else {
82  unset($langObj);
83  }
84  }
85  return $objects;
86  }
87 
88 
94  public function getKey(): string
95  {
96  return $this->key;
97  }
98 
104  public function getStatus(): string
105  {
106  return $this->status;
107  }
108 
112  public function isSystemLanguage(): bool
113  {
114  if ($this->key == $this->lang_default) {
115  return true;
116  } else {
117  return false;
118  }
119  }
120 
124  public function isUserLanguage(): bool
125  {
126  if ($this->key == $this->lang_user) {
127  return true;
128  } else {
129  return false;
130  }
131  }
132 
138  public function isInstalled(): bool
139  {
140  if (str_starts_with($this->getStatus(), "installed")) {
141  return true;
142  } else {
143  return false;
144  }
145  }
146 
153  public function isLocal(): bool
154  {
155  if (substr($this->getStatus(), 10) === "local") {
156  return true;
157  } else {
158  return false;
159  }
160  }
161 
168  public function install(string $scope = ""): string
169  {
170  if (!empty($scope)) {
171  if ($scope === "global") {
172  $scope = "";
173  } else {
174  $scopeExtension = "." . $scope;
175  }
176  }
177 
178  if (!$this->isInstalled() || (!$this->isLocal() && !empty($scope))) {
179  if ($this->check($scope)) {
180  // lang-file is ok. Flush data in db and...
181  if (empty($scope)) {
182  $this->flush("keep_local");
183  }
184 
185  // ...re-insert data from lang-file
186  $this->insert($scope);
187 
188  // update information in db-table about available/installed languages
189  $newDesc = '';
190  if (empty($scope)) {
191  $newDesc = "installed";
192  } elseif ($scope === "local") {
193  $newDesc = "installed_local";
194  }
195  $this->setDescription($newDesc);
196  $this->update();
197  return $this->getKey();
198  }
199  }
200  return "";
201  }
202 
203 
209  public function uninstall(): string
210  {
211  if ((str_starts_with($this->status, "installed")) && ($this->key != $this->lang_default) && ($this->key != $this->lang_user)) {
212  $this->flush();
213  $this->setTitle($this->key);
214  $this->setDescription("not_installed");
215  $this->update();
216  $this->resetUserLanguage($this->key);
217 
218  return $this->key;
219  }
220  return "";
221  }
222 
223 
227  public function refresh(): bool
228  {
229  if ($this->isInstalled() && $this->check()) {
230  $this->flush("keep_local");
231  $this->insert();
232  $this->setTitle($this->getKey());
233  $this->setDescription($this->getStatus());
234  $this->update();
235 
236  if ($this->isLocal() && $this->check("local")) {
237  $this->insert("local");
238  $this->setTitle($this->getKey());
239  $this->setDescription($this->getStatus());
240  $this->update();
241  }
242 
243  return true;
244  }
245 
246  return false;
247  }
248 
252  public static function refreshAll(): void
253  {
254  $languages = ilObject::_getObjectsByType("lng");
255  $refreshed = array();
256 
257  foreach ($languages as $lang) {
258  $langObj = new ilObjLanguage($lang["obj_id"], false);
259  if ($langObj->refresh()) {
260  $refreshed[] = $langObj->getKey();
261  }
262  unset($langObj);
263  }
264 
265  self::refreshPlugins($refreshed);
266  }
267 
268 
273  public static function refreshPlugins(?array $a_lang_keys = null): void
274  {
275  global $DIC;
276 
277  $component_repository = $DIC["component.repository"];
278  foreach ($component_repository->getPlugins() as $plugin) {
279  if (!$plugin->isActive()) {
280  continue;
281  }
283  $handler->updateLanguages($a_lang_keys);
284  }
285  }
286 
287 
292  public static function _deleteLangData(string $a_lang_key, bool $a_keep_local_change = false): void
293  {
294  global $DIC;
295  $ilDB = $DIC->database();
296 
297  if (!$a_keep_local_change) {
298  $ilDB->manipulate("DELETE FROM lng_data WHERE lang_key = " .
299  $ilDB->quote($a_lang_key, "text"));
300  } else {
301  $ilDB->manipulate("DELETE FROM lng_data WHERE lang_key = " .
302  $ilDB->quote($a_lang_key, "text") .
303  " AND local_change IS NULL");
304  }
305  }
306 
311  public function flush(string $a_mode = "all"): void
312  {
313  global $DIC;
314  $ilDB = $DIC->database();
315 
316  self::_deleteLangData($this->key, ($a_mode === "keep_local"));
317 
318  if ($a_mode === "all") {
319  $ilDB->manipulate("DELETE FROM lng_modules WHERE lang_key = " .
320  $ilDB->quote($this->key, "text"));
321  }
322  }
323 
324 
331  public function getLocalChanges(string $a_min_date = "", string $a_max_date = ""): array
332  {
333  global $DIC;
334  $ilDB = $DIC->database();
335 
336  if ($a_min_date === "") {
337  $a_min_date = "1980-01-01 00:00:00";
338  }
339  if ($a_max_date === "") {
340  $a_max_date = "2200-01-01 00:00:00";
341  }
342 
343  $q = sprintf(
344  "SELECT * FROM lng_data WHERE lang_key = %s " .
345  "AND local_change >= %s AND local_change <= %s",
346  $ilDB->quote($this->key, "text"),
347  $ilDB->quote($a_min_date, "timestamp"),
348  $ilDB->quote($a_max_date, "timestamp")
349  );
350  $result = $ilDB->query($q);
351 
352  $changes = array();
353  while ($row = $result->fetchRow(ilDBConstants::FETCHMODE_ASSOC)) {
354  $changes[$row["module"]][$row["identifier"]] = $row["value"];
355  }
356  return $changes;
357  }
358 
359 
365  public static function _getLastLocalChange(string $a_key): string
366  {
367  global $DIC;
368  $ilDB = $DIC->database();
369 
370  $q = sprintf(
371  "SELECT MAX(local_change) last_change FROM lng_data " .
372  "WHERE lang_key = %s AND local_change IS NOT NULL",
373  $ilDB->quote($a_key, "text")
374  );
375  $result = $ilDB->query($q);
376 
377  if ($row = $result->fetchRow(ilDBConstants::FETCHMODE_ASSOC)) {
378  return (string) $row["last_change"];
379  } else {
380  return "";
381  }
382  }
383 
384 
391  public static function _getLocalChangesByModule(string $a_key, string $a_module): array
392  {
393  global $DIC;
394  $ilDB = $DIC->database();
395 
396  $changes = array();
397  $result = $ilDB->queryF(
398  "SELECT * FROM lng_data WHERE lang_key = %s AND module = %s AND local_change IS NOT NULL",
399  array("text", "text"),
400  array($a_key, $a_module)
401  );
402 
403  while ($row = $ilDB->fetchAssoc($result)) {
404  $changes[$row["identifier"]] = $row["value"];
405  }
406  return $changes;
407  }
408 
409 
415  public function insert(string $scope = ""): void
416  {
417  global $DIC;
418  $ilDB = $DIC->database();
419  $scopeExtension = "";
420  if (!empty($scope)) {
421  if ($scope === "global") {
422  $scope = "";
423  } else {
424  $scopeExtension = "." . $scope;
425  }
426  }
427 
429  if ($scope === "local") {
431  }
432 
433  $lang_file = $path . "/ilias_" . $this->key . ".lang" . $scopeExtension;
434 
435  if (is_file($lang_file)) {
436  // remove header first
437  if ($content = self::cut_header(file($lang_file))) {
438  $local_changes = null;
439  if (empty($scope)) {
440  // get all local changes for a global file
441  $local_changes = $this->getLocalChanges();
442  } elseif ($scope === "local") {
443  // get the modification date of the local file
444  // get the newer local changes for a local file
445  $min_date = gmdate("Y-m-d H:i:s", filemtime($lang_file));
446  $local_changes = $this->getLocalChanges($min_date);
447  }
448  $dbAccess = new ilObjLanguageDBAccess($ilDB, $this->key, $content, $local_changes, $scope);
449  $lang_array = $dbAccess->insertLangEntries($lang_file);
450  $dbAccess->replaceLangModules($lang_array);
451  }
452  }
453  }
454 
458  final public static function replaceLangModule(string $a_key, string $a_module, array $a_array): void
459  {
460  global $DIC;
461  $ilDB = $DIC->database();
462 
463  // avoid flushing the whole cache (see mantis #28818)
464  ilCachedLanguage::getInstance($a_key)->deleteInCache();
465 
466  $ilDB->manipulate(sprintf(
467  "DELETE FROM lng_modules WHERE lang_key = %s AND module = %s",
468  $ilDB->quote($a_key, "text"),
469  $ilDB->quote($a_module, "text")
470  ));
471 
472  /*$ilDB->manipulate(sprintf("INSERT INTO lng_modules (lang_key, module, lang_array) VALUES ".
473  "(%s,%s,%s)", $ilDB->quote($a_key, "text"),
474  $ilDB->quote($a_module, "text"),
475  $ilDB->quote(serialize($a_array), "clob")));*/
476  $ilDB->insert("lng_modules", array(
477  "lang_key" => array("text", $a_key),
478  "module" => array("text", $a_module),
479  "lang_array" => array("clob", serialize($a_array))
480  ));
481 
482  // check if the module is correctly saved
483  // see mantis #20046 and #19140
484  $result = $ilDB->queryF(
485  "SELECT lang_array FROM lng_modules WHERE lang_key = %s AND module = %s",
486  array("text","text"),
487  array($a_key, $a_module)
488  );
489  $row = $ilDB->fetchAssoc($result);
490 
491  $unserialied = unserialize($row["lang_array"], ["allowed_classes" => false]);
492  if (!is_array($unserialied)) {
493  $DIC->ui()->mainTemplate()->setOnScreenMessage(
494  'failure',
495  "Data for module '" . $a_module . "' of language '" . $a_key . "' is not correctly saved. " .
496  "Please check the collation of your database tables lng_data and lng_modules. It must be utf8_unicode_ci.",
497  true
498  );
499  $DIC->ctrl()->redirectByClass(ilobjlanguagefoldergui::class, 'view');
500  }
501  }
502 
506  final public static function replaceLangEntry(
507  string $a_module,
508  string $a_identifier,
509  string $a_lang_key,
510  string $a_value,
511  ?string $a_local_change = null,
512  ?string $a_remarks = null
513  ): bool {
514  global $DIC;
515  $ilDB = $DIC->database();
516 
517  // avoid a cache flush here (see mantis #28818)
518  // ilGlobalCache::flushAll();
519 
520  if (is_string($a_remarks) && $a_remarks !== '') {
521  $a_remarks = substr($a_remarks, 0, 250);
522  }
523 
524  if ($a_remarks === '') {
525  $a_remarks = null;
526  }
527 
528  if ($a_value === "") {
529  $a_value = null;
530  } else {
531  $a_value = substr($a_value, 0, 4000);
532  }
533 
534  $ilDB->replace(
535  "lng_data",
536  array(
537  "module" => array("text",$a_module),
538  "identifier" => array("text",$a_identifier),
539  "lang_key" => array("text",$a_lang_key)
540  ),
541  array(
542  "value" => array("text",$a_value),
543  "local_change" => array("timestamp",$a_local_change),
544  "remarks" => array("text", $a_remarks)
545  )
546  );
547  return true;
548  }
549 
553  final public static function updateLangEntry(
554  string $a_module,
555  string $a_identifier,
556  string $a_lang_key,
557  string $a_value,
558  ?string $a_local_change = null,
559  ?string $a_remarks = null
560  ): void {
561  global $DIC;
562  $ilDB = $DIC->database();
563 
564  if (is_string($a_remarks) && $a_remarks !== '') {
565  $a_remarks = substr($a_remarks, 0, 250);
566  }
567 
568  if ($a_remarks === '') {
569  $a_remarks = null;
570  }
571 
572  if ($a_value === "") {
573  $a_value = null;
574  } else {
575  $a_value = substr($a_value, 0, 4000);
576  }
577 
578  $ilDB->manipulate(sprintf(
579  "UPDATE lng_data " .
580  "SET value = %s, local_change = %s, remarks = %s " .
581  "WHERE module = %s AND identifier = %s AND lang_key = %s ",
582  $ilDB->quote($a_value, "text"),
583  $ilDB->quote($a_local_change, "timestamp"),
584  $ilDB->quote($a_remarks, "text"),
585  $ilDB->quote($a_module, "text"),
586  $ilDB->quote($a_identifier, "text"),
587  $ilDB->quote($a_lang_key, "text")
588  ));
589  }
590 
591 
595  final public static function deleteLangEntry(string $a_module, string $a_identifier, string $a_lang_key): bool
596  {
597  global $DIC;
598  $ilDB = $DIC->database();
599 
600  $ilDB->manipulate(sprintf(
601  "DELETE FROM lng_data " .
602  "WHERE module = %s AND identifier = %s AND lang_key = %s ",
603  $ilDB->quote($a_module, "text"),
604  $ilDB->quote($a_identifier, "text"),
605  $ilDB->quote($a_lang_key, "text")
606  ));
607 
608  return true;
609  }
610 
611 
618  public function resetUserLanguage(string $lang_key): void
619  {
620  global $DIC;
621  $ilDB = $DIC->database();
622 
623  $query = "UPDATE usr_pref SET " .
624  "value = " . $ilDB->quote($this->lang_default, "text") . " " .
625  "WHERE keyword = " . $ilDB->quote('language', "text") . " " .
626  "AND value = " . $ilDB->quote($lang_key, "text");
627  $ilDB->manipulate($query);
628  }
629 
639  public static function cut_header(array $content)
640  {
641  foreach ($content as $key => $val) {
642  if (trim($val) === "<!-- language file start -->") {
643  return array_slice($content, $key + 1);
644  }
645  }
646 
647  return false;
648  }
649 
656  public function optimizeData(): bool
657  {
658  // Mantis #22313: removed table optimization
659  return true;
660  }
661 
671  public function check(string $scope = ""): bool
672  {
673  global $DIC;
674  $scopeExtension = "";
675  if (!empty($scope)) {
676  if ($scope === "global") {
677  $scope = "";
678  } else {
679  $scopeExtension = "." . $scope;
680  }
681  }
682 
684  if ($scope === "local") {
686  }
687 
688  $tmpPath = getcwd();
689 
690  // dir check
691  if (!is_dir($path)) {
692  $DIC->ui()->mainTemplate()->setOnScreenMessage(
693  'failure',
694  "Directory not found: " . $path,
695  true
696  );
697  $DIC->ctrl()->redirectByClass(ilobjlanguagefoldergui::class, 'view');
698  }
699 
700  chdir($path);
701 
702  // compute lang-file name format
703  $lang_file = "ilias_" . $this->key . ".lang" . $scopeExtension;
704 
705  // file check
706  if (!is_file($lang_file)) {
707  $DIC->ui()->mainTemplate()->setOnScreenMessage(
708  'failure',
709  "File not found: " . $lang_file,
710  true
711  );
712  $DIC->ctrl()->redirectByClass(ilobjlanguagefoldergui::class, 'view');
713  }
714 
715  // header check
716  $content = self::cut_header(file($lang_file));
717  if ($content === false) {
718  $DIC->ui()->mainTemplate()->setOnScreenMessage(
719  'failure',
720  "Wrong Header in " . $lang_file,
721  true
722  );
723  $DIC->ctrl()->redirectByClass(ilobjlanguagefoldergui::class, 'view');
724  }
725 
726  // check (counting) elements of each lang-entry
727  $line = 0;
728  $n = 0;
729  foreach ($content as $key => $val) {
730  $separated = explode($this->separator, trim($val));
731  $num = count($separated);
732  ++$n;
733  if ($num !== 3) {
734  $line = $n + 36;
735  $DIC->ui()->mainTemplate()->setOnScreenMessage(
736  'failure',
737  "Wrong parameter count in " . $lang_file . " in line $line (Value: $val)! Please check your language file!",
738  true
739  );
740  $DIC->ctrl()->redirectByClass(ilobjlanguagefoldergui::class, 'view');
741  }
742  if (!ilStr::isUtf8($separated[2])) {
743  $DIC->ui()->mainTemplate()->setOnScreenMessage(
744  'failure',
745  "Non UTF8 character found in " . $lang_file . " in line $line (Value: $val)! Please check your language file!",
746  true
747  );
748  $DIC->ctrl()->redirectByClass(ilobjlanguagefoldergui::class, 'view');
749  }
750  }
751 
752  chdir($tmpPath);
753 
754  // no error occured
755  return true;
756  }
757 
761  public static function countUsers(string $a_lang): int
762  {
763  global $DIC;
764  $ilDB = $DIC->database();
765  $lng = $DIC->language();
766 
767  $set = $ilDB->query("SELECT COUNT(*) cnt FROM usr_data ud JOIN usr_pref up" .
768  " ON ud.usr_id = up.usr_id " .
769  " WHERE up.value = " . $ilDB->quote($a_lang, "text") .
770  " AND up.keyword = " . $ilDB->quote("language", "text"));
771  $rec = $ilDB->fetchAssoc($set);
772 
773  // add users with no usr_pref set to default language
774  if ($a_lang == $lng->lang_default) {
775  $set2 = $ilDB->query("SELECT COUNT(*) cnt FROM usr_data ud LEFT JOIN usr_pref up" .
776  " ON (ud.usr_id = up.usr_id AND up.keyword = " . $ilDB->quote("language", "text") . ")" .
777  " WHERE up.value IS NULL ");
778  $rec2 = $ilDB->fetchAssoc($set2);
779  }
780 
781  return (int) $rec["cnt"] + (int) ($rec2["cnt"] ?? 0);
782  }
783 } // END class.LanguageObject
insert(string $scope="")
insert language data from file into database
optimizeData()
optimizes the db-table langdata
string $title
static _deleteLangData(string $a_lang_key, bool $a_keep_local_change=false)
Delete languge data $a_lang_key lang key.
isUserLanguage()
check if language is system language
$scope
Definition: ltiregstart.php:47
static _getObjectsByType(string $obj_type="", ?int $owner=null)
string $separator
separator of module, comment separator, identifier & values in language files
string $desc
static getInstalledLanguages()
Get the language objects of the installed languages.
static countUsers(string $a_lang)
Count number of users that use a language.
setTitle(string $title)
isInstalled()
Check language object status, and return true if language is installed.
static updateLangEntry(string $a_module, string $a_identifier, string $a_lang_key, string $a_value, ?string $a_local_change=null, ?string $a_remarks=null)
Replace lang entry.
$path
Definition: ltiservices.php:29
while($session_entry=$r->fetchRow(ilDBConstants::FETCHMODE_ASSOC)) return null
getStatus()
get language status
uninstall()
uninstall current language
getLocalChanges(string $a_min_date="", string $a_max_date="")
get locally changed language entries $a_min_date minimum change date "yyyy-mm-dd hh:mm:ss" $a_max_dat...
static refreshPlugins(?array $a_lang_keys=null)
Refresh languages of activated plugins $a_lang_keys keys of languages to be refreshed (not yet suppor...
ilLanguage $lng
isSystemLanguage()
check if language is system language
global $DIC
Definition: shib_login.php:26
resetUserLanguage(string $lang_key)
search ILIAS for users which have selected &#39;$lang_key&#39; as their prefered language and reset them to d...
install(string $scope="")
install current language
static isUtf8(string $a_str)
Check whether string is utf-8.
refresh()
refresh current language
static replaceLangEntry(string $a_module, string $a_identifier, string $a_lang_key, string $a_value, ?string $a_local_change=null, ?string $a_remarks=null)
Replace lang entry.
$lang
Definition: xapiexit.php:25
$handler
Definition: oai.php:29
check(string $scope="")
Validate the logical structure of a lang file.
__construct(Container $dic, ilPlugin $plugin)
static replaceLangModule(string $a_key, string $a_module, array $a_array)
Replace language module array.
$q
Definition: shib_logout.php:23
flush(string $a_mode="all")
remove language data from database $a_mode "all" or "keep_local"
static refreshAll()
Refresh all installed languages.
isLocal()
Check language object status, and return true if a local language file is installed.
static _getLocalChangesByModule(string $a_key, string $a_module)
Get the local changes of a language module $a_key Language key $a_module Module key Return array iden...
static cut_header(array $content)
remove lang-file haeder information from &#39;$content&#39; This function seeks for a special keyword where t...
__construct(int $a_id=0, bool $a_call_by_reference=false)
Constructor.
static _getLastLocalChange(string $a_key)
get the date of the last local change $a_key language key Return change_date "yyyy-mm-dd hh:mm:ss" ...
setDescription(string $description)
static deleteLangEntry(string $a_module, string $a_identifier, string $a_lang_key)
Delete lang entry.
getKey()
get language key