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