ILIAS  trunk Revision v13.0_alpha-62-g3b72d83f2e5
InstallLanguage.php
Go to the documentation of this file.
1<?php
2
19declare(strict_types=1);
20
22
30
32{
34
35 public const string MODE_INSTALL = 'install';
36 public const string MODE_INSTALL_LOCAL = 'install_local';
37
38 public function __construct(
39 ?RBAC $rbac,
40 private readonly \ilSetupLanguage $setup_language,
41 LanguageFolder $language_folder,
42 ) {
43 parent::__construct($rbac, $language_folder);
44 }
45
46 public static function forSetup(\ilSetupLanguage $setup_language): self
47 {
48 return new self(
49 null,
50 $setup_language,
51 // never read: without RBAC, isAllowedToPerform() throws first
53 );
54 }
55
57 {
58 return $this->data->text()->markdown()->simpleDocument(
59 <<<'MARKDOWN'
60Installs one or more languages in the ILIAS system, or applies just their
61customizing/local language file, depending on the chosen mode.
62
63Mode "install" fully installs a language that is not yet installed (base
64data, plus a customizing/local file if one exists) and leaves an already
65installed language completely untouched. Mode "install_local" instead
66(re-)applies only the customizing/local file on top of an already installed
67language, without touching its base data, and leaves a not-yet-installed
68language completely untouched.
69
70In both modes, invalid language files (including a shipped `.po`) reject the
71request as a whole before anything is written.
72MARKDOWN
73 );
74 }
75
76 public function getInputDescription(FieldFactory $f): FormInput
77 {
78 $language_keys = $f->text(
79 'Language keys',
80 'Comma-separated list of language keys, e.g. de, fr, it.'
81 )->withRequired(true)->withDedicatedName('language_keys');
82
83 $mode = $f->select(
84 'Mode',
85 [
86 self::MODE_INSTALL => 'Install',
87 self::MODE_INSTALL_LOCAL => 'Install local',
88 ],
89 'Whether to fully install the given languages, or to only ' .
90 '(re-)apply their customizing/local file on top of an existing ' .
91 'installation.'
92 )->withRequired(true)->withDedicatedName('mode');
93
94 return $f->section([
95 'language_keys' => $language_keys,
96 'mode' => $mode,
97 ], 'Install languages');
98 }
99
101 {
102 $text = $this->data->text()->markdown();
103
104 return $f->object(
105 $text->simpleDocument('Result of the language installation.'),
106 [
107 'installed_language_keys' => $f->list(
108 $text->simpleDocument('Newly installed languages without a custom language file.'),
109 $f->string($text->simpleDocument('Language key of a newly installed language.'))
110 ),
111 'installed_with_local_language_keys' => $f->list(
112 $text->simpleDocument(
113 'Languages for which a custom/local language file was (re-)installed - ' .
114 'either as part of a fresh installation (mode "install"), or applied on ' .
115 'top of an already installed language (mode "install_local").'
116 ),
117 $f->string($text->simpleDocument('Language key of a language with an installed custom language file.'))
118 ),
119 'already_installed_language_keys' => $f->list(
120 $text->simpleDocument(
121 'Languages for which this run changed nothing: mode "install" was ' .
122 'requested for a language that was already installed (a no-op by design ' .
123 '- use mode "install_local" to (re-)apply a customizing file instead), ' .
124 'or mode "install_local" was requested for an installed language that ' .
125 'has no customizing/local file to apply.'
126 ),
127 $f->string($text->simpleDocument('Language key of an already installed language.'))
128 ),
129 'not_installed_language_keys' => $f->list(
130 $text->simpleDocument(
131 'Languages requested with mode "install_local" that are not installed - ' .
132 'skipped entirely, since there is nothing installed yet to apply local ' .
133 'changes on top of.'
134 ),
135 $f->string($text->simpleDocument('Language key of a not-yet-installed language.'))
136 ),
137 'invalid_local_language_files' => $f->list(
138 $text->simpleDocument('Local language files with an invalid file name.'),
139 $f->string($text->simpleDocument('File name of an invalid local language file.'))
140 ),
141 'overlay_write_failed_language_keys' => $this->overlayWriteFailedOutputField($f),
142 'invalid_markup_customizing_entries' => $this->invalidMarkupCustomizingOutputField($f),
143 'duplicate_customizing_entries' => $this->duplicateCustomizingOutputField($f),
144 ]
145 );
146 }
147
148 public function perform(mixed $parameters): array
149 {
150 if (!is_array($parameters)) {
151 throw new InvalidInputException('Parameters must be an array.');
152 }
153
154 $language_keys = $this->toLanguageKeyList($parameters['language_keys'] ?? null);
155 $mode = $this->toMode($parameters['mode'] ?? null);
156
157 $currently_installed_language_keys = $this->setup_language->getInstalledLanguages();
158
159 $to_fully_install = [];
160 $to_apply_local_changes = [];
161 $already_installed_no_op = [];
162 $not_installed_no_op = [];
163
164 foreach ($language_keys as $language_key) {
165 $is_installed = in_array($language_key, $currently_installed_language_keys, true);
166 if ($mode === self::MODE_INSTALL) {
167 if ($is_installed) {
168 $already_installed_no_op[] = $language_key;
169 } else {
170 $to_fully_install[] = $language_key;
171 }
172 } elseif ($is_installed) {
173 $to_apply_local_changes[] = $language_key;
174 } else {
175 $not_installed_no_op[] = $language_key;
176 }
177 }
178
179 $error_language_keys = [];
180 // "install_local" merges the shipped .po as well, so an invalid one is refused before writing too
181 foreach (array_merge($to_fully_install, $to_apply_local_changes) as $language_key) {
182 if (!$this->setup_language->checkLanguageForInstallation($language_key)) {
183 $error_language_keys[] = $language_key;
184 }
185 }
186
187 if ($error_language_keys !== []) {
188 // a language without a language file is the caller's mistake, an invalid file the server's
189 $unknown_language_keys = array_values(array_diff(
190 array_intersect($error_language_keys, $to_fully_install),
191 $this->setup_language->getInstallableLanguagesForInstallation()
192 ));
193 if ($unknown_language_keys !== []) {
194 throw new InvalidInputException('Unknown languages: ' . implode(', ', $unknown_language_keys));
195 }
196 throw new InvalidLanguageFilesException($error_language_keys);
197 }
198
199 $installed_language_keys = [];
200 $installed_with_local_language_keys = [];
201 $invalid_local_language_files = [];
202 $overlay_write_failed_language_keys = [];
203
204 $affected_language_keys = array_merge($to_fully_install, $to_apply_local_changes);
205 if ($affected_language_keys !== []) {
206 $db_languages = $this->setup_language->getAvailableLanguagesForInstallation();
207 $local_language_keys = $this->setup_language->getLocalLanguages();
208 $invalid_local_language_files = $this->setup_language->getInvalidLocalLanguageFiles(
209 $affected_language_keys
210 );
211
212 foreach ($to_fully_install as $language_key) {
213 $this->setup_language->flushLanguageForInstallation($language_key);
214 if ($this->setup_language->insertLanguageForInstallation($language_key) !== []) {
215 $overlay_write_failed_language_keys[] = $language_key;
216 }
217 $this->setup_language->registerInstalledLanguage($language_key, $db_languages, $local_language_keys);
218
219 if (in_array($language_key, $local_language_keys, true)) {
220 $installed_with_local_language_keys[] = $language_key;
221 } else {
222 $installed_language_keys[] = $language_key;
223 }
224 }
225
226 foreach ($to_apply_local_changes as $language_key) {
227 if ($this->setup_language->insertLanguageForApplyingLocalChanges($language_key) !== []) {
228 $overlay_write_failed_language_keys[] = $language_key;
229 }
230 $this->setup_language->registerInstalledLanguage($language_key, $db_languages, $local_language_keys);
231
232 if (in_array($language_key, $local_language_keys, true)) {
233 $installed_with_local_language_keys[] = $language_key;
234 } else {
235 // "install_local" had nothing to apply: same as "already installed"
236 $already_installed_no_op[] = $language_key;
237 }
238 }
239 }
240
241 return [
242 'installed_language_keys' => $installed_language_keys,
243 'installed_with_local_language_keys' => $installed_with_local_language_keys,
244 'already_installed_language_keys' => $already_installed_no_op,
245 'not_installed_language_keys' => $not_installed_no_op,
246 'invalid_local_language_files' => $invalid_local_language_files,
247 'overlay_write_failed_language_keys' => $overlay_write_failed_language_keys,
248 'invalid_markup_customizing_entries' => $this->invalidMarkupCustomizingEntries(
249 $this->setup_language->getSkippedInvalidMarkupEntries(),
250 $affected_language_keys
251 ),
252 'duplicate_customizing_entries' => $this->duplicateCustomizingEntries(
253 $this->setup_language->getSkippedDuplicateEntries(),
254 $affected_language_keys
255 ),
256 ];
257 }
258
259 private function toMode(mixed $value): string
260 {
261 if ($value === self::MODE_INSTALL || $value === self::MODE_INSTALL_LOCAL) {
262 return $value;
263 }
264
265 throw new InvalidInputException(
266 'mode must be either "' . self::MODE_INSTALL . '" or "' . self::MODE_INSTALL_LOCAL . '".'
267 );
268 }
269
274 protected function normalizeParameters(array $grind_result): array
275 {
276 return [
277 'language_keys' => $this->toLanguageKeyList($grind_result['language_keys']),
278 'mode' => $this->toMode($grind_result['mode']),
279 ];
280 }
281}
This describes some datastructure in terms of standard data structures such as primitives,...
Definition: Description.php:33
static forSetup(\ilSetupLanguage $setup_language)
__construct(?RBAC $rbac, private readonly \ilSetupLanguage $setup_language, LanguageFolder $language_folder,)
perform(mixed $parameters)
This shall perform the activity.
A state of the server, not of the call: 409, so Failure::of() passes it on and the GUI can name the l...
duplicateCustomizingEntries(array $skipped, array $language_keys)
invalidMarkupCustomizingEntries(array $skipped, array $language_keys)
Looks the ref_id up on every call, so it can be built at build time without database access.
language handling for setup
Public RBAC service of the AccessControl component.
Definition: RBAC.php:30
The language folder in the administration, whose "write" permission guards the language Activities.
This describes inputs that can be used in forms.
Definition: FormInput.php:33
This is what a factory for input fields looks like.
Definition: Factory.php:31
__construct(Container $dic, ilPlugin $plugin)
@inheritDoc
Interface Observer \BackgroundTasks Contains several chained tasks and infos about them.
$text
Definition: xapiexit.php:21