1<?php
2
3namespace Drupal\language\Form;
4
5use Drupal\Core\Config\ConfigFactoryInterface;
6use Drupal\Core\Form\ConfigFormBase;
7use Drupal\Core\Form\FormStateInterface;
8use Drupal\Core\Url;
9use Drupal\language\ConfigurableLanguageManagerInterface;
10use Symfony\Component\DependencyInjection\ContainerInterface;
11
12/**
13 * Configure the browser language negotiation method for this site.
14 *
15 * @internal
16 */
17class NegotiationBrowserForm extends ConfigFormBase {
18
19  /**
20   * The configurable language manager.
21   *
22   * @var \Drupal\language\ConfigurableLanguageManagerInterface
23   */
24  protected $languageManager;
25
26  /**
27   * {@inheritdoc}
28   */
29  public function __construct(ConfigFactoryInterface $config_factory, ConfigurableLanguageManagerInterface $language_manager) {
30    parent::__construct($config_factory);
31    $this->languageManager = $language_manager;
32  }
33
34  /**
35   * {@inheritdoc}
36   */
37  public static function create(ContainerInterface $container) {
38    return new static(
39      $container->get('config.factory'),
40      $container->get('language_manager')
41    );
42  }
43
44  /**
45   * {@inheritdoc}
46   */
47  public function getFormId() {
48    return 'language_negotiation_configure_browser_form';
49  }
50
51  /**
52   * {@inheritdoc}
53   */
54  protected function getEditableConfigNames() {
55    return ['language.mappings'];
56  }
57
58  /**
59   * {@inheritdoc}
60   */
61  public function buildForm(array $form, FormStateInterface $form_state) {
62    $form = [];
63
64    // Initialize a language list to the ones available, including English.
65    $languages = $this->languageManager->getLanguages();
66
67    $existing_languages = [];
68    foreach ($languages as $langcode => $language) {
69      $existing_languages[$langcode] = $language->getName();
70    }
71
72    // If we have no languages available, present the list of predefined languages
73    // only. If we do have already added languages, set up two option groups with
74    // the list of existing and then predefined languages.
75    if (empty($existing_languages)) {
76      $language_options = $this->languageManager->getStandardLanguageListWithoutConfigured();
77    }
78    else {
79      $language_options = [
80        (string) $this->t('Existing languages') => $existing_languages,
81        (string) $this->t('Languages not yet added') => $this->languageManager->getStandardLanguageListWithoutConfigured(),
82      ];
83    }
84
85    $form['mappings'] = [
86      '#type' => 'table',
87      '#header' => [
88        $this->t('Browser language code'),
89        $this->t('Site language'),
90        $this->t('Operations'),
91      ],
92      '#attributes' => ['id' => 'language-negotiation-browser'],
93      '#empty' => $this->t('No browser language mappings available.'),
94    ];
95
96    $mappings = $this->language_get_browser_drupal_langcode_mappings();
97    foreach ($mappings as $browser_langcode => $drupal_langcode) {
98      $form['mappings'][$browser_langcode] = [
99        'browser_langcode' => [
100          '#title' => $this->t('Browser language code'),
101          '#title_display' => 'invisible',
102          '#type' => 'textfield',
103          '#default_value' => $browser_langcode,
104          '#size' => 20,
105          '#required' => TRUE,
106        ],
107        'drupal_langcode' => [
108          '#title' => $this->t('Site language'),
109          '#title_display' => 'invisible',
110          '#type' => 'select',
111          '#options' => $language_options,
112          '#default_value' => $drupal_langcode,
113          '#required' => TRUE,
114        ],
115      ];
116      // Operations column.
117      $form['mappings'][$browser_langcode]['operations'] = [
118        '#type' => 'operations',
119        '#links' => [],
120      ];
121      $form['mappings'][$browser_langcode]['operations']['#links']['delete'] = [
122        'title' => $this->t('Delete'),
123        'url' => Url::fromRoute('language.negotiation_browser_delete', ['browser_langcode' => $browser_langcode]),
124      ];
125    }
126
127    // Add empty row.
128    $form['new_mapping'] = [
129      '#type' => 'details',
130      '#title' => $this->t('Add a new mapping'),
131      '#tree' => TRUE,
132    ];
133    $form['new_mapping']['browser_langcode'] = [
134      '#type' => 'textfield',
135      '#title' => $this->t('Browser language code'),
136      '#description' => $this->t('Use language codes as <a href=":w3ctags">defined by the W3C</a> for interoperability. <em>Examples: "en", "en-gb" and "zh-hant".</em>', [':w3ctags' => 'http://www.w3.org/International/articles/language-tags/']),
137      '#size' => 20,
138    ];
139    $form['new_mapping']['drupal_langcode'] = [
140      '#type' => 'select',
141      '#title' => $this->t('Site language'),
142      '#options' => $language_options,
143    ];
144
145    return parent::buildForm($form, $form_state);
146  }
147
148  /**
149   * {@inheritdoc}
150   */
151  public function validateForm(array &$form, FormStateInterface $form_state) {
152    // Array to check if all browser language codes are unique.
153    $unique_values = [];
154
155    // Check all mappings.
156    if ($form_state->hasValue('mappings')) {
157      $mappings = $form_state->getValue('mappings');
158      foreach ($mappings as $key => $data) {
159        // Make sure browser_langcode is unique.
160        if (array_key_exists($data['browser_langcode'], $unique_values)) {
161          $form_state->setErrorByName('mappings][new_mapping][browser_langcode', $this->t('Browser language codes must be unique.'));
162        }
163        elseif (preg_match('/[^a-z\-]/', $data['browser_langcode'])) {
164          $form_state->setErrorByName('mappings][new_mapping][browser_langcode', $this->t('Browser language codes can only contain lowercase letters and a hyphen(-).'));
165        }
166        $unique_values[$data['browser_langcode']] = $data['drupal_langcode'];
167      }
168    }
169
170    // Check new mapping.
171    $data = $form_state->getValue('new_mapping');
172    if (!empty($data['browser_langcode'])) {
173      // Make sure browser_langcode is unique.
174      if (array_key_exists($data['browser_langcode'], $unique_values)) {
175        $form_state->setErrorByName('mappings][' . $key . '][browser_langcode', $this->t('Browser language codes must be unique.'));
176      }
177      elseif (preg_match('/[^a-z\-]/', $data['browser_langcode'])) {
178        $form_state->setErrorByName('mappings][' . $key . '][browser_langcode', $this->t('Browser language codes can only contain lowercase letters and a hyphen(-).'));
179      }
180      $unique_values[$data['browser_langcode']] = $data['drupal_langcode'];
181    }
182
183    $form_state->set('mappings', $unique_values);
184  }
185
186  /**
187   * {@inheritdoc}
188   */
189  public function submitForm(array &$form, FormStateInterface $form_state) {
190    $mappings = $form_state->get('mappings');
191    if (!empty($mappings)) {
192      $config = $this->config('language.mappings');
193      $config->setData(['map' => $mappings]);
194      $config->save();
195    }
196
197    parent::submitForm($form, $form_state);
198  }
199
200  /**
201   * Retrieves the browser's langcode mapping configuration array.
202   *
203   * @return array
204   *   The browser's langcode mapping configuration array.
205   */
206  protected function language_get_browser_drupal_langcode_mappings() {
207    $config = $this->config('language.mappings');
208    if ($config->isNew()) {
209      return [];
210    }
211    return $config->get('map');
212  }
213
214}
215