ConfigurableLanguageManager.php 15 KB

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