| Total Complexity | 51 |
| Total Lines | 340 |
| Duplicated Lines | 0 % |
| Changes | 0 | ||
Complex classes like LanguageService often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.
Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.
While breaking up the class, it is a good idea to analyze how other classes use LanguageService, and based on these observations, apply Extract Interface, too.
| 1 | <?php |
||
| 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) |
||
| 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 : []; |
||
| 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 |
||
| 377 | } |
||
| 378 | |||
| 379 | public static function createFromSiteLanguage(SiteLanguage $language): self |
||
| 380 | { |
||
| 381 | return static::create($language->getTypo3Language()); |
||
| 382 | } |
||
| 383 | } |
||
| 384 |