3 namespace Drupal\language\Plugin\LanguageNegotiation;
5 use Drupal\Core\Language\LanguageInterface;
6 use Drupal\Core\PathProcessor\InboundPathProcessorInterface;
7 use Drupal\Core\PathProcessor\OutboundPathProcessorInterface;
8 use Drupal\Core\Render\BubbleableMetadata;
10 use Drupal\language\LanguageNegotiationMethodBase;
11 use Drupal\language\LanguageSwitcherInterface;
12 use Symfony\Component\HttpFoundation\Request;
15 * Class for identifying language via URL prefix or domain.
17 * @LanguageNegotiation(
18 * id = \Drupal\language\Plugin\LanguageNegotiation\LanguageNegotiationUrl::METHOD_ID,
19 * types = {\Drupal\Core\Language\LanguageInterface::TYPE_INTERFACE,
20 * \Drupal\Core\Language\LanguageInterface::TYPE_CONTENT,
21 * \Drupal\Core\Language\LanguageInterface::TYPE_URL},
23 * name = @Translation("URL"),
24 * description = @Translation("Language from the URL (Path prefix or domain)."),
25 * config_route_name = "language.negotiation_url"
28 class LanguageNegotiationUrl extends LanguageNegotiationMethodBase implements InboundPathProcessorInterface, OutboundPathProcessorInterface, LanguageSwitcherInterface {
31 * The language negotiation method id.
33 const METHOD_ID = 'language-url';
36 * URL language negotiation: use the path prefix as URL language indicator.
38 const CONFIG_PATH_PREFIX = 'path_prefix';
41 * URL language negotiation: use the domain as URL language indicator.
43 const CONFIG_DOMAIN = 'domain';
48 public function getLangcode(Request $request = NULL) {
51 if ($request && $this->languageManager) {
52 $languages = $this->languageManager->getLanguages();
53 $config = $this->config->get('language.negotiation')->get('url');
55 switch ($config['source']) {
56 case LanguageNegotiationUrl::CONFIG_PATH_PREFIX:
57 $request_path = urldecode(trim($request->getPathInfo(), '/'));
58 $path_args = explode('/', $request_path);
59 $prefix = array_shift($path_args);
61 // Search prefix within added languages.
62 $negotiated_language = FALSE;
63 foreach ($languages as $language) {
64 if (isset($config['prefixes'][$language->getId()]) && $config['prefixes'][$language->getId()] == $prefix) {
65 $negotiated_language = $language;
70 if ($negotiated_language) {
71 $langcode = $negotiated_language->getId();
75 case LanguageNegotiationUrl::CONFIG_DOMAIN:
76 // Get only the host, not the port.
77 $http_host = $request->getHost();
78 foreach ($languages as $language) {
79 // Skip the check if the language doesn't have a domain.
80 if (!empty($config['domains'][$language->getId()])) {
81 // Ensure that there is exactly one protocol in the URL when
82 // checking the hostname.
83 $host = 'http://' . str_replace(['http://', 'https://'], '', $config['domains'][$language->getId()]);
84 $host = parse_url($host, PHP_URL_HOST);
85 if ($http_host == $host) {
86 $langcode = $language->getId();
101 public function processInbound($path, Request $request) {
102 $config = $this->config->get('language.negotiation')->get('url');
104 if ($config['source'] == LanguageNegotiationUrl::CONFIG_PATH_PREFIX) {
105 $parts = explode('/', trim($path, '/'));
106 $prefix = array_shift($parts);
108 // Search prefix within added languages.
109 foreach ($this->languageManager->getLanguages() as $language) {
110 if (isset($config['prefixes'][$language->getId()]) && $config['prefixes'][$language->getId()] == $prefix) {
111 // Rebuild $path with the language removed.
112 $path = '/' . implode('/', $parts);
124 public function processOutbound($path, &$options = [], Request $request = NULL, BubbleableMetadata $bubbleable_metadata = NULL) {
125 $url_scheme = 'http';
128 $url_scheme = $request->getScheme();
129 $port = $request->getPort();
131 $languages = array_flip(array_keys($this->languageManager->getLanguages()));
132 // Language can be passed as an option, or we go for current URL language.
133 if (!isset($options['language'])) {
134 $language_url = $this->languageManager->getCurrentLanguage(LanguageInterface::TYPE_URL);
135 $options['language'] = $language_url;
137 // We allow only added languages here.
138 elseif (!is_object($options['language']) || !isset($languages[$options['language']->getId()])) {
141 $config = $this->config->get('language.negotiation')->get('url');
142 if ($config['source'] == LanguageNegotiationUrl::CONFIG_PATH_PREFIX) {
143 if (is_object($options['language']) && !empty($config['prefixes'][$options['language']->getId()])) {
144 $options['prefix'] = $config['prefixes'][$options['language']->getId()] . '/';
145 if ($bubbleable_metadata) {
146 $bubbleable_metadata->addCacheContexts(['languages:' . LanguageInterface::TYPE_URL]);
150 elseif ($config['source'] == LanguageNegotiationUrl::CONFIG_DOMAIN) {
151 if (is_object($options['language']) && !empty($config['domains'][$options['language']->getId()])) {
153 // Save the original base URL. If it contains a port, we need to
155 if (!empty($options['base_url'])) {
156 // The colon in the URL scheme messes up the port checking below.
157 $normalized_base_url = str_replace(['https://', 'http://'], '', $options['base_url']);
160 // Ask for an absolute URL with our modified base URL.
161 $options['absolute'] = TRUE;
162 $options['base_url'] = $url_scheme . '://' . $config['domains'][$options['language']->getId()];
164 // In case either the original base URL or the HTTP host contains a
166 if (isset($normalized_base_url) && strpos($normalized_base_url, ':') !== FALSE) {
167 list(, $port) = explode(':', $normalized_base_url);
168 $options['base_url'] .= ':' . $port;
170 elseif (($url_scheme == 'http' && $port != 80) || ($url_scheme == 'https' && $port != 443)) {
171 $options['base_url'] .= ':' . $port;
174 if (isset($options['https'])) {
175 if ($options['https'] === TRUE) {
176 $options['base_url'] = str_replace('http://', 'https://', $options['base_url']);
178 elseif ($options['https'] === FALSE) {
179 $options['base_url'] = str_replace('https://', 'http://', $options['base_url']);
183 // Add Drupal's subfolder from the base_path if there is one.
184 $options['base_url'] .= rtrim(base_path(), '/');
185 if ($bubbleable_metadata) {
186 $bubbleable_metadata->addCacheContexts(['languages:' . LanguageInterface::TYPE_URL, 'url.site']);
196 public function getLanguageSwitchLinks(Request $request, $type, Url $url) {
198 $query = $request->query->all();
200 foreach ($this->languageManager->getNativeLanguages() as $language) {
201 $links[$language->getId()] = [
202 // We need to clone the $url object to avoid using the same one for all
203 // links. When the links are rendered, options are set on the $url
204 // object, so if we use the same one, they would be set for all links.
206 'title' => $language->getName(),
207 'language' => $language,
208 'attributes' => ['class' => ['language-link']],