Chris@0
|
1 <?php
|
Chris@0
|
2
|
Chris@0
|
3 namespace Drupal\language;
|
Chris@0
|
4
|
Chris@0
|
5 use Drupal\Component\Plugin\PluginManagerInterface;
|
Chris@0
|
6 use Drupal\Core\Config\ConfigFactoryInterface;
|
Chris@0
|
7 use Drupal\Core\Session\AccountInterface;
|
Chris@0
|
8 use Drupal\Core\Site\Settings;
|
Chris@0
|
9 use Drupal\language\Plugin\LanguageNegotiation\LanguageNegotiationUI;
|
Chris@0
|
10 use Symfony\Component\HttpFoundation\RequestStack;
|
Chris@0
|
11
|
Chris@0
|
12 /**
|
Chris@0
|
13 * Class responsible for performing language negotiation.
|
Chris@0
|
14 */
|
Chris@0
|
15 class LanguageNegotiator implements LanguageNegotiatorInterface {
|
Chris@0
|
16
|
Chris@0
|
17 /**
|
Chris@0
|
18 * The language negotiation method plugin manager.
|
Chris@0
|
19 *
|
Chris@0
|
20 * @var \Drupal\Component\Plugin\PluginManagerInterface
|
Chris@0
|
21 */
|
Chris@0
|
22 protected $negotiatorManager;
|
Chris@0
|
23
|
Chris@0
|
24 /**
|
Chris@0
|
25 * The language manager.
|
Chris@0
|
26 *
|
Chris@0
|
27 * @var \Drupal\language\ConfigurableLanguageManagerInterface
|
Chris@0
|
28 */
|
Chris@0
|
29 protected $languageManager;
|
Chris@0
|
30
|
Chris@0
|
31 /**
|
Chris@0
|
32 * The configuration factory.
|
Chris@0
|
33 *
|
Chris@0
|
34 * @var \Drupal\Core\Config\ConfigFactoryInterface
|
Chris@0
|
35 */
|
Chris@0
|
36 protected $configFactory;
|
Chris@0
|
37
|
Chris@0
|
38 /**
|
Chris@0
|
39 * The settings instance.
|
Chris@0
|
40 *
|
Chris@12
|
41 * @var \Drupal\Core\Site\Settings
|
Chris@0
|
42 */
|
Chris@0
|
43 protected $settings;
|
Chris@0
|
44
|
Chris@0
|
45 /**
|
Chris@0
|
46 * The request stack object.
|
Chris@0
|
47 *
|
Chris@0
|
48 * @var \Symfony\Component\HttpFoundation\RequestStack
|
Chris@0
|
49 */
|
Chris@0
|
50 protected $requestStack;
|
Chris@0
|
51
|
Chris@0
|
52 /**
|
Chris@0
|
53 * The current active user.
|
Chris@0
|
54 *
|
Chris@12
|
55 * @var \Drupal\Core\Session\AccountInterface
|
Chris@0
|
56 */
|
Chris@0
|
57 protected $currentUser;
|
Chris@0
|
58
|
Chris@0
|
59 /**
|
Chris@0
|
60 * Local cache for language negotiation method instances.
|
Chris@0
|
61 *
|
Chris@0
|
62 * @var array
|
Chris@0
|
63 */
|
Chris@0
|
64 protected $methods;
|
Chris@0
|
65
|
Chris@0
|
66 /**
|
Chris@0
|
67 * An array of language objects keyed by method id.
|
Chris@0
|
68 *
|
Chris@0
|
69 * @var \Drupal\Core\Language\LanguageInterface[]
|
Chris@0
|
70 */
|
Chris@0
|
71 protected $negotiatedLanguages = [];
|
Chris@0
|
72
|
Chris@0
|
73 /**
|
Chris@0
|
74 * Constructs a new LanguageNegotiator object.
|
Chris@0
|
75 *
|
Chris@0
|
76 * @param \Drupal\language\ConfigurableLanguageManagerInterface $language_manager
|
Chris@0
|
77 * The language manager.
|
Chris@0
|
78 * @param \Drupal\Component\Plugin\PluginManagerInterface $negotiator_manager
|
Chris@0
|
79 * The language negotiation methods plugin manager
|
Chris@0
|
80 * @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory
|
Chris@0
|
81 * The configuration factory.
|
Chris@0
|
82 * @param \Drupal\Core\Site\Settings $settings
|
Chris@0
|
83 * The settings instance.
|
Chris@0
|
84 */
|
Chris@0
|
85 public function __construct(ConfigurableLanguageManagerInterface $language_manager, PluginManagerInterface $negotiator_manager, ConfigFactoryInterface $config_factory, Settings $settings, RequestStack $requestStack) {
|
Chris@0
|
86 $this->languageManager = $language_manager;
|
Chris@0
|
87 $this->negotiatorManager = $negotiator_manager;
|
Chris@0
|
88 $this->configFactory = $config_factory;
|
Chris@0
|
89 $this->settings = $settings;
|
Chris@0
|
90 $this->requestStack = $requestStack;
|
Chris@0
|
91 }
|
Chris@0
|
92
|
Chris@0
|
93 /**
|
Chris@0
|
94 * Initializes the injected language manager with the negotiator.
|
Chris@0
|
95 *
|
Chris@0
|
96 * This should be called right after instantiating the negotiator to make it
|
Chris@0
|
97 * available to the language manager without introducing a circular
|
Chris@0
|
98 * dependency.
|
Chris@0
|
99 */
|
Chris@0
|
100 public function initLanguageManager() {
|
Chris@0
|
101 $this->languageManager->setNegotiator($this);
|
Chris@0
|
102 }
|
Chris@0
|
103
|
Chris@0
|
104 /**
|
Chris@0
|
105 * {@inheritdoc}
|
Chris@0
|
106 */
|
Chris@0
|
107 public function reset() {
|
Chris@0
|
108 $this->negotiatedLanguages = [];
|
Chris@0
|
109 $this->methods = [];
|
Chris@0
|
110 }
|
Chris@0
|
111
|
Chris@0
|
112 /**
|
Chris@0
|
113 * {@inheritdoc}
|
Chris@0
|
114 */
|
Chris@0
|
115 public function setCurrentUser(AccountInterface $current_user) {
|
Chris@0
|
116 $this->currentUser = $current_user;
|
Chris@0
|
117 $this->reset();
|
Chris@0
|
118 }
|
Chris@0
|
119
|
Chris@0
|
120 /**
|
Chris@0
|
121 * {@inheritdoc}
|
Chris@0
|
122 */
|
Chris@0
|
123 public function initializeType($type) {
|
Chris@0
|
124 $language = NULL;
|
Chris@0
|
125
|
Chris@0
|
126 if ($this->currentUser) {
|
Chris@0
|
127 // Execute the language negotiation methods in the order they were set up
|
Chris@0
|
128 // and return the first valid language found.
|
Chris@0
|
129 foreach ($this->getEnabledNegotiators($type) as $method_id => $info) {
|
Chris@0
|
130 if (!isset($this->negotiatedLanguages[$method_id])) {
|
Chris@0
|
131 $this->negotiatedLanguages[$method_id] = $this->negotiateLanguage($type, $method_id);
|
Chris@0
|
132 }
|
Chris@0
|
133
|
Chris@0
|
134 // Since objects are references, we need to return a clone to prevent
|
Chris@0
|
135 // the language negotiation method cache from being unintentionally
|
Chris@0
|
136 // altered. The same methods might be used with different language types
|
Chris@0
|
137 // based on configuration.
|
Chris@0
|
138 $language = !empty($this->negotiatedLanguages[$method_id]) ? clone($this->negotiatedLanguages[$method_id]) : NULL;
|
Chris@0
|
139
|
Chris@0
|
140 if ($language) {
|
Chris@0
|
141 $this->getNegotiationMethodInstance($method_id)->persist($language);
|
Chris@0
|
142 break;
|
Chris@0
|
143 }
|
Chris@0
|
144 }
|
Chris@0
|
145 }
|
Chris@0
|
146
|
Chris@0
|
147 if (!$language) {
|
Chris@0
|
148 // If no other language was found use the default one.
|
Chris@0
|
149 $language = $this->languageManager->getDefaultLanguage();
|
Chris@0
|
150 $method_id = static::METHOD_ID;
|
Chris@0
|
151 }
|
Chris@0
|
152
|
Chris@0
|
153 return [$method_id => $language];
|
Chris@0
|
154 }
|
Chris@0
|
155
|
Chris@0
|
156 /**
|
Chris@0
|
157 * Gets enabled detection methods for the provided language type.
|
Chris@0
|
158 *
|
Chris@0
|
159 * @param string $type
|
Chris@0
|
160 * The language type.
|
Chris@0
|
161 *
|
Chris@0
|
162 * @return array
|
Chris@0
|
163 * An array of enabled detection methods for the provided language type.
|
Chris@0
|
164 */
|
Chris@0
|
165 protected function getEnabledNegotiators($type) {
|
Chris@0
|
166 return $this->configFactory->get('language.types')->get('negotiation.' . $type . '.enabled') ?: [];
|
Chris@0
|
167 }
|
Chris@0
|
168
|
Chris@0
|
169 /**
|
Chris@0
|
170 * Performs language negotiation using the specified negotiation method.
|
Chris@0
|
171 *
|
Chris@0
|
172 * @param string $type
|
Chris@0
|
173 * The language type to be initialized.
|
Chris@0
|
174 * @param string $method_id
|
Chris@0
|
175 * The string identifier of the language negotiation method to use to detect
|
Chris@0
|
176 * language.
|
Chris@0
|
177 *
|
Chris@0
|
178 * @return \Drupal\Core\Language\LanguageInterface|null
|
Chris@0
|
179 * Negotiated language object for given type and method, FALSE otherwise.
|
Chris@0
|
180 */
|
Chris@0
|
181 protected function negotiateLanguage($type, $method_id) {
|
Chris@0
|
182 $langcode = NULL;
|
Chris@0
|
183 $method = $this->negotiatorManager->getDefinition($method_id);
|
Chris@0
|
184
|
Chris@0
|
185 if (!isset($method['types']) || in_array($type, $method['types'])) {
|
Chris@0
|
186 $langcode = $this->getNegotiationMethodInstance($method_id)->getLangcode($this->requestStack->getCurrentRequest());
|
Chris@0
|
187 }
|
Chris@0
|
188
|
Chris@0
|
189 $languages = $this->languageManager->getLanguages();
|
Chris@0
|
190 return isset($languages[$langcode]) ? $languages[$langcode] : NULL;
|
Chris@0
|
191 }
|
Chris@0
|
192
|
Chris@0
|
193 /**
|
Chris@0
|
194 * {@inheritdoc}
|
Chris@0
|
195 */
|
Chris@0
|
196 public function getNegotiationMethods($type = NULL) {
|
Chris@0
|
197 $definitions = $this->negotiatorManager->getDefinitions();
|
Chris@0
|
198 if (isset($type)) {
|
Chris@0
|
199 $enabled_methods = $this->getEnabledNegotiators($type);
|
Chris@0
|
200 $definitions = array_intersect_key($definitions, $enabled_methods);
|
Chris@0
|
201 }
|
Chris@0
|
202 return $definitions;
|
Chris@0
|
203 }
|
Chris@0
|
204
|
Chris@0
|
205 /**
|
Chris@0
|
206 * {@inheritdoc}
|
Chris@0
|
207 */
|
Chris@0
|
208 public function getNegotiationMethodInstance($method_id) {
|
Chris@0
|
209 if (!isset($this->methods[$method_id])) {
|
Chris@0
|
210 $instance = $this->negotiatorManager->createInstance($method_id, []);
|
Chris@0
|
211 $instance->setLanguageManager($this->languageManager);
|
Chris@0
|
212 $instance->setConfig($this->configFactory);
|
Chris@0
|
213 $instance->setCurrentUser($this->currentUser);
|
Chris@0
|
214 $this->methods[$method_id] = $instance;
|
Chris@0
|
215 }
|
Chris@0
|
216 return $this->methods[$method_id];
|
Chris@0
|
217 }
|
Chris@0
|
218
|
Chris@0
|
219 /**
|
Chris@0
|
220 * {@inheritdoc}
|
Chris@0
|
221 */
|
Chris@0
|
222 public function getPrimaryNegotiationMethod($type) {
|
Chris@0
|
223 $enabled_methods = $this->getEnabledNegotiators($type);
|
Chris@0
|
224 return empty($enabled_methods) ? LanguageNegotiatorInterface::METHOD_ID : key($enabled_methods);
|
Chris@0
|
225 }
|
Chris@0
|
226
|
Chris@0
|
227 /**
|
Chris@0
|
228 * {@inheritdoc}
|
Chris@0
|
229 */
|
Chris@0
|
230 public function isNegotiationMethodEnabled($method_id, $type = NULL) {
|
Chris@0
|
231 $enabled = FALSE;
|
Chris@0
|
232 $language_types = !empty($type) ? [$type] : $this->languageManager->getLanguageTypes();
|
Chris@0
|
233
|
Chris@0
|
234 foreach ($language_types as $type) {
|
Chris@0
|
235 $enabled_methods = $this->getEnabledNegotiators($type);
|
Chris@0
|
236 if (isset($enabled_methods[$method_id])) {
|
Chris@0
|
237 $enabled = TRUE;
|
Chris@0
|
238 break;
|
Chris@0
|
239 }
|
Chris@0
|
240 }
|
Chris@0
|
241
|
Chris@0
|
242 return $enabled;
|
Chris@0
|
243 }
|
Chris@0
|
244
|
Chris@0
|
245 /**
|
Chris@0
|
246 * {@inheritdoc}
|
Chris@0
|
247 */
|
Chris@0
|
248 public function saveConfiguration($type, $enabled_methods) {
|
Chris@0
|
249 // As configurable language types might have changed, we reset the cache.
|
Chris@0
|
250 $this->languageManager->reset();
|
Chris@0
|
251 $definitions = $this->getNegotiationMethods();
|
Chris@0
|
252 $default_types = $this->languageManager->getLanguageTypes();
|
Chris@0
|
253
|
Chris@18
|
254 // Ensure that the weights are integers.
|
Chris@18
|
255 $enabled_methods = array_map('intval', $enabled_methods);
|
Chris@18
|
256
|
Chris@0
|
257 // Order the language negotiation method list by weight.
|
Chris@0
|
258 asort($enabled_methods);
|
Chris@0
|
259 foreach ($enabled_methods as $method_id => $weight) {
|
Chris@0
|
260 if (isset($definitions[$method_id])) {
|
Chris@0
|
261 $method = $definitions[$method_id];
|
Chris@0
|
262 // If the language negotiation method does not express any preference
|
Chris@0
|
263 // about types, make it available for any configurable type.
|
Chris@0
|
264 $types = array_flip(!empty($method['types']) ? $method['types'] : $default_types);
|
Chris@0
|
265 // Check whether the method is defined and has the right type.
|
Chris@0
|
266 if (!isset($types[$type])) {
|
Chris@0
|
267 unset($enabled_methods[$method_id]);
|
Chris@0
|
268 }
|
Chris@0
|
269 }
|
Chris@0
|
270 else {
|
Chris@0
|
271 unset($enabled_methods[$method_id]);
|
Chris@0
|
272 }
|
Chris@0
|
273 }
|
Chris@18
|
274 $this->configFactory->getEditable('language.types')->set('negotiation.' . $type . '.enabled', $enabled_methods)->save(TRUE);
|
Chris@0
|
275 }
|
Chris@0
|
276
|
Chris@0
|
277 /**
|
Chris@0
|
278 * {@inheritdoc}
|
Chris@0
|
279 */
|
Chris@0
|
280 public function purgeConfiguration() {
|
Chris@0
|
281 // Ensure that we are getting the defined language negotiation information.
|
Chris@0
|
282 // An invocation of \Drupal\Core\Extension\ModuleInstaller::install() or
|
Chris@0
|
283 // \Drupal\Core\Extension\ModuleInstaller::uninstall() could invalidate the
|
Chris@0
|
284 // cached information.
|
Chris@0
|
285 $this->negotiatorManager->clearCachedDefinitions();
|
Chris@0
|
286 $this->languageManager->reset();
|
Chris@0
|
287 foreach ($this->languageManager->getDefinedLanguageTypesInfo() as $type => $info) {
|
Chris@0
|
288 $this->saveConfiguration($type, $this->getEnabledNegotiators($type));
|
Chris@0
|
289 }
|
Chris@0
|
290 }
|
Chris@0
|
291
|
Chris@0
|
292 /**
|
Chris@0
|
293 * {@inheritdoc}
|
Chris@0
|
294 */
|
Chris@0
|
295 public function updateConfiguration(array $types) {
|
Chris@0
|
296 // Ensure that we are getting the defined language negotiation information.
|
Chris@0
|
297 // An invocation of \Drupal\Core\Extension\ModuleInstaller::install() or
|
Chris@0
|
298 // \Drupal\Core\Extension\ModuleInstaller::uninstall() could invalidate the
|
Chris@0
|
299 // cached information.
|
Chris@0
|
300 $this->negotiatorManager->clearCachedDefinitions();
|
Chris@0
|
301 $this->languageManager->reset();
|
Chris@0
|
302
|
Chris@0
|
303 $language_types = [];
|
Chris@0
|
304 $language_types_info = $this->languageManager->getDefinedLanguageTypesInfo();
|
Chris@0
|
305 $method_definitions = $this->getNegotiationMethods();
|
Chris@0
|
306
|
Chris@0
|
307 foreach ($language_types_info as $type => $info) {
|
Chris@0
|
308 $configurable = in_array($type, $types);
|
Chris@0
|
309
|
Chris@0
|
310 // The default language negotiation settings, if available, are stored in
|
Chris@0
|
311 // $info['fixed'].
|
Chris@0
|
312 $has_default_settings = !empty($info['fixed']);
|
Chris@0
|
313 // Check whether the language type is unlocked. Only the status of
|
Chris@0
|
314 // unlocked language types can be toggled between configurable and
|
Chris@0
|
315 // non-configurable.
|
Chris@0
|
316 if (empty($info['locked'])) {
|
Chris@0
|
317 if (!$configurable && !$has_default_settings) {
|
Chris@0
|
318 // If we have an unlocked non-configurable language type without
|
Chris@0
|
319 // default language negotiation settings, we use the values
|
Chris@0
|
320 // negotiated for the interface language which, should always be
|
Chris@0
|
321 // available.
|
Chris@0
|
322 $method_weights = [LanguageNegotiationUI::METHOD_ID];
|
Chris@0
|
323 $method_weights = array_flip($method_weights);
|
Chris@0
|
324 $this->saveConfiguration($type, $method_weights);
|
Chris@0
|
325 }
|
Chris@0
|
326 }
|
Chris@0
|
327 else {
|
Chris@0
|
328 // The language type is locked. Locked language types with default
|
Chris@0
|
329 // settings are always considered non-configurable. In turn if default
|
Chris@0
|
330 // settings are missing, the language type is always considered
|
Chris@0
|
331 // configurable.
|
Chris@0
|
332
|
Chris@0
|
333 // If the language type is locked we can just store its default language
|
Chris@0
|
334 // negotiation settings if it has some, since it is not configurable.
|
Chris@0
|
335 if ($has_default_settings) {
|
Chris@0
|
336 $method_weights = [];
|
Chris@0
|
337 // Default settings are in $info['fixed'].
|
Chris@0
|
338
|
Chris@0
|
339 foreach ($info['fixed'] as $weight => $method_id) {
|
Chris@0
|
340 if (isset($method_definitions[$method_id])) {
|
Chris@0
|
341 $method_weights[$method_id] = $weight;
|
Chris@0
|
342 }
|
Chris@0
|
343 }
|
Chris@0
|
344 $this->saveConfiguration($type, $method_weights);
|
Chris@0
|
345 }
|
Chris@0
|
346 else {
|
Chris@0
|
347 // It was missing default settings, so force it to be configurable.
|
Chris@0
|
348 $configurable = TRUE;
|
Chris@0
|
349 }
|
Chris@0
|
350 }
|
Chris@0
|
351
|
Chris@0
|
352 // Accumulate information for each language type so it can be saved later.
|
Chris@0
|
353 $language_types[$type] = $configurable;
|
Chris@0
|
354 }
|
Chris@0
|
355
|
Chris@0
|
356 // Store the language type configuration.
|
Chris@0
|
357 $config = [
|
Chris@0
|
358 'configurable' => array_keys(array_filter($language_types)),
|
Chris@0
|
359 'all' => array_keys($language_types),
|
Chris@0
|
360 ];
|
Chris@0
|
361 $this->languageManager->saveLanguageTypesConfiguration($config);
|
Chris@0
|
362 }
|
Chris@0
|
363
|
Chris@0
|
364 }
|