17df4ff9eb191bc91206dfb11f87b3f3bf4577a1
[yaffs-website] / web / core / modules / language / src / LanguageNegotiator.php
1 <?php
2
3 namespace Drupal\language;
4
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;
11
12 /**
13  * Class responsible for performing language negotiation.
14  */
15 class LanguageNegotiator implements LanguageNegotiatorInterface {
16
17   /**
18    * The language negotiation method plugin manager.
19    *
20    * @var \Drupal\Component\Plugin\PluginManagerInterface
21    */
22   protected $negotiatorManager;
23
24   /**
25    * The language manager.
26    *
27    * @var \Drupal\language\ConfigurableLanguageManagerInterface
28    */
29   protected $languageManager;
30
31   /**
32    * The configuration factory.
33    *
34    * @var \Drupal\Core\Config\ConfigFactoryInterface
35    */
36   protected $configFactory;
37
38   /**
39    * The settings instance.
40    *
41    * @var \Drupal\Core\Site\Settings
42    */
43   protected $settings;
44
45   /**
46    * The request stack object.
47    *
48    * @var \Symfony\Component\HttpFoundation\RequestStack
49    */
50   protected $requestStack;
51
52   /**
53    * The current active user.
54    *
55    * @var \Drupal\Core\Session\AccountInterface
56    */
57   protected $currentUser;
58
59   /**
60    * Local cache for language negotiation method instances.
61    *
62    * @var array
63    */
64   protected $methods;
65
66   /**
67    * An array of language objects keyed by method id.
68    *
69    * @var \Drupal\Core\Language\LanguageInterface[]
70    */
71   protected $negotiatedLanguages = [];
72
73   /**
74    * Constructs a new LanguageNegotiator object.
75    *
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.
84    */
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;
91   }
92
93   /**
94    * Initializes the injected language manager with the negotiator.
95    *
96    * This should be called right after instantiating the negotiator to make it
97    * available to the language manager without introducing a circular
98    * dependency.
99    */
100   public function initLanguageManager() {
101     $this->languageManager->setNegotiator($this);
102   }
103
104   /**
105    * {@inheritdoc}
106    */
107   public function reset() {
108     $this->negotiatedLanguages = [];
109     $this->methods = [];
110   }
111
112   /**
113    * {@inheritdoc}
114    */
115   public function setCurrentUser(AccountInterface $current_user) {
116     $this->currentUser = $current_user;
117     $this->reset();
118   }
119
120   /**
121    * {@inheritdoc}
122    */
123   public function initializeType($type) {
124     $language = NULL;
125
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);
132         }
133
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;
139
140         if ($language) {
141           $this->getNegotiationMethodInstance($method_id)->persist($language);
142           break;
143         }
144       }
145     }
146
147     if (!$language) {
148       // If no other language was found use the default one.
149       $language = $this->languageManager->getDefaultLanguage();
150       $method_id = static::METHOD_ID;
151     }
152
153     return [$method_id => $language];
154   }
155
156   /**
157    * Gets enabled detection methods for the provided language type.
158    *
159    * @param string $type
160    *   The language type.
161    *
162    * @return array
163    *   An array of enabled detection methods for the provided language type.
164    */
165   protected function getEnabledNegotiators($type) {
166     return $this->configFactory->get('language.types')->get('negotiation.' . $type . '.enabled') ?: [];
167   }
168
169   /**
170    * Performs language negotiation using the specified negotiation method.
171    *
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
176    *   language.
177    *
178    * @return \Drupal\Core\Language\LanguageInterface|null
179    *   Negotiated language object for given type and method, FALSE otherwise.
180    */
181   protected function negotiateLanguage($type, $method_id) {
182     $langcode = NULL;
183     $method = $this->negotiatorManager->getDefinition($method_id);
184
185     if (!isset($method['types']) || in_array($type, $method['types'])) {
186       $langcode = $this->getNegotiationMethodInstance($method_id)->getLangcode($this->requestStack->getCurrentRequest());
187     }
188
189     $languages = $this->languageManager->getLanguages();
190     return isset($languages[$langcode]) ? $languages[$langcode] : NULL;
191   }
192
193   /**
194    * {@inheritdoc}
195    */
196   public function getNegotiationMethods($type = NULL) {
197     $definitions = $this->negotiatorManager->getDefinitions();
198     if (isset($type)) {
199       $enabled_methods = $this->getEnabledNegotiators($type);
200       $definitions = array_intersect_key($definitions, $enabled_methods);
201     }
202     return $definitions;
203   }
204
205   /**
206    * {@inheritdoc}
207    */
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;
215     }
216     return $this->methods[$method_id];
217   }
218
219   /**
220    * {@inheritdoc}
221    */
222   public function getPrimaryNegotiationMethod($type) {
223     $enabled_methods = $this->getEnabledNegotiators($type);
224     return empty($enabled_methods) ? LanguageNegotiatorInterface::METHOD_ID : key($enabled_methods);
225   }
226
227   /**
228    * {@inheritdoc}
229    */
230   public function isNegotiationMethodEnabled($method_id, $type = NULL) {
231     $enabled = FALSE;
232     $language_types = !empty($type) ? [$type] : $this->languageManager->getLanguageTypes();
233
234     foreach ($language_types as $type) {
235       $enabled_methods = $this->getEnabledNegotiators($type);
236       if (isset($enabled_methods[$method_id])) {
237         $enabled = TRUE;
238         break;
239       }
240     }
241
242     return $enabled;
243   }
244
245   /**
246    * {@inheritdoc}
247    */
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();
253
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]);
265         }
266       }
267       else {
268         unset($enabled_methods[$method_id]);
269       }
270     }
271     $this->configFactory->getEditable('language.types')->set('negotiation.' . $type . '.enabled', $enabled_methods)->save();
272   }
273
274   /**
275    * {@inheritdoc}
276    */
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));
286     }
287   }
288
289   /**
290    * {@inheritdoc}
291    */
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();
299
300     $language_types = [];
301     $language_types_info = $this->languageManager->getDefinedLanguageTypesInfo();
302     $method_definitions = $this->getNegotiationMethods();
303
304     foreach ($language_types_info as $type => $info) {
305       $configurable = in_array($type, $types);
306
307       // The default language negotiation settings, if available, are stored in
308       // $info['fixed'].
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
312       // non-configurable.
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
318           // available.
319           $method_weights = [LanguageNegotiationUI::METHOD_ID];
320           $method_weights = array_flip($method_weights);
321           $this->saveConfiguration($type, $method_weights);
322         }
323       }
324       else {
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
328         // configurable.
329
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'].
335
336           foreach ($info['fixed'] as $weight => $method_id) {
337             if (isset($method_definitions[$method_id])) {
338               $method_weights[$method_id] = $weight;
339             }
340           }
341           $this->saveConfiguration($type, $method_weights);
342         }
343         else {
344           // It was missing default settings, so force it to be configurable.
345           $configurable = TRUE;
346         }
347       }
348
349       // Accumulate information for each language type so it can be saved later.
350       $language_types[$type] = $configurable;
351     }
352
353     // Store the language type configuration.
354     $config = [
355       'configurable' => array_keys(array_filter($language_types)),
356       'all' => array_keys($language_types),
357     ];
358     $this->languageManager->saveLanguageTypesConfiguration($config);
359   }
360
361 }