LanguageNegotiationUrl.php 7.75 KB
Newer Older
1
2
3
4
<?php

namespace Drupal\language\Plugin\LanguageNegotiation;

5
use Drupal\Core\Language\LanguageInterface;
6
7
use Drupal\Core\PathProcessor\InboundPathProcessorInterface;
use Drupal\Core\PathProcessor\OutboundPathProcessorInterface;
8
use Drupal\Core\Render\BubbleableMetadata;
9
use Drupal\Core\Url;
10
11
12
13
14
15
16
use Drupal\language\LanguageNegotiationMethodBase;
use Drupal\language\LanguageSwitcherInterface;
use Symfony\Component\HttpFoundation\Request;

/**
 * Class for identifying language via URL prefix or domain.
 *
17
 * @LanguageNegotiation(
18
 *   id = \Drupal\language\Plugin\LanguageNegotiation\LanguageNegotiationUrl::METHOD_ID,
19
20
21
 *   types = {\Drupal\Core\Language\LanguageInterface::TYPE_INTERFACE,
 *   \Drupal\Core\Language\LanguageInterface::TYPE_CONTENT,
 *   \Drupal\Core\Language\LanguageInterface::TYPE_URL},
22
23
24
 *   weight = -8,
 *   name = @Translation("URL"),
 *   description = @Translation("Language from the URL (Path prefix or domain)."),
25
 *   config_route_name = "language.negotiation_url"
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
 * )
 */
class LanguageNegotiationUrl extends LanguageNegotiationMethodBase implements InboundPathProcessorInterface, OutboundPathProcessorInterface, LanguageSwitcherInterface {

  /**
   * The language negotiation method id.
   */
  const METHOD_ID = 'language-url';

  /**
   * URL language negotiation: use the path prefix as URL language indicator.
   */
  const CONFIG_PATH_PREFIX = 'path_prefix';

  /**
   * URL language negotiation: use the domain as URL language indicator.
   */
  const CONFIG_DOMAIN = 'domain';

  /**
   * {@inheritdoc}
   */
  public function getLangcode(Request $request = NULL) {
    $langcode = NULL;

    if ($request && $this->languageManager) {
      $languages = $this->languageManager->getLanguages();
      $config = $this->config->get('language.negotiation')->get('url');

      switch ($config['source']) {
        case LanguageNegotiationUrl::CONFIG_PATH_PREFIX:
          $request_path = urldecode(trim($request->getPathInfo(), '/'));
          $path_args = explode('/', $request_path);
          $prefix = array_shift($path_args);

61
          // Search prefix within added languages.
62
63
          $negotiated_language = FALSE;
          foreach ($languages as $language) {
64
            if (isset($config['prefixes'][$language->getId()]) && $config['prefixes'][$language->getId()] == $prefix) {
65
66
67
68
69
70
              $negotiated_language = $language;
              break;
            }
          }

          if ($negotiated_language) {
71
            $langcode = $negotiated_language->getId();
72
73
74
75
76
77
78
79
          }
          break;

        case LanguageNegotiationUrl::CONFIG_DOMAIN:
          // Get only the host, not the port.
          $http_host = $request->getHost();
          foreach ($languages as $language) {
            // Skip the check if the language doesn't have a domain.
80
            if (!empty($config['domains'][$language->getId()])) {
81
82
              // Ensure that there is exactly one protocol in the URL when
              // checking the hostname.
83
              $host = 'http://' . str_replace(array('http://', 'https://'), '', $config['domains'][$language->getId()]);
84
85
              $host = parse_url($host, PHP_URL_HOST);
              if ($http_host == $host) {
86
                $langcode = $language->getId();
87
88
89
90
91
92
93
94
95
96
97
98
                break;
              }
            }
          }
          break;
      }
    }

    return $langcode;
  }

  /**
99
   * {@inheritdoc}
100
101
102
   */
  public function processInbound($path, Request $request) {
    $config = $this->config->get('language.negotiation')->get('url');
103
    $parts = explode('/', trim($path, '/'));
104
105
    $prefix = array_shift($parts);

106
    // Search prefix within added languages.
107
    foreach ($this->languageManager->getLanguages() as $language) {
108
      if (isset($config['prefixes'][$language->getId()]) && $config['prefixes'][$language->getId()] == $prefix) {
109
        // Rebuild $path with the language removed.
110
        $path = '/' . implode('/', $parts);
111
112
113
114
115
116
117
118
        break;
      }
    }

    return $path;
  }

  /**
119
   * {@inheritdoc}
120
   */
121
  public function processOutbound($path, &$options = array(), Request $request = NULL, BubbleableMetadata $bubbleable_metadata = NULL) {
122
123
124
125
126
127
128
129
130
    $url_scheme = 'http';
    $port = 80;
    if ($request) {
      $url_scheme = $request->getScheme();
      $port = $request->getPort();
    }
    $languages = array_flip(array_keys($this->languageManager->getLanguages()));
    // Language can be passed as an option, or we go for current URL language.
    if (!isset($options['language'])) {
131
      $language_url = $this->languageManager->getCurrentLanguage(LanguageInterface::TYPE_URL);
132
133
      $options['language'] = $language_url;
    }
134
    // We allow only added languages here.
135
    elseif (!is_object($options['language']) || !isset($languages[$options['language']->getId()])) {
136
137
138
139
      return $path;
    }
    $config = $this->config->get('language.negotiation')->get('url');
    if ($config['source'] == LanguageNegotiationUrl::CONFIG_PATH_PREFIX) {
140
141
      if (is_object($options['language']) && !empty($config['prefixes'][$options['language']->getId()])) {
        $options['prefix'] = $config['prefixes'][$options['language']->getId()] . '/';
142
143
        if ($bubbleable_metadata) {
          $bubbleable_metadata->addCacheContexts(['languages:' . LanguageInterface::TYPE_URL]);
144
        }
145
146
      }
    }
147
    elseif ($config['source'] == LanguageNegotiationUrl::CONFIG_DOMAIN) {
148
      if (is_object($options['language']) && !empty($config['domains'][$options['language']->getId()])) {
149
150
151
152
153
154
155
156
157
158

        // Save the original base URL. If it contains a port, we need to
        // retain it below.
        if (!empty($options['base_url'])) {
          // The colon in the URL scheme messes up the port checking below.
          $normalized_base_url = str_replace(array('https://', 'http://'), '', $options['base_url']);
        }

        // Ask for an absolute URL with our modified base URL.
        $options['absolute'] = TRUE;
159
        $options['base_url'] = $url_scheme . '://' . $config['domains'][$options['language']->getId()];
160
161
162
163
164
165
166

        // In case either the original base URL or the HTTP host contains a
        // port, retain it.
        if (isset($normalized_base_url) && strpos($normalized_base_url, ':') !== FALSE) {
          list(, $port) = explode(':', $normalized_base_url);
          $options['base_url'] .= ':' . $port;
        }
167
        elseif (($url_scheme == 'http' && $port != 80) || ($url_scheme == 'https' && $port != 443)) {
168
169
170
          $options['base_url'] .= ':' . $port;
        }

171
        if (isset($options['https'])) {
172
173
174
175
176
177
178
179
180
181
          if ($options['https'] === TRUE) {
            $options['base_url'] = str_replace('http://', 'https://', $options['base_url']);
          }
          elseif ($options['https'] === FALSE) {
            $options['base_url'] = str_replace('https://', 'http://', $options['base_url']);
          }
        }

        // Add Drupal's subfolder from the base_path if there is one.
        $options['base_url'] .= rtrim(base_path(), '/');
182
183
        if ($bubbleable_metadata) {
          $bubbleable_metadata->addCacheContexts(['languages:' . LanguageInterface::TYPE_URL, 'url.site']);
184
        }
185
186
187
188
189
190
191
192
      }
    }
    return $path;
  }

  /**
   * {@inheritdoc}
   */
193
  public function getLanguageSwitchLinks(Request $request, $type, Url $url) {
194
    $links = array();
195
    $query = $request->query->all();
196

197
    foreach ($this->languageManager->getNativeLanguages() as $language) {
198
      $links[$language->getId()] = array(
199
200
201
        // We need to clone the $url object to avoid using the same one for all
        // links. When the links are rendered, options are set on the $url
        // object, so if we use the same one, they would be set for all links.
202
        'url' => clone $url,
203
        'title' => $language->getName(),
204
205
        'language' => $language,
        'attributes' => array('class' => array('language-link')),
206
        'query' => $query,
207
208
209
210
211
212
213
      );
    }

    return $links;
  }

}