[TASK] EXT:lang CodeCleanup
[Packages/TYPO3.CMS.git] / typo3 / sysext / lang / Classes / LanguageService.php
1 <?php
2 namespace TYPO3\CMS\Lang;
3
4 /*
5 * This file is part of the TYPO3 CMS project.
6 *
7 * It is free software; you can redistribute it and/or modify it under
8 * the terms of the GNU General Public License, either version 2
9 * of the License, or any later version.
10 *
11 * For the full copyright and license information, please read the
12 * LICENSE.txt file that was distributed with this source code.
13 *
14 * The TYPO3 project - inspiring people to share!
15 */
16
17 use TYPO3\CMS\Core\Charset\CharsetConverter;
18 use TYPO3\CMS\Core\Localization\Locales;
19 use TYPO3\CMS\Core\Localization\LocalizationFactory;
20 use TYPO3\CMS\Core\Utility\ArrayUtility;
21 use TYPO3\CMS\Core\Utility\GeneralUtility;
22
23 /**
24 * Contains the TYPO3 Backend Language class
25 * For detailed information about how localization is handled,
26 * please refer to the 'Inside TYPO3' document which describes this.
27 * This class is normally instantiated as the global variable $GLOBALS['LANG']
28 * It's only available in the backend and under certain circumstances in the frontend
29 * @see \TYPO3\CMS\Backend\Template\DocumentTemplate
30 */
31 class LanguageService
32 {
33 /**
34 * This is set to the language that is currently running for the user
35 *
36 * @var string
37 */
38 public $lang = 'default';
39
40 /**
41 * Default charset in backend
42 *
43 * @var string
44 */
45 public $charSet = 'utf-8';
46
47 /**
48 * If TRUE, will show the key/location of labels in the backend.
49 *
50 * @var bool
51 */
52 public $debugKey = false;
53
54 /**
55 * Can contain labels and image references from the backend modules.
56 * Relies on \TYPO3\CMS\Backend\Module\ModuleLoader to initialize modules after a global instance of $LANG has been created.
57 *
58 * @var array
59 */
60 public $moduleLabels = array();
61
62 /**
63 * Internal cache for read LL-files
64 *
65 * @var array
66 */
67 public $LL_files_cache = array();
68
69 /**
70 * Internal cache for ll-labels (filled as labels are requested)
71 *
72 * @var array
73 */
74 public $LL_labels_cache = array();
75
76 /**
77 * instance of the "\TYPO3\CMS\Core\Charset\CharsetConverter" class. May be used by any application.
78 *
79 * @var \TYPO3\CMS\Core\Charset\CharsetConverter
80 */
81 public $csConvObj;
82
83 /**
84 * instance of the parser factory
85 *
86 * @var LocalizationFactory
87 */
88 public $parserFactory;
89
90 /**
91 * List of language dependencies for actual language. This is used for local variants of a language
92 * that depend on their "main" language, like Brazilian Portuguese or Canadian French.
93 *
94 * @var array
95 */
96 protected $languageDependencies = array();
97
98 /**
99 * Initializes the backend language.
100 * This is for example done in \TYPO3\CMS\Backend\Template\DocumentTemplate with lines like these:
101 * $LANG = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance(\TYPO3\CMS\Lang\LanguageService::class);
102 * $LANG->init($GLOBALS['BE_USER']->uc['lang']);
103 *
104 * @throws \RuntimeException
105 * @param string $lang The language key (two character string from backend users profile)
106 * @return void
107 */
108 public function init($lang)
109 {
110 // Initialize the conversion object:
111 $this->csConvObj = GeneralUtility::makeInstance(CharsetConverter::class);
112 // Initialize the parser factory object
113 $this->parserFactory = GeneralUtility::makeInstance(LocalizationFactory::class);
114 // Find the requested language in this list based
115 // on the $lang key being inputted to this function.
116 /** @var $locales \TYPO3\CMS\Core\Localization\Locales */
117 $locales = GeneralUtility::makeInstance(Locales::class);
118 // Language is found. Configure it:
119 if (in_array($lang, $locales->getLocales())) {
120 // The current language key
121 $this->lang = $lang;
122 $this->languageDependencies[] = $this->lang;
123 foreach ($locales->getLocaleDependencies($this->lang) as $language) {
124 $this->languageDependencies[] = $language;
125 }
126 }
127 if ($GLOBALS['TYPO3_CONF_VARS']['BE']['lang']['debug']) {
128 $this->debugKey = true;
129 }
130 }
131
132 /**
133 * Gets the parser factory.
134 *
135 * @return LocalizationFactory
136 */
137 public function getParserFactory()
138 {
139 return $this->parserFactory;
140 }
141
142 /**
143 * Adds labels and image references from the backend modules to the internal moduleLabels array
144 *
145 * @param array $arr Array with references to module labels, keys: ['labels']['table'],
146 * @param string $prefix Module name prefix
147 * @return void
148 * @see \TYPO3\CMS\Backend\Module\ModuleLoader
149 */
150 public function addModuleLabels($arr, $prefix)
151 {
152 if (is_array($arr)) {
153 foreach ($arr as $k => $larr) {
154 if (!isset($this->moduleLabels[$k])) {
155 $this->moduleLabels[$k] = array();
156 }
157 if (is_array($larr)) {
158 foreach ($larr as $l => $v) {
159 $this->moduleLabels[$k][$prefix . $l] = $v;
160 }
161 }
162 }
163 }
164 }
165
166 /**
167 * Will convert the input strings special chars (all above 127) to entities.
168 * The string is expected to be encoded in UTF-8
169 * This function is used to create strings that can be used in the Click Menu
170 * (Context Sensitive Menus). The reason is that the values that are dynamically
171 * written into the <div> layer is decoded as iso-8859-1 no matter what charset
172 * is used in the document otherwise (only MSIE, Mozilla is OK).
173 * So by converting we by-pass this problem.
174 *
175 * @param string $str Input string
176 * @return string Output string
177 * @deprecated since TYPO3 v8, will be removed in TYPO3 v9
178 */
179 public function makeEntities($str)
180 {
181 GeneralUtility::logDeprecatedFunction();
182 // Convert string back again, but using the full entity conversion:
183 return $this->csConvObj->utf8_to_entities($str);
184 }
185
186 /**
187 * Debugs localization key.
188 *
189 * @param string $value value to debug
190 * @return string
191 */
192 public function debugLL($value)
193 {
194 return $this->debugKey ? '[' . $value . ']' : '';
195 }
196
197 /**
198 * Returns the label with key $index from the globally loaded $LOCAL_LANG array.
199 * Mostly used from modules with only one LOCAL_LANG file loaded into the global space.
200 *
201 * @param string $index Label key
202 * @param bool $hsc If set, the return value is htmlspecialchar'ed
203 * @return string
204 */
205 public function getLL($index, $hsc = false)
206 {
207 return $this->getLLL($index, $GLOBALS['LOCAL_LANG'], $hsc);
208 }
209
210 /**
211 * Returns the label with key $index from the $LOCAL_LANG array used as the second argument
212 *
213 * @param string $index Label key
214 * @param array $localLanguage $LOCAL_LANG array to get label key from
215 * @param bool $hsc If set, the return value is htmlspecialchar'ed
216 * @return string
217 */
218 public function getLLL($index, $localLanguage, $hsc = false)
219 {
220 // Get Local Language. Special handling for all extensions that
221 // read PHP LL files and pass arrays here directly.
222 if (isset($localLanguage[$this->lang][$index])) {
223 $value = is_string($localLanguage[$this->lang][$index])
224 ? $localLanguage[$this->lang][$index]
225 : $localLanguage[$this->lang][$index][0]['target'];
226 } elseif (isset($localLanguage['default'][$index])) {
227 $value = is_string($localLanguage['default'][$index])
228 ? $localLanguage['default'][$index]
229 : $localLanguage['default'][$index][0]['target'];
230 } else {
231 $value = '';
232 }
233 if ($hsc) {
234 $value = htmlspecialchars($value);
235 }
236 return $value . $this->debugLL($index);
237 }
238
239 /**
240 * splitLabel function
241 *
242 * All translations are based on $LOCAL_LANG variables.
243 * 'language-splitted' labels can therefore refer to a local-lang file + index.
244 * Refer to 'Inside TYPO3' for more details
245 *
246 * @param string $input Label key/reference
247 * @param bool $hsc If set, the return value is htmlspecialchar'ed
248 * @return string
249 */
250 public function sL($input, $hsc = false)
251 {
252 $identifier = $input . '_' . (int)$hsc . '_' . (int)$this->debugKey;
253 if (isset($this->LL_labels_cache[$this->lang][$identifier])) {
254 return $this->LL_labels_cache[$this->lang][$identifier];
255 }
256 if (strpos($input, 'LLL:') === 0) {
257 $restStr = trim(substr($input, 4));
258 $extPrfx = '';
259 // ll-file referred to is found in an extension.
260 if (strpos($restStr, 'EXT:') === 0) {
261 $restStr = trim(substr($restStr, 4));
262 $extPrfx = 'EXT:';
263 }
264 $parts = explode(':', $restStr);
265 $parts[0] = $extPrfx . $parts[0];
266 // Getting data if not cached
267 if (!isset($this->LL_files_cache[$parts[0]])) {
268 $this->LL_files_cache[$parts[0]] = $this->readLLfile($parts[0]);
269 }
270 $output = $this->getLLL($parts[1], $this->LL_files_cache[$parts[0]]);
271 } else {
272 // Use a constant non-localizable label
273 $output = $input;
274 }
275 if ($hsc) {
276 $output = htmlspecialchars($output, ENT_COMPAT, 'UTF-8', false);
277 }
278 $output .= $this->debugLL($input);
279 $this->LL_labels_cache[$this->lang][$identifier] = $output;
280 return $output;
281 }
282
283 /**
284 * Loading $TCA_DESCR[$table]['columns'] with content from locallang files
285 * as defined in $TCA_DESCR[$table]['refs']
286 * $TCA_DESCR is a global var
287 *
288 * @param string $table Table name found as key in global array $TCA_DESCR
289 * @return void
290 */
291 public function loadSingleTableDescription($table)
292 {
293 // First the 'table' cannot already be loaded in [columns]
294 // and secondly there must be a references to locallang files available in [refs]
295 if (is_array($GLOBALS['TCA_DESCR'][$table]) && !isset($GLOBALS['TCA_DESCR'][$table]['columns']) && is_array($GLOBALS['TCA_DESCR'][$table]['refs'])) {
296 // Init $TCA_DESCR for $table-key
297 $GLOBALS['TCA_DESCR'][$table]['columns'] = array();
298 // Get local-lang for each file in $TCA_DESCR[$table]['refs'] as they are ordered.
299 foreach ($GLOBALS['TCA_DESCR'][$table]['refs'] as $llfile) {
300 $localLanguage = $this->includeLLFile($llfile, 0, 1);
301 // Traverse all keys
302 if (is_array($localLanguage['default'])) {
303 foreach ($localLanguage['default'] as $lkey => $lVal) {
304 // Exploding by '.':
305 // 0-n => fieldname,
306 // n+1 => type from (alttitle, description, details, syntax, image_descr,image,seeAlso),
307 // n+2 => special instruction, if any
308 $keyParts = explode('.', $lkey);
309 $keyPartsCount = count($keyParts);
310 // Check if last part is special instruction
311 // Only "+" is currently supported
312 $specialInstruction = $keyParts[$keyPartsCount - 1] === '+';
313 if ($specialInstruction) {
314 array_pop($keyParts);
315 }
316 // If there are more than 2 parts, get the type from the last part
317 // and merge back the other parts with a dot (.)
318 // Otherwise just get type and field name straightaway
319 if ($keyPartsCount > 2) {
320 $type = array_pop($keyParts);
321 $fieldName = implode('.', $keyParts);
322 } else {
323 $fieldName = $keyParts[0];
324 $type = $keyParts[1];
325 }
326 // Detecting 'hidden' labels, converting to normal fieldname
327 if ($fieldName === '_') {
328 $fieldName = '';
329 }
330 if ($fieldName !== '' && $fieldName[0] === '_') {
331 $fieldName = substr($fieldName, 1);
332 }
333 // Append label
334 $label = $lVal[0]['target'] ? :
335 $lVal[0]['source'];
336 if ($specialInstruction) {
337 $GLOBALS['TCA_DESCR'][$table]['columns'][$fieldName][$type] .= LF . $label;
338 } else {
339 // Substitute label
340 $GLOBALS['TCA_DESCR'][$table]['columns'][$fieldName][$type] = $label;
341 }
342 }
343 }
344 }
345 }
346 }
347
348 /**
349 * Includes locallang file (and possibly additional localized version if configured for)
350 * Read language labels will be merged with $LOCAL_LANG (if $setGlobal = TRUE).
351 *
352 * @param string $fileRef $fileRef is a file-reference
353 * @param bool $setGlobal Setting in global variable $LOCAL_LANG (or returning the variable)
354 * @param bool $mergeLocalOntoDefault
355 * @return mixed if $setGlobal===TRUE, LL-files set $LOCAL_LANG in global scope, or array is returned from function
356 */
357 public function includeLLFile($fileRef, $setGlobal = true, $mergeLocalOntoDefault = false)
358 {
359 $globalLanguage = array();
360 // Get default file
361 $localLanguage = $this->readLLfile($fileRef);
362 if (is_array($localLanguage) && !empty($localLanguage)) {
363 // it depends on, whether we should return the result or set it in the global $LOCAL_LANG array
364 if ($setGlobal) {
365 $globalLanguage = (array)$GLOBALS['LOCAL_LANG'];
366 ArrayUtility::mergeRecursiveWithOverrule($globalLanguage, $localLanguage);
367 } else {
368 $globalLanguage = $localLanguage;
369 }
370 // Merge local onto default
371 if ($mergeLocalOntoDefault && $this->lang !== 'default' && is_array($globalLanguage[$this->lang]) && is_array($globalLanguage['default'])) {
372 // array_merge can be used so far the keys are not
373 // numeric - which we assume they are not...
374 $globalLanguage['default'] = array_merge($globalLanguage['default'], $globalLanguage[$this->lang]);
375 unset($globalLanguage[$this->lang]);
376 }
377 }
378 // Return value if not global is set.
379 if (!$setGlobal) {
380 return $globalLanguage;
381 } else {
382 $GLOBALS['LOCAL_LANG'] = $globalLanguage;
383 return null;
384 }
385 }
386
387 /**
388 * Includes a locallang file and returns the $LOCAL_LANG array found inside.
389 *
390 * @param string $fileRef Input is a file-reference to be a 'local_lang' file containing a $LOCAL_LANG array
391 * @return array value of $LOCAL_LANG found in the included file, empty if non found
392 */
393 protected function readLLfile($fileRef)
394 {
395 // @todo: Usually, an instance of the LocalizationFactory is found in $this->parserFactory.
396 // @todo: This is not the case if $GLOBALS['LANG'] is not used to get hold of this object,
397 // @todo: but the objectManager instead. If then init() is not called, this will fatal ...
398 // @todo: To be sure, we always create an instance here for now.
399 /** @var $languageFactory LocalizationFactory */
400 $languageFactory = GeneralUtility::makeInstance(LocalizationFactory::class);
401
402 if ($this->lang !== 'default') {
403 $languages = array_reverse($this->languageDependencies);
404 } else {
405 $languages = array('default');
406 }
407 $localLanguage = array();
408 foreach ($languages as $language) {
409 $tempLL = $languageFactory->getParsedData($fileRef, $language, $this->charSet);
410 $localLanguage['default'] = $tempLL['default'];
411 if (!isset($localLanguage[$this->lang])) {
412 $localLanguage[$this->lang] = $localLanguage['default'];
413 }
414 if ($this->lang !== 'default' && isset($tempLL[$language])) {
415 // Merge current language labels onto labels from previous language
416 // This way we have a labels with fall back applied
417 ArrayUtility::mergeRecursiveWithOverrule($localLanguage[$this->lang], $tempLL[$language], true, false);
418 }
419 }
420 return $localLanguage;
421 }
422
423 /**
424 * Overrides a label.
425 *
426 * @param string $index
427 * @param string $value
428 * @param bool $overrideDefault Overrides default language
429 * @return void
430 * @deprecated since TYPO3 v8, will be removed in TYPO3 v9
431 */
432 public function overrideLL($index, $value, $overrideDefault = true)
433 {
434 GeneralUtility::logDeprecatedFunction();
435 if (!isset($GLOBALS['LOCAL_LANG'])) {
436 $GLOBALS['LOCAL_LANG'] = array();
437 }
438 $GLOBALS['LOCAL_LANG'][$this->lang][$index][0]['target'] = $value;
439 if ($overrideDefault) {
440 $GLOBALS['LOCAL_LANG']['default'][$index][0]['target'] = $value;
441 }
442 }
443
444 /**
445 * Gets labels with a specific fetched from the current locallang file.
446 * This is useful for e.g gathering javascript labels.
447 *
448 * @param string $prefix Prefix to select the correct labels
449 * @param string $strip Sub-prefix to be removed from label names in the result
450 * @return array Processed labels
451 */
452 public function getLabelsWithPrefix($prefix, $strip = '')
453 {
454 $extraction = array();
455 $labels = array_merge((array)$GLOBALS['LOCAL_LANG']['default'], (array)$GLOBALS['LOCAL_LANG'][$this->lang]);
456 // Regular expression to strip the selection prefix and possibly something from the label name:
457 $labelPattern = '#^' . preg_quote($prefix, '#') . '(' . preg_quote($strip, '#') . ')?#';
458 // Iterate through all locallang labels:
459 foreach ($labels as $label => $value) {
460 if (strpos($label, $prefix) === 0) {
461 $key = preg_replace($labelPattern, '', $label);
462 $extraction[$key] = $value;
463 }
464 }
465 return $extraction;
466 }
467 }