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