3 namespace Drupal\language;
5 use Drupal\Component\Plugin\PluginManagerInterface;
6 use Drupal\Core\Config\ConfigFactoryInterface;
7 use Drupal\Core\Session\AccountInterface;
8 use Drupal\Core\Site\Settings;
9 use Drupal\language\Plugin\LanguageNegotiation\LanguageNegotiationUI;
10 use Symfony\Component\HttpFoundation\RequestStack;
13 * Class responsible for performing language negotiation.
15 class LanguageNegotiator implements LanguageNegotiatorInterface {
18 * The language negotiation method plugin manager.
20 * @var \Drupal\Component\Plugin\PluginManagerInterface
22 protected $negotiatorManager;
25 * The language manager.
27 * @var \Drupal\language\ConfigurableLanguageManagerInterface
29 protected $languageManager;
32 * The configuration factory.
34 * @var \Drupal\Core\Config\ConfigFactoryInterface
36 protected $configFactory;
39 * The settings instance.
41 * @var \Drupal\Core\Site\Settings
46 * The request stack object.
48 * @var \Symfony\Component\HttpFoundation\RequestStack
50 protected $requestStack;
53 * The current active user.
55 * @var \Drupal\Core\Session\AccountInterface
57 protected $currentUser;
60 * Local cache for language negotiation method instances.
67 * An array of language objects keyed by method id.
69 * @var \Drupal\Core\Language\LanguageInterface[]
71 protected $negotiatedLanguages = [];
74 * Constructs a new LanguageNegotiator object.
76 * @param \Drupal\language\ConfigurableLanguageManagerInterface $language_manager
77 * The language manager.
78 * @param \Drupal\Component\Plugin\PluginManagerInterface $negotiator_manager
79 * The language negotiation methods plugin manager
80 * @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory
81 * The configuration factory.
82 * @param \Drupal\Core\Site\Settings $settings
83 * The settings instance.
85 public function __construct(ConfigurableLanguageManagerInterface $language_manager, PluginManagerInterface $negotiator_manager, ConfigFactoryInterface $config_factory, Settings $settings, RequestStack $requestStack) {
86 $this->languageManager = $language_manager;
87 $this->negotiatorManager = $negotiator_manager;
88 $this->configFactory = $config_factory;
89 $this->settings = $settings;
90 $this->requestStack = $requestStack;
94 * Initializes the injected language manager with the negotiator.
96 * This should be called right after instantiating the negotiator to make it
97 * available to the language manager without introducing a circular
100 public function initLanguageManager() {
101 $this->languageManager->setNegotiator($this);
107 public function reset() {
108 $this->negotiatedLanguages = [];
115 public function setCurrentUser(AccountInterface $current_user) {
116 $this->currentUser = $current_user;
123 public function initializeType($type) {
126 if ($this->currentUser) {
127 // Execute the language negotiation methods in the order they were set up
128 // and return the first valid language found.
129 foreach ($this->getEnabledNegotiators($type) as $method_id => $info) {
130 if (!isset($this->negotiatedLanguages[$method_id])) {
131 $this->negotiatedLanguages[$method_id] = $this->negotiateLanguage($type, $method_id);
134 // Since objects are references, we need to return a clone to prevent
135 // the language negotiation method cache from being unintentionally
136 // altered. The same methods might be used with different language types
137 // based on configuration.
138 $language = !empty($this->negotiatedLanguages[$method_id]) ? clone($this->negotiatedLanguages[$method_id]) : NULL;
141 $this->getNegotiationMethodInstance($method_id)->persist($language);
148 // If no other language was found use the default one.
149 $language = $this->languageManager->getDefaultLanguage();
150 $method_id = static::METHOD_ID;
153 return [$method_id => $language];
157 * Gets enabled detection methods for the provided language type.
159 * @param string $type
163 * An array of enabled detection methods for the provided language type.
165 protected function getEnabledNegotiators($type) {
166 return $this->configFactory->get('language.types')->get('negotiation.' . $type . '.enabled') ?: [];
170 * Performs language negotiation using the specified negotiation method.
172 * @param string $type
173 * The language type to be initialized.
174 * @param string $method_id
175 * The string identifier of the language negotiation method to use to detect
178 * @return \Drupal\Core\Language\LanguageInterface|null
179 * Negotiated language object for given type and method, FALSE otherwise.
181 protected function negotiateLanguage($type, $method_id) {
183 $method = $this->negotiatorManager->getDefinition($method_id);
185 if (!isset($method['types']) || in_array($type, $method['types'])) {
186 $langcode = $this->getNegotiationMethodInstance($method_id)->getLangcode($this->requestStack->getCurrentRequest());
189 $languages = $this->languageManager->getLanguages();
190 return isset($languages[$langcode]) ? $languages[$langcode] : NULL;
196 public function getNegotiationMethods($type = NULL) {
197 $definitions = $this->negotiatorManager->getDefinitions();
199 $enabled_methods = $this->getEnabledNegotiators($type);
200 $definitions = array_intersect_key($definitions, $enabled_methods);
208 public function getNegotiationMethodInstance($method_id) {
209 if (!isset($this->methods[$method_id])) {
210 $instance = $this->negotiatorManager->createInstance($method_id, []);
211 $instance->setLanguageManager($this->languageManager);
212 $instance->setConfig($this->configFactory);
213 $instance->setCurrentUser($this->currentUser);
214 $this->methods[$method_id] = $instance;
216 return $this->methods[$method_id];
222 public function getPrimaryNegotiationMethod($type) {
223 $enabled_methods = $this->getEnabledNegotiators($type);
224 return empty($enabled_methods) ? LanguageNegotiatorInterface::METHOD_ID : key($enabled_methods);
230 public function isNegotiationMethodEnabled($method_id, $type = NULL) {
232 $language_types = !empty($type) ? [$type] : $this->languageManager->getLanguageTypes();
234 foreach ($language_types as $type) {
235 $enabled_methods = $this->getEnabledNegotiators($type);
236 if (isset($enabled_methods[$method_id])) {
248 public function saveConfiguration($type, $enabled_methods) {
249 // As configurable language types might have changed, we reset the cache.
250 $this->languageManager->reset();
251 $definitions = $this->getNegotiationMethods();
252 $default_types = $this->languageManager->getLanguageTypes();
254 // Order the language negotiation method list by weight.
255 asort($enabled_methods);
256 foreach ($enabled_methods as $method_id => $weight) {
257 if (isset($definitions[$method_id])) {
258 $method = $definitions[$method_id];
259 // If the language negotiation method does not express any preference
260 // about types, make it available for any configurable type.
261 $types = array_flip(!empty($method['types']) ? $method['types'] : $default_types);
262 // Check whether the method is defined and has the right type.
263 if (!isset($types[$type])) {
264 unset($enabled_methods[$method_id]);
268 unset($enabled_methods[$method_id]);
271 $this->configFactory->getEditable('language.types')->set('negotiation.' . $type . '.enabled', $enabled_methods)->save();
277 public function purgeConfiguration() {
278 // Ensure that we are getting the defined language negotiation information.
279 // An invocation of \Drupal\Core\Extension\ModuleInstaller::install() or
280 // \Drupal\Core\Extension\ModuleInstaller::uninstall() could invalidate the
281 // cached information.
282 $this->negotiatorManager->clearCachedDefinitions();
283 $this->languageManager->reset();
284 foreach ($this->languageManager->getDefinedLanguageTypesInfo() as $type => $info) {
285 $this->saveConfiguration($type, $this->getEnabledNegotiators($type));
292 public function updateConfiguration(array $types) {
293 // Ensure that we are getting the defined language negotiation information.
294 // An invocation of \Drupal\Core\Extension\ModuleInstaller::install() or
295 // \Drupal\Core\Extension\ModuleInstaller::uninstall() could invalidate the
296 // cached information.
297 $this->negotiatorManager->clearCachedDefinitions();
298 $this->languageManager->reset();
300 $language_types = [];
301 $language_types_info = $this->languageManager->getDefinedLanguageTypesInfo();
302 $method_definitions = $this->getNegotiationMethods();
304 foreach ($language_types_info as $type => $info) {
305 $configurable = in_array($type, $types);
307 // The default language negotiation settings, if available, are stored in
309 $has_default_settings = !empty($info['fixed']);
310 // Check whether the language type is unlocked. Only the status of
311 // unlocked language types can be toggled between configurable and
313 if (empty($info['locked'])) {
314 if (!$configurable && !$has_default_settings) {
315 // If we have an unlocked non-configurable language type without
316 // default language negotiation settings, we use the values
317 // negotiated for the interface language which, should always be
319 $method_weights = [LanguageNegotiationUI::METHOD_ID];
320 $method_weights = array_flip($method_weights);
321 $this->saveConfiguration($type, $method_weights);
325 // The language type is locked. Locked language types with default
326 // settings are always considered non-configurable. In turn if default
327 // settings are missing, the language type is always considered
330 // If the language type is locked we can just store its default language
331 // negotiation settings if it has some, since it is not configurable.
332 if ($has_default_settings) {
333 $method_weights = [];
334 // Default settings are in $info['fixed'].
336 foreach ($info['fixed'] as $weight => $method_id) {
337 if (isset($method_definitions[$method_id])) {
338 $method_weights[$method_id] = $weight;
341 $this->saveConfiguration($type, $method_weights);
344 // It was missing default settings, so force it to be configurable.
345 $configurable = TRUE;
349 // Accumulate information for each language type so it can be saved later.
350 $language_types[$type] = $configurable;
353 // Store the language type configuration.
355 'configurable' => array_keys(array_filter($language_types)),
356 'all' => array_keys($language_types),
358 $this->languageManager->saveLanguageTypesConfiguration($config);