Commit 30a5046e authored by catch's avatar catch

Issue #2355909 by penyaskito, alexpott, Gábor Hojtsy, DuaelFr:...

Issue #2355909 by penyaskito, alexpott, Gábor Hojtsy, DuaelFr: language.settings config is not scalable
parent 4cd9d457
......@@ -29,6 +29,7 @@ public function form(array $form, FormStateInterface $form_state) {
// @todo: convert to a language selection widget defined in the base field.
// Blocked on https://drupal.org/node/2226493 which adds a generic
// language widget.
// Language module may expose or hide this element, see language_form_alter().
$form['langcode'] = array(
'#title' => $this->t('Language'),
'#type' => 'language_select',
......
......@@ -13,6 +13,7 @@
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Language\LanguageInterface;
use Drupal\Core\Language\LanguageManagerInterface;
use Drupal\language\Entity\ContentLanguageSettings;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
......@@ -103,22 +104,13 @@ public function form(array $form, FormStateInterface $form_state) {
// names.
$form['#attributes']['class'][0] = drupal_html_class('block-' . $block->bundle() . '-form');
if ($this->moduleHandler->moduleExists('language')) {
$language_configuration = language_get_default_configuration('block_content', $block->bundle());
// Set the correct default language.
if ($block->isNew()) {
$language_default = $this->languageManager->getCurrentLanguage($language_configuration['langcode']);
$block->langcode->value = $language_default->getId();
}
}
$form['langcode'] = array(
'#title' => $this->t('Language'),
'#type' => 'language_select',
'#default_value' => $block->getUntranslated()->language()->getId(),
'#languages' => LanguageInterface::STATE_ALL,
'#access' => isset($language_configuration['language_show']) && $language_configuration['language_show'],
// Language module may expose or hide this element, see language_form_alter().
'#access' => FALSE,
);
$form['advanced'] = array(
......
......@@ -12,6 +12,7 @@
use Drupal\Core\Form\FormStateInterface;
use Drupal\field\Entity\FieldConfig;
use Drupal\field\Entity\FieldStorageConfig;
use Drupal\language\Entity\ContentLanguageSettings;
/**
* Base form for category edit forms.
......@@ -65,7 +66,7 @@ public function form(array $form, FormStateInterface $form_state) {
'#group' => 'additional_settings',
);
$language_configuration = language_get_default_configuration('block_content', $block_type->id());
$language_configuration = ContentLanguageSettings::loadByEntityTypeBundle('block_content', $block_type->id());
$form['language']['language_configuration'] = array(
'#type' => 'language_configuration',
'#entity_information' => array(
......
......@@ -17,6 +17,7 @@
use Drupal\Core\Language\Language;
use Drupal\Core\Language\LanguageInterface;
use Drupal\Core\Session\AccountInterface;
use Drupal\language\Entity\ContentLanguageSettings;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
......@@ -161,13 +162,13 @@ public function form(array $form, FormStateInterface $form_state) {
$form['author']['name']['#attributes']['data-drupal-default-value'] = $this->config('user.settings')->get('anonymous');
}
$language_configuration = \Drupal::moduleHandler()->invoke('language', 'get_default_configuration', array('comment', $comment->getTypeId()));
$form['langcode'] = array(
'#title' => t('Language'),
'#type' => 'language_select',
'#default_value' => $comment->getUntranslated()->language()->getId(),
'#languages' => Language::STATE_ALL,
'#access' => isset($language_configuration['language_show']) && $language_configuration['language_show'],
// Language module may expose or hide this element, see language_form_alter().
'#access' => FALSE,
);
// Add author email and homepage fields depending on the current user.
......
......@@ -12,6 +12,7 @@
use Drupal\Core\Entity\EntityManagerInterface;
use Drupal\Core\Entity\EntityTypeInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\language\Entity\ContentLanguageSettings;
use Psr\Log\LoggerInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
......@@ -132,7 +133,7 @@ public function form(array $form, FormStateInterface $form_state) {
'#group' => 'additional_settings',
);
$language_configuration = language_get_default_configuration('comment', $comment_type->id());
$language_configuration = ContentLanguageSettings::loadByEntityTypeBundle('comment', $comment_type->id());
$form['language']['language_configuration'] = array(
'#type' => 'language_configuration',
'#entity_information' => array(
......
......@@ -44,7 +44,7 @@ protected function setUp() {
$this->drupalPostForm('admin/config/regional/language/add', $edit, t('Add language'));
// Set "Article" content type to use multilingual support.
$edit = array('language_configuration[language_show]' => TRUE);
$edit = array('language_configuration[language_alterable]' => TRUE);
$this->drupalPostForm('admin/structure/types/manage/article', $edit, t('Save content type'));
// Enable content language negotiation UI.
......
......@@ -98,13 +98,13 @@ public function form(array $form, FormStateInterface $form_state) {
$form['preview']['message'] = $this->entityManager->getViewBuilder('contact_message')->view($message, 'full');
}
$language_configuration = $this->moduleHandler->invoke('language', 'get_default_configuration', array('contact_message', $message->getContactForm()->id()));
$form['langcode'] = array(
'#title' => $this->t('Language'),
'#type' => 'language_select',
'#default_value' => $message->getUntranslated()->language()->getId(),
'#languages' => Language::STATE_ALL,
'#access' => isset($language_configuration['language_show']) && $language_configuration['language_show'],
// Language module may expose or hide this element, see language_form_alter().
'#access' => FALSE,
);
$form['name'] = array(
......
......@@ -266,7 +266,7 @@ function content_translation_form_language_content_settings_validate(array $form
}
$values = $bundle_settings['settings']['language'];
if (empty($values['language_show']) && \Drupal::languageManager()->isLanguageLocked($values['langcode'])) {
if (empty($values['language_alterable']) && \Drupal::languageManager()->isLanguageLocked($values['langcode'])) {
foreach (\Drupal::languageManager()->getLanguages(LanguageInterface::STATE_LOCKED) as $language) {
$locked_languages[] = $language->getName();
}
......
......@@ -90,7 +90,7 @@
var $settings = $bundleSettings.nextUntil('.bundle-settings');
var $fieldSettings = $settings.filter('.field-settings');
if ($target.is(':checked')) {
$bundleSettings.find('.operations :input[name$="[language_show]"]').prop('checked', true);
$bundleSettings.find('.operations :input[name$="[language_alterable]"]').prop('checked', true);
$fieldSettings.find('.translatable :input').prop('checked', true);
$settings.show();
}
......
......@@ -675,7 +675,7 @@ function content_translation_language_configuration_element_process(array $eleme
function content_translation_language_configuration_element_validate($element, FormStateInterface $form_state, array $form) {
$key = $form_state->get(['content_translation', 'key']);
$values = $form_state->getValue($key);
if (!$values['language_show'] && $values['content_translation'] && \Drupal::languageManager()->isLanguageLocked($values['langcode'])) {
if (!$values['language_alterable'] && $values['content_translation'] && \Drupal::languageManager()->isLanguageLocked($values['langcode'])) {
foreach (\Drupal::languageManager()->getLanguages(LanguageInterface::STATE_LOCKED) as $language) {
$locked_languages[] = $language->getName();
}
......
......@@ -9,6 +9,7 @@
use Drupal\Component\Serialization\Json;
use Drupal\language\Entity\ConfigurableLanguage;
use Drupal\language\Entity\ContentLanguageSettings;
use Drupal\node\Entity\NodeType;
use Drupal\simpletest\WebTestBase;
......@@ -100,11 +101,10 @@ protected function setUp() {
->save();
// Enable content translation.
$configuration = array(
'langcode' => language_default()->getId(),
'language_show' => TRUE,
);
language_save_default_configuration('node', $this->bundle, $configuration);
ContentLanguageSettings::loadByEntityTypeBundle('node', $this->bundle)
->setLanguageAlterable(TRUE)
->setDefaultLangcode(\Drupal::languageManager()->getDefaultLanguage()->getId())
->save();
// Create a translator user.
$permissions = array(
'access contextual links',
......
......@@ -11,6 +11,7 @@
use Drupal\Core\Field\Entity\BaseFieldOverride;
use Drupal\Core\Language\Language;
use Drupal\field\Entity\FieldConfig;
use Drupal\language\Entity\ContentLanguageSettings;
use Drupal\simpletest\WebTestBase;
/**
......@@ -93,7 +94,7 @@ function testSettingsUI() {
$edit = array(
'entity_types[comment]' => TRUE,
'settings[comment][comment_article][settings][language][langcode]' => Language::LANGCODE_NOT_SPECIFIED,
'settings[comment][comment_article][settings][language][language_show]' => FALSE,
'settings[comment][comment_article][settings][language][language_alterable]' => FALSE,
'settings[comment][comment_article][translatable]' => TRUE,
'settings[comment][comment_article][fields][comment_body]' => TRUE,
);
......@@ -105,7 +106,7 @@ function testSettingsUI() {
$edit = array(
'entity_types[comment]' => TRUE,
'settings[comment][comment_article][settings][language][langcode]' => 'current_interface',
'settings[comment][comment_article][settings][language][language_show]' => TRUE,
'settings[comment][comment_article][settings][language][language_alterable]' => TRUE,
'settings[comment][comment_article][translatable]' => TRUE,
'settings[comment][comment_article][fields][comment_body]' => TRUE,
// Override both comment subject fields to untranslatable.
......@@ -137,9 +138,9 @@ function testSettingsUI() {
$this->assertTrue($definitions['name']->isTranslatable() && !$definitions['user_id']->isTranslatable(), 'Base field bundle overrides were correctly altered.');
// Test that language settings are correctly stored.
$language_configuration = language_get_default_configuration('comment', 'comment_article');
$this->assertEqual($language_configuration['langcode'], 'current_interface', 'The default language for article comments is set to the current interface language.');
$this->assertTrue($language_configuration['language_show'], 'The language selector for article comments is shown.');
$language_configuration = ContentLanguageSettings::loadByEntityTypeBundle('comment', 'comment_article');
$this->assertEqual($language_configuration->getDefaultLangcode(), 'current_interface', 'The default language for article comments is set to the current interface language.');
$this->assertTrue($language_configuration->isLanguageAlterable(), 'The language selector for article comments is shown.');
// Verify language widget appears on comment type form.
$this->drupalGet('admin/structure/comment/manage/comment_article');
......@@ -162,7 +163,7 @@ function testSettingsUI() {
$edit = array(
'entity_types[node]' => TRUE,
'settings[node][article][settings][language][langcode]' => 'current_interface',
'settings[node][article][settings][language][language_show]' => TRUE,
'settings[node][article][settings][language][language_alterable]' => TRUE,
'settings[node][article][translatable]' => TRUE,
'settings[node][article][fields][title]' => TRUE
);
......@@ -259,7 +260,7 @@ function testFieldTranslatableSettingsUI() {
// Note: this field is not translatable when enable bundle translatability.
$edit = array(
'entity_types[node]' => TRUE,
'settings[node][article][settings][language][language_show]' => TRUE,
'settings[node][article][settings][language][language_alterable]' => TRUE,
'settings[node][article][translatable]' => TRUE,
'settings[node][article][fields][article_text]' => TRUE,
);
......
......@@ -101,34 +101,30 @@ language.entity.*:
type: boolean
label: 'Locked'
language.settings:
type: mapping
label: 'Language settings'
language.content_settings.*.*:
type: config_entity
label: 'Content Language Settings'
mapping:
entities:
id:
type: string
label: 'ID'
target_entity_type_id:
type: string
label: 'Entity Type ID'
target_bundle:
type: string
label: 'Bundle'
default_langcode:
type: string
label: 'Default language'
language_alterable:
type: boolean
label: 'Allow to alter the language'
third_party_settings:
type: sequence
label: 'Entity type'
label: 'Third party settings'
sequence:
- type: sequence
label: 'Bundle'
sequence:
- type: mapping
label: 'Custom language settings'
mapping:
language:
type: mapping
label: 'Custom language settings'
mapping:
default_configuration:
type: mapping
label: 'Default language'
mapping:
langcode:
type: string
label: 'Default language'
language_show:
type: boolean
label: 'Show language selector on create and edit pages'
- type: content_settings.third_party.[%key]
condition.plugin.language:
type: condition.plugin
......
......@@ -5,14 +5,17 @@
* Add language handling functionality to Drupal.
*/
use Drupal\Component\Utility\String;
use Drupal\Core\Entity\ContentEntityFormInterface;
use Drupal\Core\Entity\EntityFormInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Language\LanguageInterface;
use Drupal\Core\Routing\RouteMatchInterface;
use Drupal\language\ConfigurableLanguageInterface;
use Drupal\language\Entity\ContentLanguageSettings;
use Drupal\language\Plugin\LanguageNegotiation\LanguageNegotiationUI;
use Drupal\language\Plugin\LanguageNegotiation\LanguageNegotiationUrl;
use Drupal\language\Plugin\LanguageNegotiation\LanguageNegotiationUrlFallback;
use Drupal\node\NodeTypeInterface;
/**
* Implements hook_help().
......@@ -176,92 +179,35 @@ function language_process_language_select($element) {
function language_configuration_element_submit(&$form, FormStateInterface $form_state) {
// Iterate through all the language_configuration elements and save their
// values.
// In case we are editing a bundle, we must check the new bundle name,
// because e.g. hook_ENTITY_update fired before.
if ($language = $form_state->get('language')) {
foreach ($language as $element_name => $values) {
language_save_default_configuration($values['entity_type'], $values['bundle'], $form_state->getValue($element_name));
$entity_type_id = $values['entity_type'];
$bundle = $values['bundle'];
$form_object = $form_state->getFormObject();
if ($form_object instanceof EntityFormInterface && !$form_object->getEntity()->isNew() && in_array($form_object->getOperation(), ['default', 'edit'])) {
/** @var EntityFormInterface $form_object */
$entity = $form_object->getEntity();
if ($entity->getEntityType()->getBundleOf()) {
$bundle = $entity->id();
}
}
$config = ContentLanguageSettings::loadByEntityTypeBundle($entity_type_id, $bundle);
$config->setDefaultLangcode($form_state->getValue(array($element_name, 'langcode')));
$config->setLanguageAlterable($form_state->getValue(array($element_name, 'language_alterable')));
$config->save();
}
}
}
/**
* Saves a language configuration that is attached to an entity type and bundle.
*
* @param string $entity_type
* A string representing the entity type.
* @param string $bundle
* A string representing the bundle.
* @param array $values
* An array holding the values to be saved having the following keys:
* - langcode: the language code.
* - language_show: if the language element should be hidden or not.
*/
function language_save_default_configuration($entity_type, $bundle, $values = array()) {
\Drupal::config('language.settings')->set(language_get_default_configuration_settings_key($entity_type, $bundle), array('langcode' => $values['langcode'], 'language_show' => $values['language_show']))->save();
}
/**
* Returns the language configuration stored for an entity type and bundle.
*
* @param string $entity_type
* A string representing the entity type.
* @param string $bundle
* A string representing the bundle.
*
* @return array
* An array with the following keys:
* - langcode: the language code.
* - language_show: if the language element is hidden or not.
*/
function language_get_default_configuration($entity_type, $bundle) {
$configuration = \Drupal::config('language.settings')->get(language_get_default_configuration_settings_key($entity_type, $bundle));
if (is_null($configuration)) {
$configuration = array();
}
$configuration += array('langcode' => LanguageInterface::LANGCODE_SITE_DEFAULT, 'language_show' => FALSE);
return $configuration;
}
/**
* Clears the default language configuration for an entity type and bundle.
*
* @param string $entity_type
* A string representing the entity type.
* @param string $bundle
* A string representing the bundle.
*/
function language_clear_default_configuration($entity_type, $bundle) {
\Drupal::config('language.settings')->clear(language_get_default_configuration_settings_key($entity_type, $bundle))->save();
}
/**
* Returns the root name of the variables used to store the configuration.
*
* Based on the entity type and bundle, the variables used to store the
* configuration will have a common root name.
*
* @param string $entity_type
* A string representing the entity type.
* @param string $bundle
* A string representing the bundle.
*
* @return string
* The root name of the variables.
*/
function language_get_default_configuration_settings_key($entity_type, $bundle) {
// Replace all the characters that are not letters, numbers or "_" with "_".
$entity_type = preg_replace('/[^0-9a-zA-Z_]/', "_", $entity_type);
$bundle = preg_replace('/[^0-9a-zA-Z_]/', "_", $bundle);
return 'entities.' . $entity_type . '.' . $bundle . '.language.default_configuration';
}
/**
* Implements hook_ENTITY_TYPE_update() for node_type entities.
* Implements hook_entity_bundle_rename().
*/
function language_node_type_update(NodeTypeInterface $type) {
if ($type->original->id() != $type->id()) {
language_save_default_configuration('node', $type->id(), language_get_default_configuration('node', $type->original->id()));
language_clear_default_configuration('node', $type->original->id());
}
function language_entity_bundle_rename($entity_type_id, $bundle_old, $bundle_new) {
ContentLanguageSettings::loadByEntityTypeBundle($entity_type_id, $bundle_old)
->setTargetBundle($bundle_new)
->save();
}
/**
......@@ -276,15 +222,11 @@ function language_node_type_update(NodeTypeInterface $type) {
* The language code.
*/
function language_get_default_langcode($entity_type, $bundle) {
$configuration = language_get_default_configuration($entity_type, $bundle);
if (!isset($configuration['langcode'])) {
$configuration['langcode'] = LanguageInterface::LANGCODE_SITE_DEFAULT;
}
$configuration = ContentLanguageSettings::loadByEntityTypeBundle($entity_type, $bundle);
$default_value = NULL;
$language_interface = \Drupal::languageManager()->getCurrentLanguage();
switch ($configuration['langcode']) {
switch ($configuration->getDefaultLangcode()) {
case LanguageInterface::LANGCODE_SITE_DEFAULT:
$default_value = \Drupal::languageManager()->getDefaultLanguage()->getId();
break;
......@@ -310,7 +252,7 @@ function language_get_default_langcode($entity_type, $bundle) {
// If we still do not have a default value, just return the value stored in
// the configuration; it has to be an actual language code.
return $configuration['langcode'];
return $configuration->getDefaultLangcode();
}
/**
......@@ -504,6 +446,25 @@ function language_form_system_regional_settings_alter(&$form, FormStateInterface
$form['#submit'][] = 'language_system_regional_settings_form_submit';
}
/**
* Implements hook_form_alter().
*/
function language_form_alter(&$form, FormStateInterface $form_state, $form_id) {
// Content entity forms may have added a langcode field. But content language
// configuration should decide if it should be exposed or not in the forms.
$form_object = $form_state->getFormObject();
if ($form_object instanceof ContentEntityFormInterface && isset($form['langcode'])) {
/** @var \Drupal\Core\Entity\ContentEntityInterface $entity */
$entity_type_id = $form_object->getEntity()->getEntityTypeId();
$bundle = $form_object->getEntity()->bundle();
$entity_type = Drupal::entityManager()->getDefinition($entity_type_id);
if ($entity_type->isTranslatable()) {
$language_configuration = ContentLanguageSettings::loadByEntityTypeBundle($entity_type_id, $bundle);
$form['langcode']['#access'] = $language_configuration->isLanguageAlterable();
}
}
}
/**
* Form submission handler for system_regional_settings().
*
......
<?php
/*
* @file
* Contains \Drupal\language\ContentLanguageSettingsException.
*/
namespace Drupal\language;
/**
* Exception thrown by ContentLanguageSettings when target bundle is not set.
*/
class ContentLanguageSettingsException extends \RuntimeException {}
<?php
/**
* @file
* Contains \Drupal\language\ContentLanguageSettingsInterface.
*/
namespace Drupal\language;
use Drupal\Core\Config\Entity\ConfigEntityInterface;
use Drupal\Core\Config\Entity\ThirdPartySettingsInterface;
/**
* Provides an interface defining language settings for content entities.
*/
interface ContentLanguageSettingsInterface extends ConfigEntityInterface, ThirdPartySettingsInterface {
/**
* Gets the entity type ID this config applies to.
*
* @return string
*/
public function getTargetEntityTypeId();
/**
* Gets the bundle this config applies to.
*
* @return string
*/
public function getTargetBundle();
/**
* Sets the bundle this config applies to.
*
* @param string $target_bundle
* The bundle.
*
* @return $this
*/
public function setTargetBundle($target_bundle);
/**
* Sets the default language code.
*
* @param string $default_langcode
* The default language code.
*
* @return $this;
*/
public function setDefaultLangcode($default_langcode);
/**
* Gets the default language code.
*
* @return string
*/
public function getDefaultLangcode();
/**
* Sets if the language must be alterable or not.
*
* @param bool $language_alterable
* Flag indicating if the language must be alterable.
*
* @return $this
*/
public function setLanguageAlterable($language_alterable);
/**
* Checks if the language is alterable or not.
*
* @return bool
*/
public function isLanguageAlterable();
/**
* Checks if this config object contains the default values in every property.
*
* @return bool
* True if all the properties contain the default values. False otherwise.
*/
public function isDefaultConfiguration();
}
......@@ -10,6 +10,7 @@
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Language\LanguageInterface;
use Drupal\Core\Render\Element\FormElement;
use Drupal\language\Entity\ContentLanguageSettings;
/**
* Provides language element configuration.
......@@ -40,24 +41,26 @@ public static function processLanguageConfiguration(&$element, FormStateInterfac
// Avoid validation failure since we are moving the '#options' key in the
// nested 'language' select element.
unset($element['#options']);
/** @var ContentLanguageSettings $default_config */
$default_config = $element['#default_value'];
$element['langcode'] = array(
'#type' => 'select',
'#title' => t('Default language'),
'#options' => $options + static::getDefaultOptions(),
'#description' => t('Explanation of the language options is found on the <a href="@languages_list_page">languages list page</a>.', array('@languages_list_page' => \Drupal::url('language.admin_overview'))),
'#default_value' => isset($element['#default_value']['langcode']) ? $element['#default_value']['langcode'] : NULL,
'#default_value' => ($default_config != NULL) ? $default_config->getDefaultLangcode() : LanguageInterface::LANGCODE_SITE_DEFAULT,
);
$element['language_show'] = array(
$element['language_alterable'] = array(
'#type' => 'checkbox',
'#title' => t('Show language selector on create and edit pages'),
'#default_value' => isset($element['#default_value']['language_show']) ? $element['#default_value']['language_show'] : NULL,
'#default_value' => ($default_config != NULL) ? $default_config->isLanguageAlterable() : FALSE,
);
// Add the entity type and bundle information to the form if they are set.
// They will be used, in the submit handler, to generate the names of the
// variables that will store the settings and are a way to uniquely identify
// the entity.
// configuration entities that will store the settings and are a way to uniquely
// identify the entity.
$language = $form_state->get('language') ?: [];
$language += array(
$element['#name'] => array(
......
<?php
/**
* @file
* Contains \Drupal\language\Entity\ContentLanguageSettings.
*/
namespace Drupal\language\Entity;
use Drupal\Component\Utility\String;
use Drupal\Core\Config\Entity\ConfigEntityBase;
use Drupal\Core\Config\Entity\ThirdPartySettingsTrait;
use Drupal\Core\Entity\EntityStorageInterface;
use Drupal\Core\Language\LanguageInterface;
use Drupal\language\ContentLanguageSettingsException;
use Drupal\language\ContentLanguageSettingsInterface;
/**
* Defines the ContentLanguageSettings entity.
*
* @ConfigEntityType(
* id = "language_content_settings",
* label = @Translation("Content Language Settings"),
* admin_permission = "administer languages",
* config_prefix = "content_settings",
* entity_keys = {
* "id" = "id"
* },
* )
*/
class ContentLanguageSettings extends ConfigEntityBase implements ContentLanguageSettingsInterface {
use ThirdPartySettingsTrait;
/**
* The id. Combination of $target_entity_type_id.$target_bundle.
*
* @var string
*/
protected $id;
/**
* The entity type ID (machine name).
*
* @var string
*/
protected $target_entity_type_id;
/**
* The bundle (machine name).
*
* @var string
*/
protected $target_bundle;
/**
* The default language code.
*
* @var string
*/
protected $default_langcode = LanguageInterface::LANGCODE_SITE_DEFAULT;
/**
* Indicates if the language is alterable or not.
*
* @var bool
*/
protected $language_alterable = FALSE;
/**
* Constructs a ContentLanguageSettings object.
*