Upgraded drupal core with security updates
[yaffs-website] / web / core / modules / language / src / Plugin / LanguageNegotiation / LanguageNegotiationUrl.php
1 <?php
2
3 namespace Drupal\language\Plugin\LanguageNegotiation;
4
5 use Drupal\Core\Language\LanguageInterface;
6 use Drupal\Core\PathProcessor\InboundPathProcessorInterface;
7 use Drupal\Core\PathProcessor\OutboundPathProcessorInterface;
8 use Drupal\Core\Render\BubbleableMetadata;
9 use Drupal\Core\Url;
10 use Drupal\language\LanguageNegotiationMethodBase;
11 use Drupal\language\LanguageSwitcherInterface;
12 use Symfony\Component\HttpFoundation\Request;
13
14 /**
15  * Class for identifying language via URL prefix or domain.
16  *
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},
22  *   weight = -8,
23  *   name = @Translation("URL"),
24  *   description = @Translation("Language from the URL (Path prefix or domain)."),
25  *   config_route_name = "language.negotiation_url"
26  * )
27  */
28 class LanguageNegotiationUrl extends LanguageNegotiationMethodBase implements InboundPathProcessorInterface, OutboundPathProcessorInterface, LanguageSwitcherInterface {
29
30   /**
31    * The language negotiation method id.
32    */
33   const METHOD_ID = 'language-url';
34
35   /**
36    * URL language negotiation: use the path prefix as URL language indicator.
37    */
38   const CONFIG_PATH_PREFIX = 'path_prefix';
39
40   /**
41    * URL language negotiation: use the domain as URL language indicator.
42    */
43   const CONFIG_DOMAIN = 'domain';
44
45   /**
46    * {@inheritdoc}
47    */
48   public function getLangcode(Request $request = NULL) {
49     $langcode = NULL;
50
51     if ($request && $this->languageManager) {
52       $languages = $this->languageManager->getLanguages();
53       $config = $this->config->get('language.negotiation')->get('url');
54
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);
60
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;
66               break;
67             }
68           }
69
70           if ($negotiated_language) {
71             $langcode = $negotiated_language->getId();
72           }
73           break;
74
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();
87                 break;
88               }
89             }
90           }
91           break;
92       }
93     }
94
95     return $langcode;
96   }
97
98   /**
99    * {@inheritdoc}
100    */
101   public function processInbound($path, Request $request) {
102     $config = $this->config->get('language.negotiation')->get('url');
103
104     if ($config['source'] == LanguageNegotiationUrl::CONFIG_PATH_PREFIX) {
105       $parts = explode('/', trim($path, '/'));
106       $prefix = array_shift($parts);
107
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);
113           break;
114         }
115       }
116     }
117
118     return $path;
119   }
120
121   /**
122    * {@inheritdoc}
123    */
124   public function processOutbound($path, &$options = [], Request $request = NULL, BubbleableMetadata $bubbleable_metadata = NULL) {
125     $url_scheme = 'http';
126     $port = 80;
127     if ($request) {
128       $url_scheme = $request->getScheme();
129       $port = $request->getPort();
130     }
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;
136     }
137     // We allow only added languages here.
138     elseif (!is_object($options['language']) || !isset($languages[$options['language']->getId()])) {
139       return $path;
140     }
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]);
147         }
148       }
149     }
150     elseif ($config['source'] == LanguageNegotiationUrl::CONFIG_DOMAIN) {
151       if (is_object($options['language']) && !empty($config['domains'][$options['language']->getId()])) {
152
153         // Save the original base URL. If it contains a port, we need to
154         // retain it below.
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']);
158         }
159
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()];
163
164         // In case either the original base URL or the HTTP host contains a
165         // port, retain it.
166         if (isset($normalized_base_url) && strpos($normalized_base_url, ':') !== FALSE) {
167           list(, $port) = explode(':', $normalized_base_url);
168           $options['base_url'] .= ':' . $port;
169         }
170         elseif (($url_scheme == 'http' && $port != 80) || ($url_scheme == 'https' && $port != 443)) {
171           $options['base_url'] .= ':' . $port;
172         }
173
174         if (isset($options['https'])) {
175           if ($options['https'] === TRUE) {
176             $options['base_url'] = str_replace('http://', 'https://', $options['base_url']);
177           }
178           elseif ($options['https'] === FALSE) {
179             $options['base_url'] = str_replace('https://', 'http://', $options['base_url']);
180           }
181         }
182
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']);
187         }
188       }
189     }
190     return $path;
191   }
192
193   /**
194    * {@inheritdoc}
195    */
196   public function getLanguageSwitchLinks(Request $request, $type, Url $url) {
197     $links = [];
198     $query = $request->query->all();
199
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.
205         'url' => clone $url,
206         'title' => $language->getName(),
207         'language' => $language,
208         'attributes' => ['class' => ['language-link']],
209         'query' => $query,
210       ];
211     }
212
213     return $links;
214   }
215
216 }