1<?php 2 3namespace Drupal\language; 4 5use Drupal\Core\Language\LanguageInterface; 6use Drupal\Core\Config\ConfigFactoryInterface; 7use Drupal\Core\Extension\ModuleHandlerInterface; 8use Drupal\Core\Language\Language; 9use Drupal\Core\Language\LanguageDefault; 10use Drupal\Core\Language\LanguageManager; 11use Drupal\Core\StringTranslation\TranslatableMarkup; 12use Drupal\Core\Url; 13use Drupal\language\Config\LanguageConfigFactoryOverrideInterface; 14use Drupal\language\Entity\ConfigurableLanguage; 15use Symfony\Component\HttpFoundation\RequestStack; 16 17/** 18 * Overrides default LanguageManager to provide configured languages. 19 */ 20class ConfigurableLanguageManager extends LanguageManager implements ConfigurableLanguageManagerInterface { 21 22 /** 23 * The configuration storage service. 24 * 25 * @var \Drupal\Core\Config\ConfigFactoryInterface 26 */ 27 protected $configFactory; 28 29 /** 30 * The module handler service. 31 * 32 * @var \Drupal\Core\Extension\ModuleHandlerInterface 33 */ 34 protected $moduleHandler; 35 36 /** 37 * The language configuration override service. 38 * 39 * @var \Drupal\language\Config\LanguageConfigFactoryOverrideInterface 40 */ 41 protected $configFactoryOverride; 42 43 /** 44 * The request object. 45 * 46 * @var \Symfony\Component\HttpFoundation\RequestStack 47 */ 48 protected $requestStack; 49 50 /** 51 * The language negotiator. 52 * 53 * @var \Drupal\language\LanguageNegotiatorInterface 54 */ 55 protected $negotiator; 56 57 /** 58 * Local cache for language type configuration data. 59 * 60 * @var array 61 */ 62 protected $languageTypes; 63 64 /** 65 * Local cache for language type information. 66 * 67 * @var array 68 */ 69 protected $languageTypesInfo; 70 71 /** 72 * An array of language objects keyed by language type. 73 * 74 * @var \Drupal\Core\Language\LanguageInterface[] 75 */ 76 protected $negotiatedLanguages; 77 78 /** 79 * An array of language negotiation method IDs keyed by language type. 80 * 81 * @var array 82 */ 83 protected $negotiatedMethods; 84 85 /** 86 * Whether or not the language manager has been initialized. 87 * 88 * @var bool 89 */ 90 protected $initialized = FALSE; 91 92 /** 93 * Whether language types are in the process of language initialization. 94 * 95 * @var bool[] 96 */ 97 protected $initializing = []; 98 99 /** 100 * {@inheritdoc} 101 */ 102 public static function rebuildServices() { 103 \Drupal::service('kernel')->invalidateContainer(); 104 } 105 106 /** 107 * Constructs a new ConfigurableLanguageManager object. 108 * 109 * @param \Drupal\Core\Language\LanguageDefault $default_language 110 * The default language service. 111 * @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory 112 * The configuration factory service. 113 * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler 114 * The module handler service. 115 * @param \Drupal\language\Config\LanguageConfigFactoryOverrideInterface $config_override 116 * The language configuration override service. 117 * @param \Symfony\Component\HttpFoundation\RequestStack $request_stack 118 * The request stack object. 119 */ 120 public function __construct(LanguageDefault $default_language, ConfigFactoryInterface $config_factory, ModuleHandlerInterface $module_handler, LanguageConfigFactoryOverrideInterface $config_override, RequestStack $request_stack) { 121 $this->defaultLanguage = $default_language; 122 $this->configFactory = $config_factory; 123 $this->moduleHandler = $module_handler; 124 $this->configFactoryOverride = $config_override; 125 $this->requestStack = $request_stack; 126 } 127 128 /** 129 * {@inheritdoc} 130 */ 131 public function init() { 132 if (!$this->initialized) { 133 foreach ($this->getDefinedLanguageTypes() as $type) { 134 $this->getCurrentLanguage($type); 135 } 136 $this->initialized = TRUE; 137 } 138 } 139 140 /** 141 * {@inheritdoc} 142 */ 143 public function isMultilingual() { 144 return count($this->getLanguages(LanguageInterface::STATE_CONFIGURABLE)) > 1; 145 } 146 147 /** 148 * {@inheritdoc} 149 */ 150 public function getLanguageTypes() { 151 $this->loadLanguageTypesConfiguration(); 152 return $this->languageTypes['configurable']; 153 } 154 155 /** 156 * {@inheritdoc} 157 */ 158 public function getDefinedLanguageTypes() { 159 $this->loadLanguageTypesConfiguration(); 160 return $this->languageTypes['all']; 161 } 162 163 /** 164 * Retrieves language types from the configuration storage. 165 * 166 * @return array 167 * An array of language type names. 168 */ 169 protected function loadLanguageTypesConfiguration() { 170 if (!$this->languageTypes) { 171 $this->languageTypes = $this->configFactory->get('language.types')->get() ?: ['configurable' => [], 'all' => parent::getLanguageTypes()]; 172 } 173 return $this->languageTypes; 174 } 175 176 /** 177 * {@inheritdoc} 178 */ 179 public function getDefinedLanguageTypesInfo() { 180 if (!isset($this->languageTypesInfo)) { 181 $defaults = parent::getDefinedLanguageTypesInfo(); 182 183 $info = $this->moduleHandler->invokeAll('language_types_info'); 184 $language_info = $info + $defaults; 185 186 // Let other modules alter the list of language types. 187 $this->moduleHandler->alter('language_types_info', $language_info); 188 $this->languageTypesInfo = $language_info; 189 } 190 return $this->languageTypesInfo; 191 } 192 193 /** 194 * {@inheritdoc} 195 */ 196 public function saveLanguageTypesConfiguration(array $values) { 197 $config = $this->configFactory->getEditable('language.types'); 198 if (isset($values['configurable'])) { 199 $config->set('configurable', $values['configurable']); 200 } 201 if (isset($values['all'])) { 202 $config->set('all', $values['all']); 203 } 204 $config->save(TRUE); 205 } 206 207 /** 208 * {@inheritdoc} 209 */ 210 public function getCurrentLanguage($type = LanguageInterface::TYPE_INTERFACE) { 211 if (!isset($this->negotiatedLanguages[$type])) { 212 // Ensure we have a valid value for this language type. 213 $this->negotiatedLanguages[$type] = $this->getDefaultLanguage(); 214 215 if ($this->negotiator && $this->isMultilingual()) { 216 if (!isset($this->initializing[$type])) { 217 $this->initializing[$type] = TRUE; 218 $negotiation = $this->negotiator->initializeType($type); 219 $this->negotiatedLanguages[$type] = reset($negotiation); 220 $this->negotiatedMethods[$type] = key($negotiation); 221 unset($this->initializing[$type]); 222 } 223 // If the current interface language needs to be retrieved during 224 // initialization we return the system language. This way string 225 // translation calls happening during initialization will return the 226 // original strings which can be translated by calling them again 227 // afterwards. This can happen for instance while parsing negotiation 228 // method definitions. 229 elseif ($type == LanguageInterface::TYPE_INTERFACE) { 230 return new Language(['id' => LanguageInterface::LANGCODE_SYSTEM]); 231 } 232 } 233 } 234 235 return $this->negotiatedLanguages[$type]; 236 } 237 238 /** 239 * {@inheritdoc} 240 */ 241 public function reset($type = NULL) { 242 if (!isset($type)) { 243 $this->initialized = FALSE; 244 $this->negotiatedLanguages = []; 245 $this->negotiatedMethods = []; 246 $this->languageTypes = NULL; 247 $this->languageTypesInfo = NULL; 248 $this->languages = []; 249 if ($this->negotiator) { 250 $this->negotiator->reset(); 251 } 252 } 253 elseif (isset($this->negotiatedLanguages[$type])) { 254 unset($this->negotiatedLanguages[$type]); 255 unset($this->negotiatedMethods[$type]); 256 } 257 return $this; 258 } 259 260 /** 261 * {@inheritdoc} 262 */ 263 public function getNegotiator() { 264 return $this->negotiator; 265 } 266 267 /** 268 * {@inheritdoc} 269 */ 270 public function setNegotiator(LanguageNegotiatorInterface $negotiator) { 271 $this->negotiator = $negotiator; 272 $this->initialized = FALSE; 273 $this->negotiatedLanguages = []; 274 } 275 276 /** 277 * {@inheritdoc} 278 */ 279 public function getLanguages($flags = LanguageInterface::STATE_CONFIGURABLE) { 280 // If a config override is set, cache using that language's ID. 281 if ($override_language = $this->getConfigOverrideLanguage()) { 282 $static_cache_id = $override_language->getId(); 283 } 284 else { 285 $static_cache_id = $this->getCurrentLanguage()->getId(); 286 } 287 288 if (!isset($this->languages[$static_cache_id][$flags])) { 289 // Initialize the language list with the default language and default 290 // locked languages. These cannot be removed. This serves as a fallback 291 // list if this method is invoked while the language module is installed 292 // and the configuration entities for languages are not yet fully 293 // imported. 294 $default = $this->getDefaultLanguage(); 295 $languages = [$default->getId() => $default]; 296 $languages += $this->getDefaultLockedLanguages($default->getWeight()); 297 298 // Load configurable languages on top of the defaults. Ideally this could 299 // use the entity API to load and instantiate ConfigurableLanguage 300 // objects. However the entity API depends on the language system, so that 301 // would result in infinite loops. We use the configuration system 302 // directly and instantiate runtime Language objects. When language 303 // entities are imported those cover the default and locked languages, so 304 // site-specific configuration will prevail over the fallback values. 305 // Having them in the array already ensures if this is invoked in the 306 // middle of importing language configuration entities, the defaults are 307 // always present. 308 $config_ids = $this->configFactory->listAll('language.entity.'); 309 foreach ($this->configFactory->loadMultiple($config_ids) as $config) { 310 $data = $config->get(); 311 $data['name'] = $data['label']; 312 $languages[$data['id']] = new Language($data); 313 } 314 Language::sort($languages); 315 316 // Filter the full list of languages based on the value of $flags. 317 $this->languages[$static_cache_id][$flags] = $this->filterLanguages($languages, $flags); 318 } 319 320 return $this->languages[$static_cache_id][$flags]; 321 } 322 323 /** 324 * {@inheritdoc} 325 */ 326 public function getNativeLanguages() { 327 $languages = $this->getLanguages(LanguageInterface::STATE_CONFIGURABLE); 328 $natives = []; 329 330 $original_language = $this->getConfigOverrideLanguage(); 331 332 foreach ($languages as $langcode => $language) { 333 $this->setConfigOverrideLanguage($language); 334 $natives[$langcode] = ConfigurableLanguage::load($langcode); 335 } 336 $this->setConfigOverrideLanguage($original_language); 337 Language::sort($natives); 338 return $natives; 339 } 340 341 /** 342 * {@inheritdoc} 343 */ 344 public function updateLockedLanguageWeights() { 345 // Get the weight of the last configurable language. 346 $configurable_languages = $this->getLanguages(LanguageInterface::STATE_CONFIGURABLE); 347 $max_weight = end($configurable_languages)->getWeight(); 348 349 $locked_languages = $this->getLanguages(LanguageInterface::STATE_LOCKED); 350 // Update locked language weights to maintain the existing order, if 351 // necessary. 352 if (reset($locked_languages)->getWeight() <= $max_weight) { 353 foreach ($locked_languages as $language) { 354 // Update system languages weight. 355 $max_weight++; 356 ConfigurableLanguage::load($language->getId()) 357 ->setWeight($max_weight) 358 ->save(); 359 } 360 } 361 } 362 363 /** 364 * {@inheritdoc} 365 */ 366 public function getFallbackCandidates(array $context = []) { 367 if ($this->isMultilingual()) { 368 $candidates = []; 369 if (empty($context['operation']) || $context['operation'] != 'locale_lookup') { 370 // If the fallback context is not locale_lookup, initialize the 371 // candidates with languages ordered by weight and add 372 // LanguageInterface::LANGCODE_NOT_SPECIFIED at the end. Interface 373 // translation fallback should only be based on explicit configuration 374 // gathered via the alter hooks below. 375 $candidates = array_keys($this->getLanguages()); 376 $candidates[] = LanguageInterface::LANGCODE_NOT_SPECIFIED; 377 $candidates = array_combine($candidates, $candidates); 378 379 // The first candidate should always be the desired language if 380 // specified. 381 if (!empty($context['langcode'])) { 382 $candidates = [$context['langcode'] => $context['langcode']] + $candidates; 383 } 384 } 385 386 // Let other modules hook in and add/change candidates. 387 $type = 'language_fallback_candidates'; 388 $types = []; 389 if (!empty($context['operation'])) { 390 $types[] = $type . '_' . $context['operation']; 391 } 392 $types[] = $type; 393 $this->moduleHandler->alter($types, $candidates, $context); 394 } 395 else { 396 $candidates = parent::getFallbackCandidates($context); 397 } 398 399 return $candidates; 400 } 401 402 /** 403 * {@inheritdoc} 404 */ 405 public function getLanguageSwitchLinks($type, Url $url) { 406 $links = FALSE; 407 408 if ($this->negotiator) { 409 foreach ($this->negotiator->getNegotiationMethods($type) as $method_id => $method) { 410 $reflector = new \ReflectionClass($method['class']); 411 412 if ($reflector->implementsInterface('\Drupal\language\LanguageSwitcherInterface')) { 413 $result = $this->negotiator->getNegotiationMethodInstance($method_id)->getLanguageSwitchLinks($this->requestStack->getCurrentRequest(), $type, $url); 414 415 if (!empty($result)) { 416 // Allow modules to provide translations for specific links. 417 $this->moduleHandler->alter('language_switch_links', $result, $type, $url); 418 $links = (object) ['links' => $result, 'method_id' => $method_id]; 419 break; 420 } 421 } 422 } 423 } 424 425 return $links; 426 } 427 428 /** 429 * Sets the configuration override language. 430 * 431 * @param \Drupal\Core\Language\LanguageInterface $language 432 * The language to override configuration with. 433 * 434 * @return $this 435 */ 436 public function setConfigOverrideLanguage(LanguageInterface $language = NULL) { 437 $this->configFactoryOverride->setLanguage($language); 438 return $this; 439 } 440 441 /** 442 * {@inheritdoc} 443 */ 444 public function getConfigOverrideLanguage() { 445 return $this->configFactoryOverride->getLanguage(); 446 } 447 448 /** 449 * {@inheritdoc} 450 */ 451 public function getLanguageConfigOverride($langcode, $name) { 452 return $this->configFactoryOverride->getOverride($langcode, $name); 453 } 454 455 /** 456 * {@inheritdoc} 457 */ 458 public function getLanguageConfigOverrideStorage($langcode) { 459 return $this->configFactoryOverride->getStorage($langcode); 460 } 461 462 /** 463 * {@inheritdoc} 464 */ 465 public function getStandardLanguageListWithoutConfigured() { 466 $languages = $this->getLanguages(); 467 $predefined = $this->getStandardLanguageList(); 468 foreach ($predefined as $key => $value) { 469 if (isset($languages[$key])) { 470 unset($predefined[$key]); 471 continue; 472 } 473 $predefined[$key] = new TranslatableMarkup($value[0]); 474 } 475 natcasesort($predefined); 476 return $predefined; 477 } 478 479 /** 480 * {@inheritdoc} 481 */ 482 public function getNegotiatedLanguageMethod($type = LanguageInterface::TYPE_INTERFACE) { 483 if (isset($this->negotiatedLanguages[$type]) && isset($this->negotiatedMethods[$type])) { 484 return $this->negotiatedMethods[$type]; 485 } 486 } 487 488} 489