diff --git a/core/config/schema/core.entity.schema.yml b/core/config/schema/core.entity.schema.yml index 7b44b0a..ae11357 100644 --- a/core/config/schema/core.entity.schema.yml +++ b/core/config/schema/core.entity.schema.yml @@ -261,6 +261,29 @@ field.widget.settings.entity_reference_autocomplete: type: label label: 'Placeholder' +field.widget.settings.machine_name: + type: mapping + label: 'Machine Name' + mapping: + source_field: + type: string + label: 'The source field for the machine name' + disable_on_edit: + type: boolean + label: 'Disable the machine name after initial creation' + standalone: + type: boolean + label: 'Keep the machine name live preview as its own form element' + replace_pattern: + type: string + label: 'A regular expression (without delimiters) matching disallowed characters in the machine name' + replace: + type: string + label: 'A character to replace disallowed characters in the machine name' + size: + type: integer + label: 'Size of the machine name' + field.formatter.settings.boolean: type: mapping mapping: diff --git a/core/lib/Drupal/Core/Entity/Entity/EntityFormDisplay.php b/core/lib/Drupal/Core/Entity/Entity/EntityFormDisplay.php index 3dcffd2..6d7e108 100644 --- a/core/lib/Drupal/Core/Entity/Entity/EntityFormDisplay.php +++ b/core/lib/Drupal/Core/Entity/Entity/EntityFormDisplay.php @@ -2,6 +2,7 @@ namespace Drupal\Core\Entity\Entity; +use Drupal\Component\Utility\SortArray; use Drupal\Core\Entity\EntityConstraintViolationListInterface; use Drupal\Core\Entity\EntityDisplayPluginCollection; use Drupal\Core\Entity\FieldableEntityInterface; @@ -173,8 +174,15 @@ public function buildForm(FieldableEntityInterface $entity, array &$form, FormSt // Set #parents to 'top-level' by default. $form += ['#parents' => []]; + // Sort the components by their weight in order to allow a form element from + // a widget to depend on the processed elements of another form element from + // another widget. For example, the 'machine_name' widget needs the + // processed '#id' property of its source field. + $components = $this->getComponents(); + uasort($components, SortArray::sortByWeightElement(...)); + // Let each widget generate the form elements. - foreach ($this->getComponents() as $name => $options) { + foreach ($components as $name => $options) { if ($widget = $this->getRenderer($name)) { $items = $entity->get($name); $items->filterEmptyItems(); diff --git a/core/lib/Drupal/Core/Validation/Plugin/Validation/Constraint/UniqueFieldValueValidator.php b/core/lib/Drupal/Core/Validation/Plugin/Validation/Constraint/UniqueFieldValueValidator.php index 1aad291..f7f3c1f 100644 --- a/core/lib/Drupal/Core/Validation/Plugin/Validation/Constraint/UniqueFieldValueValidator.php +++ b/core/lib/Drupal/Core/Validation/Plugin/Validation/Constraint/UniqueFieldValueValidator.php @@ -28,11 +28,10 @@ public function validate($items, Constraint $constraint) { // @todo Don't check access. http://www.drupal.org/node/3171047 $query->accessCheck(TRUE); - $entity_id = $entity->id(); - // Using isset() instead of !empty() as 0 and '0' are valid ID values for - // entity types using string IDs. - if (isset($entity_id)) { - $query->condition($id_key, $entity_id, '<>'); + // If the entity already exists in the storage, ensure that we don't compare + // the field value with the pre-existing one. + if (!$entity->isNew()) { + $query->condition($id_key, $entity->id(), '<>'); } $value_taken = (bool) $query diff --git a/core/modules/system/tests/modules/entity_test/src/Entity/EntityTestStringId.php b/core/modules/system/tests/modules/entity_test/src/Entity/EntityTestStringId.php index 9e39a5a..f533f41 100644 --- a/core/modules/system/tests/modules/entity_test/src/Entity/EntityTestStringId.php +++ b/core/modules/system/tests/modules/entity_test/src/Entity/EntityTestStringId.php @@ -49,7 +49,13 @@ public static function baseFieldDefinitions(EntityTypeInterface $entity_type) { ->setReadOnly(TRUE) // In order to work around the InnoDB 191 character limit on utf8mb4 // primary keys, we set the character set for the field to ASCII. - ->setSetting('is_ascii', TRUE); + ->setSetting('is_ascii', TRUE) + ->addConstraint('UniqueField', []) + ->setDisplayConfigurable('form', TRUE); + + // Make the label field configurable in the UI. + $fields['name']->setDisplayConfigurable('form', TRUE); + return $fields; } diff --git a/core/modules/workspaces/src/Entity/Workspace.php b/core/modules/workspaces/src/Entity/Workspace.php index a7c96a5..441caf9 100644 --- a/core/modules/workspaces/src/Entity/Workspace.php +++ b/core/modules/workspaces/src/Entity/Workspace.php @@ -80,16 +80,26 @@ public static function baseFieldDefinitions(EntityTypeInterface $entity_type) { ->setDescription(new TranslatableMarkup('The workspace ID.')) ->setSetting('max_length', 128) ->setRequired(TRUE) - ->addConstraint('UniqueField') - ->addConstraint('DeletedWorkspace') - ->addPropertyConstraints('value', ['Regex' => ['pattern' => '/^[a-z0-9_]+$/']]); + ->addConstraint('UniqueField', []) + ->addConstraint('DeletedWorkspace', []) + ->addPropertyConstraints('value', ['Regex' => ['pattern' => '/^[a-z0-9_]+$/']]) + ->setDisplayOptions('form', [ + 'type' => 'machine_name', + 'weight' => -5, + 'settings' => [ + 'source_field' => 'label', + ], + ]); $fields['label'] = BaseFieldDefinition::create('string') ->setLabel(new TranslatableMarkup('Workspace name')) - ->setDescription(new TranslatableMarkup('The workspace name.')) ->setRevisionable(TRUE) ->setSetting('max_length', 128) - ->setRequired(TRUE); + ->setRequired(TRUE) + ->setDisplayOptions('form', [ + 'type' => 'string_textfield', + 'weight' => -10, + ]); $fields['uid'] ->setLabel(new TranslatableMarkup('Owner')) diff --git a/core/modules/workspaces/src/Form/WorkspaceForm.php b/core/modules/workspaces/src/Form/WorkspaceForm.php index 13a5b60..de8d6d9 100644 --- a/core/modules/workspaces/src/Form/WorkspaceForm.php +++ b/core/modules/workspaces/src/Form/WorkspaceForm.php @@ -4,7 +4,6 @@ use Drupal\Component\Datetime\TimeInterface; use Drupal\Core\Entity\ContentEntityForm; -use Drupal\Core\Entity\EntityConstraintViolationListInterface; use Drupal\Core\Entity\EntityRepositoryInterface; use Drupal\Core\Entity\EntityTypeBundleInfoInterface; use Drupal\Core\Form\FormStateInterface; @@ -69,25 +68,6 @@ public function form(array $form, FormStateInterface $form_state) { if ($this->operation == 'edit') { $form['#title'] = $this->t('Edit workspace %label', ['%label' => $workspace->label()]); } - $form['label'] = [ - '#type' => 'textfield', - '#title' => $this->t('Label'), - '#maxlength' => 255, - '#default_value' => $workspace->label(), - '#required' => TRUE, - ]; - - $form['id'] = [ - '#type' => 'machine_name', - '#title' => $this->t('Workspace ID'), - '#maxlength' => 255, - '#default_value' => $workspace->id(), - '#disabled' => !$workspace->isNew(), - '#machine_name' => [ - 'exists' => '\Drupal\workspaces\Entity\Workspace::load', - ], - '#element_validate' => [], - ]; return parent::form($form, $form_state); } @@ -95,34 +75,6 @@ public function form(array $form, FormStateInterface $form_state) { /** * {@inheritdoc} */ - protected function getEditedFieldNames(FormStateInterface $form_state) { - return array_merge([ - 'label', - 'id', - ], parent::getEditedFieldNames($form_state)); - } - - /** - * {@inheritdoc} - */ - protected function flagViolations(EntityConstraintViolationListInterface $violations, array $form, FormStateInterface $form_state) { - // Manually flag violations of fields not handled by the form display. This - // is necessary as entity form displays only flag violations for fields - // contained in the display. - $field_names = [ - 'label', - 'id', - ]; - foreach ($violations->getByFields($field_names) as $violation) { - [$field_name] = explode('.', $violation->getPropertyPath(), 2); - $form_state->setErrorByName($field_name, $violation->getMessage()); - } - parent::flagViolations($violations, $form, $form_state); - } - - /** - * {@inheritdoc} - */ public function save(array $form, FormStateInterface $form_state) { $workspace = $this->entity; $workspace->setNewRevision(TRUE); diff --git a/core/modules/workspaces/tests/src/Functional/WorkspacePermissionsTest.php b/core/modules/workspaces/tests/src/Functional/WorkspacePermissionsTest.php index 7d0a317..af37ab4 100644 --- a/core/modules/workspaces/tests/src/Functional/WorkspacePermissionsTest.php +++ b/core/modules/workspaces/tests/src/Functional/WorkspacePermissionsTest.php @@ -74,8 +74,7 @@ public function testEditOwnWorkspace() { $this->assertSession()->statusCodeEquals(200); $page = $this->getSession()->getPage(); - $page->fillField('label', 'Bears again'); - $page->fillField('id', 'bears'); + $page->fillField('label[0][value]', 'Bears again'); $page->findButton('Save')->click(); $page->hasContent('Bears again (bears)'); @@ -118,8 +117,7 @@ public function testEditAnyWorkspace() { $this->assertSession()->statusCodeEquals(200); $page = $this->getSession()->getPage(); - $page->fillField('label', 'Bears again'); - $page->fillField('id', 'bears'); + $page->fillField('label[0][value]', 'Bears again'); $page->findButton('Save')->click(); $page->hasContent('Bears again (bears)'); diff --git a/core/modules/workspaces/tests/src/Functional/WorkspaceTest.php b/core/modules/workspaces/tests/src/Functional/WorkspaceTest.php index 5c04a00..ca0e41e 100644 --- a/core/modules/workspaces/tests/src/Functional/WorkspaceTest.php +++ b/core/modules/workspaces/tests/src/Functional/WorkspaceTest.php @@ -73,21 +73,22 @@ protected function setUp(): void { /** * Tests creating a workspace with special characters. */ - public function testSpecialCharacters() { + public function testWorkspaceCreate() { $this->drupalLogin($this->editor1); + $page = $this->getSession()->getPage(); - // Test a valid workspace name. - $this->createWorkspaceThroughUi('Workspace 1', 'a0_$()+-/'); + // Test a valid workspace ID. + $workspace = $this->createWorkspaceThroughUi('Workspace 1', 'workspace_1'); + $this->assertEquals('workspace_1', $workspace->id()); - // Test and invalid workspace name. - $this->drupalGet('/admin/config/workflow/workspaces/add'); - $this->assertSession()->statusCodeEquals(200); + // Test an invalid workspace ID. + $workspace = $this->createWorkspaceThroughUi('Workspace 2', 'workspace A@-'); + $this->assertNull($workspace); + $this->assertTrue($page->hasContent('The machine-readable name must contain only lowercase letters, numbers, and underscores.')); - $page = $this->getSession()->getPage(); - $page->fillField('label', 'workspace2'); - $page->fillField('id', 'A!"£%^&*{}#~@?'); - $page->findButton('Save')->click(); - $page->hasContent("This value is not valid"); + // Test a duplicate workspace ID. + $this->createWorkspaceThroughUi('Workspace 1 again', 'workspace_1'); + $this->assertTrue($page->hasContent('A workspace with workspace id workspace_1 already exists.')); } /** @@ -96,11 +97,7 @@ public function testSpecialCharacters() { public function testWorkspaceToolbar() { $this->drupalLogin($this->editor1); - $this->drupalGet('/admin/config/workflow/workspaces/add'); - $this->submitForm([ - 'id' => 'test_workspace', - 'label' => 'Test workspace', - ], 'Save'); + $this->createWorkspaceThroughUi('Test workspace', 'test_workspace'); // Activate the test workspace. $this->drupalGet('/admin/config/workflow/workspaces/manage/test_workspace/activate'); @@ -113,7 +110,7 @@ public function testWorkspaceToolbar() { // Change the workspace label. $this->drupalGet('/admin/config/workflow/workspaces/manage/test_workspace/edit'); - $this->submitForm(['label' => 'New name'], 'Save'); + $this->submitForm(['label[0][value]' => 'New name'], 'Save'); $this->drupalGet(''); $page = $this->getSession()->getPage(); @@ -127,11 +124,7 @@ public function testWorkspaceToolbar() { public function testWorkspaceOwner() { $this->drupalLogin($this->editor1); - $this->drupalGet('/admin/config/workflow/workspaces/add'); - $this->submitForm([ - 'id' => 'test_workspace', - 'label' => 'Test workspace', - ], 'Save'); + $this->createWorkspaceThroughUi('Test workspace', 'test_workspace'); $storage = \Drupal::entityTypeManager()->getStorage('workspace'); $test_workspace = $storage->load('test_workspace'); diff --git a/core/modules/workspaces/tests/src/Functional/WorkspaceTestUtilities.php b/core/modules/workspaces/tests/src/Functional/WorkspaceTestUtilities.php index 01fbfab..16c1c65 100644 --- a/core/modules/workspaces/tests/src/Functional/WorkspaceTestUtilities.php +++ b/core/modules/workspaces/tests/src/Functional/WorkspaceTestUtilities.php @@ -60,13 +60,11 @@ protected function getOneEntityByLabel($type, $label) { protected function createWorkspaceThroughUi($label, $id, $parent = '_none') { $this->drupalGet('/admin/config/workflow/workspaces/add'); $this->submitForm([ - 'id' => $id, - 'label' => $label, + 'id[0][value]' => $id, + 'label[0][value]' => $label, 'parent' => $parent, ], 'Save'); - $this->getSession()->getPage()->hasContent("$label ($id)"); - return Workspace::load($id); } diff --git a/core/tests/Drupal/FunctionalJavascriptTests/Core/MachineNameTest.php b/core/tests/Drupal/FunctionalJavascriptTests/Core/MachineNameTest.php index 6782a23..ae718a1 100644 --- a/core/tests/Drupal/FunctionalJavascriptTests/Core/MachineNameTest.php +++ b/core/tests/Drupal/FunctionalJavascriptTests/Core/MachineNameTest.php @@ -5,9 +5,9 @@ use Drupal\FunctionalJavascriptTests\WebDriverTestBase; /** - * Tests for the machine name field. + * Tests for the machine name form element. * - * @group field + * @group Form */ class MachineNameTest extends WebDriverTestBase {