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