[!!!][TASK] Remove config.language_alt and config.language
[Packages/TYPO3.CMS.git] / typo3 / sysext / form / Classes / Service / TranslationService.php
1 <?php
2 declare(strict_types = 1);
3 namespace TYPO3\CMS\Form\Service;
4
5 /*
6 * This file is part of the TYPO3 CMS project.
7 *
8 * It is free software; you can redistribute it and/or modify it under
9 * the terms of the GNU General Public License, either version 2
10 * of the License, or any later version.
11 *
12 * For the full copyright and license information, please read the
13 * LICENSE.txt file that was distributed with this source code.
14 *
15 * The TYPO3 project - inspiring people to share!
16 */
17
18 use Psr\Http\Message\ServerRequestInterface;
19 use TYPO3\CMS\Core\Localization\LanguageService;
20 use TYPO3\CMS\Core\Localization\Locales;
21 use TYPO3\CMS\Core\Localization\LocalizationFactory;
22 use TYPO3\CMS\Core\SingletonInterface;
23 use TYPO3\CMS\Core\Site\Entity\SiteLanguage;
24 use TYPO3\CMS\Core\Utility\ArrayUtility;
25 use TYPO3\CMS\Core\Utility\Exception\MissingArrayPathException;
26 use TYPO3\CMS\Core\Utility\GeneralUtility;
27 use TYPO3\CMS\Extbase\Configuration\ConfigurationManagerInterface;
28 use TYPO3\CMS\Extbase\Object\ObjectManager;
29 use TYPO3\CMS\Form\Domain\Model\FormElements\FormElementInterface;
30 use TYPO3\CMS\Form\Domain\Model\Renderable\RootRenderableInterface;
31 use TYPO3\CMS\Form\Domain\Runtime\FormRuntime;
32
33 /**
34 * Advanced translations
35 * This class is subjected to change.
36 * **Do NOT subclass**
37 *
38 * Scope: frontend / backend
39 * @internal
40 */
41 class TranslationService implements SingletonInterface
42 {
43
44 /**
45 * Local Language content
46 *
47 * @var array
48 */
49 protected $LOCAL_LANG = [];
50
51 /**
52 * Contains those LL keys, which have been set to (empty) in TypoScript.
53 * This is necessary, as we cannot distinguish between a nonexisting
54 * translation and a label that has been cleared by TS.
55 * In both cases ['key'][0]['target'] is "".
56 *
57 * @var array
58 */
59 protected $LOCAL_LANG_UNSET = [];
60
61 /**
62 * Key of the language to use
63 *
64 * @var string
65 */
66 protected $languageKey;
67
68 /**
69 * Pointer to alternative fall-back language to use
70 *
71 * @var array
72 */
73 protected $alternativeLanguageKeys = [];
74
75 /**
76 * @var \TYPO3\CMS\Extbase\Configuration\ConfigurationManagerInterface
77 */
78 protected $configurationManager;
79
80 /**
81 * Return TranslationService as singleton
82 *
83 * @return TranslationService
84 * @internal
85 */
86 public static function getInstance()
87 {
88 return GeneralUtility::makeInstance(ObjectManager::class)->get(self::class);
89 }
90
91 /**
92 * Returns the localized label of the LOCAL_LANG key, $key.
93 *
94 * @param mixed $key The key from the LOCAL_LANG array for which to return the value.
95 * @param array $arguments the arguments of the extension, being passed over to vsprintf
96 * @param string $locallangPathAndFilename
97 * @param string $language
98 * @param mixed $defaultValue
99 * @return mixed The value from LOCAL_LANG or $defaultValue if no translation was found.
100 * @internal
101 */
102 public function translate(
103 $key,
104 array $arguments = null,
105 string $locallangPathAndFilename = null,
106 string $language = null,
107 $defaultValue = ''
108 ) {
109 $value = null;
110 $key = (string)$key;
111
112 if ($locallangPathAndFilename) {
113 $key = $locallangPathAndFilename . ':' . $key;
114 }
115
116 $keyParts = explode(':', $key);
117 if (GeneralUtility::isFirstPartOfStr($key, 'LLL:')) {
118 $locallangPathAndFilename = $keyParts[1] . ':' . $keyParts[2];
119 $key = $keyParts[3];
120 } elseif (GeneralUtility::isFirstPartOfStr($key, 'EXT:')) {
121 $locallangPathAndFilename = $keyParts[0] . ':' . $keyParts[1];
122 $key = $keyParts[2];
123 } else {
124 if (count($keyParts) === 2) {
125 $locallangPathAndFilename = $keyParts[0];
126 $key = $keyParts[1];
127 }
128 }
129
130 if ($language) {
131 $this->languageKey = $language;
132 }
133
134 $this->initializeLocalization($locallangPathAndFilename);
135
136 // The "from" charset of csConv() is only set for strings from TypoScript via _LOCAL_LANG
137 if (!empty($this->LOCAL_LANG[$this->languageKey][$key][0]['target'])
138 || isset($this->LOCAL_LANG_UNSET[$this->languageKey][$key])
139 ) {
140 // Local language translation for key exists
141 $value = $this->LOCAL_LANG[$this->languageKey][$key][0]['target'];
142 } elseif (!empty($this->alternativeLanguageKeys)) {
143 $languages = array_reverse($this->alternativeLanguageKeys);
144 foreach ($languages as $language) {
145 if (!empty($this->LOCAL_LANG[$language][$key][0]['target'])
146 || isset($this->LOCAL_LANG_UNSET[$language][$key])
147 ) {
148 // Alternative language translation for key exists
149 $value = $this->LOCAL_LANG[$language][$key][0]['target'];
150 break;
151 }
152 }
153 }
154
155 if ($value === null && (!empty($this->LOCAL_LANG['default'][$key][0]['target'])
156 || isset($this->LOCAL_LANG_UNSET['default'][$key]))
157 ) {
158 // Default language translation for key exists
159 // No charset conversion because default is English and thereby ASCII
160 $value = $this->LOCAL_LANG['default'][$key][0]['target'];
161 }
162
163 if (is_array($arguments) && !empty($arguments) && $value !== null) {
164 $value = vsprintf($value, $arguments);
165 } else {
166 if (empty($value)) {
167 $value = $defaultValue;
168 }
169 }
170
171 return $value;
172 }
173
174 /**
175 * Recursively translate values.
176 *
177 * @param array $array
178 * @param array $translationFiles
179 * @return array the modified array
180 * @internal
181 */
182 public function translateValuesRecursive(array $array, array $translationFiles = []): array
183 {
184 $result = $array;
185 foreach ($result as $key => $value) {
186 if (is_array($value)) {
187 $result[$key] = $this->translateValuesRecursive($value, $translationFiles);
188 } else {
189 $this->sortArrayWithIntegerKeysDescending($translationFiles);
190
191 if (!empty($translationFiles)) {
192 foreach ($translationFiles as $translationFile) {
193 $translatedValue = $this->translate($value, null, $translationFile, null);
194 if (!empty($translatedValue)) {
195 $result[$key] = $translatedValue;
196 break;
197 }
198 }
199 } else {
200 $result[$key] = $this->translate($value, null, null, null, $value);
201 }
202 }
203 }
204 return $result;
205 }
206
207 /**
208 * @param FormRuntime $formRuntime
209 * @param string $finisherIdentifier
210 * @param string $optionKey
211 * @param string $optionValue
212 * @param array $renderingOptions
213 * @return string
214 * @throws \InvalidArgumentException
215 */
216 public function translateFinisherOption(
217 FormRuntime $formRuntime,
218 string $finisherIdentifier,
219 string $optionKey,
220 string $optionValue,
221 array $renderingOptions = []
222 ): string {
223 if (empty($finisherIdentifier)) {
224 throw new \InvalidArgumentException('The argument "finisherIdentifier" is empty', 1476216059);
225 }
226 if (empty($optionKey)) {
227 throw new \InvalidArgumentException('The argument "optionKey" is empty', 1476216060);
228 }
229
230 $finisherIdentifier = preg_replace('/Finisher$/', '', $finisherIdentifier);
231 $translationFiles = $renderingOptions['translationFiles'] ?? [];
232 if (empty($translationFiles)) {
233 $translationFiles = $formRuntime->getRenderingOptions()['translation']['translationFiles'];
234 }
235
236 $translationFiles = $this->sortArrayWithIntegerKeysDescending($translationFiles);
237
238 if (isset($renderingOptions['translatePropertyValueIfEmpty'])) {
239 $translatePropertyValueIfEmpty = (bool)$renderingOptions['translatePropertyValueIfEmpty'];
240 } else {
241 $translatePropertyValueIfEmpty = true;
242 }
243
244 if (empty($optionValue) && !$translatePropertyValueIfEmpty) {
245 return $optionValue;
246 }
247
248 $language = null;
249 if (isset($renderingOptions['language'])) {
250 $language = $renderingOptions['language'];
251 }
252
253 try {
254 $arguments = ArrayUtility::getValueByPath($renderingOptions['arguments'] ?? [], $optionKey, '.');
255 } catch (MissingArrayPathException $e) {
256 $arguments = [];
257 }
258
259 $originalFormIdentifier = null;
260 if (isset($formRuntime->getRenderingOptions()['_originalIdentifier'])) {
261 $originalFormIdentifier = $formRuntime->getRenderingOptions()['_originalIdentifier'];
262 }
263
264 $translationKeyChain = [];
265 foreach ($translationFiles as $translationFile) {
266 if (!empty($originalFormIdentifier)) {
267 $translationKeyChain[] = sprintf('%s:%s.finisher.%s.%s', $translationFile, $originalFormIdentifier, $finisherIdentifier, $optionKey);
268 }
269 $translationKeyChain[] = sprintf('%s:%s.finisher.%s.%s', $translationFile, $formRuntime->getIdentifier(), $finisherIdentifier, $optionKey);
270 $translationKeyChain[] = sprintf('%s:finisher.%s.%s', $translationFile, $finisherIdentifier, $optionKey);
271 }
272
273 $translatedValue = $this->processTranslationChain($translationKeyChain, $language, $arguments);
274 $translatedValue = empty($translatedValue) ? $optionValue : $translatedValue;
275
276 return $translatedValue;
277 }
278
279 /**
280 * @param RootRenderableInterface $element
281 * @param array $propertyParts
282 * @param FormRuntime $formRuntime
283 * @return string|array
284 * @throws \InvalidArgumentException
285 * @internal
286 */
287 public function translateFormElementValue(
288 RootRenderableInterface $element,
289 array $propertyParts,
290 FormRuntime $formRuntime
291 ) {
292 if (empty($propertyParts)) {
293 throw new \InvalidArgumentException('The argument "propertyParts" is empty', 1476216007);
294 }
295
296 $propertyType = 'properties';
297 $property = implode('.', $propertyParts);
298 $renderingOptions = $element->getRenderingOptions();
299
300 if ($property === 'label') {
301 $defaultValue = $element->getLabel();
302 } else {
303 if ($element instanceof FormElementInterface) {
304 try {
305 $defaultValue = ArrayUtility::getValueByPath($element->getProperties(), $propertyParts, '.');
306 } catch (MissingArrayPathException $exception) {
307 $defaultValue = null;
308 }
309 } else {
310 $propertyType = 'renderingOptions';
311 try {
312 $defaultValue = ArrayUtility::getValueByPath($renderingOptions, $propertyParts, '.');
313 } catch (MissingArrayPathException $exception) {
314 $defaultValue = null;
315 }
316 }
317 }
318
319 if (isset($renderingOptions['translation']['translatePropertyValueIfEmpty'])) {
320 $translatePropertyValueIfEmpty = $renderingOptions['translation']['translatePropertyValueIfEmpty'];
321 } else {
322 $translatePropertyValueIfEmpty = true;
323 }
324
325 if (empty($defaultValue) && !$translatePropertyValueIfEmpty) {
326 return $defaultValue;
327 }
328
329 $defaultValue = empty($defaultValue) ? '' : $defaultValue;
330 $translationFiles = $renderingOptions['translation']['translationFiles'] ?? [];
331 if (empty($translationFiles)) {
332 $translationFiles = $formRuntime->getRenderingOptions()['translation']['translationFiles'];
333 }
334
335 $translationFiles = $this->sortArrayWithIntegerKeysDescending($translationFiles);
336
337 $language = null;
338 if (isset($renderingOptions['translation']['language'])) {
339 $language = $renderingOptions['translation']['language'];
340 }
341
342 try {
343 $arguments = ArrayUtility::getValueByPath($renderingOptions['translation']['arguments'] ?? [], $propertyParts, '.');
344 } catch (MissingArrayPathException $e) {
345 $arguments = [];
346 }
347
348 $originalFormIdentifier = null;
349 if (isset($formRuntime->getRenderingOptions()['_originalIdentifier'])) {
350 $originalFormIdentifier = $formRuntime->getRenderingOptions()['_originalIdentifier'];
351 }
352
353 if ($property === 'options' && is_array($defaultValue)) {
354 foreach ($defaultValue as $optionValue => &$optionLabel) {
355 $translationKeyChain = [];
356 foreach ($translationFiles as $translationFile) {
357 if (!empty($originalFormIdentifier)) {
358 if ($element instanceof FormRuntime) {
359 $translationKeyChain[] = sprintf('%s:%s.element.%s.%s.%s.%s', $translationFile, $originalFormIdentifier, $originalFormIdentifier, $propertyType, $property, $optionValue);
360 $translationKeyChain[] = sprintf('%s:element.%s.%s.%s.%s', $translationFile, $originalFormIdentifier, $propertyType, $property, $optionValue);
361 } else {
362 $translationKeyChain[] = sprintf('%s:%s.element.%s.%s.%s.%s', $translationFile, $originalFormIdentifier, $element->getIdentifier(), $propertyType, $property, $optionValue);
363 }
364 }
365 $translationKeyChain[] = sprintf('%s:%s.element.%s.%s.%s.%s', $translationFile, $formRuntime->getIdentifier(), $element->getIdentifier(), $propertyType, $property, $optionValue);
366 $translationKeyChain[] = sprintf('%s:element.%s.%s.%s.%s', $translationFile, $element->getIdentifier(), $propertyType, $property, $optionValue);
367 $translationKeyChain[] = sprintf('%s:element.%s.%s.%s.%s', $translationFile, $element->getType(), $propertyType, $property, $optionValue);
368 }
369
370 $translatedValue = $this->processTranslationChain($translationKeyChain, $language, $arguments);
371 $optionLabel = empty($translatedValue) ? $optionLabel : $translatedValue;
372 }
373 $translatedValue = $defaultValue;
374 } elseif ($property === 'fluidAdditionalAttributes' && is_array($defaultValue)) {
375 foreach ($defaultValue as $propertyName => &$propertyValue) {
376 $translationKeyChain = [];
377 foreach ($translationFiles as $translationFile) {
378 if (!empty($originalFormIdentifier)) {
379 if ($element instanceof FormRuntime) {
380 $translationKeyChain[] = sprintf('%s:%s.element.%s.%s.%s', $translationFile, $originalFormIdentifier, $originalFormIdentifier, $propertyType, $propertyName);
381 $translationKeyChain[] = sprintf('%s:element.%s.%s.%s', $translationFile, $originalFormIdentifier, $propertyType, $propertyName);
382 } else {
383 $translationKeyChain[] = sprintf('%s:%s.element.%s.%s.%s', $translationFile, $originalFormIdentifier, $element->getIdentifier(), $propertyType, $propertyName);
384 }
385 }
386 $translationKeyChain[] = sprintf('%s:%s.element.%s.%s.%s', $translationFile, $formRuntime->getIdentifier(), $element->getIdentifier(), $propertyType, $propertyName);
387 $translationKeyChain[] = sprintf('%s:element.%s.%s.%s', $translationFile, $element->getIdentifier(), $propertyType, $propertyName);
388 $translationKeyChain[] = sprintf('%s:element.%s.%s.%s', $translationFile, $element->getType(), $propertyType, $propertyName);
389 }
390
391 $translatedValue = $this->processTranslationChain($translationKeyChain, $language, $arguments);
392 $propertyValue = empty($translatedValue) ? $propertyValue : $translatedValue;
393 }
394 $translatedValue = $defaultValue;
395 } else {
396 $translationKeyChain = [];
397 foreach ($translationFiles as $translationFile) {
398 if (!empty($originalFormIdentifier)) {
399 if ($element instanceof FormRuntime) {
400 $translationKeyChain[] = sprintf('%s:%s.element.%s.%s.%s', $translationFile, $originalFormIdentifier, $originalFormIdentifier, $propertyType, $property);
401 $translationKeyChain[] = sprintf('%s:element.%s.%s.%s', $translationFile, $originalFormIdentifier, $propertyType, $property);
402 } else {
403 $translationKeyChain[] = sprintf('%s:%s.element.%s.%s.%s', $translationFile, $originalFormIdentifier, $element->getIdentifier(), $propertyType, $property);
404 }
405 }
406 $translationKeyChain[] = sprintf('%s:%s.element.%s.%s.%s', $translationFile, $formRuntime->getIdentifier(), $element->getIdentifier(), $propertyType, $property);
407 $translationKeyChain[] = sprintf('%s:element.%s.%s.%s', $translationFile, $element->getIdentifier(), $propertyType, $property);
408 $translationKeyChain[] = sprintf('%s:element.%s.%s.%s', $translationFile, $element->getType(), $propertyType, $property);
409 }
410
411 $translatedValue = $this->processTranslationChain($translationKeyChain, $language, $arguments);
412 $translatedValue = empty($translatedValue) ? $defaultValue : $translatedValue;
413 }
414
415 return $translatedValue;
416 }
417
418 /**
419 * @param RootRenderableInterface $element
420 * @param int $code
421 * @param string $defaultValue
422 * @param array $arguments
423 * @param FormRuntime $formRuntime
424 * @return string
425 * @throws \InvalidArgumentException
426 * @internal
427 */
428 public function translateFormElementError(
429 RootRenderableInterface $element,
430 int $code,
431 array $arguments,
432 string $defaultValue = '',
433 FormRuntime $formRuntime
434 ): string {
435 if (empty($code)) {
436 throw new \InvalidArgumentException('The argument "code" is empty', 1489272978);
437 }
438
439 $validationErrors = $element->getProperties()['validationErrorMessages'] ?? null;
440 if (is_array($validationErrors)) {
441 foreach ($validationErrors as $validationError) {
442 if ((int)$validationError['code'] === $code) {
443 return sprintf($validationError['message'], $arguments);
444 }
445 }
446 }
447
448 $renderingOptions = $element->getRenderingOptions();
449 $translationFiles = $renderingOptions['translation']['translationFiles'] ?? [];
450 if (empty($translationFiles)) {
451 $translationFiles = $formRuntime->getRenderingOptions()['translation']['translationFiles'];
452 }
453
454 $translationFiles = $this->sortArrayWithIntegerKeysDescending($translationFiles);
455
456 $language = null;
457 if (isset($renderingOptions['language'])) {
458 $language = $renderingOptions['language'];
459 }
460
461 $originalFormIdentifier = null;
462 if (isset($formRuntime->getRenderingOptions()['_originalIdentifier'])) {
463 $originalFormIdentifier = $formRuntime->getRenderingOptions()['_originalIdentifier'];
464 }
465
466 $translationKeyChain = [];
467 foreach ($translationFiles as $translationFile) {
468 if (!empty($originalFormIdentifier)) {
469 if ($element instanceof FormRuntime) {
470 $translationKeyChain[] = sprintf('%s:%s.validation.error.%s.%s', $translationFile, $originalFormIdentifier, $originalFormIdentifier, $code);
471 $translationKeyChain[] = sprintf('%s:validation.error.%s.%s', $translationFile, $originalFormIdentifier, $code);
472 } else {
473 $translationKeyChain[] = sprintf('%s:%s.validation.error.%s.%s', $translationFile, $originalFormIdentifier, $element->getIdentifier(), $code);
474 }
475 $translationKeyChain[] = sprintf('%s:%s.validation.error.%s', $translationFile, $originalFormIdentifier, $code);
476 }
477 $translationKeyChain[] = sprintf('%s:%s.validation.error.%s.%s', $translationFile, $formRuntime->getIdentifier(), $element->getIdentifier(), $code);
478 $translationKeyChain[] = sprintf('%s:%s.validation.error.%s', $translationFile, $formRuntime->getIdentifier(), $code);
479 $translationKeyChain[] = sprintf('%s:validation.error.%s.%s', $translationFile, $element->getIdentifier(), $code);
480 $translationKeyChain[] = sprintf('%s:validation.error.%s', $translationFile, $code);
481 }
482
483 $translatedValue = $this->processTranslationChain($translationKeyChain, $language, $arguments);
484 $translatedValue = empty($translatedValue) ? $defaultValue : $translatedValue;
485 return $translatedValue;
486 }
487
488 /**
489 * @param string $languageKey
490 * @internal
491 */
492 public function setLanguage(string $languageKey)
493 {
494 $this->languageKey = $languageKey;
495 }
496
497 /**
498 * @return string
499 * @internal
500 */
501 public function getLanguage(): string
502 {
503 return $this->languageKey;
504 }
505
506 /**
507 * @param array $translationKeyChain
508 * @param string $language
509 * @param array $arguments
510 * @return string|null
511 */
512 protected function processTranslationChain(
513 array $translationKeyChain,
514 string $language = null,
515 array $arguments = null
516 ) {
517 $translatedValue = null;
518 foreach ($translationKeyChain as $translationKey) {
519 $translatedValue = $this->translate($translationKey, $arguments, null, $language);
520 if (!empty($translatedValue)) {
521 break;
522 }
523 }
524 return $translatedValue;
525 }
526
527 /**
528 * @param string $locallangPathAndFilename
529 */
530 protected function initializeLocalization(string $locallangPathAndFilename)
531 {
532 if (empty($this->languageKey)) {
533 $this->setLanguageKeys();
534 }
535
536 if (!empty($locallangPathAndFilename)) {
537 /** @var LocalizationFactory $languageFactory */
538 $languageFactory = GeneralUtility::makeInstance(LocalizationFactory::class);
539 $this->LOCAL_LANG = $languageFactory->getParsedData($locallangPathAndFilename, $this->languageKey);
540
541 foreach ($this->alternativeLanguageKeys as $language) {
542 $tempLL = $languageFactory->getParsedData($locallangPathAndFilename, $language);
543 if ($this->languageKey !== 'default' && isset($tempLL[$language])) {
544 $this->LOCAL_LANG[$language] = $tempLL[$language];
545 }
546 }
547 }
548 $this->loadTypoScriptLabels();
549 }
550
551 /**
552 * Sets the currently active language keys.
553 */
554 protected function setLanguageKeys()
555 {
556 $this->languageKey = 'default';
557
558 $this->alternativeLanguageKeys = [];
559 if (TYPO3_MODE === 'FE') {
560 $this->languageKey = $this->getCurrentSiteLanguage()->getTypo3Language();
561
562 if ($this->languageKey !== 'default') {
563 /** @var \TYPO3\CMS\Core\Localization\Locales $locales */
564 $locales = GeneralUtility::makeInstance(Locales::class);
565 if (in_array($this->languageKey, $locales->getLocales(), true)) {
566 foreach ($locales->getLocaleDependencies($this->languageKey) as $language) {
567 $this->alternativeLanguageKeys[] = $language;
568 }
569 }
570 }
571 } elseif (!empty($GLOBALS['BE_USER']->uc['lang'])) {
572 $this->languageKey = $GLOBALS['BE_USER']->uc['lang'];
573 } elseif (!empty($this->getLanguageService()->lang)) {
574 $this->languageKey = $this->getLanguageService()->lang;
575 }
576 }
577
578 /**
579 * Overwrites labels that are set via TypoScript.
580 * TS locallang labels have to be configured like:
581 * plugin.tx_form._LOCAL_LANG.languageKey.key = value
582 */
583 protected function loadTypoScriptLabels()
584 {
585 $frameworkConfiguration = $this->getConfigurationManager()
586 ->getConfiguration(ConfigurationManagerInterface::CONFIGURATION_TYPE_FRAMEWORK, 'form');
587
588 if (!is_array($frameworkConfiguration['_LOCAL_LANG'])) {
589 return;
590 }
591 $this->LOCAL_LANG_UNSET = [];
592 foreach ($frameworkConfiguration['_LOCAL_LANG'] as $languageKey => $labels) {
593 if (!(is_array($labels) && isset($this->LOCAL_LANG[$languageKey]))) {
594 continue;
595 }
596 foreach ($labels as $labelKey => $labelValue) {
597 if (is_string($labelValue)) {
598 $this->LOCAL_LANG[$languageKey][$labelKey][0]['target'] = $labelValue;
599 if ($labelValue === '') {
600 $this->LOCAL_LANG_UNSET[$languageKey][$labelKey] = '';
601 }
602 } elseif (is_array($labelValue)) {
603 $labelValue = $this->flattenTypoScriptLabelArray($labelValue, $labelKey);
604 foreach ($labelValue as $key => $value) {
605 $this->LOCAL_LANG[$languageKey][$key][0]['target'] = $value;
606 if ($value === '') {
607 $this->LOCAL_LANG_UNSET[$languageKey][$key] = '';
608 }
609 }
610 }
611 }
612 }
613 }
614
615 /**
616 * Flatten TypoScript label array; converting a hierarchical array into a flat
617 * array with the keys separated by dots.
618 *
619 * Example Input: array('k1' => array('subkey1' => 'val1'))
620 * Example Output: array('k1.subkey1' => 'val1')
621 *
622 * @param array $labelValues Hierarchical array of labels
623 * @param string $parentKey the name of the parent key in the recursion; is only needed for recursion.
624 * @return array flattened array of labels.
625 */
626 protected function flattenTypoScriptLabelArray(array $labelValues, string $parentKey = ''): array
627 {
628 $result = [];
629 foreach ($labelValues as $key => $labelValue) {
630 if (!empty($parentKey)) {
631 $key = $parentKey . '.' . $key;
632 }
633 if (is_array($labelValue)) {
634 $labelValue = $this->flattenTypoScriptLabelArray($labelValue, $key);
635 $result = array_merge($result, $labelValue);
636 } else {
637 $result[$key] = $labelValue;
638 }
639 }
640 return $result;
641 }
642
643 /**
644 * If the array contains numerical keys only, sort it in descending order
645 *
646 * @param array $array
647 * @return array
648 */
649 protected function sortArrayWithIntegerKeysDescending(array $array)
650 {
651 if (count(array_filter(array_keys($array), 'is_string')) === 0) {
652 krsort($array);
653 }
654 return $array;
655 }
656
657 /**
658 * Returns instance of the configuration manager
659 *
660 * @return ConfigurationManagerInterface
661 */
662 protected function getConfigurationManager(): ConfigurationManagerInterface
663 {
664 if ($this->configurationManager !== null) {
665 return $this->configurationManager;
666 }
667
668 $this->configurationManager = GeneralUtility::makeInstance(ObjectManager::class)
669 ->get(ConfigurationManagerInterface::class);
670 return $this->configurationManager;
671 }
672
673 /**
674 * Returns the currently configured "site language" if a site is configured (= resolved) in the current request.
675 *
676 * @return SiteLanguage|null
677 */
678 protected function getCurrentSiteLanguage(): ?SiteLanguage
679 {
680 if ($GLOBALS['TYPO3_REQUEST'] instanceof ServerRequestInterface) {
681 return $GLOBALS['TYPO3_REQUEST']->getAttribute('language', null);
682 }
683 return null;
684 }
685
686 /**
687 * @return LanguageService
688 */
689 protected function getLanguageService(): LanguageService
690 {
691 return $GLOBALS['LANG'];
692 }
693 }