Commit 780a770d authored by alexpott's avatar alexpott
Browse files

Issue #2403703 by claudiu.cristea, pritish.kumar, larowlan, alexpott, jibran,...

Issue #2403703 by claudiu.cristea, pritish.kumar, larowlan, alexpott, jibran, pwolanin, dawehner, Berdir: Allow field types to enforce the cardinality
parent 3441b94e
......@@ -91,4 +91,13 @@ class FieldType extends DataType {
*/
public $list_class;
/**
* An integer defining a fixed cardinality for this field type.
*
* If this value is not set, cardinality can be configured in the field UI.
*
* @var int|null
*/
public $cardinality;
}
......@@ -331,18 +331,6 @@ function comment_form_field_ui_display_overview_form_alter(&$form, FormStateInte
}
}
/**
* Implements hook_form_FORM_ID_alter() for 'field_storage_config_edit_form'.
*/
function comment_form_field_storage_config_edit_form_alter(&$form, FormStateInterface $form_state) {
if ($form_state->getFormObject()->getEntity()->getType() == 'comment') {
// We only support posting one comment at the time so it doesn't make sense
// to let the site builder choose anything else.
$form['cardinality_container']['cardinality']['#default_value'] = 1;
$form['cardinality_container']['#access'] = FALSE;
}
}
/**
* Implements hook_entity_storage_load().
*
......
......@@ -21,7 +21,8 @@
* description = @Translation("This field manages configuration and presentation of comments on an entity."),
* list_class = "\Drupal\comment\CommentFieldItemList",
* default_widget = "comment_default",
* default_formatter = "comment_default"
* default_formatter = "comment_default",
* cardinality = 1,
* )
*/
class CommentItem extends FieldItemBase implements CommentItemInterface {
......
......@@ -628,7 +628,17 @@ public function getDescription() {
* {@inheritdoc}
*/
public function getCardinality() {
return $this->cardinality;
/** @var \Drupal\Core\Field\FieldTypePluginManager $field_type_manager */
$field_type_manager = \Drupal::service('plugin.manager.field.field_type');
$definition = $field_type_manager->getDefinition($this->getType());
$enforced_cardinality = isset($definition['cardinality']) ? $definition['cardinality'] : NULL;
// Enforced cardinality is a positive integer or -1.
if ($enforced_cardinality !== NULL && $enforced_cardinality < 1 && $enforced_cardinality !== FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED) {
throw new FieldException("Invalid enforced cardinality '$enforced_cardinality'. Allowed values: a positive integer or -1.");
}
return $enforced_cardinality ?: $this->cardinality;
}
/**
......
......@@ -8,6 +8,7 @@
namespace Drupal\Tests\field\Unit;
use Drupal\Core\DependencyInjection\ContainerBuilder;
use Drupal\Core\Field\FieldException;
use Drupal\Core\Field\FieldStorageDefinitionInterface;
use Drupal\Core\Field\FieldTypePluginManagerInterface;
use Drupal\field\Entity\FieldStorageConfig;
......@@ -110,6 +111,104 @@ public function testCalculateDependencies() {
$this->assertEquals(['stark'], $dependencies['theme']);
}
/**
* Tests stored cardinality.
*
* @covers ::getCardinality
*/
public function testStoredCardinality() {
$this->fieldTypeManager->expects($this->any())
->method('getDefinition')
->with('test_field_type')
->willReturn([
'class' => TestFieldType::class,
// The field type definition has no enforced cardinality.
'cardinality' => NULL,
]);
$field_storage = new FieldStorageConfig([
'entity_type' => 'entity_test',
'field_name' => 'test_field',
'type' => 'test_field_type',
'module' => 'test_module',
]);
$field_storage->setCardinality(8);
// Check that the stored cardinality is returned.
$this->assertEquals(8, $field_storage->getCardinality());
}
/**
* Tests enforced cardinality.
*
* @covers ::getCardinality
*/
public function testEnforcedCardinality() {
$this->fieldTypeManager->expects($this->any())
->method('getDefinition')
->with('test_field_type')
->willReturn([
'class' => TestFieldType::class,
// This field type defines an enforced cardinality.
'cardinality' => 21,
]);
$field_storage = new FieldStorageConfig([
'entity_type' => 'entity_test',
'field_name' => 'test_field',
'type' => 'test_field_type',
'module' => 'test_module',
]);
// Custom cardinality tentative.
$field_storage->setCardinality(8);
// Check that the enforced cardinality is returned.
$this->assertEquals(21, $field_storage->getCardinality());
}
/**
* Tests invalid enforced cardinality.
*
* @covers ::getCardinality
* @dataProvider providerInvalidEnforcedCardinality
*
* @param mixed $enforced_cardinality
* Enforced cardinality
*/
public function testInvalidEnforcedCardinality($enforced_cardinality) {
$this->fieldTypeManager->expects($this->any())
->method('getDefinition')
->with('test_field_type')
->willReturn([
'class' => TestFieldType::class,
'cardinality' => $enforced_cardinality,
]);
$field_storage = new FieldStorageConfig([
'entity_type' => 'entity_test',
'field_name' => 'test_field',
'type' => 'test_field_type',
'module' => 'test_module',
]);
$this->setExpectedException(FieldException::class, "Invalid enforced cardinality '$enforced_cardinality'. Allowed values: a positive integer or -1.");
$field_storage->getCardinality();
}
/**
* Data provider for ::testInvalidEnforcedCardinality()
*
* @return array
* Test cases.
*/
public function providerInvalidEnforcedCardinality() {
return [
'zero' => [0],
'negative_other_than_-1' => [-70],
'non_numeric' => ['abc%$#@!'],
];
}
}
/**
......
......@@ -89,9 +89,20 @@ public function form(array $form, FormStateInterface $form_state) {
$item = $items->first() ?: $items->appendItem();
$form['settings'] += $item->storageSettingsForm($form, $form_state, $this->entity->hasData());
// Build the configurable field values.
$cardinality = $this->entity->getCardinality();
$form['cardinality_container'] = [
// Add the cardinality sub-form.
$form['cardinality_container'] = $this->getCardinalityForm();
return $form;
}
/**
* Builds the cardinality form.
*
* @return array
* The cardinality form render array.
*/
protected function getCardinalityForm() {
$form = [
// Reset #parents so the additional container does not appear.
'#parents' => [],
'#type' => 'fieldset',
......@@ -99,35 +110,49 @@ public function form(array $form, FormStateInterface $form_state) {
'#attributes' => ['class' => [
'container-inline',
'fieldgroup',
'form-composite'
'form-composite',
]],
];
$form['cardinality_container']['cardinality'] = [
'#type' => 'select',
'#title' => $this->t('Allowed number of values'),
'#title_display' => 'invisible',
'#options' => [
'number' => $this->t('Limited'),
FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED => $this->t('Unlimited'),
],
'#default_value' => ($cardinality == FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED) ? FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED : 'number',
];
$form['cardinality_container']['cardinality_number'] = [
'#type' => 'number',
'#default_value' => $cardinality != FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED ? $cardinality : 1,
'#min' => 1,
'#title' => $this->t('Limit'),
'#title_display' => 'invisible',
'#size' => 2,
'#states' => [
'visible' => [
':input[name="cardinality"]' => ['value' => 'number'],
if ($enforced_cardinality = $this->getEnforcedCardinality()) {
if ($enforced_cardinality === FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED) {
$markup = $this->t("This field cardinality is set to unlimited and cannot be configured.");
}
else {
$markup = $this->t("This field cardinality is set to @cardinality and cannot be configured.", ['@cardinality' => $enforced_cardinality]);
}
$form['cardinality'] = ['#markup' => $markup];
}
else {
$form['#element_validate'][] = '::validateCardinality';
$cardinality = $this->entity->getCardinality();
$form['cardinality'] = [
'#type' => 'select',
'#title' => $this->t('Allowed number of values'),
'#title_display' => 'invisible',
'#options' => [
'number' => $this->t('Limited'),
FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED => $this->t('Unlimited'),
],
'disabled' => [
':input[name="cardinality"]' => ['value' => FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED],
'#default_value' => ($cardinality == FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED) ? FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED : 'number',
];
$form['cardinality_number'] = [
'#type' => 'number',
'#default_value' => $cardinality != FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED ? $cardinality : 1,
'#min' => 1,
'#title' => $this->t('Limit'),
'#title_display' => 'invisible',
'#size' => 2,
'#states' => [
'visible' => [
':input[name="cardinality"]' => ['value' => 'number'],
],
'disabled' => [
':input[name="cardinality"]' => ['value' => FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED],
],
],
],
];
];
}
return $form;
}
......@@ -143,16 +168,19 @@ protected function actions(array $form, FormStateInterface $form_state) {
}
/**
* {@inheritdoc}
* Validates the cardinality.
*
* @param array $element
* The cardinality form render array.
* @param \Drupal\Core\Form\FormStateInterface $form_state
* The form state.
*/
public function validateForm(array &$form, FormStateInterface $form_state) {
parent::validateForm($form, $form_state);
public function validateCardinality(array &$element, FormStateInterface $form_state) {
$field_storage_definitions = \Drupal::service('entity_field.manager')->getFieldStorageDefinitions($this->entity->getTargetEntityTypeId());
// Validate field cardinality.
if ($form_state->getValue('cardinality') === 'number' && !$form_state->getValue('cardinality_number')) {
$form_state->setErrorByName('cardinality_number', $this->t('Number of values is required.'));
$form_state->setError($element['cardinality_number'], $this->t('Number of values is required.'));
}
// If a specific cardinality is used, validate that there are no entities
// with a higher delta.
......@@ -166,7 +194,7 @@ public function validateForm(array &$form, FormStateInterface $form_state) {
->count()
->execute();
if ($entities_with_higher_delta) {
$form_state->setErrorByName('cardinality_number', $this->formatPlural($entities_with_higher_delta, 'There is @count entity with @delta or more values in this field.', 'There are @count entities with @delta or more values in this field.', ['@delta' => $form_state->getValue('cardinality') + 1]));
$form_state->setError($element['cardinality_number'], $this->formatPlural($entities_with_higher_delta, 'There is @count entity with @delta or more values in this field.', 'There are @count entities with @delta or more values in this field.', ['@delta' => $form_state->getValue('cardinality') + 1]));
}
}
}
......@@ -176,7 +204,7 @@ public function validateForm(array &$form, FormStateInterface $form_state) {
*/
public function buildEntity(array $form, FormStateInterface $form_state) {
// Save field cardinality.
if ($form_state->getValue('cardinality') === 'number' && $form_state->getValue('cardinality_number')) {
if (!$this->getEnforcedCardinality() && $form_state->getValue('cardinality') === 'number' && $form_state->getValue('cardinality_number')) {
$form_state->setValue('cardinality', $form_state->getValue('cardinality_number'));
}
......@@ -205,4 +233,19 @@ public function save(array $form, FormStateInterface $form_state) {
}
}
/**
* Returns the cardinality enforced by the field type.
*
* Some field types choose to enforce a fixed cardinality. This method
* returns that cardinality or NULL if no cardinality has been enforced.
*
* @return int|null
*/
protected function getEnforcedCardinality() {
/** @var \Drupal\Core\Field\FieldTypePluginManager $field_type_manager */
$field_type_manager = \Drupal::service('plugin.manager.field.field_type');
$definition = $field_type_manager->getDefinition($this->entity->getType());
return isset($definition['cardinality']) ? $definition['cardinality'] : NULL;
}
}
......@@ -65,3 +65,11 @@ function system_post_update_hashes_clear_cache() {
function system_post_update_timestamp_plugins() {
// Empty post-update hook.
}
/**
* Force field type plugin definitions to be cleared.
*
* @see https://www.drupal.org/node/2403703
*/
function system_post_update_field_type_plugins() {
// Empty post-update hook.
}
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment