ILIAS  trunk Revision v12.0_alpha-377-g3641b37b9db
class.ilObjLanguage.php
Go to the documentation of this file.
1<?php
2
19declare(strict_types=1);
20
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 static function getLangKeysOfInstalledLanguages(): array
95 {
96 $lang_keys = [];
97 foreach (ilObject::_getObjectsByType("lng") as $lang) {
98 if ($lang['desc'] === 'installed') {
99 $lang_keys[] = $lang['title'];
100 }
101 }
102 return $lang_keys;
103 }
104
105
111 public function getKey(): string
112 {
113 return $this->key;
114 }
115
121 public function getStatus(): string
122 {
123 return $this->status;
124 }
125
129 public function isSystemLanguage(): bool
130 {
131 if ($this->key == $this->lang_default) {
132 return true;
133 } else {
134 return false;
135 }
136 }
137
141 public function isUserLanguage(): bool
142 {
143 if ($this->key == $this->lang_user) {
144 return true;
145 } else {
146 return false;
147 }
148 }
149
155 public function isInstalled(): bool
156 {
157 if (str_starts_with($this->getStatus(), "installed")) {
158 return true;
159 } else {
160 return false;
161 }
162 }
163
170 public function isLocal(): bool
171 {
172 if (substr($this->getStatus(), 10) === "local") {
173 return true;
174 } else {
175 return false;
176 }
177 }
178
185 public function install(string $scope = ""): string
186 {
187 if (!empty($scope)) {
188 if ($scope === "global") {
189 $scope = "";
190 } else {
191 $scopeExtension = "." . $scope;
192 }
193 }
194
195 if (!$this->isInstalled() || (!$this->isLocal() && !empty($scope))) {
196 if ($this->check($scope)) {
197 // lang-file is ok. Flush data in db and...
198 if (empty($scope)) {
199 $this->flush("keep_local");
200 }
201
202 // ...re-insert data from lang-file
203 $this->insert($scope);
204
205 // update information in db-table about available/installed languages
206 $newDesc = '';
207 if (empty($scope)) {
208 $newDesc = "installed";
209 } elseif ($scope === "local") {
210 $newDesc = "installed_local";
211 }
212 $this->setDescription($newDesc);
213 $this->update();
214 return $this->getKey();
215 }
216 }
217 return "";
218 }
219
220
226 public function uninstall(): string
227 {
228 if ((str_starts_with($this->status, "installed")) && ($this->key != $this->lang_default) && ($this->key != $this->lang_user)) {
229 $this->flush();
230 $this->setTitle($this->key);
231 $this->setDescription("not_installed");
232 $this->update();
233 $this->resetUserLanguage($this->key);
234
235 return $this->key;
236 }
237 return "";
238 }
239
240
244 public function refresh(): bool
245 {
246 if ($this->isInstalled() && $this->check()) {
247 $this->flush("keep_local");
248 $this->insert();
249 $this->setTitle($this->getKey());
250 $this->setDescription($this->getStatus());
251 $this->update();
252
253 if ($this->isLocal() && $this->check("local")) {
254 $this->insert("local");
255 $this->setTitle($this->getKey());
256 $this->setDescription($this->getStatus());
257 $this->update();
258 }
259
260 return true;
261 }
262
263 return false;
264 }
265
269 public static function refreshAll(): void
270 {
271 $languages = ilObject::_getObjectsByType("lng");
272 $refreshed = array();
273
274 foreach ($languages as $lang) {
275 $langObj = new ilObjLanguage($lang["obj_id"], false);
276 if ($langObj->refresh()) {
277 $refreshed[] = $langObj->getKey();
278 }
279 unset($langObj);
280 }
281
282 self::refreshPlugins($refreshed);
283 }
284
285
290 public static function refreshPlugins(?array $a_lang_keys = null): void
291 {
292 global $DIC;
293
294 $component_repository = $DIC["component.repository"];
295 foreach ($component_repository->getPlugins() as $plugin) {
296 if (!$plugin->isActive()) {
297 continue;
298 }
300 $handler->updateLanguages($a_lang_keys);
301 }
302 }
303
304
309 public static function _deleteLangData(string $a_lang_key, bool $a_keep_local_change = false): void
310 {
311 global $DIC;
312 $ilDB = $DIC->database();
313
314 if (!$a_keep_local_change) {
315 $ilDB->manipulate("DELETE FROM lng_data WHERE lang_key = " .
316 $ilDB->quote($a_lang_key, "text"));
317 } else {
318 $ilDB->manipulate("DELETE FROM lng_data WHERE lang_key = " .
319 $ilDB->quote($a_lang_key, "text") .
320 " AND local_change IS NULL");
321 }
322 }
323
328 public function flush(string $a_mode = "all"): void
329 {
330 global $DIC;
331 $ilDB = $DIC->database();
332
333 self::_deleteLangData($this->key, ($a_mode === "keep_local"));
334
335 if ($a_mode === "all") {
336 $ilDB->manipulate("DELETE FROM lng_modules WHERE lang_key = " .
337 $ilDB->quote($this->key, "text"));
338 }
339 }
340
341
348 public function getLocalChanges(string $a_min_date = "", string $a_max_date = ""): array
349 {
350 global $DIC;
351 $ilDB = $DIC->database();
352
353 if ($a_min_date === "") {
354 $a_min_date = "1980-01-01 00:00:00";
355 }
356 if ($a_max_date === "") {
357 $a_max_date = "2200-01-01 00:00:00";
358 }
359
360 $q = sprintf(
361 "SELECT * FROM lng_data WHERE lang_key = %s " .
362 "AND local_change >= %s AND local_change <= %s",
363 $ilDB->quote($this->key, "text"),
364 $ilDB->quote($a_min_date, "timestamp"),
365 $ilDB->quote($a_max_date, "timestamp")
366 );
367 $result = $ilDB->query($q);
368
369 $changes = array();
370 while ($row = $result->fetchRow(ilDBConstants::FETCHMODE_ASSOC)) {
371 $changes[$row["module"]][$row["identifier"]] = $row["value"];
372 }
373 return $changes;
374 }
375
376
382 public static function _getLastLocalChange(string $a_key): string
383 {
384 global $DIC;
385 $ilDB = $DIC->database();
386
387 $q = sprintf(
388 "SELECT MAX(local_change) last_change FROM lng_data " .
389 "WHERE lang_key = %s AND local_change IS NOT NULL",
390 $ilDB->quote($a_key, "text")
391 );
392 $result = $ilDB->query($q);
393
394 if ($row = $result->fetchRow(ilDBConstants::FETCHMODE_ASSOC)) {
395 return (string) $row["last_change"];
396 } else {
397 return "";
398 }
399 }
400
401
408 public static function _getLocalChangesByModule(string $a_key, string $a_module): array
409 {
410 global $DIC;
411 $ilDB = $DIC->database();
412
413 $changes = array();
414 $result = $ilDB->queryF(
415 "SELECT * FROM lng_data WHERE lang_key = %s AND module = %s AND local_change IS NOT NULL",
416 array("text", "text"),
417 array($a_key, $a_module)
418 );
419
420 while ($row = $ilDB->fetchAssoc($result)) {
421 $changes[$row["identifier"]] = $row["value"];
422 }
423 return $changes;
424 }
425
426
432 public function insert(string $scope = ""): void
433 {
434 global $DIC;
435 $ilDB = $DIC->database();
436 $scopeExtension = "";
437 if (!empty($scope)) {
438 if ($scope === "global") {
439 $scope = "";
440 } else {
441 $scopeExtension = "." . $scope;
442 }
443 }
444
446 if ($scope === "local") {
448 }
449
450 $lang_file = $path . "/ilias_" . $this->key . ".lang" . $scopeExtension;
451
452 if (is_file($lang_file)) {
453 // remove header first
454 if ($content = self::cut_header(file($lang_file))) {
455 $local_changes = null;
456 if (empty($scope)) {
457 // get all local changes for a global file
458 $local_changes = $this->getLocalChanges();
459 } elseif ($scope === "local") {
460 // get the modification date of the local file
461 // get the newer local changes for a local file
462 $min_date = gmdate("Y-m-d H:i:s", filemtime($lang_file));
463 $local_changes = $this->getLocalChanges($min_date);
464 }
465 $dbAccess = new ilObjLanguageDBAccess($ilDB, $this->key, $content, $local_changes, $scope);
466 $lang_array = $dbAccess->insertLangEntries($lang_file);
467 $dbAccess->replaceLangModules($lang_array);
468 }
469 }
470 }
471
475 final public static function replaceLangModule(string $a_key, string $a_module, array $a_array): void
476 {
477 global $DIC;
478 $ilDB = $DIC->database();
479
480 // avoid flushing the whole cache (see mantis #28818)
481 ilCachedLanguage::getInstance($a_key)->deleteInCache();
482
483 $ilDB->manipulate(sprintf(
484 "DELETE FROM lng_modules WHERE lang_key = %s AND module = %s",
485 $ilDB->quote($a_key, "text"),
486 $ilDB->quote($a_module, "text")
487 ));
488
489 /*$ilDB->manipulate(sprintf("INSERT INTO lng_modules (lang_key, module, lang_array) VALUES ".
490 "(%s,%s,%s)", $ilDB->quote($a_key, "text"),
491 $ilDB->quote($a_module, "text"),
492 $ilDB->quote(serialize($a_array), "clob")));*/
493 $ilDB->insert("lng_modules", array(
494 "lang_key" => array("text", $a_key),
495 "module" => array("text", $a_module),
496 "lang_array" => array("clob", serialize($a_array))
497 ));
498
499 // check if the module is correctly saved
500 // see mantis #20046 and #19140
501 $result = $ilDB->queryF(
502 "SELECT lang_array FROM lng_modules WHERE lang_key = %s AND module = %s",
503 array("text","text"),
504 array($a_key, $a_module)
505 );
506 $row = $ilDB->fetchAssoc($result);
507
508 $unserialied = unserialize($row["lang_array"], ["allowed_classes" => false]);
509 if (!is_array($unserialied)) {
510 $DIC->ui()->mainTemplate()->setOnScreenMessage(
511 'failure',
512 "Data for module '" . $a_module . "' of language '" . $a_key . "' is not correctly saved. " .
513 "Please check the collation of your database tables lng_data and lng_modules. It must be utf8_unicode_ci.",
514 true
515 );
516 $DIC->ctrl()->redirectByClass(ilobjlanguagefoldergui::class, 'view');
517 }
518 }
519
523 final public static function replaceLangEntry(
524 string $a_module,
525 string $a_identifier,
526 string $a_lang_key,
527 string $a_value,
528 ?string $a_local_change = null,
529 ?string $a_remarks = null
530 ): bool {
531 global $DIC;
532 $ilDB = $DIC->database();
533
534 // avoid a cache flush here (see mantis #28818)
535 // ilGlobalCache::flushAll();
536
537 if (is_string($a_remarks) && $a_remarks !== '') {
538 $a_remarks = substr($a_remarks, 0, 250);
539 }
540
541 if ($a_remarks === '') {
542 $a_remarks = null;
543 }
544
545 if ($a_value === "") {
546 $a_value = null;
547 } else {
548 $a_value = substr($a_value, 0, 4000);
549 }
550
551 $ilDB->replace(
552 "lng_data",
553 array(
554 "module" => array("text",$a_module),
555 "identifier" => array("text",$a_identifier),
556 "lang_key" => array("text",$a_lang_key)
557 ),
558 array(
559 "value" => array("text",$a_value),
560 "local_change" => array("timestamp",$a_local_change),
561 "remarks" => array("text", $a_remarks)
562 )
563 );
564 return true;
565 }
566
570 final public static function updateLangEntry(
571 string $a_module,
572 string $a_identifier,
573 string $a_lang_key,
574 string $a_value,
575 ?string $a_local_change = null,
576 ?string $a_remarks = null
577 ): void {
578 global $DIC;
579 $ilDB = $DIC->database();
580
581 if (is_string($a_remarks) && $a_remarks !== '') {
582 $a_remarks = substr($a_remarks, 0, 250);
583 }
584
585 if ($a_remarks === '') {
586 $a_remarks = null;
587 }
588
589 if ($a_value === "") {
590 $a_value = null;
591 } else {
592 $a_value = substr($a_value, 0, 4000);
593 }
594
595 $ilDB->manipulate(sprintf(
596 "UPDATE lng_data " .
597 "SET value = %s, local_change = %s, remarks = %s " .
598 "WHERE module = %s AND identifier = %s AND lang_key = %s ",
599 $ilDB->quote($a_value, "text"),
600 $ilDB->quote($a_local_change, "timestamp"),
601 $ilDB->quote($a_remarks, "text"),
602 $ilDB->quote($a_module, "text"),
603 $ilDB->quote($a_identifier, "text"),
604 $ilDB->quote($a_lang_key, "text")
605 ));
606 }
607
608
612 final public static function deleteLangEntry(string $a_module, string $a_identifier, string $a_lang_key): bool
613 {
614 global $DIC;
615 $ilDB = $DIC->database();
616
617 $ilDB->manipulate(sprintf(
618 "DELETE FROM lng_data " .
619 "WHERE module = %s AND identifier = %s AND lang_key = %s ",
620 $ilDB->quote($a_module, "text"),
621 $ilDB->quote($a_identifier, "text"),
622 $ilDB->quote($a_lang_key, "text")
623 ));
624
625 return true;
626 }
627
628
635 public function resetUserLanguage(string $lang_key): void
636 {
637 global $DIC;
638 $ilDB = $DIC->database();
639
640 $query = "UPDATE usr_pref SET " .
641 "value = " . $ilDB->quote($this->lang_default, "text") . " " .
642 "WHERE keyword = " . $ilDB->quote('language', "text") . " " .
643 "AND value = " . $ilDB->quote($lang_key, "text");
644 $ilDB->manipulate($query);
645 }
646
656 public static function cut_header(array $content)
657 {
658 foreach ($content as $key => $val) {
659 if (trim($val) === "<!-- language file start -->") {
660 return array_slice($content, $key + 1);
661 }
662 }
663
664 return false;
665 }
666
673 public function optimizeData(): bool
674 {
675 // Mantis #22313: removed table optimization
676 return true;
677 }
678
688 public function check(string $scope = ""): bool
689 {
690 global $DIC;
691 $scopeExtension = "";
692 if (!empty($scope)) {
693 if ($scope === "global") {
694 $scope = "";
695 } else {
696 $scopeExtension = "." . $scope;
697 }
698 }
699
700 $path = $this->lang_path;
701 if ($scope === "local") {
702 $path = $this->cust_lang_path;
703 }
704
705 $tmpPath = getcwd();
706
707 // dir check
708 if (!is_dir($path)) {
709 $DIC->ui()->mainTemplate()->setOnScreenMessage(
710 'failure',
711 "Directory not found: " . $path,
712 true
713 );
714 $DIC->ctrl()->redirectByClass(ilobjlanguagefoldergui::class, 'view');
715 }
716
717 chdir($path);
718
719 // compute lang-file name format
720 $lang_file = "ilias_" . $this->key . ".lang" . $scopeExtension;
721
722 // file check
723 if (!is_file($lang_file)) {
724 $DIC->ui()->mainTemplate()->setOnScreenMessage(
725 'failure',
726 "File not found: " . $lang_file,
727 true
728 );
729 $DIC->ctrl()->redirectByClass(ilobjlanguagefoldergui::class, 'view');
730 }
731
732 // header check
733 $content = self::cut_header(file($lang_file));
734 if ($content === false) {
735 $DIC->ui()->mainTemplate()->setOnScreenMessage(
736 'failure',
737 "Wrong Header in " . $lang_file,
738 true
739 );
740 $DIC->ctrl()->redirectByClass(ilobjlanguagefoldergui::class, 'view');
741 }
742
743 // check (counting) elements of each lang-entry
744 $line = 0;
745 $n = 0;
746 foreach ($content as $key => $val) {
747 $separated = explode($this->separator, trim($val));
748 $num = count($separated);
749 ++$n;
750 if ($num !== 3) {
751 $line = $n + 36;
752 $DIC->ui()->mainTemplate()->setOnScreenMessage(
753 'failure',
754 "Wrong parameter count in " . $lang_file . " in line $line (Value: $val)! Please check your language file!",
755 true
756 );
757 $DIC->ctrl()->redirectByClass(ilobjlanguagefoldergui::class, 'view');
758 }
759 if (!ilStr::isUtf8($separated[2])) {
760 $DIC->ui()->mainTemplate()->setOnScreenMessage(
761 'failure',
762 "Non UTF8 character found in " . $lang_file . " in line $line (Value: $val)! Please check your language file!",
763 true
764 );
765 $DIC->ctrl()->redirectByClass(ilobjlanguagefoldergui::class, 'view');
766 }
767 }
768
769 chdir($tmpPath);
770
771 // no error occured
772 return true;
773 }
774
778 public static function countUsers(string $a_lang): int
779 {
780 global $DIC;
781 $ilDB = $DIC->database();
782 $lng = $DIC->language();
783
784 $set = $ilDB->query("SELECT COUNT(*) cnt FROM usr_data ud JOIN usr_pref up" .
785 " ON ud.usr_id = up.usr_id " .
786 " WHERE up.value = " . $ilDB->quote($a_lang, "text") .
787 " AND up.keyword = " . $ilDB->quote("language", "text"));
788 $rec = $ilDB->fetchAssoc($set);
789
790 // add users with no usr_pref set to default language
791 if ($a_lang == $lng->lang_default) {
792 $set2 = $ilDB->query("SELECT COUNT(*) cnt FROM usr_data ud LEFT JOIN usr_pref up" .
793 " ON (ud.usr_id = up.usr_id AND up.keyword = " . $ilDB->quote("language", "text") . ")" .
794 " WHERE up.value IS NULL ");
795 $rec2 = $ilDB->fetchAssoc($set2);
796 }
797
798 return (int) $rec["cnt"] + (int) ($rec2["cnt"] ?? 0);
799 }
800} // END class.LanguageObject
Class ilObjLanguage.
flush(string $a_mode="all")
remove language data from database $a_mode "all" or "keep_local"
static _deleteLangData(string $a_lang_key, bool $a_keep_local_change=false)
Delete languge data $a_lang_key lang key.
static deleteLangEntry(string $a_module, string $a_identifier, string $a_lang_key)
Delete lang entry.
resetUserLanguage(string $lang_key)
search ILIAS for users which have selected '$lang_key' as their prefered language and reset them to d...
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.
string $separator
separator of module, comment separator, identifier & values in language files
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"
static refreshAll()
Refresh all installed languages.
isInstalled()
Check language object status, and return true if language is installed.
uninstall()
uninstall current language
getKey()
get language key
getStatus()
get language status
isLocal()
Check language object status, and return true if a local language file is installed.
static getInstalledLanguages()
Get the language objects of the installed languages.
install(string $scope="")
install current language
check(string $scope="")
Validate the logical structure of a lang file.
static getLangKeysOfInstalledLanguages()
Return the language keys of the installed languages.
isSystemLanguage()
check if language is system language
isUserLanguage()
check if language is system language
static refreshPlugins(?array $a_lang_keys=null)
Refresh languages of activated plugins $a_lang_keys keys of languages to be refreshed (not yet suppor...
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.
static replaceLangModule(string $a_key, string $a_module, array $a_array)
Replace language module array.
insert(string $scope="")
insert language data from file into database
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...
refresh()
refresh current language
static cut_header(array $content)
remove lang-file haeder information from '$content' This function seeks for a special keyword where t...
static countUsers(string $a_lang)
Count number of users that use a language.
optimizeData()
optimizes the db-table langdata
__construct(int $a_id=0, bool $a_call_by_reference=false)
Constructor.
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...
Class ilObject Basic functions for all objects.
string $desc
string $title
ilLanguage $lng
setTitle(string $title)
static _getObjectsByType(string $obj_type="", ?int $owner=null)
setDescription(string $description)
static isUtf8(string $a_str)
Check whether string is utf-8.
$scope
Definition: ltiregstart.php:51
$path
Definition: ltiservices.php:30
__construct(Container $dic, ilPlugin $plugin)
@inheritDoc
$handler
Definition: oai.php:29
global $lng
Definition: privfeed.php:31
global $DIC
Definition: shib_login.php:26
$q
Definition: shib_logout.php:23