Completed
Push — master ( e15776...fcbc68 )
by
unknown
14:17
created

LanguageService::loadSingleTableDescription()   C

Complexity

Conditions 14
Paths 36

Size

Total Lines 49
Code Lines 26

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 14
eloc 26
nc 36
nop 1
dl 0
loc 49
rs 6.2666
c 0
b 0
f 0

How to fix   Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
/*
4
 * This file is part of the TYPO3 CMS project.
5
 *
6
 * It is free software; you can redistribute it and/or modify it under
7
 * the terms of the GNU General Public License, either version 2
8
 * of the License, or any later version.
9
 *
10
 * For the full copyright and license information, please read the
11
 * LICENSE.txt file that was distributed with this source code.
12
 *
13
 * The TYPO3 project - inspiring people to share!
14
 */
15
16
namespace TYPO3\CMS\Core\Localization;
17
18
use TYPO3\CMS\Core\Authentication\AbstractUserAuthentication;
19
use TYPO3\CMS\Core\Site\Entity\SiteLanguage;
20
use TYPO3\CMS\Core\Utility\ArrayUtility;
21
use TYPO3\CMS\Core\Utility\GeneralUtility;
22
23
/**
24
 * Main API to fetch labels from XLF (label files) based on the current system
25
 * language of TYPO3. It is able to resolve references to files + their pointers to the
26
 * proper language. If you see something about "LLL", this class does the trick for you. It
27
 * is not related for language handling of content, but rather of labels for plugins.
28
 *
29
 * Usually this is injected into $GLOBALS['LANG'] when in backend or CLI context, and
30
 * populated by the current backend user. Don't rely on $GLOBAL['LANG'] in frontend, as it is only
31
 * available in certain circumstances!
32
 * In Frontend, this is also used to translate "labels", see TypoScriptFrontendController->sL()
33
 * for that.
34
 *
35
 * As TYPO3 internally does not match the proper ISO locale standard, the "locale" here
36
 * is actually a list of supported language keys, (see Locales class), whereas "english"
37
 * has the language key "default".
38
 *
39
 * For detailed information about how localization is handled,
40
 * please refer to the 'Inside TYPO3' document which describes this.
41
 */
42
class LanguageService
43
{
44
    /**
45
     * This is set to the language that is currently running for the user
46
     *
47
     * @var string
48
     */
49
    public $lang = 'default';
50
51
    /**
52
     * If TRUE, will show the key/location of labels in the backend.
53
     *
54
     * @var bool
55
     */
56
    public $debugKey = false;
57
58
    /**
59
     * Internal cache for read LL-files
60
     *
61
     * @var array
62
     */
63
    protected $LL_files_cache = [];
64
65
    /**
66
     * Internal cache for ll-labels (filled as labels are requested)
67
     *
68
     * @var array
69
     */
70
    protected $LL_labels_cache = [];
71
72
    /**
73
     * List of language dependencies for actual language. This is used for local variants of a language
74
     * that depend on their "main" language, like Brazilian Portuguese or Canadian French.
75
     *
76
     * @var array
77
     */
78
    protected $languageDependencies = [];
79
80
    /**
81
     * An internal cache for storing loaded files, see readLLfile()
82
     *
83
     * @var array
84
     */
85
    protected $languageFileCache = [];
86
87
    /**
88
     * @var string[][]
89
     */
90
    protected $labels = [];
91
92
    /**
93
     * @var Locales
94
     */
95
    protected $locales;
96
97
    /**
98
     * @var LocalizationFactory
99
     */
100
    protected $localizationFactory;
101
102
    /**
103
     * @internal use one of the factory methods instead
104
     */
105
    public function __construct(Locales $locales, LocalizationFactory $localizationFactory)
106
    {
107
        $this->locales = $locales;
108
        $this->localizationFactory = $localizationFactory;
109
        $this->debugKey = (bool)$GLOBALS['TYPO3_CONF_VARS']['BE']['languageDebug'];
110
    }
111
112
    /**
113
     * Initializes the language to fetch XLF labels for.
114
     * $languageService = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance(\TYPO3\CMS\Core\Localization\LanguageService::class);
115
     * $languageService->init($GLOBALS['BE_USER']->uc['lang']);
116
     *
117
     * @throws \RuntimeException
118
     * @param string $languageKey The language key (two character string from backend users profile)
119
     * @internal use one of the factory methods instead
120
     */
121
    public function init($languageKey)
122
    {
123
        // Find the requested language in this list based on the $languageKey
124
        // Language is found. Configure it:
125
        if (in_array($languageKey, $this->locales->getLocales(), true)) {
126
            // The current language key
127
            $this->lang = $languageKey;
128
            $this->languageDependencies[] = $languageKey;
129
            foreach ($this->locales->getLocaleDependencies($languageKey) as $language) {
130
                $this->languageDependencies[] = $language;
131
            }
132
        }
133
    }
134
135
    /**
136
     * Debugs localization key.
137
     *
138
     * @param string $value value to debug
139
     * @return string
140
     */
141
    protected function debugLL($value)
142
    {
143
        return $this->debugKey ? '[' . $value . ']' : '';
144
    }
145
146
    /**
147
     * Returns the label with key $index from the globally loaded $LOCAL_LANG array.
148
     * Mostly used from modules with only one LOCAL_LANG file loaded into the global space.
149
     *
150
     * @param string $index Label key
151
     * @return string
152
     */
153
    public function getLL($index)
154
    {
155
        return $this->getLLL($index, $this->labels);
156
    }
157
158
    /**
159
     * Returns the label with key $index from the $LOCAL_LANG array used as the second argument
160
     *
161
     * @param string $index Label key
162
     * @param array $localLanguage $LOCAL_LANG array to get label key from
163
     * @return string
164
     */
165
    protected function getLLL($index, $localLanguage)
166
    {
167
        // Get Local Language. Special handling for all extensions that
168
        // read PHP LL files and pass arrays here directly.
169
        if (isset($localLanguage[$this->lang][$index])) {
170
            $value = is_string($localLanguage[$this->lang][$index])
171
                ? $localLanguage[$this->lang][$index]
0 ignored issues
show
Coding Style introduced by
Expected 1 space before "?"; newline found
Loading history...
172
                : $localLanguage[$this->lang][$index][0]['target'];
0 ignored issues
show
Coding Style introduced by
Expected 1 space before ":"; newline found
Loading history...
173
        } elseif (isset($localLanguage['default'][$index])) {
174
            $value = is_string($localLanguage['default'][$index])
175
                ? $localLanguage['default'][$index]
0 ignored issues
show
Coding Style introduced by
Expected 1 space before "?"; newline found
Loading history...
176
                : $localLanguage['default'][$index][0]['target'];
0 ignored issues
show
Coding Style introduced by
Expected 1 space before ":"; newline found
Loading history...
177
        } else {
178
            $value = '';
179
        }
180
        return $value . $this->debugLL($index);
181
    }
182
183
    /**
184
     * splitLabel function
185
     *
186
     * All translations are based on $LOCAL_LANG variables.
187
     * 'language-splitted' labels can therefore refer to a local-lang file + index.
188
     * Refer to 'Inside TYPO3' for more details
189
     *
190
     * @param string $input Label key/reference
191
     * @return string
192
     */
193
    public function sL($input)
194
    {
195
        $identifier = $input . '_' . (int)$this->debugKey;
196
        if (isset($this->LL_labels_cache[$this->lang][$identifier])) {
197
            return $this->LL_labels_cache[$this->lang][$identifier];
198
        }
199
        if (strpos($input, 'LLL:') === 0) {
200
            $restStr = trim(substr($input, 4));
201
            $extPrfx = '';
202
            // ll-file referred to is found in an extension.
203
            if (strpos($restStr, 'EXT:') === 0) {
204
                $restStr = trim(substr($restStr, 4));
205
                $extPrfx = 'EXT:';
206
            }
207
            $parts = explode(':', $restStr);
208
            $parts[0] = $extPrfx . $parts[0];
209
            // Getting data if not cached
210
            if (!isset($this->LL_files_cache[$parts[0]])) {
211
                $this->LL_files_cache[$parts[0]] = $this->readLLfile($parts[0]);
212
            }
213
            $output = $this->getLLL($parts[1], $this->LL_files_cache[$parts[0]]);
214
        } else {
215
            // Use a constant non-localizable label
216
            $output = $input;
217
        }
218
        $output .= $this->debugLL($input);
219
        $this->LL_labels_cache[$this->lang][$identifier] = $output;
220
        return $output;
221
    }
222
223
    /**
224
     * Loading $TCA_DESCR[$table]['columns'] with content from locallang files
225
     * as defined in $TCA_DESCR[$table]['refs']
226
     * $TCA_DESCR is a global var
227
     *
228
     * @param string $table Table name found as key in global array $TCA_DESCR
229
     * @internal
230
     */
231
    public function loadSingleTableDescription($table)
232
    {
233
        // First the 'table' cannot already be loaded in [columns]
234
        // and secondly there must be a references to locallang files available in [refs]
235
        if (is_array($GLOBALS['TCA_DESCR'][$table]) && !isset($GLOBALS['TCA_DESCR'][$table]['columns']) && is_array($GLOBALS['TCA_DESCR'][$table]['refs'])) {
236
            // Init $TCA_DESCR for $table-key
237
            $GLOBALS['TCA_DESCR'][$table]['columns'] = [];
238
            // Get local-lang for each file in $TCA_DESCR[$table]['refs'] as they are ordered.
239
            foreach ($GLOBALS['TCA_DESCR'][$table]['refs'] as $llfile) {
240
                $localLanguage = $this->includeLanguageFileRaw($llfile);
241
                // Traverse all keys
242
                if (is_array($localLanguage['default'])) {
243
                    foreach ($localLanguage['default'] as $lkey => $lVal) {
244
                        // Exploding by '.':
245
                        // 0-n => fieldname,
246
                        // n+1 => type from (alttitle, description, details, syntax, image_descr,image,seeAlso),
247
                        // n+2 => special instruction, if any
248
                        $keyParts = explode('.', $lkey);
249
                        $keyPartsCount = count($keyParts);
250
                        // Check if last part is special instruction
251
                        // Only "+" is currently supported
252
                        $specialInstruction = $keyParts[$keyPartsCount - 1] === '+';
253
                        if ($specialInstruction) {
254
                            array_pop($keyParts);
255
                        }
256
                        // If there are more than 2 parts, get the type from the last part
257
                        // and merge back the other parts with a dot (.)
258
                        // Otherwise just get type and field name straightaway
259
                        if ($keyPartsCount > 2) {
260
                            $type = array_pop($keyParts);
261
                            $fieldName = implode('.', $keyParts);
262
                        } else {
263
                            $fieldName = $keyParts[0];
264
                            $type = $keyParts[1];
265
                        }
266
                        // Detecting 'hidden' labels, converting to normal fieldname
267
                        if ($fieldName === '_') {
268
                            $fieldName = '';
269
                        }
270
                        if ($fieldName !== '' && $fieldName[0] === '_') {
271
                            $fieldName = substr($fieldName, 1);
272
                        }
273
                        // Append label
274
                        $label = $lVal[0]['target'] ?: $lVal[0]['source'];
275
                        if ($specialInstruction) {
276
                            $GLOBALS['TCA_DESCR'][$table]['columns'][$fieldName][$type] .= LF . $label;
277
                        } else {
278
                            // Substitute label
279
                            $GLOBALS['TCA_DESCR'][$table]['columns'][$fieldName][$type] = $label;
280
                        }
281
                    }
282
                }
283
            }
284
        }
285
    }
286
287
    /**
288
     * Includes locallang file (and possibly additional localized version if configured for)
289
     * Read language labels will be merged with $LOCAL_LANG (if $setGlobal = TRUE).
290
     *
291
     * @param string $fileRef $fileRef is a file-reference
292
     * @return array returns the loaded label file
293
     */
294
    public function includeLLFile($fileRef)
295
    {
296
        $localLanguage = $this->readLLfile($fileRef);
297
        if (!empty($localLanguage)) {
298
            $this->labels = array_replace_recursive($this->labels, $localLanguage);
299
        }
300
        return $localLanguage;
301
    }
302
303
    /**
304
     * Includes a locallang file (and possibly additional localized version if configured for),
305
     * and then puts everything into "default", so "default" is kept as fallback
306
     *
307
     * @param string $fileRef $fileRef is a file-reference
308
     * @return array
309
     */
310
    protected function includeLanguageFileRaw($fileRef)
311
    {
312
        $labels = $this->readLLfile($fileRef);
313
        if (!empty($labels)) {
314
            // Merge local onto default
315
            if ($this->lang !== 'default' && is_array($labels[$this->lang]) && is_array($labels['default'])) {
316
                // array_merge can be used so far the keys are not
317
                // numeric - which we assume they are not...
318
                $labels['default'] = array_merge($labels['default'], $labels[$this->lang]);
319
                unset($labels[$this->lang]);
320
            }
321
        }
322
        return is_array($labels) ? $labels : [];
0 ignored issues
show
introduced by
The condition is_array($labels) is always true.
Loading history...
323
    }
324
325
    /**
326
     * Includes a locallang file and returns the $LOCAL_LANG array found inside.
327
     *
328
     * @param string $fileRef Input is a file-reference to be a 'local_lang' file containing a $LOCAL_LANG array
329
     * @return array value of $LOCAL_LANG found in the included file, empty if non found
330
     */
331
    protected function readLLfile($fileRef): array
332
    {
333
        if (isset($this->languageFileCache[$fileRef . $this->lang])) {
334
            return $this->languageFileCache[$fileRef . $this->lang];
335
        }
336
337
        if ($this->lang !== 'default') {
338
            $languages = array_reverse($this->languageDependencies);
339
        } else {
340
            $languages = ['default'];
341
        }
342
        $localLanguage = [];
343
        foreach ($languages as $language) {
344
            $tempLL = $this->localizationFactory->getParsedData($fileRef, $language);
345
            $localLanguage['default'] = $tempLL['default'];
346
            if (!isset($localLanguage[$this->lang])) {
347
                $localLanguage[$this->lang] = $localLanguage['default'];
348
            }
349
            if ($this->lang !== 'default' && isset($tempLL[$language])) {
350
                // Merge current language labels onto labels from previous language
351
                // This way we have a labels with fall back applied
352
                ArrayUtility::mergeRecursiveWithOverrule($localLanguage[$this->lang], $tempLL[$language], true, false);
353
            }
354
        }
355
        $this->languageFileCache[$fileRef . $this->lang] = $localLanguage;
356
357
        return $localLanguage;
358
    }
359
360
    /**
361
     * Factory method to create a language service object.
362
     *
363
     * @param string $locale the locale (= the TYPO3-internal locale given)
364
     * @return static
365
     */
366
    public static function create(string $locale): self
367
    {
368
        return GeneralUtility::makeInstance(LanguageServiceFactory::class)->create($locale);
369
    }
370
371
    public static function createFromUserPreferences(?AbstractUserAuthentication $user): self
372
    {
373
        if ($user && ($user->uc['lang'] ?? false)) {
374
            return static::create($user->uc['lang']);
375
        }
376
        return static::create('default');
377
    }
378
379
    public static function createFromSiteLanguage(SiteLanguage $language): self
380
    {
381
        return static::create($language->getTypo3Language());
382
    }
383
}
384