Initial Drupal 11 with DDEV setup
This commit is contained in:
@ -0,0 +1,7 @@
|
||||
field.formatter.settings.field_plugins_test_text_formatter:
|
||||
type: field.formatter.settings.text_trimmed
|
||||
label: 'Test text formatter display format settings'
|
||||
|
||||
field.widget.settings.field_plugins_test_text_widget:
|
||||
type: field.widget.settings.text_textfield
|
||||
label: 'Test text field widget settings'
|
||||
@ -0,0 +1,6 @@
|
||||
test_category:
|
||||
label: 'Test category'
|
||||
description: 'This is a test field type category.'
|
||||
weight: -10
|
||||
libraries:
|
||||
- field_plugins_test/test_library
|
||||
@ -0,0 +1,7 @@
|
||||
name: 'Field Plugins Test'
|
||||
type: module
|
||||
description: 'Support module for the field and entity display tests.'
|
||||
package: Testing
|
||||
version: VERSION
|
||||
dependencies:
|
||||
- drupal:text
|
||||
@ -0,0 +1,24 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\field_plugins_test\Plugin\Field\FieldFormatter;
|
||||
|
||||
use Drupal\Core\Field\Attribute\FieldFormatter;
|
||||
use Drupal\Core\StringTranslation\TranslatableMarkup;
|
||||
use Drupal\text\Plugin\Field\FieldFormatter\TextTrimmedFormatter;
|
||||
|
||||
/**
|
||||
* Plugin implementation of the 'field_plugins_test_text_formatter' formatter.
|
||||
*/
|
||||
#[FieldFormatter(
|
||||
id: 'field_plugins_test_text_formatter',
|
||||
label: new TranslatableMarkup('Test Trimmed'),
|
||||
field_types: [
|
||||
'text',
|
||||
'text_long',
|
||||
'text_with_summary',
|
||||
],
|
||||
)]
|
||||
class TestTextTrimmedFormatter extends TextTrimmedFormatter {
|
||||
}
|
||||
@ -0,0 +1,23 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\field_plugins_test\Plugin\Field\FieldWidget;
|
||||
|
||||
use Drupal\Core\Field\Attribute\FieldWidget;
|
||||
use Drupal\Core\StringTranslation\TranslatableMarkup;
|
||||
use Drupal\text\Plugin\Field\FieldWidget\TextfieldWidget;
|
||||
|
||||
/**
|
||||
* Plugin implementation of the 'field_plugins_test_text_widget' widget.
|
||||
*/
|
||||
#[FieldWidget(
|
||||
id: 'field_plugins_test_text_widget',
|
||||
label: new TranslatableMarkup('Test Text field'),
|
||||
field_types: [
|
||||
'text',
|
||||
'string',
|
||||
],
|
||||
)]
|
||||
class TestTextfieldWidget extends TextfieldWidget {
|
||||
}
|
||||
@ -0,0 +1,151 @@
|
||||
field.formatter.settings.field_test_default:
|
||||
type: mapping
|
||||
label: 'Field test default display format settings'
|
||||
mapping:
|
||||
test_formatter_setting:
|
||||
type: string
|
||||
label: 'Test setting'
|
||||
|
||||
field.formatter.settings.field_test_multiple:
|
||||
type: mapping
|
||||
label: 'Multiple field test display format settings'
|
||||
mapping:
|
||||
test_formatter_setting_multiple:
|
||||
type: string
|
||||
label: 'Test setting'
|
||||
alter:
|
||||
type: boolean
|
||||
label: 'Test altering'
|
||||
|
||||
field.formatter.settings.field_empty_setting:
|
||||
type: mapping
|
||||
label: 'Empty setting field display format settings'
|
||||
mapping:
|
||||
field_empty_setting:
|
||||
type: string
|
||||
label: 'Test setting'
|
||||
|
||||
field.formatter.settings.field_test_with_prepare_view:
|
||||
type: mapping
|
||||
label: 'Field prepare step display format settings'
|
||||
mapping:
|
||||
test_formatter_setting_additional:
|
||||
type: string
|
||||
label: 'Test setting'
|
||||
|
||||
field.widget.settings.test_field_widget:
|
||||
type: mapping
|
||||
label: 'Test field widget settings'
|
||||
mapping:
|
||||
test_widget_setting:
|
||||
type: string
|
||||
label: 'Test setting'
|
||||
role:
|
||||
type: string
|
||||
label: 'A referenced role'
|
||||
role2:
|
||||
type: string
|
||||
label: 'A 2nd referenced role'
|
||||
|
||||
field.widget.settings.test_field_widget_multiple:
|
||||
type: mapping
|
||||
label: 'Test multiple field widget settings'
|
||||
mapping:
|
||||
test_widget_setting_multiple:
|
||||
type: string
|
||||
label: 'Test setting'
|
||||
|
||||
field.widget.settings.test_field_widget_multiple_single_value:
|
||||
type: mapping
|
||||
label: 'Test multiple field widget settings: single values'
|
||||
mapping:
|
||||
test_widget_setting_multiple:
|
||||
type: string
|
||||
label: 'Test setting'
|
||||
|
||||
field.widget.settings.test_field_widget_multilingual:
|
||||
type: field.widget.settings.test_field_widget
|
||||
label: 'Test multiple field widget settings: multilingual'
|
||||
|
||||
field.widget.third_party.help:
|
||||
type: mapping
|
||||
label: 'Field test entity display help module third party settings'
|
||||
mapping:
|
||||
foo:
|
||||
type: string
|
||||
label: 'Foo setting'
|
||||
|
||||
field.storage_settings.test_field:
|
||||
type: mapping
|
||||
label: 'Test field storage settings'
|
||||
mapping:
|
||||
test_field_storage_setting:
|
||||
type: string
|
||||
label: 'Test field storage setting'
|
||||
changeable:
|
||||
type: string
|
||||
label: 'A changeable field storage setting'
|
||||
unchangeable:
|
||||
type: string
|
||||
label: 'An unchangeable field storage setting'
|
||||
config_data_from_storage_setting:
|
||||
type: boolean
|
||||
label: 'Test FieldItemInterface::storageSettingsToConfigData()'
|
||||
translatable_storage_setting:
|
||||
type: label
|
||||
label: 'Translatable storage setting'
|
||||
|
||||
field.storage_settings.test_field_with_dependencies:
|
||||
type: field.storage_settings.test_field
|
||||
label: 'Test field with dependencies storage settings'
|
||||
|
||||
field.storage_settings.hidden_test_field:
|
||||
type: field.storage_settings.test_field
|
||||
label: 'Hidden test field storage settings'
|
||||
|
||||
field.storage_settings.test_field_with_preconfigured_options:
|
||||
type: field.storage_settings.test_field
|
||||
label: 'Test field with preconfigured options storage settings'
|
||||
|
||||
field.field_settings.test_field:
|
||||
type: mapping
|
||||
label: 'Test field field settings'
|
||||
mapping:
|
||||
test_field_setting:
|
||||
type: string
|
||||
label: 'Test field setting'
|
||||
config_data_from_field_setting:
|
||||
type: boolean
|
||||
label: 'Test FieldItemInterface::fieldSettingsToConfigData()'
|
||||
translatable_field_setting:
|
||||
type: label
|
||||
label: 'Translatable field setting'
|
||||
|
||||
field.field_settings.test_field_with_dependencies:
|
||||
type: field.field_settings.test_field
|
||||
label: 'Test field with dependencies field settings'
|
||||
|
||||
field.field_settings.hidden_test_field:
|
||||
type: field.field_settings.test_field
|
||||
label: 'Hidden test field field settings'
|
||||
|
||||
field.field_settings.test_field_with_preconfigured_options:
|
||||
type: field.field_settings.test_field
|
||||
label: 'Test field with preconfigured settings'
|
||||
|
||||
field.value.test_field:
|
||||
type: mapping
|
||||
label: 'Default value'
|
||||
mapping:
|
||||
value:
|
||||
type: label
|
||||
label: 'Value'
|
||||
|
||||
|
||||
field.formatter.third_party.field_test:
|
||||
type: mapping
|
||||
label: 'Field test entity display third party setting'
|
||||
mapping:
|
||||
foo:
|
||||
type: string
|
||||
label: 'Test setting'
|
||||
@ -0,0 +1,3 @@
|
||||
field_test_descriptions:
|
||||
label: 'Field Test'
|
||||
description: 'Fields for testing descriptions.'
|
||||
@ -0,0 +1,7 @@
|
||||
name: 'Field API Test'
|
||||
type: module
|
||||
description: 'Support module for the Field API tests.'
|
||||
package: Testing
|
||||
version: VERSION
|
||||
dependencies:
|
||||
- drupal:entity_test
|
||||
@ -0,0 +1,6 @@
|
||||
view test_view_field content:
|
||||
title: 'View test field content'
|
||||
description: 'View published test_view_field content.'
|
||||
administer field_test content:
|
||||
title: 'Administer field_test content'
|
||||
description: 'Manage field_test content'
|
||||
@ -0,0 +1,27 @@
|
||||
field_test.entity_nested_form:
|
||||
path: '/test-entity/nested/{entity_1}/{entity_2}'
|
||||
defaults:
|
||||
_title: 'Nested entity form'
|
||||
_form: '\Drupal\field_test\Form\NestedEntityTestForm'
|
||||
options:
|
||||
parameters:
|
||||
entity_1:
|
||||
type: 'entity:entity_test'
|
||||
entity_2:
|
||||
type: 'entity:entity_test'
|
||||
requirements:
|
||||
_permission: 'administer entity_test content'
|
||||
|
||||
field_test.entity_constraints_nested_form:
|
||||
path: '/test-entity-constraints/nested/{entity_1}/{entity_2}'
|
||||
defaults:
|
||||
_title: 'Nested entity form'
|
||||
_form: '\Drupal\field_test\Form\NestedEntityTestForm'
|
||||
options:
|
||||
parameters:
|
||||
entity_1:
|
||||
type: 'entity:entity_test_constraints'
|
||||
entity_2:
|
||||
type: 'entity:entity_test_constraints'
|
||||
requirements:
|
||||
_permission: 'administer entity_test content'
|
||||
@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\field_test;
|
||||
|
||||
/**
|
||||
* Helper class for \Drupal\Tests\field\Functional\FieldDefaultValueCallbackTest.
|
||||
*/
|
||||
class FieldDefaultValueCallbackProvider {
|
||||
|
||||
/**
|
||||
* Helper callback calculating a default value.
|
||||
*/
|
||||
public static function calculateDefaultValue() {
|
||||
return [['value' => 'Calculated default value']];
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,97 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\field_test;
|
||||
|
||||
use Drupal\Core\Entity\FieldableEntityInterface;
|
||||
use Drupal\Core\Field\FieldDefinitionInterface;
|
||||
|
||||
/**
|
||||
* Helper class for testing fields.
|
||||
*/
|
||||
class FieldTestHelper {
|
||||
|
||||
/**
|
||||
* Helper function to enable entity translations.
|
||||
*/
|
||||
public static function entityInfoTranslatable($entity_type_id = NULL, $translatable = NULL): array {
|
||||
static $stored_value = [];
|
||||
if (isset($entity_type_id)) {
|
||||
$entity_definition_update_manager = \Drupal::entityDefinitionUpdateManager();
|
||||
$entity_type = $entity_definition_update_manager->getEntityType($entity_type_id);
|
||||
$stored_value[$entity_type_id] = $translatable;
|
||||
if ($translatable != $entity_type->isTranslatable()) {
|
||||
$entity_definition_update_manager->uninstallEntityType($entity_type);
|
||||
$entity_type->set('translatable', $translatable);
|
||||
$entity_definition_update_manager->installEntityType($entity_type);
|
||||
}
|
||||
}
|
||||
return $stored_value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sample 'default value' callback.
|
||||
*/
|
||||
public static function defaultValue(FieldableEntityInterface $entity, FieldDefinitionInterface $definition): array {
|
||||
return [['value' => 99]];
|
||||
}
|
||||
|
||||
/**
|
||||
* Store and retrieve keyed data for later verification by unit tests.
|
||||
*
|
||||
* This function is a simple in-memory key-value store with the
|
||||
* distinction that it stores all values for a given key instead of
|
||||
* just the most recently set value. field_test module hooks call
|
||||
* this function to record their arguments, keyed by hook name. The
|
||||
* unit tests later call this function to verify that the correct
|
||||
* hooks were called and were passed the correct arguments.
|
||||
*
|
||||
* This function ignores all calls until the first time it is called
|
||||
* with $key of NULL. Each time it is called with $key of NULL, it
|
||||
* erases all previously stored data from its internal cache, but also
|
||||
* returns the previously stored data to the caller. A typical usage
|
||||
* scenario is:
|
||||
*
|
||||
* @code
|
||||
* // calls to FieldTestHelper::memorize() here are ignored
|
||||
*
|
||||
* // turn on memorization
|
||||
* FieldTestHelper::memorize();
|
||||
*
|
||||
* // call some Field API functions that invoke field_test hooks
|
||||
* FieldStorageConfig::create($field_definition)->save();
|
||||
*
|
||||
* // retrieve and reset the memorized hook call data
|
||||
* $mem = FieldTestHelper::memorize();
|
||||
*
|
||||
* // make sure hook_field_storage_config_create() is invoked correctly
|
||||
* assertEquals(1, count($mem['fieldStorageConfigCreate']));
|
||||
* assertEquals([$field], $mem['fieldStorageConfigCreate'][0]);
|
||||
* @endcode
|
||||
*
|
||||
* @param string $key
|
||||
* The key under which to store to $value, or NULL as described above.
|
||||
* @param mixed|null $value
|
||||
* A value to store for $key.
|
||||
*
|
||||
* @return array|null
|
||||
* An array mapping each $key to an array of each $value passed in
|
||||
* for that key.
|
||||
*/
|
||||
public static function memorize($key = NULL, $value = NULL): array|null {
|
||||
static $memorize;
|
||||
|
||||
if (!isset($key)) {
|
||||
$return = $memorize;
|
||||
$memorize = [];
|
||||
return $return;
|
||||
}
|
||||
if (is_array($memorize)) {
|
||||
$memorize[$key][] = $value;
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,115 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\field_test\Form;
|
||||
|
||||
use Drupal\Core\Entity\EntityChangedInterface;
|
||||
use Drupal\Core\Entity\EntityInterface;
|
||||
use Drupal\Core\Form\FormBase;
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
use Drupal\Core\Entity\Entity\EntityFormDisplay;
|
||||
|
||||
/**
|
||||
* Provides a form for field_test routes.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
class NestedEntityTestForm extends FormBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getFormId() {
|
||||
return 'field_test_entity_nested_form';
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function buildForm(array $form, FormStateInterface $form_state, ?EntityInterface $entity_1 = NULL, ?EntityInterface $entity_2 = NULL) {
|
||||
// First entity.
|
||||
$form_state->set('entity_1', $entity_1);
|
||||
$form_display_1 = EntityFormDisplay::collectRenderDisplay($entity_1, 'default');
|
||||
$form_state->set('form_display_1', $form_display_1);
|
||||
$form_display_1->buildForm($entity_1, $form, $form_state);
|
||||
|
||||
// Second entity.
|
||||
$form_state->set('entity_2', $entity_2);
|
||||
$form_display_2 = EntityFormDisplay::collectRenderDisplay($entity_2, 'default');
|
||||
$form_state->set('form_display_2', $form_display_2);
|
||||
$form['entity_2'] = [
|
||||
'#type' => 'details',
|
||||
'#title' => $this->t('Second entity'),
|
||||
'#tree' => TRUE,
|
||||
'#parents' => ['entity_2'],
|
||||
'#weight' => 50,
|
||||
'#attributes' => ['class' => ['entity-2']],
|
||||
];
|
||||
|
||||
$form_display_2->buildForm($entity_2, $form['entity_2'], $form_state);
|
||||
|
||||
if ($entity_2 instanceof EntityChangedInterface) {
|
||||
// Changed must be sent to the client, for later overwrite error checking.
|
||||
// @see \Drupal\Tests\field\Functional\NestedFormTest::testNestedEntityFormEntityLevelValidation()
|
||||
$form['entity_2']['changed'] = [
|
||||
'#type' => 'hidden',
|
||||
'#default_value' => $entity_1->getChangedTime(),
|
||||
];
|
||||
}
|
||||
|
||||
$form['save'] = [
|
||||
'#type' => 'submit',
|
||||
'#value' => $this->t('Save'),
|
||||
'#weight' => 100,
|
||||
];
|
||||
|
||||
return $form;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function validateForm(array &$form, FormStateInterface $form_state) {
|
||||
$entity_1 = $form_state->get('entity_1');
|
||||
/** @var \Drupal\Core\Entity\Display\EntityFormDisplayInterface $form_display_1 */
|
||||
$form_display_1 = $form_state->get('form_display_1');
|
||||
$form_display_1->extractFormValues($entity_1, $form, $form_state);
|
||||
$form_display_1->validateFormValues($entity_1, $form, $form_state);
|
||||
|
||||
$entity_2 = $form_state->get('entity_2');
|
||||
/** @var \Drupal\Core\Entity\Display\EntityFormDisplayInterface $form_display_2 */
|
||||
$form_display_2 = $form_state->get('form_display_2');
|
||||
$extracted = $form_display_2->extractFormValues($entity_2, $form['entity_2'], $form_state);
|
||||
// Extract the values of fields that are not rendered through widgets, by
|
||||
// simply copying from top-level form values. This leaves the fields that
|
||||
// are not being edited within this form untouched.
|
||||
// @see \Drupal\Tests\field\Functional\NestedFormTest::testNestedEntityFormEntityLevelValidation()
|
||||
foreach ($form_state->getValues()['entity_2'] as $name => $values) {
|
||||
if ($entity_2->hasField($name) && !isset($extracted[$name])) {
|
||||
$entity_2->set($name, $values);
|
||||
}
|
||||
}
|
||||
$form_display_2->validateFormValues($entity_2, $form['entity_2'], $form_state);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function submitForm(array &$form, FormStateInterface $form_state) {
|
||||
/** @var \Drupal\Core\Entity\EntityInterface $entity_1 */
|
||||
$entity_1 = $form_state->get('entity_1');
|
||||
$entity_1->save();
|
||||
|
||||
/** @var \Drupal\Core\Entity\EntityInterface $entity_2 */
|
||||
$entity_2 = $form_state->get('entity_2');
|
||||
$entity_2->save();
|
||||
|
||||
$this->messenger()
|
||||
->addStatus($this->t('test_entities @id_1 and @id_2 have been updated.', [
|
||||
'@id_1' => $entity_1->id(),
|
||||
'@id_2' => $entity_2->id(),
|
||||
]));
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,26 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\field_test\Hook;
|
||||
|
||||
use Drupal\Core\Hook\Attribute\Hook;
|
||||
use Drupal\field_test\FieldTestHelper;
|
||||
|
||||
/**
|
||||
* Hook implementations for field_test.
|
||||
*/
|
||||
class FieldTestEntityHooks {
|
||||
|
||||
/**
|
||||
* Implements hook_entity_type_alter().
|
||||
*/
|
||||
#[Hook('entity_type_alter')]
|
||||
public function entityTypeAlter(array &$entity_types) : void {
|
||||
/** @var \Drupal\Core\Entity\EntityTypeInterface[] $entity_types */
|
||||
foreach (FieldTestHelper::entityInfoTranslatable() as $entity_type => $translatable) {
|
||||
$entity_types[$entity_type]->set('translatable', $translatable);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,63 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\field_test\Hook;
|
||||
|
||||
use Drupal\Core\Access\AccessResultInterface;
|
||||
use Drupal\Core\Access\AccessResult;
|
||||
use Drupal\Core\Field\FieldItemListInterface;
|
||||
use Drupal\Core\Session\AccountInterface;
|
||||
use Drupal\Core\Field\FieldDefinitionInterface;
|
||||
use Drupal\Core\Entity\Exception\FieldStorageDefinitionUpdateForbiddenException;
|
||||
use Drupal\field\FieldStorageConfigInterface;
|
||||
use Drupal\Core\Hook\Attribute\Hook;
|
||||
|
||||
/**
|
||||
* Hook implementations for field_test.
|
||||
*/
|
||||
class FieldTestFieldHooks {
|
||||
|
||||
/**
|
||||
* Implements hook_field_widget_info_alter().
|
||||
*/
|
||||
#[Hook('field_widget_info_alter')]
|
||||
public function fieldWidgetInfoAlter(&$info): void {
|
||||
$info['test_field_widget_multiple']['field_types'][] = 'test_field';
|
||||
$info['test_field_widget_multiple']['field_types'][] = 'test_field_with_preconfigured_options';
|
||||
// Add extra widget when needed for tests.
|
||||
// @see \Drupal\field\Tests\FormTest::widgetAlterTest().
|
||||
if ($alter_info = \Drupal::state()->get("field_test.widget_alter_test")) {
|
||||
if ($alter_info['widget'] === 'test_field_widget_multiple_single_value') {
|
||||
$info['test_field_widget_multiple_single_value']['field_types'][] = 'test_field';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_field_storage_config_update_forbid().
|
||||
*/
|
||||
#[Hook('field_storage_config_update_forbid')]
|
||||
public function fieldStorageConfigUpdateForbid(FieldStorageConfigInterface $field_storage, FieldStorageConfigInterface $prior_field_storage): void {
|
||||
if ($field_storage->getType() == 'test_field' && $field_storage->getSetting('unchangeable') != $prior_field_storage->getSetting('unchangeable')) {
|
||||
throw new FieldStorageDefinitionUpdateForbiddenException("field_test 'unchangeable' setting cannot be changed'");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_entity_field_access().
|
||||
*/
|
||||
#[Hook('entity_field_access')]
|
||||
public function entityFieldAccess($operation, FieldDefinitionInterface $field_definition, AccountInterface $account, ?FieldItemListInterface $items = NULL): AccessResultInterface {
|
||||
if ($field_definition->getName() == "field_no_{$operation}_access") {
|
||||
return AccessResult::forbidden();
|
||||
}
|
||||
// Only grant view access to test_view_field fields when the user has
|
||||
// 'view test_view_field content' permission.
|
||||
if ($field_definition->getName() == 'test_view_field' && $operation == 'view') {
|
||||
return AccessResult::forbiddenIf(!$account->hasPermission('view test_view_field content'))->cachePerPermissions();
|
||||
}
|
||||
return AccessResult::allowed();
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,230 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\field_test\Hook;
|
||||
|
||||
use Drupal\Core\Entity\Query\QueryInterface;
|
||||
use Drupal\Core\StringTranslation\TranslatableMarkup;
|
||||
use Drupal\Core\Entity\EntityTypeInterface;
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
use Drupal\Core\Hook\Attribute\Hook;
|
||||
use Drupal\Core\Render\Element;
|
||||
use Drupal\field_test\FieldTestHelper;
|
||||
use Drupal\field\FieldStorageConfigInterface;
|
||||
|
||||
/**
|
||||
* Hook implementations for field_test.
|
||||
*/
|
||||
class FieldTestHooks {
|
||||
|
||||
/**
|
||||
* Implements hook_entity_display_build_alter().
|
||||
*/
|
||||
#[Hook('entity_display_build_alter')]
|
||||
public function entityDisplayBuildAlter(&$output, $context): void {
|
||||
$display_options = $context['display']->getComponent('test_field');
|
||||
if (isset($display_options['settings']['alter'])) {
|
||||
$output['test_field'][] = ['#markup' => 'field_test_entity_display_build_alter'];
|
||||
}
|
||||
if (isset($output['test_field'])) {
|
||||
$output['test_field'][] = ['#markup' => 'entity language is ' . $context['entity']->language()->getId()];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_field_widget_single_element_form_alter().
|
||||
*/
|
||||
#[Hook('field_widget_single_element_form_alter')]
|
||||
public function fieldWidgetSingleElementFormAlter(&$element, FormStateInterface $form_state, $context): void {
|
||||
// Set a message if this is for the form displayed to set default value for
|
||||
// the field.
|
||||
if ($context['default']) {
|
||||
\Drupal::messenger()->addStatus('From hook_field_widget_single_element_form_alter(): Default form is true.');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_field_widget_complete_form_alter().
|
||||
*/
|
||||
#[Hook('field_widget_complete_form_alter')]
|
||||
public function fieldWidgetCompleteFormAlter(array &$field_widget_complete_form, FormStateInterface $form_state, array $context): void {
|
||||
$this->alterWidget("hook_field_widget_complete_form_alter", $field_widget_complete_form, $form_state, $context);
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_field_widget_complete_WIDGET_TYPE_form_alter().
|
||||
*/
|
||||
#[Hook('field_widget_complete_test_field_widget_multiple_form_alter')]
|
||||
public function fieldWidgetCompleteTestFieldWidgetMultipleFormAlter(array &$field_widget_complete_form, FormStateInterface $form_state, array $context): void {
|
||||
$this->alterWidget("hook_field_widget_complete_WIDGET_TYPE_form_alter", $field_widget_complete_form, $form_state, $context);
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_field_widget_complete_WIDGET_TYPE_form_alter().
|
||||
*/
|
||||
#[Hook('field_widget_complete_test_field_widget_multiple_single_value_form_alter')]
|
||||
public function fieldWidgetCompleteTestFieldWidgetMultipleSingleValueFormAlter(array &$field_widget_complete_form, FormStateInterface $form_state, array $context): void {
|
||||
$this->alterWidget("hook_field_widget_complete_WIDGET_TYPE_form_alter", $field_widget_complete_form, $form_state, $context);
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_query_TAG_alter() for tag 'efq_table_prefixing_test'.
|
||||
*
|
||||
* @see \Drupal\system\Tests\Entity\EntityFieldQueryTest::testTablePrefixing()
|
||||
*/
|
||||
#[Hook('query_efq_table_prefixing_test_alter')]
|
||||
public function queryEfqTablePrefixingTestAlter(&$query): void {
|
||||
// Add an additional join onto the entity base table. This will cause an
|
||||
// exception if the EFQ does not properly prefix the base table.
|
||||
$query->join('entity_test', 'et2', '[%alias].[id] = [entity_test].[id]');
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_query_TAG_alter() for tag 'efq_metadata_test'.
|
||||
*
|
||||
* @see \Drupal\system\Tests\Entity\EntityQueryTest::testMetaData()
|
||||
*/
|
||||
#[Hook('query_efq_metadata_test_alter')]
|
||||
public function queryEfqMetadataTestAlter(&$query): void {
|
||||
FieldTestHelper::memorize('field_test_query_efq_metadata_test_alter', $query->getMetadata('foo'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_entity_extra_field_info_alter().
|
||||
*/
|
||||
#[Hook('entity_extra_field_info_alter')]
|
||||
public function entityExtraFieldInfoAlter(&$info): void {
|
||||
// Remove all extra fields from the 'no_fields' content type;
|
||||
unset($info['node']['no_fields']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_entity_bundle_field_info_alter().
|
||||
*/
|
||||
#[Hook('entity_bundle_field_info_alter')]
|
||||
public function entityBundleFieldInfoAlter(&$fields, EntityTypeInterface $entity_type, $bundle): void {
|
||||
if (($field_name = \Drupal::state()->get('field_test_constraint', FALSE)) && $entity_type->id() == 'entity_test' && $bundle == 'entity_test' && !empty($fields[$field_name])) {
|
||||
// Set a property constraint using
|
||||
// \Drupal\Core\Field\FieldConfigInterface::setPropertyConstraints().
|
||||
$fields[$field_name]->setPropertyConstraints('value', [
|
||||
'TestField' => [
|
||||
'value' => -2,
|
||||
'message' => "$field_name does not accept the value -2.",
|
||||
],
|
||||
]);
|
||||
// Add a property constraint using
|
||||
// \Drupal\Core\Field\FieldConfigInterface::addPropertyConstraints().
|
||||
$fields[$field_name]->addPropertyConstraints('value', ['Range' => ['min' => 0, 'max' => 32]]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_field_ui_preconfigured_options_alter().
|
||||
*/
|
||||
#[Hook('field_ui_preconfigured_options_alter')]
|
||||
public function fieldUiPreconfiguredOptionsAlter(array &$options, $field_type): void {
|
||||
if ($field_type === 'test_field_with_preconfigured_options') {
|
||||
$options['custom_options']['entity_view_display']['settings'] = ['test_formatter_setting_multiple' => 'altered dummy test string'];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_field_info_entity_type_ui_definitions_alter().
|
||||
*/
|
||||
#[Hook('field_info_entity_type_ui_definitions_alter')]
|
||||
public function fieldInfoEntityTypeUiDefinitionsAlter(array &$ui_definitions, string $entity_type_id): void {
|
||||
if ($entity_type_id === 'node') {
|
||||
$ui_definitions['boolean']['label'] = new TranslatableMarkup('Boolean (overridden by alter)');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_entity_query_alter().
|
||||
*
|
||||
* @see Drupal\KernelTests\Core\Entity\EntityQueryTest::testAlterHook
|
||||
*/
|
||||
#[Hook('entity_query_alter')]
|
||||
public function entityQueryAlter(QueryInterface $query) : void {
|
||||
if ($query->hasTag('entity_query_alter_hook_test')) {
|
||||
$query->condition('id', '5', '<>');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_entity_query_ENTITY_TYPE_alter() for 'entity_test_mulrev'.
|
||||
*
|
||||
* @see Drupal\KernelTests\Core\Entity\EntityQueryTest::testAlterHook
|
||||
*/
|
||||
#[Hook('entity_query_entity_test_mulrev_alter')]
|
||||
public function entityQueryEntityTestMulrevAlter(QueryInterface $query) : void {
|
||||
if ($query->hasTag('entity_query_entity_test_mulrev_alter_hook_test')) {
|
||||
$query->condition('id', '7', '<>');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_entity_query_tag__TAG_alter() for 'entity_query_alter_tag_test'.
|
||||
*
|
||||
* @see Drupal\KernelTests\Core\Entity\EntityQueryTest::testAlterHook
|
||||
*/
|
||||
#[Hook('entity_query_tag__entity_query_alter_tag_test_alter')]
|
||||
public function entityQueryTagEntityQueryAlterTagTestAlter(QueryInterface $query) : void {
|
||||
$query->condition('id', '13', '<>');
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_entity_query_tag__ENTITY_TYPE__TAG_alter().
|
||||
*
|
||||
* Entity type is 'entity_test_mulrev' and tag is
|
||||
* 'entity_query_entity_test_mulrev_alter_tag_test'.
|
||||
*
|
||||
* @see Drupal\KernelTests\Core\Entity\EntityQueryTest::testAlterHook
|
||||
*/
|
||||
#[Hook('entity_query_tag__entity_test_mulrev__entity_query_entity_test_mulrev_alter_tag_test_alter')]
|
||||
public function entityQueryTagEntityTestMulrevEntityQueryEntityTestMulrevAlterTagTestAlter(QueryInterface $query) : void {
|
||||
$query->condition('id', '15', '<>');
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_field_storage_config_create().
|
||||
*/
|
||||
#[Hook('field_storage_config_create')]
|
||||
public function fieldStorageConfigCreate(FieldStorageConfigInterface $field_storage): void {
|
||||
$args = func_get_args();
|
||||
FieldTestHelper::memorize(__METHOD__, $args);
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_entity_reference_selection_alter().
|
||||
*/
|
||||
#[Hook('entity_reference_selection_alter')]
|
||||
public function entityReferenceSelectionAlter(array &$definitions): void {
|
||||
if (\Drupal::state()->get('field_test_disable_broken_entity_reference_handler')) {
|
||||
unset($definitions['broken']);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets up alterations for widget alter tests.
|
||||
*
|
||||
* @see \Drupal\field\Tests\FormTest::widgetAlterTest()
|
||||
*/
|
||||
public function alterWidget($hook, array &$field_widget_complete_form, FormStateInterface $form_state, array $context): void {
|
||||
$elements = &$field_widget_complete_form['widget'];
|
||||
// Set a message if this is for the form displayed to set default value for
|
||||
// the field.
|
||||
if ($context['default']) {
|
||||
\Drupal::messenger()->addStatus("From $hook(): Default form is true.");
|
||||
}
|
||||
$alter_info = \Drupal::state()->get("field_test.widget_alter_test");
|
||||
$name = $context['items']->getFieldDefinition()->getName();
|
||||
if (!empty($alter_info) && $hook === $alter_info['hook'] && $name === $alter_info['field_name']) {
|
||||
$elements['#prefix'] = "From $hook(): prefix on $name parent element.";
|
||||
foreach (Element::children($elements) as $delta => $element) {
|
||||
$elements[$delta]['#suffix'] = "From $hook(): suffix on $name child element.";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,43 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\field_test\Plugin\Field\FieldFormatter;
|
||||
|
||||
use Drupal\Core\Field\Attribute\FieldFormatter;
|
||||
use Drupal\Core\Field\FieldDefinitionInterface;
|
||||
use Drupal\Core\Field\FieldItemListInterface;
|
||||
use Drupal\Core\Field\FormatterBase;
|
||||
use Drupal\Core\StringTranslation\TranslatableMarkup;
|
||||
|
||||
/**
|
||||
* Plugin implementation of the 'field_test_applicable' formatter.
|
||||
*
|
||||
* It is applicable to test_field fields unless their name is 'deny_applicable'.
|
||||
*/
|
||||
#[FieldFormatter(
|
||||
id: 'field_test_applicable',
|
||||
label: new TranslatableMarkup('Applicable'),
|
||||
description: new TranslatableMarkup('Applicable formatter'),
|
||||
field_types: [
|
||||
'test_field',
|
||||
],
|
||||
weight: 15,
|
||||
)]
|
||||
class TestFieldApplicableFormatter extends FormatterBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function isApplicable(FieldDefinitionInterface $field_definition) {
|
||||
return $field_definition->getName() != 'deny_applicable';
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function viewElements(FieldItemListInterface $items, $langcode) {
|
||||
return ['#markup' => 'Nothing to see here'];
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,76 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\field_test\Plugin\Field\FieldFormatter;
|
||||
|
||||
use Drupal\Core\Field\Attribute\FieldFormatter;
|
||||
use Drupal\Core\Field\FormatterBase;
|
||||
use Drupal\Core\Field\FieldItemListInterface;
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
use Drupal\Core\StringTranslation\TranslatableMarkup;
|
||||
|
||||
/**
|
||||
* Plugin implementation of the 'field_test_default' formatter.
|
||||
*/
|
||||
#[FieldFormatter(
|
||||
id: 'field_test_default',
|
||||
label: new TranslatableMarkup('Default'),
|
||||
description: new TranslatableMarkup('Default formatter'),
|
||||
field_types: [
|
||||
'test_field',
|
||||
'test_field_with_preconfigured_options',
|
||||
],
|
||||
weight: 1,
|
||||
)]
|
||||
class TestFieldDefaultFormatter extends FormatterBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function defaultSettings() {
|
||||
return [
|
||||
'test_formatter_setting' => 'dummy test string',
|
||||
] + parent::defaultSettings();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function settingsForm(array $form, FormStateInterface $form_state) {
|
||||
$element['test_formatter_setting'] = [
|
||||
'#title' => $this->t('Setting'),
|
||||
'#type' => 'textfield',
|
||||
'#size' => 20,
|
||||
'#default_value' => $this->getSetting('test_formatter_setting'),
|
||||
'#required' => TRUE,
|
||||
];
|
||||
return $element;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function settingsSummary() {
|
||||
$summary = [];
|
||||
$summary[] = $this->t('@setting: @value', [
|
||||
'@setting' => 'test_formatter_setting',
|
||||
'@value' => $this->getSetting('test_formatter_setting'),
|
||||
]);
|
||||
return $summary;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function viewElements(FieldItemListInterface $items, $langcode) {
|
||||
$elements = [];
|
||||
|
||||
foreach ($items as $delta => $item) {
|
||||
$elements[$delta] = ['#markup' => $this->getSetting('test_formatter_setting') . '|' . $item->value];
|
||||
}
|
||||
|
||||
return $elements;
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,54 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\field_test\Plugin\Field\FieldFormatter;
|
||||
|
||||
use Drupal\Core\Field\Attribute\FieldFormatter;
|
||||
use Drupal\Core\Field\FieldItemListInterface;
|
||||
use Drupal\Core\Field\FormatterBase;
|
||||
use Drupal\Core\StringTranslation\TranslatableMarkup;
|
||||
|
||||
/**
|
||||
* Plugin implementation of the 'field_empty_test' formatter.
|
||||
*/
|
||||
#[FieldFormatter(
|
||||
id: 'field_empty_test',
|
||||
label: new TranslatableMarkup('Field empty test'),
|
||||
field_types: [
|
||||
'test_field',
|
||||
],
|
||||
weight: -5,
|
||||
)]
|
||||
class TestFieldEmptyFormatter extends FormatterBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function defaultSettings() {
|
||||
return [
|
||||
'test_empty_string' => '**EMPTY FIELD**',
|
||||
] + parent::defaultSettings();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function viewElements(FieldItemListInterface $items, $langcode) {
|
||||
$elements = [];
|
||||
|
||||
if ($items->isEmpty()) {
|
||||
// For fields with no value, just add the configured "empty" value.
|
||||
$elements[0] = ['#markup' => $this->getSetting('test_empty_string')];
|
||||
}
|
||||
else {
|
||||
foreach ($items as $delta => $item) {
|
||||
// This formatter only needs to output raw for testing.
|
||||
$elements[$delta] = ['#markup' => $item->value];
|
||||
}
|
||||
}
|
||||
|
||||
return $elements;
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,76 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\field_test\Plugin\Field\FieldFormatter;
|
||||
|
||||
use Drupal\Core\Field\Attribute\FieldFormatter;
|
||||
use Drupal\Core\Field\FormatterBase;
|
||||
use Drupal\Core\Field\FieldItemListInterface;
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
use Drupal\Core\StringTranslation\TranslatableMarkup;
|
||||
|
||||
/**
|
||||
* Plugin implementation of the 'field_empty_setting' formatter.
|
||||
*/
|
||||
#[FieldFormatter(
|
||||
id: 'field_empty_setting',
|
||||
label: new TranslatableMarkup('Field empty setting'),
|
||||
field_types: [
|
||||
'test_field',
|
||||
],
|
||||
weight: -1,
|
||||
)]
|
||||
class TestFieldEmptySettingFormatter extends FormatterBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function defaultSettings() {
|
||||
return [
|
||||
'field_empty_setting' => '',
|
||||
] + parent::defaultSettings();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function settingsForm(array $form, FormStateInterface $form_state) {
|
||||
$element['field_empty_setting'] = [
|
||||
'#title' => $this->t('Setting'),
|
||||
'#type' => 'textfield',
|
||||
'#size' => 20,
|
||||
'#default_value' => $this->getSetting('field_empty_setting'),
|
||||
'#required' => TRUE,
|
||||
];
|
||||
return $element;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function settingsSummary() {
|
||||
$summary = [];
|
||||
$setting = $this->getSetting('field_empty_setting');
|
||||
if (!empty($setting)) {
|
||||
$summary[] = $this->t('Default empty setting now has a value.');
|
||||
}
|
||||
return $summary;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function viewElements(FieldItemListInterface $items, $langcode) {
|
||||
$elements = [];
|
||||
|
||||
if (!empty($items)) {
|
||||
foreach ($items as $delta => $item) {
|
||||
$elements[$delta] = ['#markup' => $this->getSetting('field_empty_setting')];
|
||||
}
|
||||
}
|
||||
|
||||
return $elements;
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,81 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\field_test\Plugin\Field\FieldFormatter;
|
||||
|
||||
use Drupal\Core\Field\Attribute\FieldFormatter;
|
||||
use Drupal\Core\Field\FormatterBase;
|
||||
use Drupal\Core\Field\FieldItemListInterface;
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
use Drupal\Core\StringTranslation\TranslatableMarkup;
|
||||
|
||||
/**
|
||||
* Plugin implementation of the 'field_test_multiple' formatter.
|
||||
*/
|
||||
#[FieldFormatter(
|
||||
id: 'field_test_multiple',
|
||||
label: new TranslatableMarkup('Multiple'),
|
||||
description: new TranslatableMarkup('Multiple formatter'),
|
||||
field_types: [
|
||||
'test_field',
|
||||
'test_field_with_preconfigured_options',
|
||||
],
|
||||
weight: 5,
|
||||
)]
|
||||
class TestFieldMultipleFormatter extends FormatterBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function defaultSettings() {
|
||||
return [
|
||||
'test_formatter_setting_multiple' => 'dummy test string',
|
||||
'alter' => FALSE,
|
||||
] + parent::defaultSettings();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function settingsForm(array $form, FormStateInterface $form_state) {
|
||||
$element['test_formatter_setting_multiple'] = [
|
||||
'#title' => $this->t('Setting'),
|
||||
'#type' => 'textfield',
|
||||
'#size' => 20,
|
||||
'#default_value' => $this->getSetting('test_formatter_setting_multiple'),
|
||||
'#required' => TRUE,
|
||||
];
|
||||
return $element;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function settingsSummary() {
|
||||
$summary = [];
|
||||
$summary[] = $this->t('@setting: @value', [
|
||||
'@setting' => 'test_formatter_setting_multiple',
|
||||
'@value' => $this->getSetting('test_formatter_setting_multiple'),
|
||||
]);
|
||||
return $summary;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function viewElements(FieldItemListInterface $items, $langcode) {
|
||||
$elements = [];
|
||||
|
||||
if (!empty($items)) {
|
||||
$array = [];
|
||||
foreach ($items as $delta => $item) {
|
||||
$array[] = $delta . ':' . $item->value;
|
||||
}
|
||||
$elements[0] = ['#markup' => $this->getSetting('test_formatter_setting_multiple') . '|' . implode('|', $array)];
|
||||
}
|
||||
|
||||
return $elements;
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,39 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\field_test\Plugin\Field\FieldFormatter;
|
||||
|
||||
use Drupal\Core\Field\Attribute\FieldFormatter;
|
||||
use Drupal\Core\Field\FieldItemListInterface;
|
||||
use Drupal\Core\Field\FormatterBase;
|
||||
use Drupal\Core\StringTranslation\TranslatableMarkup;
|
||||
|
||||
/**
|
||||
* Plugin implementation of the 'field_no_settings' formatter.
|
||||
*/
|
||||
#[FieldFormatter(
|
||||
id: 'field_no_settings',
|
||||
label: new TranslatableMarkup('Field no settings'),
|
||||
field_types: [
|
||||
'test_field',
|
||||
],
|
||||
weight: -10,
|
||||
)]
|
||||
class TestFieldNoSettingsFormatter extends FormatterBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function viewElements(FieldItemListInterface $items, $langcode) {
|
||||
$elements = [];
|
||||
|
||||
foreach ($items as $delta => $item) {
|
||||
// This formatter only needs to output raw for testing.
|
||||
$elements[$delta] = ['#markup' => $item->value];
|
||||
}
|
||||
|
||||
return $elements;
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,89 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\field_test\Plugin\Field\FieldFormatter;
|
||||
|
||||
use Drupal\Core\Field\Attribute\FieldFormatter;
|
||||
use Drupal\Core\Field\FormatterBase;
|
||||
use Drupal\Core\Field\FieldItemListInterface;
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
use Drupal\Core\StringTranslation\TranslatableMarkup;
|
||||
|
||||
/**
|
||||
* Plugin implementation of the 'field_test_with_prepare_view' formatter.
|
||||
*/
|
||||
#[FieldFormatter(
|
||||
id: 'field_test_with_prepare_view',
|
||||
label: new TranslatableMarkup('With prepare step'),
|
||||
description: new TranslatableMarkup('Tests prepareView() method'),
|
||||
field_types: [
|
||||
'test_field',
|
||||
],
|
||||
weight: 10,
|
||||
)]
|
||||
class TestFieldPrepareViewFormatter extends FormatterBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function defaultSettings() {
|
||||
return [
|
||||
'test_formatter_setting_additional' => 'dummy test string',
|
||||
] + parent::defaultSettings();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function settingsForm(array $form, FormStateInterface $form_state) {
|
||||
$element['test_formatter_setting_additional'] = [
|
||||
'#title' => $this->t('Setting'),
|
||||
'#type' => 'textfield',
|
||||
'#size' => 20,
|
||||
'#default_value' => $this->getSetting('test_formatter_setting_additional'),
|
||||
'#required' => TRUE,
|
||||
];
|
||||
return $element;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function settingsSummary() {
|
||||
$summary = [];
|
||||
$summary[] = $this->t('@setting: @value', [
|
||||
'@setting' => 'test_formatter_setting_additional',
|
||||
'@value' => $this->getSetting('test_formatter_setting_additional'),
|
||||
]);
|
||||
return $summary;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function prepareView(array $entities_items) {
|
||||
foreach ($entities_items as $items) {
|
||||
foreach ($items as $item) {
|
||||
// Don't add anything on empty values.
|
||||
if (!$item->isEmpty()) {
|
||||
$item->additional_formatter_value = $item->value + 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function viewElements(FieldItemListInterface $items, $langcode) {
|
||||
$elements = [];
|
||||
|
||||
foreach ($items as $delta => $item) {
|
||||
$elements[$delta] = ['#markup' => $this->getSetting('test_formatter_setting_additional') . '|' . $item->value . '|' . $item->additional_formatter_value];
|
||||
}
|
||||
|
||||
return $elements;
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,23 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\field_test\Plugin\Field\FieldType;
|
||||
|
||||
use Drupal\Core\Field\Attribute\FieldType;
|
||||
use Drupal\Core\StringTranslation\TranslatableMarkup;
|
||||
|
||||
/**
|
||||
* Defines the 'hidden_test' entity field item.
|
||||
*/
|
||||
#[FieldType(
|
||||
id: "hidden_test_field",
|
||||
label: new TranslatableMarkup("Hidden from UI test field"),
|
||||
description: new TranslatableMarkup("Dummy hidden field type used for tests."),
|
||||
default_widget: "test_field_widget",
|
||||
default_formatter: "field_test_default",
|
||||
no_ui: TRUE
|
||||
)]
|
||||
class HiddenTestItem extends TestItem {
|
||||
|
||||
}
|
||||
@ -0,0 +1,187 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\field_test\Plugin\Field\FieldType;
|
||||
|
||||
use Drupal\Core\Field\Attribute\FieldType;
|
||||
use Drupal\Core\Field\FieldItemBase;
|
||||
use Drupal\Core\Field\FieldStorageDefinitionInterface;
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
use Drupal\Core\StringTranslation\TranslatableMarkup;
|
||||
use Drupal\Core\TypedData\DataDefinition;
|
||||
use Drupal\field_test\FieldTestHelper;
|
||||
|
||||
/**
|
||||
* Defines the 'test_field' entity field item.
|
||||
*/
|
||||
#[FieldType(
|
||||
id: "test_field",
|
||||
label: new TranslatableMarkup("Test field"),
|
||||
default_widget: "test_field_widget",
|
||||
default_formatter: "field_test_default"
|
||||
)]
|
||||
class TestItem extends FieldItemBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function defaultStorageSettings() {
|
||||
return [
|
||||
'test_field_storage_setting' => 'dummy test string',
|
||||
'changeable' => 'a changeable field storage setting',
|
||||
'unchangeable' => 'an unchangeable field storage setting',
|
||||
'translatable_storage_setting' => 'a translatable field storage setting',
|
||||
] + parent::defaultStorageSettings();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function defaultFieldSettings() {
|
||||
return [
|
||||
'test_field_setting' => 'dummy test string',
|
||||
'translatable_field_setting' => 'a translatable field setting',
|
||||
] + parent::defaultFieldSettings();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function propertyDefinitions(FieldStorageDefinitionInterface $field_definition) {
|
||||
$properties['value'] = DataDefinition::create('integer')
|
||||
->setLabel(new TranslatableMarkup('Test integer value'))
|
||||
->setRequired(TRUE);
|
||||
|
||||
return $properties;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function schema(FieldStorageDefinitionInterface $field_definition) {
|
||||
return [
|
||||
'columns' => [
|
||||
'value' => [
|
||||
'type' => 'int',
|
||||
'size' => 'medium',
|
||||
],
|
||||
],
|
||||
'indexes' => [
|
||||
'value' => ['value'],
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function storageSettingsForm(array &$form, FormStateInterface $form_state, $has_data) {
|
||||
$form['cardinality_container'][] = [
|
||||
'#type' => 'html_tag',
|
||||
'#tag' => 'p',
|
||||
'#value' => 'Greetings from ' . __METHOD__,
|
||||
];
|
||||
$element = [];
|
||||
$element['test_field_storage_setting'] = [
|
||||
'#type' => 'textfield',
|
||||
'#title' => $this->t('Field test field storage setting'),
|
||||
'#default_value' => $this->getSetting('test_field_storage_setting'),
|
||||
'#required' => FALSE,
|
||||
'#description' => $this->t('A dummy form element to simulate field storage setting.'),
|
||||
];
|
||||
|
||||
return $element;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function fieldSettingsForm(array $form, FormStateInterface $form_state) {
|
||||
$element = [];
|
||||
$element['test_field_setting'] = [
|
||||
'#type' => 'textfield',
|
||||
'#title' => $this->t('Field test field setting'),
|
||||
'#default_value' => $this->getSetting('test_field_setting'),
|
||||
'#required' => FALSE,
|
||||
'#description' => $this->t('A dummy form element to simulate field setting.'),
|
||||
];
|
||||
|
||||
return $element;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function delete() {
|
||||
// Reports that delete() method is executed for testing purposes.
|
||||
FieldTestHelper::memorize('field_test_field_delete', [$this->getEntity()]);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getConstraints() {
|
||||
$constraint_manager = \Drupal::typedDataManager()->getValidationConstraintManager();
|
||||
$constraints = parent::getConstraints();
|
||||
|
||||
$constraints[] = $constraint_manager->create('ComplexData', [
|
||||
'value' => [
|
||||
'TestField' => [
|
||||
'value' => -1,
|
||||
'message' => $this->t('%name does not accept the value @value.', [
|
||||
'%name' => $this->getFieldDefinition()
|
||||
->getLabel(),
|
||||
'@value' => -1,
|
||||
]),
|
||||
],
|
||||
],
|
||||
]);
|
||||
|
||||
return $constraints;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function isEmpty() {
|
||||
return empty($this->value);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function storageSettingsToConfigData(array $settings) {
|
||||
$settings['config_data_from_storage_setting'] = 'TRUE';
|
||||
unset($settings['storage_setting_from_config_data']);
|
||||
return $settings;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function storageSettingsFromConfigData(array $settings) {
|
||||
$settings['storage_setting_from_config_data'] = 'TRUE';
|
||||
unset($settings['config_data_from_storage_setting']);
|
||||
return $settings;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function fieldSettingsToConfigData(array $settings) {
|
||||
$settings['config_data_from_field_setting'] = 'TRUE';
|
||||
unset($settings['field_setting_from_config_data']);
|
||||
return $settings;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function fieldSettingsFromConfigData(array $settings) {
|
||||
$settings['field_setting_from_config_data'] = 'TRUE';
|
||||
unset($settings['config_data_from_field_setting']);
|
||||
return $settings;
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,33 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\field_test\Plugin\Field\FieldType;
|
||||
|
||||
use Drupal\Core\Field\Attribute\FieldType;
|
||||
use Drupal\Core\Field\FieldDefinitionInterface;
|
||||
use Drupal\Core\StringTranslation\TranslatableMarkup;
|
||||
|
||||
/**
|
||||
* Defines the 'test_field_with_dependencies' entity field item.
|
||||
*/
|
||||
#[FieldType(
|
||||
id: "test_field_with_dependencies",
|
||||
label: new TranslatableMarkup("Test field with dependencies"),
|
||||
description: new TranslatableMarkup("Dummy field type used for tests."),
|
||||
default_widget: "test_field_widget",
|
||||
default_formatter: "field_test_default",
|
||||
config_dependencies: [
|
||||
"module" => ["system"],
|
||||
]
|
||||
)]
|
||||
class TestItemWithDependencies extends TestItem {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function calculateDependencies(FieldDefinitionInterface $field_definition) {
|
||||
return ['content' => ['node:article:uuid']];
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,25 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\field_test\Plugin\Field\FieldType;
|
||||
|
||||
use Drupal\Core\Field\Attribute\FieldType;
|
||||
use Drupal\Core\StringTranslation\TranslatableMarkup;
|
||||
|
||||
/**
|
||||
* Defines the 'test_field_with_multiple_descriptions' entity field item.
|
||||
*/
|
||||
#[FieldType(
|
||||
id: "test_field_with_multiple_descriptions",
|
||||
label: new TranslatableMarkup("Test field (multiple descriptions"),
|
||||
description: [
|
||||
new TranslatableMarkup("This multiple line description needs to use an array"),
|
||||
new TranslatableMarkup("This second line contains important information"),
|
||||
],
|
||||
category: "field_test_descriptions",
|
||||
default_widget: "test_field_widget",
|
||||
default_formatter: "field_test_default"
|
||||
)]
|
||||
class TestItemWithMultipleDescriptions extends TestItem {
|
||||
}
|
||||
@ -0,0 +1,53 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\field_test\Plugin\Field\FieldType;
|
||||
|
||||
use Drupal\Core\Field\Attribute\FieldType;
|
||||
use Drupal\Core\Field\FieldStorageDefinitionInterface;
|
||||
use Drupal\Core\Field\PreconfiguredFieldUiOptionsInterface;
|
||||
use Drupal\Core\StringTranslation\TranslatableMarkup;
|
||||
|
||||
/**
|
||||
* Defines the 'test_field_with_preconfigured_options' entity field item.
|
||||
*/
|
||||
#[FieldType(
|
||||
id: "test_field_with_preconfigured_options",
|
||||
label: new TranslatableMarkup("Test field with preconfigured options"),
|
||||
description: new TranslatableMarkup("Dummy field type used for tests."),
|
||||
default_widget: "test_field_widget",
|
||||
default_formatter: "field_test_default"
|
||||
)]
|
||||
class TestItemWithPreconfiguredOptions extends TestItem implements PreconfiguredFieldUiOptionsInterface {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function getPreconfiguredOptions() {
|
||||
return [
|
||||
'custom_options' => [
|
||||
'label' => new TranslatableMarkup('All custom options'),
|
||||
'field_storage_config' => [
|
||||
'cardinality' => FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED,
|
||||
'settings' => [
|
||||
'test_field_storage_setting' => 'preconfigured_storage_setting',
|
||||
],
|
||||
],
|
||||
'field_config' => [
|
||||
'required' => TRUE,
|
||||
'settings' => [
|
||||
'test_field_setting' => 'preconfigured_field_setting',
|
||||
],
|
||||
],
|
||||
'entity_form_display' => [
|
||||
'type' => 'test_field_widget_multiple',
|
||||
],
|
||||
'entity_view_display' => [
|
||||
'type' => 'field_test_multiple',
|
||||
],
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,22 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\field_test\Plugin\Field\FieldType;
|
||||
|
||||
use Drupal\Core\Field\Attribute\FieldType;
|
||||
use Drupal\Core\StringTranslation\TranslatableMarkup;
|
||||
|
||||
/**
|
||||
* Defines the 'test_field_with_single_description' entity field item.
|
||||
*/
|
||||
#[FieldType(
|
||||
id: "test_field_with_single_description",
|
||||
label: new TranslatableMarkup("Test field (single description"),
|
||||
description: new TranslatableMarkup("This one-line field description is important for testing"),
|
||||
category: "field_test_descriptions",
|
||||
default_widget: "test_field_widget",
|
||||
default_formatter: "field_test_default"
|
||||
)]
|
||||
class TestItemWithSingleDescription extends TestItem {
|
||||
}
|
||||
@ -0,0 +1,52 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\field_test\Plugin\Field\FieldType;
|
||||
|
||||
use Drupal\Core\Field\Attribute\FieldType;
|
||||
use Drupal\Core\Field\FieldItemBase;
|
||||
use Drupal\Core\Field\FieldStorageDefinitionInterface;
|
||||
use Drupal\Core\StringTranslation\TranslatableMarkup;
|
||||
use Drupal\Core\TypedData\DataDefinition;
|
||||
|
||||
/**
|
||||
* Defines the 'test_object_field' entity field item.
|
||||
*/
|
||||
#[FieldType(
|
||||
id: "test_object_field",
|
||||
label: new TranslatableMarkup("Test object field"),
|
||||
description: new TranslatableMarkup("Test field type that has an object to test serialization"),
|
||||
default_widget: "test_object_field_widget",
|
||||
default_formatter: "object_field_test_default"
|
||||
)]
|
||||
class TestObjectItem extends FieldItemBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function propertyDefinitions(FieldStorageDefinitionInterface $field_definition) {
|
||||
$properties['value'] = DataDefinition::create('any')
|
||||
->setLabel(t('Value'))
|
||||
->setRequired(TRUE);
|
||||
|
||||
return $properties;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function schema(FieldStorageDefinitionInterface $field_definition) {
|
||||
return [
|
||||
'columns' => [
|
||||
'value' => [
|
||||
'description' => 'The object item value.',
|
||||
'type' => 'blob',
|
||||
'not null' => TRUE,
|
||||
'serialize' => TRUE,
|
||||
],
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,123 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\field_test\Plugin\Field\FieldWidget;
|
||||
|
||||
use Drupal\Core\Field\Attribute\FieldWidget;
|
||||
use Drupal\Core\Field\FieldItemListInterface;
|
||||
use Drupal\Core\Field\WidgetBase;
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
use Drupal\Core\StringTranslation\TranslatableMarkup;
|
||||
use Symfony\Component\Validator\ConstraintViolationInterface;
|
||||
|
||||
/**
|
||||
* Plugin implementation of the 'test_field_widget' widget.
|
||||
*/
|
||||
#[FieldWidget(
|
||||
id: 'test_field_widget',
|
||||
label: new TranslatableMarkup('Test widget'),
|
||||
field_types: [
|
||||
'field_test',
|
||||
'test_field',
|
||||
'hidden_test_field',
|
||||
'test_field_with_preconfigured_options',
|
||||
],
|
||||
weight: -10,
|
||||
)]
|
||||
class TestFieldWidget extends WidgetBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function defaultSettings() {
|
||||
return [
|
||||
'test_widget_setting' => 'dummy test string',
|
||||
'role' => 'anonymous',
|
||||
'role2' => 'anonymous',
|
||||
] + parent::defaultSettings();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function settingsForm(array $form, FormStateInterface $form_state) {
|
||||
$element['test_widget_setting'] = [
|
||||
'#type' => 'textfield',
|
||||
'#title' => $this->t('Field test field widget setting'),
|
||||
'#description' => $this->t('A dummy form element to simulate field widget setting.'),
|
||||
'#default_value' => $this->getSetting('test_widget_setting'),
|
||||
'#required' => FALSE,
|
||||
];
|
||||
return $element;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function settingsSummary() {
|
||||
$summary = [];
|
||||
$summary[] = $this->t('@setting: @value', [
|
||||
'@setting' => 'test_widget_setting',
|
||||
'@value' => $this->getSetting('test_widget_setting'),
|
||||
]);
|
||||
return $summary;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function formElement(FieldItemListInterface $items, $delta, array $element, array &$form, FormStateInterface $form_state) {
|
||||
$element += [
|
||||
'#type' => 'textfield',
|
||||
'#default_value' => $items[$delta]->value ?? '',
|
||||
];
|
||||
return ['value' => $element];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function errorElement(array $element, ConstraintViolationInterface $violation, array $form, FormStateInterface $form_state) {
|
||||
return $element['value'];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function calculateDependencies() {
|
||||
$dependencies = parent::calculateDependencies();
|
||||
|
||||
foreach (['role', 'role2'] as $setting) {
|
||||
if (!empty($role_id = $this->getSetting($setting))) {
|
||||
// Create a dependency on the role config entity referenced in settings.
|
||||
$dependencies['config'][] = "user.role.$role_id";
|
||||
}
|
||||
}
|
||||
|
||||
return $dependencies;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function onDependencyRemoval(array $dependencies) {
|
||||
$changed = parent::onDependencyRemoval($dependencies);
|
||||
|
||||
// Only the setting 'role' is resolved here. When the dependency related to
|
||||
// this setting is removed, is expected that the widget component will be
|
||||
// update accordingly in the display entity. The 'role2' setting is
|
||||
// deliberately left out from being updated. When the dependency
|
||||
// corresponding to this setting is removed, is expected that the widget
|
||||
// component will be disabled in the display entity.
|
||||
if (!empty($role_id = $this->getSetting('role'))) {
|
||||
if (!empty($dependencies['config']["user.role.$role_id"])) {
|
||||
$this->setSetting('role', 'anonymous');
|
||||
$changed = TRUE;
|
||||
}
|
||||
}
|
||||
|
||||
return $changed;
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,31 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\field_test\Plugin\Field\FieldWidget;
|
||||
|
||||
use Drupal\Core\Field\Attribute\FieldWidget;
|
||||
use Drupal\Core\Field\FieldItemListInterface;
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
use Drupal\Core\StringTranslation\TranslatableMarkup;
|
||||
|
||||
/**
|
||||
* Plugin implementation of the 'test_field_widget_multilingual' widget.
|
||||
*/
|
||||
#[FieldWidget(
|
||||
id: 'test_field_widget_multilingual',
|
||||
label: new TranslatableMarkup('Test widget - multilingual'),
|
||||
field_types: ['test_field'],
|
||||
)]
|
||||
class TestFieldWidgetMultilingual extends TestFieldWidget {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function form(FieldItemListInterface $items, array &$form, FormStateInterface $form_state, $get_delta = NULL) {
|
||||
$elements = parent::form($items, $form, $form_state, $get_delta);
|
||||
$elements['#multilingual'] = TRUE;
|
||||
return $elements;
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,120 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\field_test\Plugin\Field\FieldWidget;
|
||||
|
||||
use Drupal\Core\Field\Attribute\FieldWidget;
|
||||
use Drupal\Core\Field\FieldDefinitionInterface;
|
||||
use Drupal\Core\Field\FieldItemListInterface;
|
||||
use Drupal\Core\Field\WidgetBase;
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
use Drupal\Core\StringTranslation\TranslatableMarkup;
|
||||
use Symfony\Component\Validator\ConstraintViolationInterface;
|
||||
|
||||
// cspell:ignore onewidgetfield
|
||||
|
||||
/**
|
||||
* Plugin implementation of the 'test_field_widget_multiple' widget.
|
||||
*
|
||||
* The 'field_types' entry is left empty, and is populated through
|
||||
* hook_field_widget_info_alter().
|
||||
*
|
||||
* @see field_test_field_widget_info_alter()
|
||||
*/
|
||||
#[FieldWidget(
|
||||
id: 'test_field_widget_multiple',
|
||||
label: new TranslatableMarkup('Test widget - multiple'),
|
||||
multiple_values: TRUE,
|
||||
weight: 10,
|
||||
)]
|
||||
class TestFieldWidgetMultiple extends WidgetBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function defaultSettings() {
|
||||
return [
|
||||
'test_widget_setting_multiple' => 'dummy test string',
|
||||
] + parent::defaultSettings();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function settingsForm(array $form, FormStateInterface $form_state) {
|
||||
$element['test_widget_setting_multiple'] = [
|
||||
'#type' => 'textfield',
|
||||
'#title' => $this->t('Field test field widget setting'),
|
||||
'#description' => $this->t('A dummy form element to simulate field widget setting.'),
|
||||
'#default_value' => $this->getSetting('test_widget_setting_multiple'),
|
||||
'#required' => FALSE,
|
||||
];
|
||||
return $element;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function settingsSummary() {
|
||||
$summary = [];
|
||||
$summary[] = $this->t('@setting: @value', [
|
||||
'@setting' => 'test_widget_setting_multiple',
|
||||
'@value' => $this->getSetting('test_widget_setting_multiple'),
|
||||
]);
|
||||
return $summary;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function formElement(FieldItemListInterface $items, $delta, array $element, array &$form, FormStateInterface $form_state) {
|
||||
$values = [];
|
||||
foreach ($items as $item) {
|
||||
$values[] = $item->value;
|
||||
}
|
||||
$element += [
|
||||
'#type' => 'textfield',
|
||||
'#default_value' => implode(', ', $values),
|
||||
'#element_validate' => [[static::class, 'multipleValidate']],
|
||||
];
|
||||
return $element;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function errorElement(array $element, ConstraintViolationInterface $violation, array $form, FormStateInterface $form_state) {
|
||||
return $element;
|
||||
}
|
||||
|
||||
/**
|
||||
* Element validation helper.
|
||||
*/
|
||||
public static function multipleValidate($element, FormStateInterface $form_state) {
|
||||
$values = array_map('trim', explode(',', $element['#value']));
|
||||
$items = [];
|
||||
foreach ($values as $value) {
|
||||
$items[] = ['value' => $value];
|
||||
}
|
||||
$form_state->setValueForElement($element, $items);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test is the widget is applicable to the field definition.
|
||||
*
|
||||
* @param \Drupal\Core\Field\FieldDefinitionInterface $field_definition
|
||||
* The field definition that should be checked.
|
||||
*
|
||||
* @return bool
|
||||
* TRUE if the machine name of the field is not equals to
|
||||
* field_onewidgetfield, FALSE otherwise.
|
||||
*
|
||||
* @see \Drupal\Tests\field\Functional\EntityReference\EntityReferenceAdminTest::testAvailableFormatters
|
||||
*/
|
||||
public static function isApplicable(FieldDefinitionInterface $field_definition) {
|
||||
// Returns FALSE if machine name of the field equals field_onewidgetfield.
|
||||
return $field_definition->getName() != "field_onewidgetfield";
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,26 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\field_test\Plugin\Field\FieldWidget;
|
||||
|
||||
use Drupal\Core\Field\Attribute\FieldWidget;
|
||||
use Drupal\Core\StringTranslation\TranslatableMarkup;
|
||||
|
||||
/**
|
||||
* Plugin implementation of the 'test_field_widget_multiple' widget.
|
||||
*
|
||||
* The 'field_types' entry is left empty, and is populated through
|
||||
* hook_field_widget_info_alter().
|
||||
*
|
||||
* @see field_test_field_widget_info_alter()
|
||||
*/
|
||||
#[FieldWidget(
|
||||
id: 'test_field_widget_multiple_single_value',
|
||||
label: new TranslatableMarkup('Test widget - multiple - single value'),
|
||||
multiple_values: FALSE,
|
||||
weight: 10,
|
||||
)]
|
||||
class TestFieldWidgetMultipleSingleValues extends TestFieldWidgetMultiple {
|
||||
|
||||
}
|
||||
@ -0,0 +1,35 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\field_test\Plugin\Validation\Constraint;
|
||||
|
||||
use Drupal\Core\StringTranslation\TranslatableMarkup;
|
||||
use Drupal\Core\Validation\Attribute\Constraint;
|
||||
use Symfony\Component\Validator\Constraints\NotEqualTo;
|
||||
|
||||
/**
|
||||
* Checks if a value is not equal.
|
||||
*/
|
||||
#[Constraint(
|
||||
id: 'TestField',
|
||||
label: new TranslatableMarkup('Test Field', [], ['context' => 'Validation']),
|
||||
type: ['integer']
|
||||
)]
|
||||
class TestFieldConstraint extends NotEqualTo {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getRequiredOptions(): array {
|
||||
return ['value'];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function validatedBy(): string {
|
||||
return '\Symfony\Component\Validator\Constraints\NotEqualToValidator';
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,7 @@
|
||||
name: 'Boolean field Test'
|
||||
type: module
|
||||
description: 'Support module for the field and entity display tests.'
|
||||
package: Testing
|
||||
version: VERSION
|
||||
dependencies:
|
||||
- drupal:field
|
||||
@ -0,0 +1,27 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\field_test_boolean_access_denied\Hook;
|
||||
|
||||
use Drupal\Core\Access\AccessResultInterface;
|
||||
use Drupal\Core\Access\AccessResult;
|
||||
use Drupal\Core\Field\FieldItemListInterface;
|
||||
use Drupal\Core\Session\AccountInterface;
|
||||
use Drupal\Core\Field\FieldDefinitionInterface;
|
||||
use Drupal\Core\Hook\Attribute\Hook;
|
||||
|
||||
/**
|
||||
* Hook implementations for field_test_boolean_access_denied.
|
||||
*/
|
||||
class FieldTestBooleanAccessDeniedHooks {
|
||||
|
||||
/**
|
||||
* Implements hook_entity_field_access().
|
||||
*/
|
||||
#[Hook('entity_field_access')]
|
||||
public function entityFieldAccess($operation, FieldDefinitionInterface $field_definition, AccountInterface $account, ?FieldItemListInterface $items = NULL): AccessResultInterface {
|
||||
return AccessResult::forbiddenIf($field_definition->getName() === \Drupal::state()->get('field.test_boolean_field_access_field'));
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,16 @@
|
||||
langcode: en
|
||||
dependencies:
|
||||
config:
|
||||
- field.storage.entity_test.field_test_import
|
||||
id: entity_test.entity_test.field_test_import
|
||||
field_name: field_test_import
|
||||
entity_type: entity_test
|
||||
bundle: entity_test
|
||||
label: 'Test import field'
|
||||
description: ''
|
||||
required: false
|
||||
default_value: { }
|
||||
default_value_callback: ''
|
||||
settings:
|
||||
allowed_formats: { }
|
||||
field_type: text
|
||||
@ -0,0 +1,16 @@
|
||||
langcode: en
|
||||
dependencies:
|
||||
config:
|
||||
- field.storage.entity_test.field_test_import_2
|
||||
id: entity_test.entity_test.field_test_import_2
|
||||
field_name: field_test_import_2
|
||||
entity_type: entity_test
|
||||
bundle: entity_test
|
||||
label: 'Test import field 2 on entity_test bundle'
|
||||
description: ''
|
||||
required: false
|
||||
default_value: { }
|
||||
default_value_callback: ''
|
||||
settings:
|
||||
allowed_formats: { }
|
||||
field_type: text
|
||||
@ -0,0 +1,16 @@
|
||||
langcode: en
|
||||
dependencies:
|
||||
config:
|
||||
- field.storage.entity_test.field_test_import_2
|
||||
id: entity_test.test_bundle.field_test_import_2
|
||||
field_name: field_test_import_2
|
||||
entity_type: entity_test
|
||||
bundle: test_bundle
|
||||
label: 'Test import field 2 on test bundle'
|
||||
description: ''
|
||||
required: false
|
||||
default_value: { }
|
||||
default_value_callback: ''
|
||||
settings:
|
||||
allowed_formats: { }
|
||||
field_type: text
|
||||
@ -0,0 +1,20 @@
|
||||
langcode: en
|
||||
dependencies:
|
||||
module:
|
||||
- entity_test
|
||||
- text
|
||||
id: entity_test.field_test_import
|
||||
field_name: field_test_import
|
||||
entity_type: entity_test
|
||||
type: text
|
||||
settings:
|
||||
max_length: 255
|
||||
module: text
|
||||
locked: false
|
||||
cardinality: 1
|
||||
translatable: false
|
||||
indexes:
|
||||
format:
|
||||
- format
|
||||
persist_with_no_fields: false
|
||||
custom_storage: false
|
||||
@ -0,0 +1,20 @@
|
||||
langcode: en
|
||||
dependencies:
|
||||
module:
|
||||
- entity_test
|
||||
- text
|
||||
id: entity_test.field_test_import_2
|
||||
field_name: field_test_import_2
|
||||
entity_type: entity_test
|
||||
type: text
|
||||
settings:
|
||||
max_length: 255
|
||||
module: text
|
||||
locked: false
|
||||
cardinality: 1
|
||||
translatable: false
|
||||
indexes:
|
||||
format:
|
||||
- format
|
||||
persist_with_no_fields: false
|
||||
custom_storage: false
|
||||
@ -0,0 +1,5 @@
|
||||
name: 'Field API configuration tests'
|
||||
type: module
|
||||
description: 'Support module for the Field API configuration tests.'
|
||||
package: Testing
|
||||
version: VERSION
|
||||
@ -0,0 +1,17 @@
|
||||
id: entity_test.entity_test.field_test_import_sync
|
||||
uuid: ea711065-6940-47cd-813d-618f64095481
|
||||
langcode: en
|
||||
field_name: field_test_import_sync
|
||||
entity_type: entity_test
|
||||
bundle: entity_test
|
||||
label: 'Import from sync'
|
||||
description: ''
|
||||
required: '0'
|
||||
default_value: { }
|
||||
default_value_callback: ''
|
||||
settings:
|
||||
allowed_formats: { }
|
||||
field_type: text
|
||||
dependencies:
|
||||
config:
|
||||
- field.storage.entity_test.field_test_import_sync
|
||||
@ -0,0 +1,17 @@
|
||||
id: entity_test.test_bundle.field_test_import_sync_2
|
||||
uuid: f07794a2-d7cc-45b6-b40d-13cf021b5552
|
||||
langcode: en
|
||||
field_name: field_test_import_sync_2
|
||||
entity_type: entity_test
|
||||
bundle: test_bundle
|
||||
label: 'Test import field 2 on test bundle'
|
||||
description: ''
|
||||
required: '0'
|
||||
default_value: { }
|
||||
default_value_callback: ''
|
||||
settings:
|
||||
allowed_formats: { }
|
||||
field_type: text
|
||||
dependencies:
|
||||
config:
|
||||
- field.storage.entity_test.field_test_import_sync_2
|
||||
@ -0,0 +1,17 @@
|
||||
id: entity_test.test_bundle_2.field_test_import_sync_2
|
||||
uuid: 49d6dd19-5097-443d-8f00-fc79525bebce
|
||||
langcode: en
|
||||
field_name: field_test_import_sync_2
|
||||
entity_type: entity_test
|
||||
bundle: test_bundle_2
|
||||
label: 'Test import field 2 on test bundle 2'
|
||||
description: ''
|
||||
required: '0'
|
||||
default_value: { }
|
||||
default_value_callback: ''
|
||||
settings:
|
||||
allowed_formats: { }
|
||||
field_type: text
|
||||
dependencies:
|
||||
config:
|
||||
- field.storage.entity_test.field_test_import_sync_2
|
||||
@ -0,0 +1,21 @@
|
||||
id: entity_test.field_test_import_sync
|
||||
uuid: 0bf654cc-f14a-4881-b94c-76959e47466b
|
||||
langcode: en
|
||||
field_name: field_test_import_sync
|
||||
entity_type: entity_test
|
||||
type: text
|
||||
settings:
|
||||
max_length: '255'
|
||||
module: text
|
||||
locked: '0'
|
||||
cardinality: '1'
|
||||
translatable: '0'
|
||||
indexes:
|
||||
format:
|
||||
- format
|
||||
dependencies:
|
||||
module:
|
||||
- entity_test
|
||||
- text
|
||||
persist_with_no_fields: false
|
||||
custom_storage: false
|
||||
@ -0,0 +1,21 @@
|
||||
id: entity_test.field_test_import_sync_2
|
||||
uuid: 2165d9aa-9a0c-41a1-be02-2a49f3405c00
|
||||
langcode: en
|
||||
field_name: field_test_import_sync_2
|
||||
entity_type: entity_test
|
||||
type: text
|
||||
settings:
|
||||
max_length: '255'
|
||||
module: text
|
||||
locked: '0'
|
||||
cardinality: '1'
|
||||
translatable: '0'
|
||||
indexes:
|
||||
format:
|
||||
- format
|
||||
dependencies:
|
||||
module:
|
||||
- entity_test
|
||||
- text
|
||||
persist_with_no_fields: false
|
||||
custom_storage: false
|
||||
@ -0,0 +1,7 @@
|
||||
name: 'Field test views'
|
||||
type: module
|
||||
description: 'Provides default views for views field tests.'
|
||||
package: Testing
|
||||
version: VERSION
|
||||
dependencies:
|
||||
- drupal:views
|
||||
@ -0,0 +1,51 @@
|
||||
langcode: en
|
||||
status: true
|
||||
dependencies:
|
||||
module:
|
||||
- node
|
||||
- user
|
||||
config:
|
||||
- field.storage.node.field_test
|
||||
id: test_view_field_delete
|
||||
label: test_view_field_delete
|
||||
module: views
|
||||
description: ''
|
||||
tag: default
|
||||
base_table: node
|
||||
base_field: nid
|
||||
display:
|
||||
default:
|
||||
display_options:
|
||||
access:
|
||||
type: perm
|
||||
fields:
|
||||
nid:
|
||||
field: nid
|
||||
id: nid
|
||||
table: node
|
||||
plugin_id: field
|
||||
entity_type: node
|
||||
entity_field: nid
|
||||
field_test:
|
||||
id: field_test
|
||||
table: node__field_test
|
||||
field: field_test
|
||||
plugin_id: field
|
||||
entity_type: node
|
||||
entity_field: field_test
|
||||
cache:
|
||||
type: tag
|
||||
exposed_form:
|
||||
type: basic
|
||||
pager:
|
||||
type: full
|
||||
query:
|
||||
type: views_query
|
||||
style:
|
||||
type: default
|
||||
row:
|
||||
type: fields
|
||||
display_plugin: default
|
||||
display_title: Default
|
||||
id: default
|
||||
position: 0
|
||||
@ -0,0 +1,72 @@
|
||||
langcode: en
|
||||
status: true
|
||||
dependencies:
|
||||
module:
|
||||
- node
|
||||
- user
|
||||
id: test_view_fieldapi
|
||||
label: test_view_fieldapi
|
||||
module: views
|
||||
description: ''
|
||||
tag: default
|
||||
base_table: node_field_data
|
||||
base_field: nid
|
||||
display:
|
||||
default:
|
||||
display_options:
|
||||
access:
|
||||
type: perm
|
||||
fields:
|
||||
nid:
|
||||
field: nid
|
||||
id: nid
|
||||
table: node_field_data
|
||||
plugin_id: field
|
||||
entity_type: node
|
||||
entity_field: nid
|
||||
field_name_0:
|
||||
id: field_name_0
|
||||
table: node__field_name_0
|
||||
field: field_name_0
|
||||
plugin_id: field
|
||||
entity_type: node
|
||||
entity_field: field_name_0
|
||||
field_name_5:
|
||||
id: field_name_5
|
||||
table: node__field_name_5
|
||||
field: field_name_5
|
||||
plugin_id: field
|
||||
entity_type: node
|
||||
entity_field: field_name_5
|
||||
field_no_view_access:
|
||||
id: field_no_view_access
|
||||
table: node__field_no_view_access
|
||||
field: field_no_view_access
|
||||
plugin_id: field
|
||||
entity_type: node
|
||||
entity_field: field_no_view_access
|
||||
cache:
|
||||
type: tag
|
||||
exposed_form:
|
||||
type: basic
|
||||
pager:
|
||||
type: full
|
||||
query:
|
||||
type: views_query
|
||||
style:
|
||||
type: default
|
||||
row:
|
||||
type: fields
|
||||
sorts:
|
||||
nid:
|
||||
id: nid
|
||||
table: node_field_data
|
||||
field: nid
|
||||
order: ASC
|
||||
plugin_id: field
|
||||
entity_type: node
|
||||
entity_field: nid
|
||||
display_plugin: default
|
||||
display_title: Default
|
||||
id: default
|
||||
position: 0
|
||||
@ -0,0 +1,21 @@
|
||||
field.formatter.third_party.field_third_party_test:
|
||||
type: mapping
|
||||
label: 'field_third_party_test third party formatter settings'
|
||||
mapping:
|
||||
field_test_field_formatter_third_party_settings_form:
|
||||
type: string
|
||||
label: field_test_field_formatter_third_party_settings_form
|
||||
second_field_formatter_third_party_settings_form:
|
||||
type: string
|
||||
label: second_field_formatter_third_party_settings_form
|
||||
|
||||
field.widget.third_party.field_third_party_test:
|
||||
type: mapping
|
||||
label: 'field_third_party_test third party widget settings'
|
||||
mapping:
|
||||
field_test_widget_third_party_settings_form:
|
||||
type: string
|
||||
label: field_test_widget_third_party_settings_form
|
||||
second_field_widget_third_party_settings_form:
|
||||
type: string
|
||||
label: second_field_widget_third_party_settings_form
|
||||
@ -0,0 +1,8 @@
|
||||
name: 'Field Third Party Settings Test'
|
||||
type: module
|
||||
description: 'Support module for the Field API tests.'
|
||||
package: Testing
|
||||
version: VERSION
|
||||
dependencies:
|
||||
- drupal:entity_test
|
||||
- drupal:field_test
|
||||
@ -0,0 +1,89 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\field_third_party_test\Hook;
|
||||
|
||||
use Drupal\Core\Field\FormatterInterface;
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
use Drupal\Core\Field\FieldDefinitionInterface;
|
||||
use Drupal\Core\Field\WidgetInterface;
|
||||
use Drupal\Core\Hook\Attribute\Hook;
|
||||
use Drupal\Core\StringTranslation\StringTranslationTrait;
|
||||
|
||||
/**
|
||||
* Hook implementations for field_third_party_test.
|
||||
*/
|
||||
class FieldThirdPartyTestHooks {
|
||||
|
||||
use StringTranslationTrait;
|
||||
|
||||
/**
|
||||
* Implements hook_field_widget_third_party_settings_form().
|
||||
*/
|
||||
#[Hook('field_widget_third_party_settings_form')]
|
||||
public function fieldWidgetThirdPartySettingsForm(WidgetInterface $plugin, FieldDefinitionInterface $field_definition, $form_mode, $form, FormStateInterface $form_state): array {
|
||||
$element['field_test_widget_third_party_settings_form'] = [
|
||||
'#type' => 'textfield',
|
||||
'#title' => $this->t('3rd party widget settings form'),
|
||||
'#default_value' => $plugin->getThirdPartySetting('field_third_party_test', 'field_test_widget_third_party_settings_form'),
|
||||
];
|
||||
return $element;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_field_widget_third_party_settings_form().
|
||||
*/
|
||||
#[Hook('field_widget_third_party_settings_form')]
|
||||
public function fieldWidgetThirdPartySettingsFormAdditionalImplementation(WidgetInterface $plugin, FieldDefinitionInterface $field_definition, $form_mode, $form, FormStateInterface $form_state): array {
|
||||
$element['second_field_widget_third_party_settings_form'] = [
|
||||
'#type' => 'number',
|
||||
'#title' => $this->t('Second 3rd party widget settings form'),
|
||||
'#default_value' => $plugin->getThirdPartySetting('field_third_party_test', 'second_field_widget_third_party_settings_form'),
|
||||
];
|
||||
return $element;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_field_widget_settings_summary_alter().
|
||||
*/
|
||||
#[Hook('field_widget_settings_summary_alter')]
|
||||
public function fieldWidgetSettingsSummaryAlter(&$summary, $context): void {
|
||||
$summary[] = 'field_test_field_widget_settings_summary_alter';
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_field_formatter_third_party_settings_form().
|
||||
*/
|
||||
#[Hook('field_formatter_third_party_settings_form')]
|
||||
public function fieldFormatterThirdPartySettingsForm(FormatterInterface $plugin, FieldDefinitionInterface $field_definition, $view_mode, $form, FormStateInterface $form_state): array {
|
||||
$element['field_test_field_formatter_third_party_settings_form'] = [
|
||||
'#type' => 'textfield',
|
||||
'#title' => $this->t('3rd party formatter settings form'),
|
||||
'#default_value' => $plugin->getThirdPartySetting('field_third_party_test', 'field_test_field_formatter_third_party_settings_form'),
|
||||
];
|
||||
return $element;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_field_formatter_third_party_settings_form().
|
||||
*/
|
||||
#[Hook('field_formatter_third_party_settings_form')]
|
||||
public function fieldFormatterThirdPartySettingsFormAdditionalImplementation(FormatterInterface $plugin, FieldDefinitionInterface $field_definition, $view_mode, $form, FormStateInterface $form_state): array {
|
||||
$element['second_field_formatter_third_party_settings_form'] = [
|
||||
'#type' => 'number',
|
||||
'#title' => $this->t('Second 3rd party formatter settings form'),
|
||||
'#default_value' => $plugin->getThirdPartySetting('field_third_party_test', 'second_field_formatter_third_party_settings_form'),
|
||||
];
|
||||
return $element;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_field_formatter_settings_summary_alter().
|
||||
*/
|
||||
#[Hook('field_formatter_settings_summary_alter')]
|
||||
public function fieldFormatterSettingsSummaryAlter(&$summary, $context): void {
|
||||
$summary[] = 'field_test_field_formatter_settings_summary_alter';
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,19 @@
|
||||
langcode: en
|
||||
status: true
|
||||
dependencies:
|
||||
config:
|
||||
- field.storage.entity_test.timestamp
|
||||
id: entity_test.entity_test.timestamp
|
||||
field_name: timestamp
|
||||
entity_type: entity_test
|
||||
bundle: entity_test
|
||||
label: 'Time stamp'
|
||||
description: ''
|
||||
required: false
|
||||
translatable: false
|
||||
default_value:
|
||||
-
|
||||
value: 1514847537
|
||||
default_value_callback: ''
|
||||
settings: { }
|
||||
field_type: timestamp
|
||||
@ -0,0 +1,17 @@
|
||||
langcode: en
|
||||
status: true
|
||||
dependencies:
|
||||
module:
|
||||
- entity_test
|
||||
id: node.timestamp
|
||||
field_name: timestamp
|
||||
entity_type: entity_test
|
||||
type: timestamp
|
||||
settings: { }
|
||||
module: core
|
||||
locked: false
|
||||
cardinality: 1
|
||||
translatable: true
|
||||
indexes: { }
|
||||
persist_with_no_fields: false
|
||||
custom_storage: false
|
||||
@ -0,0 +1,8 @@
|
||||
name: 'Field Timestamp Test'
|
||||
type: module
|
||||
description: 'Support module for the Timestamp field item test.'
|
||||
package: Testing
|
||||
version: VERSION
|
||||
dependencies:
|
||||
- drupal:entity_test
|
||||
- drupal:field
|
||||
@ -0,0 +1,248 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\field\Functional\Boolean;
|
||||
|
||||
use Drupal\entity_test\Entity\EntityTest;
|
||||
use Drupal\field\Entity\FieldStorageConfig;
|
||||
use Drupal\field\Entity\FieldConfig;
|
||||
use Drupal\Tests\BrowserTestBase;
|
||||
|
||||
/**
|
||||
* Tests boolean field functionality.
|
||||
*
|
||||
* @group field
|
||||
*/
|
||||
class BooleanFieldTest extends BrowserTestBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = [
|
||||
'entity_test',
|
||||
'field_ui',
|
||||
'options',
|
||||
'field_test_boolean_access_denied',
|
||||
];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $defaultTheme = 'stark';
|
||||
|
||||
/**
|
||||
* A field to use in this test class.
|
||||
*
|
||||
* @var \Drupal\field\Entity\FieldStorageConfig
|
||||
*/
|
||||
protected $fieldStorage;
|
||||
|
||||
/**
|
||||
* The field used in this test class.
|
||||
*
|
||||
* @var \Drupal\field\Entity\FieldConfig
|
||||
*/
|
||||
protected $field;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp(): void {
|
||||
parent::setUp();
|
||||
|
||||
$this->drupalLogin($this->drupalCreateUser([
|
||||
'view test entity',
|
||||
'administer entity_test content',
|
||||
'administer entity_test form display',
|
||||
'administer entity_test fields',
|
||||
]));
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests boolean field.
|
||||
*/
|
||||
public function testBooleanField(): void {
|
||||
$on = $this->randomMachineName();
|
||||
$off = $this->randomMachineName();
|
||||
$label = $this->randomMachineName();
|
||||
|
||||
// Create a field with settings to validate.
|
||||
$field_name = $this->randomMachineName();
|
||||
$this->fieldStorage = FieldStorageConfig::create([
|
||||
'field_name' => $field_name,
|
||||
'entity_type' => 'entity_test',
|
||||
'type' => 'boolean',
|
||||
]);
|
||||
$this->fieldStorage->save();
|
||||
$this->field = FieldConfig::create([
|
||||
'field_name' => $field_name,
|
||||
'entity_type' => 'entity_test',
|
||||
'bundle' => 'entity_test',
|
||||
'label' => $label,
|
||||
'required' => TRUE,
|
||||
'settings' => [
|
||||
'on_label' => $on,
|
||||
'off_label' => $off,
|
||||
],
|
||||
]);
|
||||
$this->field->save();
|
||||
|
||||
/** @var \Drupal\Core\Entity\EntityDisplayRepositoryInterface $display_repository */
|
||||
$display_repository = \Drupal::service('entity_display.repository');
|
||||
|
||||
// Create a form display for the default form mode.
|
||||
$display_repository->getFormDisplay('entity_test', 'entity_test')
|
||||
->setComponent($field_name, [
|
||||
'type' => 'boolean_checkbox',
|
||||
])
|
||||
->save();
|
||||
// Create a display for the full view mode.
|
||||
$display_repository->getViewDisplay('entity_test', 'entity_test', 'full')
|
||||
->setComponent($field_name, [
|
||||
'type' => 'boolean',
|
||||
])
|
||||
->save();
|
||||
|
||||
// Display creation form.
|
||||
$this->drupalGet('entity_test/add');
|
||||
$this->assertSession()->fieldValueEquals("{$field_name}[value]", '');
|
||||
$this->assertSession()->pageTextContains($this->field->label());
|
||||
$this->assertSession()->responseNotContains($on);
|
||||
|
||||
// Submit and ensure it is accepted.
|
||||
$edit = [
|
||||
"{$field_name}[value]" => 1,
|
||||
];
|
||||
$this->submitForm($edit, 'Save');
|
||||
preg_match('|entity_test/manage/(\d+)|', $this->getUrl(), $match);
|
||||
$id = $match[1];
|
||||
$this->assertSession()->pageTextContains('entity_test ' . $id . ' has been created.');
|
||||
|
||||
// Verify that boolean value is displayed.
|
||||
$entity = EntityTest::load($id);
|
||||
$this->drupalGet($entity->toUrl());
|
||||
$this->assertSession()->pageTextContains($on);
|
||||
|
||||
// Test with "On" label option.
|
||||
$display_repository->getFormDisplay('entity_test', 'entity_test')
|
||||
->setComponent($field_name, [
|
||||
'type' => 'boolean_checkbox',
|
||||
'settings' => [
|
||||
'display_label' => FALSE,
|
||||
],
|
||||
])
|
||||
->save();
|
||||
|
||||
$this->drupalGet('entity_test/add');
|
||||
$this->assertSession()->fieldValueEquals("{$field_name}[value]", '');
|
||||
$this->assertSession()->pageTextContains($on);
|
||||
$this->assertSession()->pageTextNotContains($this->field->label());
|
||||
|
||||
// Test if we can change the on label.
|
||||
$on = $this->randomMachineName();
|
||||
$edit = [
|
||||
'settings[on_label]' => $on,
|
||||
];
|
||||
$this->drupalGet('entity_test/structure/entity_test/fields/entity_test.entity_test.' . $field_name);
|
||||
$this->submitForm($edit, 'Save settings');
|
||||
// Check if we see the updated labels in the creation form.
|
||||
$this->drupalGet('entity_test/add');
|
||||
$this->assertSession()->pageTextContains($on);
|
||||
|
||||
// Go to the form display page and check if the default settings works as
|
||||
// expected.
|
||||
$fieldEditUrl = 'entity_test/structure/entity_test/form-display';
|
||||
$this->drupalGet($fieldEditUrl);
|
||||
|
||||
// Click on the widget settings button to open the widget settings form.
|
||||
$this->submitForm([], $field_name . "_settings_edit");
|
||||
|
||||
$this->assertSession()->pageTextContains('Use field label instead of the "On" label as the label.');
|
||||
|
||||
// Enable setting.
|
||||
$edit = ['fields[' . $field_name . '][settings_edit_form][settings][display_label]' => 1];
|
||||
$this->submitForm($edit, $field_name . "_plugin_settings_update");
|
||||
$this->submitForm([], 'Save');
|
||||
|
||||
// Go again to the form display page and check if the setting
|
||||
// is stored and has the expected effect.
|
||||
$this->drupalGet($fieldEditUrl);
|
||||
$this->assertSession()->pageTextContains('Use field label: Yes');
|
||||
|
||||
$this->submitForm([], $field_name . "_settings_edit");
|
||||
$this->assertSession()->pageTextContains('Use field label instead of the "On" label as the label.');
|
||||
$this->getSession()->getPage()->hasCheckedField('fields[' . $field_name . '][settings_edit_form][settings][display_label]');
|
||||
|
||||
// Test the boolean field settings.
|
||||
$this->drupalGet('entity_test/structure/entity_test/fields/entity_test.entity_test.' . $field_name);
|
||||
$this->assertSession()->fieldValueEquals('edit-settings-on-label', $on);
|
||||
$this->assertSession()->fieldValueEquals('edit-settings-off-label', $off);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests field access.
|
||||
*/
|
||||
public function testFormAccess(): void {
|
||||
$on = 'boolean_on';
|
||||
$off = 'boolean_off';
|
||||
$label = 'boolean_label';
|
||||
$field_name = 'boolean_name';
|
||||
$this->fieldStorage = FieldStorageConfig::create([
|
||||
'field_name' => $field_name,
|
||||
'entity_type' => 'entity_test',
|
||||
'type' => 'boolean',
|
||||
]);
|
||||
$this->fieldStorage->save();
|
||||
$this->field = FieldConfig::create([
|
||||
'field_name' => $field_name,
|
||||
'entity_type' => 'entity_test',
|
||||
'bundle' => 'entity_test',
|
||||
'label' => $label,
|
||||
'settings' => [
|
||||
'on_label' => $on,
|
||||
'off_label' => $off,
|
||||
],
|
||||
]);
|
||||
$this->field->save();
|
||||
|
||||
/** @var \Drupal\Core\Entity\EntityDisplayRepositoryInterface $display_repository */
|
||||
$display_repository = \Drupal::service('entity_display.repository');
|
||||
|
||||
// Create a form display for the default form mode.
|
||||
$display_repository->getFormDisplay('entity_test', 'entity_test')
|
||||
->setComponent($field_name, [
|
||||
'type' => 'boolean_checkbox',
|
||||
])
|
||||
->save();
|
||||
|
||||
// Create a display for the full view mode.
|
||||
$display_repository->getViewDisplay('entity_test', 'entity_test', 'full')
|
||||
->setComponent($field_name, [
|
||||
'type' => 'boolean',
|
||||
])
|
||||
->save();
|
||||
|
||||
// Display creation form.
|
||||
$this->drupalGet('entity_test/add');
|
||||
$this->assertSession()->fieldExists("{$field_name}[value]");
|
||||
|
||||
// Should be posted OK.
|
||||
$this->submitForm([], 'Save');
|
||||
preg_match('|entity_test/manage/(\d+)|', $this->getUrl(), $match);
|
||||
$id = $match[1];
|
||||
$this->assertSession()->pageTextContains('entity_test ' . $id . ' has been created.');
|
||||
|
||||
// Tell the test module to disable access to the field.
|
||||
\Drupal::state()->set('field.test_boolean_field_access_field', $field_name);
|
||||
$this->drupalGet('entity_test/add');
|
||||
// Field should not be there anymore.
|
||||
$this->assertSession()->fieldNotExists("{$field_name}[value]");
|
||||
// Should still be able to post the form.
|
||||
$this->submitForm([], 'Save');
|
||||
preg_match('|entity_test/manage/(\d+)|', $this->getUrl(), $match);
|
||||
$id = $match[1];
|
||||
$this->assertSession()->pageTextContains('entity_test ' . $id . ' has been created.');
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,138 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\field\Functional\Boolean;
|
||||
|
||||
use Drupal\field\Entity\FieldConfig;
|
||||
use Drupal\field\Entity\FieldStorageConfig;
|
||||
use Drupal\Tests\BrowserTestBase;
|
||||
|
||||
/**
|
||||
* Tests the Boolean field formatter settings.
|
||||
*
|
||||
* @group field
|
||||
*/
|
||||
class BooleanFormatterSettingsTest extends BrowserTestBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = ['field', 'field_ui', 'text', 'node', 'user'];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $defaultTheme = 'stark';
|
||||
|
||||
/**
|
||||
* The name of the entity bundle that is created in the test.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $bundle;
|
||||
|
||||
/**
|
||||
* The name of the Boolean field to use for testing.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $fieldName;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp(): void {
|
||||
parent::setUp();
|
||||
|
||||
// Create a content type. Use Node because it has Field UI pages that work.
|
||||
$type_name = $this->randomMachineName(8) . '_test';
|
||||
$type = $this->drupalCreateContentType(['name' => $type_name, 'type' => $type_name]);
|
||||
$this->bundle = $type->id();
|
||||
|
||||
$admin_user = $this->drupalCreateUser([
|
||||
'access content',
|
||||
'administer content types',
|
||||
'administer node fields',
|
||||
'administer node display',
|
||||
'bypass node access',
|
||||
'administer nodes',
|
||||
]);
|
||||
$this->drupalLogin($admin_user);
|
||||
|
||||
$this->fieldName = $this->randomMachineName(8);
|
||||
|
||||
$field_storage = FieldStorageConfig::create([
|
||||
'field_name' => $this->fieldName,
|
||||
'entity_type' => 'node',
|
||||
'type' => 'boolean',
|
||||
]);
|
||||
$field_storage->save();
|
||||
|
||||
$instance = FieldConfig::create([
|
||||
'field_storage' => $field_storage,
|
||||
'bundle' => $this->bundle,
|
||||
'label' => $this->randomMachineName(),
|
||||
]);
|
||||
$instance->save();
|
||||
|
||||
\Drupal::service('entity_display.repository')
|
||||
->getViewDisplay('node', $this->bundle)
|
||||
->setComponent($this->fieldName, [
|
||||
'type' => 'boolean',
|
||||
'settings' => [],
|
||||
])
|
||||
->save();
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the formatter settings page for the Boolean formatter.
|
||||
*/
|
||||
public function testBooleanFormatterSettings(): void {
|
||||
// List the options we expect to see on the settings form. Omit the one
|
||||
// with the Unicode check/x characters, which does not appear to work
|
||||
// well in BrowserTestBase.
|
||||
$options = [
|
||||
'Yes / No',
|
||||
'True / False',
|
||||
'On / Off',
|
||||
'Enabled / Disabled',
|
||||
'1 / 0',
|
||||
'Custom',
|
||||
];
|
||||
|
||||
// For several different values of the field settings, test that the
|
||||
// options, including default, are shown correctly.
|
||||
$settings = [
|
||||
['Yes', 'No'],
|
||||
['On', 'Off'],
|
||||
['TRUE', 'FALSE'],
|
||||
];
|
||||
|
||||
$assert_session = $this->assertSession();
|
||||
foreach ($settings as $values) {
|
||||
// Set up the field settings.
|
||||
$this->drupalGet('admin/structure/types/manage/' . $this->bundle . '/fields/node.' . $this->bundle . '.' . $this->fieldName);
|
||||
$this->submitForm([
|
||||
'settings[on_label]' => $values[0],
|
||||
'settings[off_label]' => $values[1],
|
||||
], 'Save settings');
|
||||
|
||||
// Open the Manage Display page and trigger the field settings form.
|
||||
$this->drupalGet('admin/structure/types/manage/' . $this->bundle . '/display');
|
||||
$this->submitForm([], $this->fieldName . '_settings_edit');
|
||||
|
||||
// Test that the settings options are present in the correct format.
|
||||
foreach ($options as $string) {
|
||||
$assert_session->pageTextContains($string);
|
||||
}
|
||||
$assert_session->pageTextContains("Field settings ({$values[0]} / {$values[1]})");
|
||||
|
||||
// Test that the settings summary are present in the correct format.
|
||||
$this->drupalGet('admin/structure/types/manage/' . $this->bundle . '/display');
|
||||
$this->assertSession()->elementExists('xpath', "//div[contains(@class, 'field-plugin-summary')]");
|
||||
$this->assertSession()->elementTextEquals('xpath', "//div[contains(@class, 'field-plugin-summary')]", "Display: {$values[0]} / {$values[1]}");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,126 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\field\Functional\Email;
|
||||
|
||||
use Drupal\entity_test\Entity\EntityTest;
|
||||
use Drupal\field\Entity\FieldConfig;
|
||||
use Drupal\field\Entity\FieldStorageConfig;
|
||||
use Drupal\Tests\BrowserTestBase;
|
||||
|
||||
/**
|
||||
* Tests email field functionality.
|
||||
*
|
||||
* @group field
|
||||
*/
|
||||
class EmailFieldTest extends BrowserTestBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = ['node', 'entity_test', 'field_ui'];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $defaultTheme = 'stark';
|
||||
|
||||
/**
|
||||
* A field storage to use in this test class.
|
||||
*
|
||||
* @var \Drupal\field\Entity\FieldStorageConfig
|
||||
*/
|
||||
protected $fieldStorage;
|
||||
|
||||
/**
|
||||
* The field used in this test class.
|
||||
*
|
||||
* @var \Drupal\field\Entity\FieldConfig
|
||||
*/
|
||||
protected $field;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp(): void {
|
||||
parent::setUp();
|
||||
|
||||
$this->drupalLogin($this->drupalCreateUser([
|
||||
'view test entity',
|
||||
'administer entity_test content',
|
||||
'administer content types',
|
||||
]));
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests email field.
|
||||
*/
|
||||
public function testEmailField(): void {
|
||||
// Create a field with settings to validate.
|
||||
$field_name = $this->randomMachineName();
|
||||
$this->fieldStorage = FieldStorageConfig::create([
|
||||
'field_name' => $field_name,
|
||||
'entity_type' => 'entity_test',
|
||||
'type' => 'email',
|
||||
]);
|
||||
$this->fieldStorage->save();
|
||||
$this->field = FieldConfig::create([
|
||||
'field_storage' => $this->fieldStorage,
|
||||
'bundle' => 'entity_test',
|
||||
]);
|
||||
$this->field->save();
|
||||
|
||||
/** @var \Drupal\Core\Entity\EntityDisplayRepositoryInterface $display_repository */
|
||||
$display_repository = \Drupal::service('entity_display.repository');
|
||||
|
||||
// Create a form display for the default form mode.
|
||||
$display_repository->getFormDisplay('entity_test', 'entity_test')
|
||||
->setComponent($field_name, [
|
||||
'type' => 'email_default',
|
||||
'settings' => [
|
||||
'placeholder' => 'example@example.com',
|
||||
],
|
||||
])
|
||||
->save();
|
||||
// Create a display for the full view mode.
|
||||
$display_repository->getViewDisplay('entity_test', 'entity_test', 'full')
|
||||
->setComponent($field_name, [
|
||||
'type' => 'email_mailto',
|
||||
])
|
||||
->save();
|
||||
|
||||
// Display creation form.
|
||||
$this->drupalGet('entity_test/add');
|
||||
$this->assertSession()->fieldValueEquals("{$field_name}[0][value]", '');
|
||||
$this->assertSession()->responseContains('placeholder="example@example.com"');
|
||||
|
||||
// Submit a valid email address and ensure it is accepted.
|
||||
$value = 'test@example.com';
|
||||
$edit = [
|
||||
"{$field_name}[0][value]" => $value,
|
||||
];
|
||||
$this->submitForm($edit, 'Save');
|
||||
preg_match('|entity_test/manage/(\d+)|', $this->getUrl(), $match);
|
||||
$id = $match[1];
|
||||
$this->assertSession()->pageTextContains('entity_test ' . $id . ' has been created.');
|
||||
$this->assertSession()->responseContains($value);
|
||||
|
||||
// Verify that a mailto link is displayed.
|
||||
$entity = EntityTest::load($id);
|
||||
$display = $display_repository->getViewDisplay($entity->getEntityTypeId(), $entity->bundle(), 'full');
|
||||
$content = $display->build($entity);
|
||||
$rendered_content = (string) \Drupal::service('renderer')->renderRoot($content);
|
||||
$this->assertStringContainsString('href="mailto:test@example.com"', $rendered_content);
|
||||
|
||||
// Test Email validation message.
|
||||
$this->drupalGet('entity_test/add');
|
||||
$value = 'abc.@in';
|
||||
$edit = [
|
||||
"{$field_name}[0][value]" => $value,
|
||||
];
|
||||
$this->submitForm($edit, 'Save');
|
||||
$this->assertSession()->statusMessageContains("The email address {$value} is not valid. Use the format user@example.com.", 'error');
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,431 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\field\Functional\EntityReference;
|
||||
|
||||
use Behat\Mink\Element\NodeElement;
|
||||
use Drupal\Core\Field\FieldStorageDefinitionInterface;
|
||||
use Drupal\Core\Messenger\MessengerInterface;
|
||||
use Drupal\field\Entity\FieldConfig;
|
||||
use Drupal\node\Entity\Node;
|
||||
use Drupal\taxonomy\Entity\Vocabulary;
|
||||
use Drupal\Tests\BrowserTestBase;
|
||||
use Drupal\Tests\field_ui\Traits\FieldUiTestTrait;
|
||||
|
||||
/**
|
||||
* Tests for the administrative UI.
|
||||
*
|
||||
* @group entity_reference
|
||||
*/
|
||||
class EntityReferenceAdminTest extends BrowserTestBase {
|
||||
|
||||
use FieldUiTestTrait;
|
||||
|
||||
/**
|
||||
* Modules to install.
|
||||
*
|
||||
* Enable path module to ensure that the selection handler does not fail for
|
||||
* entities with a path field.
|
||||
* Enable views_ui module to see the no_view_help text.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected static $modules = [
|
||||
'node',
|
||||
'field_ui',
|
||||
'path',
|
||||
'taxonomy',
|
||||
'block',
|
||||
'views_ui',
|
||||
];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $defaultTheme = 'stark';
|
||||
|
||||
/**
|
||||
* The name of the content type created for testing purposes.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $type;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp(): void {
|
||||
parent::setUp();
|
||||
$this->drupalPlaceBlock('system_breadcrumb_block');
|
||||
|
||||
// Create a content type, with underscores.
|
||||
$type_name = $this->randomMachineName(8) . '_test';
|
||||
$type = $this->drupalCreateContentType(['name' => $type_name, 'type' => $type_name]);
|
||||
$this->type = $type->id();
|
||||
|
||||
// Create test user.
|
||||
$admin_user = $this->drupalCreateUser([
|
||||
'access content',
|
||||
'administer node fields',
|
||||
'administer node display',
|
||||
'administer views',
|
||||
'create ' . $type_name . ' content',
|
||||
'edit own ' . $type_name . ' content',
|
||||
]);
|
||||
$this->drupalLogin($admin_user);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the Entity Reference Admin UI.
|
||||
*/
|
||||
public function testFieldAdminHandler(): void {
|
||||
$bundle_path = 'admin/structure/types/manage/' . $this->type;
|
||||
// Create a new view and display it as an entity reference.
|
||||
$edit = [
|
||||
'id' => 'node_test_view',
|
||||
'label' => 'Node Test View',
|
||||
'show[wizard_key]' => 'node',
|
||||
'show[sort]' => 'none',
|
||||
'page[create]' => 1,
|
||||
'page[title]' => 'Test Node View',
|
||||
'page[path]' => 'test/node/view',
|
||||
'page[style][style_plugin]' => 'default',
|
||||
'page[style][row_plugin]' => 'fields',
|
||||
];
|
||||
$this->drupalGet('admin/structure/views/add');
|
||||
$this->submitForm($edit, 'Save and edit');
|
||||
$this->submitForm([], 'Duplicate as Entity Reference');
|
||||
$this->clickLink('Settings');
|
||||
$edit = [
|
||||
'style_options[search_fields][title]' => 'title',
|
||||
];
|
||||
$this->submitForm($edit, 'Apply');
|
||||
|
||||
// Set sort to NID ascending.
|
||||
$edit = [
|
||||
'name[node_field_data.nid]' => 1,
|
||||
];
|
||||
$this->drupalGet('admin/structure/views/nojs/add-handler/node_test_view/entity_reference_1/sort');
|
||||
$this->submitForm($edit, 'Add and configure sort criteria');
|
||||
$this->submitForm([], 'Apply');
|
||||
|
||||
$this->drupalGet('admin/structure/views/view/node_test_view/edit/entity_reference_1');
|
||||
$this->submitForm([], 'Save');
|
||||
$this->clickLink('Settings');
|
||||
|
||||
// Create a test entity reference field.
|
||||
$field_name = 'test_entity_ref_field';
|
||||
$this->fieldUIAddNewField($bundle_path, $field_name, 'Test Entity Reference Field', 'field_ui:entity_reference:node', [], [], FALSE);
|
||||
|
||||
// Set to unlimited.
|
||||
$edit = [
|
||||
'field_storage[subform][cardinality]' => FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED,
|
||||
];
|
||||
$this->submitForm($edit, 'Update settings');
|
||||
|
||||
// Add the view to the test field.
|
||||
$edit = [
|
||||
'settings[handler]' => 'views',
|
||||
];
|
||||
$this->submitForm($edit, 'Change handler');
|
||||
$edit = [
|
||||
'required' => FALSE,
|
||||
'settings[handler_settings][view][view_and_display]' => 'node_test_view:entity_reference_1',
|
||||
];
|
||||
$this->submitForm($edit, 'Save');
|
||||
$this->assertSession()->statusMessageContains("Saved Test Entity Reference Field configuration.", MessengerInterface::TYPE_STATUS);
|
||||
$this->assertFieldExistsOnOverview('Test Entity Reference Field');
|
||||
|
||||
// Create nodes.
|
||||
$node1 = Node::create([
|
||||
'type' => $this->type,
|
||||
'title' => 'Foo Node',
|
||||
]);
|
||||
$node1->save();
|
||||
$node2 = Node::create([
|
||||
'type' => $this->type,
|
||||
'title' => 'Foo Node',
|
||||
]);
|
||||
$node2->save();
|
||||
|
||||
// Try to add a new node and fill the entity reference field.
|
||||
$this->drupalGet('node/add/' . $this->type);
|
||||
$field = $this->assertSession()->fieldExists('field_test_entity_ref_field[0][target_id]');
|
||||
$this->assertStringContainsString("/entity_reference_autocomplete/node/views/", $field->getAttribute('data-autocomplete-path'));
|
||||
$target_url = $this->getAbsoluteUrl($field->getAttribute('data-autocomplete-path'));
|
||||
$this->drupalGet($target_url, ['query' => ['q' => 'Foo']]);
|
||||
$this->assertSession()->pageTextContains($node1->getTitle() . ' (' . $node1->id() . ')');
|
||||
$this->assertSession()->pageTextContains($node2->getTitle() . ' (' . $node2->id() . ')');
|
||||
|
||||
// Try to add a new node, fill the entity reference field and submit the
|
||||
// form.
|
||||
$this->drupalGet('node/add/' . $this->type);
|
||||
$this->submitForm([], 'Add another item');
|
||||
$edit = [
|
||||
'title[0][value]' => 'Example',
|
||||
'field_test_entity_ref_field[0][target_id]' => 'Foo Node (' . $node1->id() . ')',
|
||||
'field_test_entity_ref_field[1][target_id]' => 'Foo Node (' . $node2->id() . ')',
|
||||
];
|
||||
$this->submitForm($edit, 'Save');
|
||||
$this->assertSession()->statusCodeEquals(200);
|
||||
|
||||
$edit = [
|
||||
'title[0][value]' => 'Example',
|
||||
'field_test_entity_ref_field[0][target_id]' => 'Test',
|
||||
];
|
||||
$this->drupalGet('node/add/' . $this->type);
|
||||
$this->submitForm($edit, 'Save');
|
||||
|
||||
// Assert that entity reference autocomplete field is validated.
|
||||
$this->assertSession()->pageTextContains('There are no content items matching "Test"');
|
||||
|
||||
$edit = [
|
||||
'title[0][value]' => 'Test',
|
||||
'field_test_entity_ref_field[0][target_id]' => $node1->getTitle(),
|
||||
];
|
||||
$this->drupalGet('node/add/' . $this->type);
|
||||
$this->submitForm($edit, 'Save');
|
||||
|
||||
// Assert the results multiple times to avoid sorting problem of nodes with
|
||||
// the same title.
|
||||
$this->assertSession()->pageTextContains('Multiple content items match this reference;');
|
||||
$this->assertSession()->pageTextContains($node1->getTitle() . ' (' . $node1->id() . ')');
|
||||
$this->assertSession()->pageTextContains($node2->getTitle() . ' (' . $node2->id() . ')');
|
||||
$this->assertSession()->pageTextContains('Specify the one you want by appending the id in parentheses, like "' . $node2->getTitle() . ' (' . $node2->id() . ')".');
|
||||
|
||||
$edit = [
|
||||
'title[0][value]' => 'Test',
|
||||
'field_test_entity_ref_field[0][target_id]' => $node1->getTitle() . ' (' . $node1->id() . ')',
|
||||
];
|
||||
$this->drupalGet('node/add/' . $this->type);
|
||||
$this->submitForm($edit, 'Save');
|
||||
$this->assertSession()->linkExists($node1->getTitle());
|
||||
|
||||
// Tests adding default values to autocomplete widgets.
|
||||
Vocabulary::create(['vid' => 'tags', 'name' => 'tags'])->save();
|
||||
$taxonomy_term_field_name = $this->createEntityReferenceField('taxonomy_term', ['tags']);
|
||||
$field_path = 'node.' . $this->type . '.field_' . $taxonomy_term_field_name;
|
||||
$this->drupalGet($bundle_path . '/fields/' . $field_path);
|
||||
$edit = [
|
||||
'field_storage[subform][cardinality]' => -1,
|
||||
];
|
||||
$this->submitForm($edit, 'Update settings');
|
||||
|
||||
// Assert that the target bundle handler setting is initially set.
|
||||
$this->assertSession()->checkboxChecked('settings[handler_settings][target_bundles][tags]');
|
||||
// Change the handler to 'views'.
|
||||
$this->submitForm([
|
||||
'settings[handler]' => 'views',
|
||||
], 'Change handler');
|
||||
$this->assertSession()->fieldValueEquals('settings[handler]', 'views');
|
||||
// Change handler back to 'default'.
|
||||
$this->submitForm([
|
||||
'settings[handler]' => 'default:taxonomy_term',
|
||||
], 'Change handler');
|
||||
// Assert that changing the handler resets the handler settings.
|
||||
$this->assertSession()->checkboxNotChecked('settings[handler_settings][target_bundles][tags]');
|
||||
|
||||
$term_name = $this->randomString();
|
||||
$result = \Drupal::entityQuery('taxonomy_term')
|
||||
->condition('name', $term_name)
|
||||
->condition('vid', 'tags')
|
||||
->accessCheck(FALSE)
|
||||
->execute();
|
||||
$this->assertCount(0, $result, "No taxonomy terms exist with the name '$term_name'.");
|
||||
$edit = [
|
||||
'settings[handler_settings][target_bundles][tags]' => TRUE,
|
||||
// This must be set before new entities will be auto-created.
|
||||
'settings[handler_settings][auto_create]' => 1,
|
||||
];
|
||||
$this->submitForm($edit, 'Save settings');
|
||||
$this->assertFieldExistsOnOverview($taxonomy_term_field_name);
|
||||
$this->drupalGet($bundle_path . '/fields/' . $field_path);
|
||||
$edit = [
|
||||
'set_default_value' => '1',
|
||||
// A term that doesn't yet exist.
|
||||
'default_value_input[field_' . $taxonomy_term_field_name . '][0][target_id]' => $term_name,
|
||||
];
|
||||
$this->submitForm($edit, 'Save settings');
|
||||
$this->assertFieldExistsOnOverview($taxonomy_term_field_name);
|
||||
// The term should now exist.
|
||||
$result = \Drupal::entityQuery('taxonomy_term')
|
||||
->condition('name', $term_name)
|
||||
->condition('vid', 'tags')
|
||||
->accessCheck(FALSE)
|
||||
->execute();
|
||||
$this->assertCount(1, $result, 'Taxonomy term was auto created when set as field default.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the formatters for the Entity References.
|
||||
*/
|
||||
public function testAvailableFormatters(): void {
|
||||
// Create a new vocabulary.
|
||||
Vocabulary::create(['vid' => 'tags', 'name' => 'tags'])->save();
|
||||
|
||||
// Create entity reference field with taxonomy term as a target.
|
||||
$taxonomy_term_field_name = $this->createEntityReferenceField('taxonomy_term', ['tags']);
|
||||
|
||||
// Create entity reference field with user as a target.
|
||||
$user_field_name = $this->createEntityReferenceField('user');
|
||||
|
||||
// Create entity reference field with node as a target.
|
||||
$node_field_name = $this->createEntityReferenceField('node', [$this->type]);
|
||||
|
||||
// Create entity reference field with date format as a target.
|
||||
$date_format_field_name = $this->createEntityReferenceField('date_format');
|
||||
|
||||
// Display all newly created Entity Reference configuration.
|
||||
$this->drupalGet('admin/structure/types/manage/' . $this->type . '/display');
|
||||
|
||||
// Check for Taxonomy Term select box values.
|
||||
// Test if Taxonomy Term Entity Reference Field has the correct formatters.
|
||||
$this->assertFieldSelectOptions('fields[field_' . $taxonomy_term_field_name . '][type]', [
|
||||
'entity_reference_label',
|
||||
'entity_reference_entity_id',
|
||||
'entity_reference_rss_category',
|
||||
'entity_reference_entity_view',
|
||||
]);
|
||||
|
||||
// Test if User Reference Field has the correct formatters.
|
||||
// Author should be available for this field.
|
||||
// RSS Category should not be available for this field.
|
||||
$this->assertFieldSelectOptions('fields[field_' . $user_field_name . '][type]', [
|
||||
'author',
|
||||
'entity_reference_entity_id',
|
||||
'entity_reference_entity_view',
|
||||
'entity_reference_label',
|
||||
]);
|
||||
|
||||
// Test if Node Entity Reference Field has the correct formatters.
|
||||
// RSS Category should not be available for this field.
|
||||
$this->assertFieldSelectOptions('fields[field_' . $node_field_name . '][type]', [
|
||||
'entity_reference_label',
|
||||
'entity_reference_entity_id',
|
||||
'entity_reference_entity_view',
|
||||
]);
|
||||
|
||||
// Test if Date Format Reference Field has the correct formatters.
|
||||
// RSS Category & Entity View should not be available for this field.
|
||||
// This could be any field without a ViewBuilder.
|
||||
$this->assertFieldSelectOptions('fields[field_' . $date_format_field_name . '][type]', [
|
||||
'entity_reference_label',
|
||||
'entity_reference_entity_id',
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests field settings for an entity reference field.
|
||||
*
|
||||
* The tested entity reference field has multiple target bundles and is set
|
||||
* to auto-create the target entity.
|
||||
*/
|
||||
public function testMultipleTargetBundles(): void {
|
||||
/** @var \Drupal\taxonomy\Entity\Vocabulary[] $vocabularies */
|
||||
$vocabularies = [];
|
||||
for ($i = 0; $i < 2; $i++) {
|
||||
$vid = $this->randomMachineName();
|
||||
$vocabularies[$i] = Vocabulary::create([
|
||||
'name' => $this->randomString(),
|
||||
'vid' => $vid,
|
||||
]);
|
||||
$vocabularies[$i]->save();
|
||||
}
|
||||
|
||||
// Create a new field pointing to the first vocabulary.
|
||||
$field_name = $this->createEntityReferenceField('taxonomy_term', [$vocabularies[0]->id()]);
|
||||
$field_name = "field_$field_name";
|
||||
$field_id = 'node.' . $this->type . '.' . $field_name;
|
||||
$path = 'admin/structure/types/manage/' . $this->type . '/fields/' . $field_id;
|
||||
|
||||
$this->drupalGet($path);
|
||||
|
||||
// Expect that there's no 'auto_create_bundle' selected.
|
||||
$this->assertSession()->fieldNotExists('settings[handler_settings][auto_create_bundle]');
|
||||
|
||||
$edit = [
|
||||
'settings[handler_settings][target_bundles][' . $vocabularies[1]->id() . ']' => TRUE,
|
||||
];
|
||||
// Enable the second vocabulary as a target bundle.
|
||||
$this->drupalGet($path);
|
||||
$this->submitForm($edit, 'Save settings');
|
||||
$this->drupalGet($path);
|
||||
// Expect a select element with the two vocabularies as options.
|
||||
$this->assertSession()->optionExists('settings[handler_settings][auto_create_bundle]', $vocabularies[0]->id());
|
||||
$this->assertSession()->optionExists('settings[handler_settings][auto_create_bundle]', $vocabularies[1]->id());
|
||||
|
||||
$edit = [
|
||||
'settings[handler_settings][auto_create]' => TRUE,
|
||||
'settings[handler_settings][auto_create_bundle]' => $vocabularies[1]->id(),
|
||||
];
|
||||
$this->submitForm($edit, 'Save settings');
|
||||
|
||||
/** @var \Drupal\field\Entity\FieldConfig $field_config */
|
||||
$field_config = FieldConfig::load($field_id);
|
||||
// Expect that the target bundle has been saved in the backend.
|
||||
$this->assertEquals($vocabularies[1]->id(), $field_config->getSetting('handler_settings')['auto_create_bundle']);
|
||||
|
||||
// Delete the other bundle. Field config should not be affected.
|
||||
$vocabularies[0]->delete();
|
||||
$field_config = FieldConfig::load($field_id);
|
||||
$this->assertTrue($field_config->getSetting('handler_settings')['auto_create']);
|
||||
$this->assertSame($vocabularies[1]->id(), $field_config->getSetting('handler_settings')['auto_create_bundle']);
|
||||
|
||||
// Delete the bundle set for entity auto-creation. Auto-created settings
|
||||
// should be reset (no auto-creation).
|
||||
$vocabularies[1]->delete();
|
||||
$field_config = FieldConfig::load($field_id);
|
||||
$this->assertFalse($field_config->getSetting('handler_settings')['auto_create']);
|
||||
$this->assertFalse(isset($field_config->getSetting('handler_settings')['auto_create_bundle']));
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new Entity Reference fields with a given target type.
|
||||
*
|
||||
* @param string $target_type
|
||||
* The name of the target type.
|
||||
* @param string[] $bundles
|
||||
* A list of bundle IDs. Defaults to [].
|
||||
*
|
||||
* @return string
|
||||
* Returns the generated field name
|
||||
*/
|
||||
protected function createEntityReferenceField($target_type, $bundles = []) {
|
||||
// Generates a bundle path for the newly created content type.
|
||||
$bundle_path = 'admin/structure/types/manage/' . $this->type;
|
||||
|
||||
// Generate a random field name, must be only lowercase characters.
|
||||
$field_name = $this->randomMachineName();
|
||||
|
||||
$storage_edit = $field_edit = [];
|
||||
$storage_edit['settings[target_type]'] = $target_type;
|
||||
foreach ($bundles as $bundle) {
|
||||
$field_edit['settings[handler_settings][target_bundles][' . $bundle . ']'] = TRUE;
|
||||
}
|
||||
|
||||
$this->fieldUIAddNewField($bundle_path, $field_name, $field_name, 'entity_reference', $storage_edit, $field_edit);
|
||||
|
||||
// Returns the generated field name.
|
||||
return $field_name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a select element contains the specified options.
|
||||
*
|
||||
* @param string $name
|
||||
* The field name.
|
||||
* @param array $expected_options
|
||||
* An array of expected options.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
protected function assertFieldSelectOptions(string $name, array $expected_options): void {
|
||||
$options = $this->assertSession()->selectExists($name)->findAll('xpath', 'option');
|
||||
array_walk($options, function (NodeElement &$option) {
|
||||
$option = $option->getValue();
|
||||
});
|
||||
$this->assertEqualsCanonicalizing($expected_options, $options);
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,307 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\field\Functional\EntityReference;
|
||||
|
||||
use Drupal\Core\Field\FieldStorageDefinitionInterface;
|
||||
use Drupal\field\Entity\FieldConfig;
|
||||
use Drupal\Tests\BrowserTestBase;
|
||||
use Drupal\Tests\field\Traits\EntityReferenceFieldCreationTrait;
|
||||
use Drupal\taxonomy\Entity\Vocabulary;
|
||||
use Drupal\node\Entity\Node;
|
||||
use Drupal\field\Entity\FieldStorageConfig;
|
||||
|
||||
/**
|
||||
* Tests creating new entity (e.g. taxonomy-term) from an autocomplete widget.
|
||||
*
|
||||
* @group entity_reference
|
||||
*/
|
||||
class EntityReferenceAutoCreateTest extends BrowserTestBase {
|
||||
|
||||
use EntityReferenceFieldCreationTrait;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = ['node', 'taxonomy', 'entity_test'];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $defaultTheme = 'stark';
|
||||
|
||||
/**
|
||||
* The name of a content type that will reference $referencedType.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $referencingType;
|
||||
|
||||
/**
|
||||
* The name of a content type that will be referenced by $referencingType.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $referencedType;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp(): void {
|
||||
parent::setUp();
|
||||
|
||||
// Create "referencing" and "referenced" node types.
|
||||
$referencing = $this->drupalCreateContentType();
|
||||
$this->referencingType = $referencing->id();
|
||||
|
||||
$referenced = $this->drupalCreateContentType();
|
||||
$this->referencedType = $referenced->id();
|
||||
|
||||
FieldStorageConfig::create([
|
||||
'field_name' => 'test_field',
|
||||
'entity_type' => 'node',
|
||||
'translatable' => FALSE,
|
||||
'entity_types' => [],
|
||||
'settings' => [
|
||||
'target_type' => 'node',
|
||||
],
|
||||
'type' => 'entity_reference',
|
||||
'cardinality' => FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED,
|
||||
])->save();
|
||||
|
||||
FieldConfig::create([
|
||||
'label' => 'Entity reference field',
|
||||
'field_name' => 'test_field',
|
||||
'entity_type' => 'node',
|
||||
'bundle' => $referencing->id(),
|
||||
'settings' => [
|
||||
'handler' => 'default',
|
||||
'handler_settings' => [
|
||||
// Reference a single vocabulary.
|
||||
'target_bundles' => [
|
||||
$referenced->id(),
|
||||
],
|
||||
// Enable auto-create.
|
||||
'auto_create' => TRUE,
|
||||
],
|
||||
],
|
||||
])->save();
|
||||
|
||||
/** @var \Drupal\Core\Entity\EntityDisplayRepositoryInterface $display_repository */
|
||||
$display_repository = \Drupal::service('entity_display.repository');
|
||||
|
||||
$display_repository->getViewDisplay('node', $referencing->id())
|
||||
->setComponent('test_field')
|
||||
->save();
|
||||
$display_repository->getFormDisplay('node', $referencing->id(), 'default')
|
||||
->setComponent('test_field', [
|
||||
'type' => 'entity_reference_autocomplete',
|
||||
])
|
||||
->save();
|
||||
|
||||
$account = $this->drupalCreateUser([
|
||||
'access content',
|
||||
"create $this->referencingType content",
|
||||
]);
|
||||
$this->drupalLogin($account);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the autocomplete input element and entity auto-creation.
|
||||
*/
|
||||
public function testAutoCreate(): void {
|
||||
$this->drupalGet('node/add/' . $this->referencingType);
|
||||
$target = $this->assertSession()->fieldExists("edit-test-field-0-target-id");
|
||||
$this->assertTrue($target->hasClass("form-autocomplete"));
|
||||
|
||||
$new_title = $this->randomMachineName();
|
||||
|
||||
// Assert referenced node does not exist.
|
||||
$base_query = \Drupal::entityQuery('node')->accessCheck(FALSE);
|
||||
$base_query
|
||||
->condition('type', $this->referencedType)
|
||||
->condition('title', $new_title);
|
||||
|
||||
$query = clone $base_query;
|
||||
$result = $query->execute();
|
||||
$this->assertEmpty($result, 'Referenced node does not exist yet.');
|
||||
|
||||
$edit = [
|
||||
'title[0][value]' => $this->randomMachineName(),
|
||||
'test_field[0][target_id]' => $new_title,
|
||||
];
|
||||
$this->drupalGet("node/add/{$this->referencingType}");
|
||||
$this->submitForm($edit, 'Save');
|
||||
|
||||
// Assert referenced node was created.
|
||||
$query = clone $base_query;
|
||||
$result = $query->execute();
|
||||
$this->assertNotEmpty($result, 'Referenced node was created.');
|
||||
$referenced_nid = key($result);
|
||||
$referenced_node = Node::load($referenced_nid);
|
||||
|
||||
// Assert the referenced node is associated with referencing node.
|
||||
$result = \Drupal::entityQuery('node')
|
||||
->accessCheck(FALSE)
|
||||
->condition('type', $this->referencingType)
|
||||
->execute();
|
||||
|
||||
$referencing_nid = key($result);
|
||||
$referencing_node = Node::load($referencing_nid);
|
||||
$this->assertEquals($referenced_nid, $referencing_node->test_field->target_id, 'Newly created node is referenced from the referencing node.');
|
||||
|
||||
// Now try to view the node and check that the referenced node is shown.
|
||||
$this->drupalGet('node/' . $referencing_node->id());
|
||||
$this->assertSession()->pageTextContains($referencing_node->label());
|
||||
$this->assertSession()->pageTextContains($referenced_node->label());
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests multiple target bundles.
|
||||
*
|
||||
* Tests if an entity reference field having multiple target bundles is
|
||||
* storing the auto-created entity in the right destination.
|
||||
*/
|
||||
public function testMultipleTargetBundles(): void {
|
||||
/** @var \Drupal\taxonomy\Entity\Vocabulary[] $vocabularies */
|
||||
$vocabularies = [];
|
||||
for ($i = 0; $i < 2; $i++) {
|
||||
$vid = $this->randomMachineName();
|
||||
$vocabularies[$i] = Vocabulary::create([
|
||||
'name' => $this->randomMachineName(),
|
||||
'vid' => $vid,
|
||||
]);
|
||||
$vocabularies[$i]->save();
|
||||
}
|
||||
|
||||
// Create a taxonomy term entity reference field that saves the auto-created
|
||||
// taxonomy terms in the second vocabulary from the two that were configured
|
||||
// as targets.
|
||||
$field_name = $this->randomMachineName();
|
||||
$handler_settings = [
|
||||
'target_bundles' => [
|
||||
$vocabularies[0]->id() => $vocabularies[0]->id(),
|
||||
$vocabularies[1]->id() => $vocabularies[1]->id(),
|
||||
],
|
||||
'auto_create' => TRUE,
|
||||
'auto_create_bundle' => $vocabularies[1]->id(),
|
||||
];
|
||||
$this->createEntityReferenceField('node', $this->referencingType, $field_name, $this->randomString(), 'taxonomy_term', 'default', $handler_settings);
|
||||
\Drupal::service('entity_display.repository')
|
||||
->getFormDisplay('node', $this->referencingType)
|
||||
->setComponent($field_name, ['type' => 'entity_reference_autocomplete'])
|
||||
->save();
|
||||
|
||||
$term_name = $this->randomString();
|
||||
$edit = [
|
||||
$field_name . '[0][target_id]' => $term_name,
|
||||
'title[0][value]' => $this->randomString(),
|
||||
];
|
||||
|
||||
$this->drupalGet('node/add/' . $this->referencingType);
|
||||
$this->submitForm($edit, 'Save');
|
||||
|
||||
$term_storage = \Drupal::entityTypeManager()->getStorage('taxonomy_term');
|
||||
/** @var \Drupal\taxonomy\Entity\Term $term */
|
||||
$term = $term_storage->loadByProperties(['name' => $term_name]);
|
||||
$term = reset($term);
|
||||
|
||||
// The new term is expected to be stored in the second vocabulary.
|
||||
$this->assertEquals($vocabularies[1]->id(), $term->bundle());
|
||||
|
||||
/** @var \Drupal\field\Entity\FieldConfig $field_config */
|
||||
$field_config = FieldConfig::loadByName('node', $this->referencingType, $field_name);
|
||||
$handler_settings = $field_config->getSetting('handler_settings');
|
||||
|
||||
// Change the field setting to store the auto-created terms in the first
|
||||
// vocabulary and test again.
|
||||
$handler_settings['auto_create_bundle'] = $vocabularies[0]->id();
|
||||
$field_config->setSetting('handler_settings', $handler_settings);
|
||||
$field_config->save();
|
||||
|
||||
$term_name = $this->randomString();
|
||||
$edit = [
|
||||
$field_name . '[0][target_id]' => $term_name,
|
||||
'title[0][value]' => $this->randomString(),
|
||||
];
|
||||
|
||||
$this->drupalGet('node/add/' . $this->referencingType);
|
||||
$this->submitForm($edit, 'Save');
|
||||
/** @var \Drupal\taxonomy\Entity\Term $term */
|
||||
$term = $term_storage->loadByProperties(['name' => $term_name]);
|
||||
$term = reset($term);
|
||||
|
||||
// The second term is expected to be stored in the first vocabulary.
|
||||
$this->assertEquals($vocabularies[0]->id(), $term->bundle());
|
||||
|
||||
// @todo Re-enable this test when WebTestBase::curlHeaderCallback() provides
|
||||
// a way to catch and assert user-triggered errors.
|
||||
|
||||
// Test the case when the field config settings are inconsistent.
|
||||
// @code
|
||||
// unset($handler_settings['auto_create_bundle']);
|
||||
// $field_config->setSetting('handler_settings', $handler_settings);
|
||||
// $field_config->save();
|
||||
//
|
||||
// $this->drupalGet('node/add/' . $this->referencingType);
|
||||
// $error_message = sprintf(
|
||||
// "Create referenced entities if they don't already exist option is enabled but a specific destination bundle is not set. You should re-visit and fix the settings of the '%s' (%s) field.",
|
||||
// $field_config->getLabel(),
|
||||
// $field_config->getName()
|
||||
// );
|
||||
// $this->assertErrorLogged($error_message);
|
||||
// @endcode
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests autocreation for an entity that has no bundles.
|
||||
*/
|
||||
public function testNoBundles(): void {
|
||||
$account = $this->drupalCreateUser([
|
||||
'access content',
|
||||
"create $this->referencingType content",
|
||||
'administer entity_test content',
|
||||
]);
|
||||
$this->drupalLogin($account);
|
||||
|
||||
$field_name = $this->randomMachineName();
|
||||
$handler_settings = [
|
||||
'auto_create' => TRUE,
|
||||
];
|
||||
$this->createEntityReferenceField('node', $this->referencingType, $field_name, $this->randomString(), 'entity_test_no_bundle_with_label', 'default', $handler_settings);
|
||||
\Drupal::service('entity_display.repository')
|
||||
->getFormDisplay('node', $this->referencingType)
|
||||
->setComponent($field_name, ['type' => 'entity_reference_autocomplete'])
|
||||
->save();
|
||||
|
||||
$node_title = $this->randomMachineName();
|
||||
$name = $this->randomMachineName();
|
||||
$edit = [
|
||||
$field_name . '[0][target_id]' => $name,
|
||||
'title[0][value]' => $node_title,
|
||||
];
|
||||
|
||||
$this->drupalGet('node/add/' . $this->referencingType);
|
||||
$this->submitForm($edit, 'Save');
|
||||
|
||||
// Assert referenced entity was created.
|
||||
$result = \Drupal::entityQuery('entity_test_no_bundle_with_label')
|
||||
->accessCheck(FALSE)
|
||||
->condition('name', $name)
|
||||
->execute();
|
||||
$this->assertNotEmpty($result, 'Referenced entity was created.');
|
||||
$referenced_id = key($result);
|
||||
|
||||
// Assert the referenced entity is associated with referencing node.
|
||||
$result = \Drupal::entityQuery('node')
|
||||
->accessCheck(FALSE)
|
||||
->condition('type', $this->referencingType)
|
||||
->execute();
|
||||
$this->assertCount(1, $result);
|
||||
$referencing_nid = key($result);
|
||||
$referencing_node = Node::load($referencing_nid);
|
||||
$this->assertEquals($referenced_id, $referencing_node->$field_name->target_id, 'Newly created node is referenced from the referencing entity.');
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,179 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\field\Functional\EntityReference;
|
||||
|
||||
use Drupal\Tests\SchemaCheckTestTrait;
|
||||
use Drupal\field\Entity\FieldConfig;
|
||||
use Drupal\field\Entity\FieldStorageConfig;
|
||||
use Drupal\node\Entity\Node;
|
||||
use Drupal\Tests\BrowserTestBase;
|
||||
|
||||
/**
|
||||
* Tests entity reference field default values storage in CMI.
|
||||
*
|
||||
* @group entity_reference
|
||||
*/
|
||||
class EntityReferenceFieldDefaultValueTest extends BrowserTestBase {
|
||||
|
||||
use SchemaCheckTestTrait;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = ['field_ui', 'node'];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $defaultTheme = 'stark';
|
||||
|
||||
/**
|
||||
* A user with permission to administer content types, node fields, etc.
|
||||
*
|
||||
* @var \Drupal\user\UserInterface
|
||||
*/
|
||||
protected $adminUser;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp(): void {
|
||||
parent::setUp();
|
||||
|
||||
// Create default content type.
|
||||
$this->drupalCreateContentType(['type' => 'reference_content']);
|
||||
$this->drupalCreateContentType(['type' => 'referenced_content']);
|
||||
|
||||
// Create admin user.
|
||||
$this->adminUser = $this->drupalCreateUser([
|
||||
'access content',
|
||||
'administer content types',
|
||||
'administer node fields',
|
||||
'administer node form display',
|
||||
'bypass node access',
|
||||
]);
|
||||
$this->drupalLogin($this->adminUser);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that default values are correctly translated to UUIDs in config.
|
||||
*/
|
||||
public function testEntityReferenceDefaultValue(): void {
|
||||
// Create a node to be referenced.
|
||||
$referenced_node = $this->drupalCreateNode(['type' => 'referenced_content']);
|
||||
|
||||
$field_name = $this->randomMachineName();
|
||||
$field_storage = FieldStorageConfig::create([
|
||||
'field_name' => $field_name,
|
||||
'entity_type' => 'node',
|
||||
'type' => 'entity_reference',
|
||||
'settings' => ['target_type' => 'node'],
|
||||
]);
|
||||
$field_storage->save();
|
||||
$field = FieldConfig::create([
|
||||
'field_storage' => $field_storage,
|
||||
'bundle' => 'reference_content',
|
||||
'settings' => [
|
||||
'handler' => 'default',
|
||||
'handler_settings' => [
|
||||
'target_bundles' => ['referenced_content'],
|
||||
'sort' => ['field' => '_none'],
|
||||
],
|
||||
],
|
||||
]);
|
||||
$field->save();
|
||||
|
||||
// Set created node as default_value.
|
||||
$field_edit = [
|
||||
'set_default_value' => '1',
|
||||
'default_value_input[' . $field_name . '][0][target_id]' => $referenced_node->getTitle() . ' (' . $referenced_node->id() . ')',
|
||||
];
|
||||
$this->drupalGet('admin/structure/types/manage/reference_content/fields/node.reference_content.' . $field_name);
|
||||
$this->submitForm($field_edit, 'Save settings');
|
||||
|
||||
// Check that default value is selected in default value form.
|
||||
$this->drupalGet('admin/structure/types/manage/reference_content/fields/node.reference_content.' . $field_name);
|
||||
$this->assertSession()->responseContains('name="default_value_input[' . $field_name . '][0][target_id]" value="' . $referenced_node->getTitle() . ' (' . $referenced_node->id() . ')');
|
||||
|
||||
// Check if the ID has been converted to UUID in config entity.
|
||||
$config_entity = $this->config('field.field.node.reference_content.' . $field_name)->get();
|
||||
$this->assertTrue(isset($config_entity['default_value'][0]['target_uuid']), 'Default value contains target_uuid property');
|
||||
$this->assertEquals($referenced_node->uuid(), $config_entity['default_value'][0]['target_uuid'], 'Content uuid and config entity uuid are the same');
|
||||
// Ensure the configuration has the expected dependency on the entity that
|
||||
// is being used a default value.
|
||||
$this->assertEquals([$referenced_node->getConfigDependencyName()], $config_entity['dependencies']['content']);
|
||||
|
||||
// Clear field definitions cache in order to avoid stale cache values.
|
||||
\Drupal::service('entity_field.manager')->clearCachedFieldDefinitions();
|
||||
|
||||
// Create a new node to check that UUID has been converted to numeric ID.
|
||||
$new_node = Node::create(['type' => 'reference_content']);
|
||||
$this->assertEquals($new_node->get($field_name)->offsetGet(0)->target_id, $referenced_node->id());
|
||||
|
||||
// Ensure that the entity reference config schemas are correct.
|
||||
$field_config = $this->config('field.field.node.reference_content.' . $field_name);
|
||||
$this->assertConfigSchema(\Drupal::service('config.typed'), $field_config->getName(), $field_config->get());
|
||||
$field_storage_config = $this->config('field.storage.node.' . $field_name);
|
||||
$this->assertConfigSchema(\Drupal::service('config.typed'), $field_storage_config->getName(), $field_storage_config->get());
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that dependencies due to default values can be removed.
|
||||
*
|
||||
* @see \Drupal\Core\Field\Plugin\Field\FieldType\EntityReferenceItem::onDependencyRemoval()
|
||||
*/
|
||||
public function testEntityReferenceDefaultConfigValue(): void {
|
||||
// Create a node to be referenced.
|
||||
$referenced_node_type = $this->drupalCreateContentType(['type' => 'referenced_config_to_delete']);
|
||||
$referenced_node_type2 = $this->drupalCreateContentType(['type' => 'referenced_config_to_preserve']);
|
||||
|
||||
$field_name = $this->randomMachineName();
|
||||
$field_storage = FieldStorageConfig::create([
|
||||
'field_name' => $field_name,
|
||||
'entity_type' => 'node',
|
||||
'type' => 'entity_reference',
|
||||
'settings' => ['target_type' => 'node_type'],
|
||||
'cardinality' => FieldStorageConfig::CARDINALITY_UNLIMITED,
|
||||
]);
|
||||
$field_storage->save();
|
||||
$field = FieldConfig::create([
|
||||
'field_storage' => $field_storage,
|
||||
'bundle' => 'reference_content',
|
||||
'settings' => [
|
||||
'handler' => 'default',
|
||||
'handler_settings' => [
|
||||
'sort' => ['field' => '_none'],
|
||||
],
|
||||
],
|
||||
]);
|
||||
$field->save();
|
||||
|
||||
// Set created node as default_value.
|
||||
$field_edit = [
|
||||
'set_default_value' => '1',
|
||||
'default_value_input[' . $field_name . '][0][target_id]' => $referenced_node_type->label() . ' (' . $referenced_node_type->id() . ')',
|
||||
'default_value_input[' . $field_name . '][1][target_id]' => $referenced_node_type2->label() . ' (' . $referenced_node_type2->id() . ')',
|
||||
];
|
||||
$this->drupalGet('admin/structure/types/manage/reference_content/fields/node.reference_content.' . $field_name);
|
||||
$this->assertSession()->fieldExists("default_value_input[{$field_name}][0][target_id]");
|
||||
$this->assertSession()->fieldNotExists("default_value_input[{$field_name}][1][target_id]");
|
||||
$this->submitForm([], 'Add another item');
|
||||
$this->assertSession()->fieldExists("default_value_input[{$field_name}][1][target_id]");
|
||||
$this->submitForm($field_edit, 'Save settings');
|
||||
|
||||
// Check that the field has a dependency on the default value.
|
||||
$config_entity = $this->config('field.field.node.reference_content.' . $field_name)->get();
|
||||
$this->assertContains($referenced_node_type->getConfigDependencyName(), $config_entity['dependencies']['config'], 'The node type referenced_config_to_delete is a dependency of the field.');
|
||||
$this->assertContains($referenced_node_type2->getConfigDependencyName(), $config_entity['dependencies']['config'], 'The node type referenced_config_to_preserve is a dependency of the field.');
|
||||
|
||||
// Check that the field does not have a dependency on the default value
|
||||
// after deleting the node type.
|
||||
$referenced_node_type->delete();
|
||||
$config_entity = $this->config('field.field.node.reference_content.' . $field_name)->get();
|
||||
$this->assertNotContains($referenced_node_type->getConfigDependencyName(), $config_entity['dependencies']['config'], 'The node type referenced_config_to_delete not a dependency of the field.');
|
||||
$this->assertContains($referenced_node_type2->getConfigDependencyName(), $config_entity['dependencies']['config'], 'The node type referenced_config_to_preserve is a dependency of the field.');
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,364 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\field\Functional\EntityReference;
|
||||
|
||||
use Drupal\Core\Field\FieldStorageDefinitionInterface;
|
||||
use Drupal\field\Entity\FieldConfig;
|
||||
use Drupal\field\Entity\FieldStorageConfig;
|
||||
use Drupal\language\Entity\ConfigurableLanguage;
|
||||
use Drupal\Tests\BrowserTestBase;
|
||||
use Drupal\Tests\content_translation\Traits\ContentTranslationTestTrait;
|
||||
|
||||
/**
|
||||
* Tests the translation of entity reference field display on nodes.
|
||||
*
|
||||
* @group entity_reference
|
||||
*/
|
||||
class EntityReferenceFieldTranslatedReferenceViewTest extends BrowserTestBase {
|
||||
|
||||
use ContentTranslationTestTrait;
|
||||
|
||||
/**
|
||||
* Flag indicating whether the field is translatable.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
protected $translatable = TRUE;
|
||||
|
||||
/**
|
||||
* The langcode of the source language.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $baseLangcode = 'en';
|
||||
|
||||
/**
|
||||
* Target langcode for translation.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $translateToLangcode = 'hu';
|
||||
|
||||
/**
|
||||
* The test entity type name.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $testEntityTypeName = 'node';
|
||||
|
||||
/**
|
||||
* Entity type which have the entity reference field.
|
||||
*
|
||||
* @var \Drupal\node\Entity\NodeType
|
||||
*/
|
||||
protected $referrerType;
|
||||
|
||||
/**
|
||||
* Entity type which can be referenced.
|
||||
*
|
||||
* @var \Drupal\node\Entity\NodeType
|
||||
*/
|
||||
protected $referencedType;
|
||||
|
||||
/**
|
||||
* The referrer entity.
|
||||
*
|
||||
* @var \Drupal\node\Entity\Node
|
||||
*/
|
||||
protected $referrerEntity;
|
||||
|
||||
/**
|
||||
* The entity to refer.
|
||||
*
|
||||
* @var \Drupal\node\Entity\Node
|
||||
*/
|
||||
protected $referencedEntityWithoutTranslation;
|
||||
|
||||
/**
|
||||
* The entity to refer.
|
||||
*
|
||||
* @var \Drupal\node\Entity\Node
|
||||
*/
|
||||
protected $referencedEntityWithTranslation;
|
||||
|
||||
/**
|
||||
* The machine name of the entity reference field.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $referenceFieldName = 'test_reference_field';
|
||||
|
||||
/**
|
||||
* The label of the untranslated referenced entity, used in assertions.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $labelOfNotTranslatedReference;
|
||||
|
||||
/**
|
||||
* The original label of the referenced entity, used in assertions.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $originalLabel;
|
||||
|
||||
/**
|
||||
* The translated label of the referenced entity, used in assertions.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $translatedLabel;
|
||||
|
||||
/**
|
||||
* A user with permission to edit the referrer entity.
|
||||
*
|
||||
* @var \Drupal\user\UserInterface
|
||||
*/
|
||||
protected $webUser;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = [
|
||||
'language',
|
||||
'content_translation',
|
||||
'node',
|
||||
];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $defaultTheme = 'stark';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp(): void {
|
||||
parent::setUp();
|
||||
|
||||
$this->labelOfNotTranslatedReference = $this->randomMachineName();
|
||||
$this->originalLabel = $this->randomMachineName();
|
||||
$this->translatedLabel = $this->randomMachineName();
|
||||
|
||||
$this->setUpLanguages();
|
||||
|
||||
// We setup languages, so we need to ensure that the language manager
|
||||
// and language path processor is updated.
|
||||
$this->rebuildContainer();
|
||||
|
||||
$this->setUpContentTypes();
|
||||
$this->enableTranslation();
|
||||
$this->setUpEntityReferenceField();
|
||||
$this->createContent();
|
||||
|
||||
$this->webUser = $this->drupalCreateUser([
|
||||
'edit any ' . $this->referrerType->id() . ' content',
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests if the entity is displayed in an entity reference field.
|
||||
*/
|
||||
public function testEntityReferenceDisplay(): void {
|
||||
// Create a translated referrer entity.
|
||||
$this->referrerEntity = $this->createReferrerEntity();
|
||||
$this->assertEntityReferenceDisplay();
|
||||
$this->assertEntityReferenceFormDisplay();
|
||||
|
||||
// Disable translation for referrer content type.
|
||||
static::disableBundleTranslation('node', 'referrer');
|
||||
|
||||
// Create a referrer entity without translation.
|
||||
$this->referrerEntity = $this->createReferrerEntity(FALSE);
|
||||
$this->assertEntityReferenceDisplay();
|
||||
$this->assertEntityReferenceFormDisplay();
|
||||
}
|
||||
|
||||
/**
|
||||
* Assert entity reference display.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
protected function assertEntityReferenceDisplay(): void {
|
||||
$url = $this->referrerEntity->toUrl();
|
||||
$translation_url = $this->referrerEntity->toUrl('canonical', ['language' => ConfigurableLanguage::load($this->translateToLangcode)]);
|
||||
|
||||
$this->drupalGet($url);
|
||||
$this->assertSession()->pageTextContains($this->labelOfNotTranslatedReference);
|
||||
$this->assertSession()->pageTextContains($this->originalLabel);
|
||||
$this->assertSession()->pageTextNotContains($this->translatedLabel);
|
||||
$this->drupalGet($translation_url);
|
||||
$this->assertSession()->pageTextContains($this->labelOfNotTranslatedReference);
|
||||
$this->assertSession()->pageTextNotContains($this->originalLabel);
|
||||
$this->assertSession()->pageTextContains($this->translatedLabel);
|
||||
}
|
||||
|
||||
/**
|
||||
* Assert entity reference form display.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
protected function assertEntityReferenceFormDisplay(): void {
|
||||
$this->drupalLogin($this->webUser);
|
||||
$url = $this->referrerEntity->toUrl('edit-form');
|
||||
$translation_url = $this->referrerEntity->toUrl('edit-form', ['language' => ConfigurableLanguage::load($this->translateToLangcode)]);
|
||||
|
||||
$this->drupalGet($url);
|
||||
$this->assertSession()->fieldValueEquals('test_reference_field[0][target_id]', $this->originalLabel . ' (1)');
|
||||
$this->assertSession()->fieldValueEquals('test_reference_field[1][target_id]', $this->labelOfNotTranslatedReference . ' (2)');
|
||||
$this->drupalGet($translation_url);
|
||||
$this->assertSession()->fieldValueEquals('test_reference_field[0][target_id]', $this->translatedLabel . ' (1)');
|
||||
$this->assertSession()->fieldValueEquals('test_reference_field[1][target_id]', $this->labelOfNotTranslatedReference . ' (2)');
|
||||
$this->drupalLogout();
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds additional languages.
|
||||
*/
|
||||
protected function setUpLanguages(): void {
|
||||
static::createLanguageFromLangcode($this->translateToLangcode);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a test subject contents, with translation.
|
||||
*/
|
||||
protected function createContent(): void {
|
||||
$this->referencedEntityWithTranslation = $this->createReferencedEntityWithTranslation();
|
||||
$this->referencedEntityWithoutTranslation = $this->createNotTranslatedReferencedEntity();
|
||||
}
|
||||
|
||||
/**
|
||||
* Enables translations where it needed.
|
||||
*/
|
||||
protected function enableTranslation(): void {
|
||||
// Enable translation for the entity types.
|
||||
$this->enableContentTranslation($this->testEntityTypeName, $this->referrerType->id());
|
||||
$this->enableContentTranslation($this->testEntityTypeName, $this->referencedType->id());
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds term reference field for the article content type.
|
||||
*/
|
||||
protected function setUpEntityReferenceField(): void {
|
||||
FieldStorageConfig::create([
|
||||
'field_name' => $this->referenceFieldName,
|
||||
'entity_type' => $this->testEntityTypeName,
|
||||
'type' => 'entity_reference',
|
||||
'cardinality' => FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED,
|
||||
'translatable' => $this->translatable,
|
||||
'settings' => [
|
||||
'allowed_values' => [
|
||||
[
|
||||
'target_type' => $this->testEntityTypeName,
|
||||
],
|
||||
],
|
||||
],
|
||||
])->save();
|
||||
|
||||
FieldConfig::create([
|
||||
'field_name' => $this->referenceFieldName,
|
||||
'bundle' => $this->referrerType->id(),
|
||||
'entity_type' => $this->testEntityTypeName,
|
||||
])
|
||||
->save();
|
||||
|
||||
/** @var \Drupal\Core\Entity\EntityDisplayRepositoryInterface $display_repository */
|
||||
$display_repository = \Drupal::service('entity_display.repository');
|
||||
|
||||
$display_repository->getFormDisplay($this->testEntityTypeName, $this->referrerType->id())
|
||||
->setComponent($this->referenceFieldName, [
|
||||
'type' => 'entity_reference_autocomplete',
|
||||
])
|
||||
->save();
|
||||
$display_repository->getViewDisplay($this->testEntityTypeName, $this->referrerType->id())
|
||||
->setComponent($this->referenceFieldName, [
|
||||
'type' => 'entity_reference_label',
|
||||
])
|
||||
->save();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create content types.
|
||||
*/
|
||||
protected function setUpContentTypes(): void {
|
||||
$this->referrerType = $this->drupalCreateContentType([
|
||||
'type' => 'referrer',
|
||||
'name' => 'Referrer',
|
||||
]);
|
||||
$this->referencedType = $this->drupalCreateContentType([
|
||||
'type' => 'referenced_page',
|
||||
'name' => 'Referenced Page',
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a referenced entity with a translation.
|
||||
*/
|
||||
protected function createReferencedEntityWithTranslation() {
|
||||
/** @var \Drupal\node\Entity\Node $node */
|
||||
$node = \Drupal::entityTypeManager()->getStorage($this->testEntityTypeName)->create([
|
||||
'title' => $this->originalLabel,
|
||||
'type' => $this->referencedType->id(),
|
||||
'description' => [
|
||||
'value' => $this->randomMachineName(),
|
||||
'format' => 'basic_html',
|
||||
],
|
||||
'langcode' => $this->baseLangcode,
|
||||
]);
|
||||
$node->save();
|
||||
$node->addTranslation($this->translateToLangcode, [
|
||||
'title' => $this->translatedLabel,
|
||||
]);
|
||||
$node->save();
|
||||
|
||||
return $node;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the referenced entity.
|
||||
*/
|
||||
protected function createNotTranslatedReferencedEntity() {
|
||||
/** @var \Drupal\node\Entity\Node $node */
|
||||
$node = \Drupal::entityTypeManager()->getStorage($this->testEntityTypeName)->create([
|
||||
'title' => $this->labelOfNotTranslatedReference,
|
||||
'type' => $this->referencedType->id(),
|
||||
'description' => [
|
||||
'value' => $this->randomMachineName(),
|
||||
'format' => 'basic_html',
|
||||
],
|
||||
'langcode' => $this->baseLangcode,
|
||||
]);
|
||||
$node->save();
|
||||
|
||||
return $node;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the referrer entity.
|
||||
*/
|
||||
protected function createReferrerEntity($translatable = TRUE) {
|
||||
/** @var \Drupal\node\Entity\Node $node */
|
||||
$node = \Drupal::entityTypeManager()->getStorage($this->testEntityTypeName)->create([
|
||||
'title' => $this->randomMachineName(),
|
||||
'type' => $this->referrerType->id(),
|
||||
'description' => [
|
||||
'value' => $this->randomMachineName(),
|
||||
'format' => 'basic_html',
|
||||
],
|
||||
$this->referenceFieldName => [
|
||||
['target_id' => $this->referencedEntityWithTranslation->id()],
|
||||
['target_id' => $this->referencedEntityWithoutTranslation->id()],
|
||||
],
|
||||
'langcode' => $this->baseLangcode,
|
||||
]);
|
||||
if ($translatable) {
|
||||
$node->addTranslation($this->translateToLangcode, $node->toArray());
|
||||
}
|
||||
$node->save();
|
||||
|
||||
return $node;
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,151 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\field\Functional\EntityReference;
|
||||
|
||||
use Drupal\Core\Field\FieldStorageDefinitionInterface;
|
||||
use Drupal\field\Entity\FieldConfig;
|
||||
use Drupal\field\Entity\FieldStorageConfig;
|
||||
use Drupal\Tests\BrowserTestBase;
|
||||
use Drupal\Tests\TestFileCreationTrait;
|
||||
|
||||
/**
|
||||
* Tests an autocomplete widget with file upload.
|
||||
*
|
||||
* @group entity_reference
|
||||
*/
|
||||
class EntityReferenceFileUploadTest extends BrowserTestBase {
|
||||
|
||||
use TestFileCreationTrait;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = ['node', 'file'];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $defaultTheme = 'stark';
|
||||
|
||||
/**
|
||||
* The name of a content type that will reference $referencedType.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $referencingType;
|
||||
|
||||
/**
|
||||
* The name of a content type that will be referenced by $referencingType.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $referencedType;
|
||||
|
||||
/**
|
||||
* Node id.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
protected $nodeId;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp(): void {
|
||||
parent::setUp();
|
||||
|
||||
// Create "referencing" and "referenced" node types.
|
||||
$referencing = $this->drupalCreateContentType();
|
||||
$this->referencingType = $referencing->id();
|
||||
|
||||
$referenced = $this->drupalCreateContentType();
|
||||
$this->referencedType = $referenced->id();
|
||||
$this->nodeId = $this->drupalCreateNode(['type' => $referenced->id()])->id();
|
||||
|
||||
FieldStorageConfig::create([
|
||||
'field_name' => 'test_field',
|
||||
'entity_type' => 'node',
|
||||
'translatable' => FALSE,
|
||||
'entity_types' => [],
|
||||
'settings' => [
|
||||
'target_type' => 'node',
|
||||
],
|
||||
'type' => 'entity_reference',
|
||||
'cardinality' => FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED,
|
||||
])->save();
|
||||
|
||||
FieldConfig::create([
|
||||
'label' => 'Entity reference field',
|
||||
'field_name' => 'test_field',
|
||||
'entity_type' => 'node',
|
||||
'required' => TRUE,
|
||||
'bundle' => $referencing->id(),
|
||||
'settings' => [
|
||||
'handler' => 'default',
|
||||
'handler_settings' => [
|
||||
// Reference a single vocabulary.
|
||||
'target_bundles' => [
|
||||
$referenced->id(),
|
||||
],
|
||||
],
|
||||
],
|
||||
])->save();
|
||||
|
||||
// Create a file field.
|
||||
$file_field_name = 'file_field';
|
||||
$field_storage = FieldStorageConfig::create([
|
||||
'field_name' => $file_field_name,
|
||||
'entity_type' => 'node',
|
||||
'type' => 'file',
|
||||
]);
|
||||
$field_storage->save();
|
||||
FieldConfig::create([
|
||||
'entity_type' => 'node',
|
||||
'field_storage' => $field_storage,
|
||||
'bundle' => $referencing->id(),
|
||||
'label' => $this->randomMachineName() . '_label',
|
||||
])->save();
|
||||
|
||||
/** @var \Drupal\Core\Entity\EntityDisplayRepositoryInterface $display_repository */
|
||||
$display_repository = \Drupal::service('entity_display.repository');
|
||||
|
||||
$display_repository->getViewDisplay('node', $referencing->id())
|
||||
->setComponent('test_field')
|
||||
->setComponent($file_field_name)
|
||||
->save();
|
||||
$display_repository->getFormDisplay('node', $referencing->id())
|
||||
->setComponent('test_field', [
|
||||
'type' => 'entity_reference_autocomplete',
|
||||
])
|
||||
->setComponent($file_field_name, [
|
||||
'type' => 'file_generic',
|
||||
])
|
||||
->save();
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that the autocomplete input element does not cause ajax fatal.
|
||||
*/
|
||||
public function testFileUpload(): void {
|
||||
$user1 = $this->drupalCreateUser([
|
||||
'access content',
|
||||
"create $this->referencingType content",
|
||||
]);
|
||||
$this->drupalLogin($user1);
|
||||
|
||||
$test_file = current($this->getTestFiles('text'));
|
||||
$edit['files[file_field_0]'] = \Drupal::service('file_system')->realpath($test_file->uri);
|
||||
$this->drupalGet('node/add/' . $this->referencingType);
|
||||
$this->submitForm($edit, 'Upload');
|
||||
$this->assertSession()->statusCodeEquals(200);
|
||||
$edit = [
|
||||
'title[0][value]' => $this->randomMachineName(),
|
||||
'test_field[0][target_id]' => $this->nodeId,
|
||||
];
|
||||
$this->submitForm($edit, 'Save');
|
||||
$this->assertSession()->statusCodeEquals(200);
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,263 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\field\Functional\EntityReference;
|
||||
|
||||
use Drupal\entity_test\Entity\EntityTest;
|
||||
use Drupal\field\Entity\FieldConfig;
|
||||
use Drupal\Tests\BrowserTestBase;
|
||||
use Drupal\Tests\config\Traits\AssertConfigEntityImportTrait;
|
||||
use Drupal\Tests\field\Traits\EntityReferenceFieldCreationTrait;
|
||||
|
||||
/**
|
||||
* Tests various Entity reference UI components.
|
||||
*
|
||||
* @group entity_reference
|
||||
*/
|
||||
class EntityReferenceIntegrationTest extends BrowserTestBase {
|
||||
|
||||
use AssertConfigEntityImportTrait;
|
||||
use EntityReferenceFieldCreationTrait;
|
||||
|
||||
/**
|
||||
* The entity type used in this test.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $entityType = 'entity_test';
|
||||
|
||||
/**
|
||||
* The bundle used in this test.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $bundle = 'entity_test';
|
||||
|
||||
/**
|
||||
* The name of the field used in this test.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $fieldName;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = ['config_test', 'entity_test', 'field_ui'];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $defaultTheme = 'stark';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp(): void {
|
||||
parent::setUp();
|
||||
|
||||
// Create a test user.
|
||||
$web_user = $this->drupalCreateUser([
|
||||
'administer entity_test content',
|
||||
'administer entity_test fields',
|
||||
'view test entity',
|
||||
]);
|
||||
$this->drupalLogin($web_user);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the entity reference field with all its supported field widgets.
|
||||
*/
|
||||
public function testSupportedEntityTypesAndWidgets(): void {
|
||||
foreach ($this->getTestEntities() as $key => $referenced_entities) {
|
||||
$this->fieldName = 'field_test_' . $referenced_entities[0]->getEntityTypeId();
|
||||
|
||||
// Create an Entity reference field.
|
||||
$this->createEntityReferenceField($this->entityType, $this->bundle, $this->fieldName, $this->fieldName, $referenced_entities[0]->getEntityTypeId(), 'default', [], 2);
|
||||
|
||||
/** @var \Drupal\Core\Entity\EntityDisplayRepositoryInterface $display_repository */
|
||||
$display_repository = \Drupal::service('entity_display.repository');
|
||||
|
||||
// Test the default 'entity_reference_autocomplete' widget.
|
||||
$display_repository->getFormDisplay($this->entityType, $this->bundle)
|
||||
->setComponent($this->fieldName)
|
||||
->save();
|
||||
|
||||
$entity_name = $this->randomMachineName();
|
||||
$edit = [
|
||||
'name[0][value]' => $entity_name,
|
||||
$this->fieldName . '[0][target_id]' => $referenced_entities[0]->label() . ' (' . $referenced_entities[0]->id() . ')',
|
||||
// Test an input of the entity label without an ' (entity_id)' suffix.
|
||||
$this->fieldName . '[1][target_id]' => $referenced_entities[1]->label(),
|
||||
];
|
||||
$this->drupalGet($this->entityType . '/add');
|
||||
$this->submitForm($edit, 'Save');
|
||||
$this->assertFieldValues($entity_name, $referenced_entities);
|
||||
|
||||
// Try to post the form again with no modification and check if the field
|
||||
// values remain the same.
|
||||
/** @var \Drupal\Core\Entity\EntityStorageInterface $storage */
|
||||
$storage = $this->container->get('entity_type.manager')->getStorage($this->entityType);
|
||||
$entity = current($storage->loadByProperties(['name' => $entity_name]));
|
||||
$this->drupalGet($this->entityType . '/manage/' . $entity->id() . '/edit');
|
||||
$this->assertSession()->fieldValueEquals($this->fieldName . '[0][target_id]', $referenced_entities[0]->label() . ' (' . $referenced_entities[0]->id() . ')');
|
||||
$this->assertSession()->fieldValueEquals($this->fieldName . '[1][target_id]', $referenced_entities[1]->label() . ' (' . $referenced_entities[1]->id() . ')');
|
||||
|
||||
$this->submitForm([], 'Save');
|
||||
$this->assertFieldValues($entity_name, $referenced_entities);
|
||||
|
||||
// Test the 'entity_reference_autocomplete_tags' widget.
|
||||
$display_repository->getFormDisplay($this->entityType, $this->bundle)
|
||||
->setComponent($this->fieldName, [
|
||||
'type' => 'entity_reference_autocomplete_tags',
|
||||
])->save();
|
||||
|
||||
$entity_name = $this->randomMachineName();
|
||||
$target_id = $referenced_entities[0]->label() . ' (' . $referenced_entities[0]->id() . ')';
|
||||
// Test an input of the entity label without an ' (entity_id)' suffix.
|
||||
$target_id .= ', ' . $referenced_entities[1]->label();
|
||||
$edit = [
|
||||
'name[0][value]' => $entity_name,
|
||||
$this->fieldName . '[target_id]' => $target_id,
|
||||
];
|
||||
$this->drupalGet($this->entityType . '/add');
|
||||
$this->submitForm($edit, 'Save');
|
||||
$this->assertFieldValues($entity_name, $referenced_entities);
|
||||
|
||||
// Try to post the form again with no modification and check if the field
|
||||
// values remain the same.
|
||||
$entity = current($storage->loadByProperties(['name' => $entity_name]));
|
||||
$this->drupalGet($this->entityType . '/manage/' . $entity->id() . '/edit');
|
||||
$this->assertSession()->fieldValueEquals($this->fieldName . '[target_id]', $target_id . ' (' . $referenced_entities[1]->id() . ')');
|
||||
|
||||
$this->submitForm([], 'Save');
|
||||
$this->assertFieldValues($entity_name, $referenced_entities);
|
||||
|
||||
// Test all the other widgets supported by the entity reference field.
|
||||
// Since we don't know the form structure for these widgets, just test
|
||||
// that editing and saving an already created entity works.
|
||||
$exclude = ['entity_reference_autocomplete', 'entity_reference_autocomplete_tags'];
|
||||
$entity = current($storage->loadByProperties(['name' => $entity_name]));
|
||||
$supported_widgets = \Drupal::service('plugin.manager.field.widget')->getOptions('entity_reference');
|
||||
$supported_widget_types = array_diff(array_keys($supported_widgets), $exclude);
|
||||
|
||||
foreach ($supported_widget_types as $widget_type) {
|
||||
$display_repository->getFormDisplay($this->entityType, $this->bundle)
|
||||
->setComponent($this->fieldName, [
|
||||
'type' => $widget_type,
|
||||
])->save();
|
||||
|
||||
$this->drupalGet($this->entityType . '/manage/' . $entity->id() . '/edit');
|
||||
$this->submitForm([], 'Save');
|
||||
// Some widget types do not guarantee that the order is kept, accept any
|
||||
// order.
|
||||
$this->assertFieldValues($entity_name, $referenced_entities, FALSE);
|
||||
}
|
||||
|
||||
// Reset to the default 'entity_reference_autocomplete' widget.
|
||||
$display_repository->getFormDisplay($this->entityType, $this->bundle)
|
||||
->setComponent($this->fieldName)
|
||||
->save();
|
||||
|
||||
// Set first entity as the default_value.
|
||||
$field_edit = [
|
||||
'set_default_value' => '1',
|
||||
'default_value_input[' . $this->fieldName . '][0][target_id]' => $referenced_entities[0]->label() . ' (' . $referenced_entities[0]->id() . ')',
|
||||
];
|
||||
if ($key == 'content') {
|
||||
$field_edit['settings[handler_settings][target_bundles][' . $referenced_entities[0]->getEntityTypeId() . ']'] = TRUE;
|
||||
}
|
||||
$this->drupalGet($this->entityType . '/structure/' . $this->bundle . '/fields/' . $this->entityType . '.' . $this->bundle . '.' . $this->fieldName);
|
||||
$this->submitForm($field_edit, 'Save settings');
|
||||
// Ensure the configuration has the expected dependency on the entity that
|
||||
// is being used a default value.
|
||||
$field = FieldConfig::loadByName($this->entityType, $this->bundle, $this->fieldName);
|
||||
$this->assertContains($referenced_entities[0]->getConfigDependencyName(), $field->getDependencies()[$key], 'Expected ' . $key . ' dependency ' . $referenced_entities[0]->getConfigDependencyName() . ' found');
|
||||
// Ensure that the field can be imported without change even after the
|
||||
// default value deleted.
|
||||
$referenced_entities[0]->delete();
|
||||
// Reload the field since deleting the default value can change the field.
|
||||
\Drupal::entityTypeManager()->getStorage($field->getEntityTypeId())->resetCache([$field->id()]);
|
||||
$field = FieldConfig::loadByName($this->entityType, $this->bundle, $this->fieldName);
|
||||
$this->assertConfigEntityImport($field);
|
||||
|
||||
// Once the default value has been removed after saving the dependency
|
||||
// should be removed.
|
||||
$field = FieldConfig::loadByName($this->entityType, $this->bundle, $this->fieldName);
|
||||
$field->save();
|
||||
$dependencies = $field->getDependencies();
|
||||
$this->assertFalse(isset($dependencies[$key]) && in_array($referenced_entities[0]->getConfigDependencyName(), $dependencies[$key]), $key . ' dependency ' . $referenced_entities[0]->getConfigDependencyName() . ' does not exist.');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Asserts that the reference field values are correct.
|
||||
*
|
||||
* @param string $entity_name
|
||||
* The name of the test entity.
|
||||
* @param \Drupal\Core\Entity\EntityInterface[] $referenced_entities
|
||||
* An array of referenced entities.
|
||||
* @param bool $assert_order
|
||||
* Whether the correct order of the references should be assert or just
|
||||
* that they exist.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
protected function assertFieldValues(string $entity_name, array $referenced_entities, $assert_order = TRUE): void {
|
||||
$entity = current($this->container->get('entity_type.manager')->getStorage(
|
||||
$this->entityType)->loadByProperties(['name' => $entity_name]));
|
||||
|
||||
$this->assertNotEmpty($entity, "$this->entityType: Entity found in the database.");
|
||||
|
||||
if ($assert_order) {
|
||||
$this->assertEquals($referenced_entities[0]->id(), $entity->{$this->fieldName}->target_id);
|
||||
$this->assertEquals($referenced_entities[0]->id(), $entity->{$this->fieldName}->entity->id());
|
||||
$this->assertEquals($referenced_entities[0]->label(), $entity->{$this->fieldName}->entity->label());
|
||||
|
||||
$this->assertEquals($referenced_entities[1]->id(), $entity->{$this->fieldName}[1]->target_id);
|
||||
$this->assertEquals($referenced_entities[1]->id(), $entity->{$this->fieldName}[1]->entity->id());
|
||||
$this->assertEquals($referenced_entities[1]->label(), $entity->{$this->fieldName}[1]->entity->label());
|
||||
}
|
||||
else {
|
||||
$ids = [$referenced_entities[0]->id(), $referenced_entities[1]->id()];
|
||||
$labels = [$referenced_entities[0]->label(), $referenced_entities[1]->label()];
|
||||
$this->assertContains($entity->{$this->fieldName}->target_id, $ids);
|
||||
$this->assertContains($entity->{$this->fieldName}->entity->label(), $labels);
|
||||
$this->assertContains($entity->{$this->fieldName}[1]->target_id, $ids);
|
||||
$this->assertContains($entity->{$this->fieldName}[1]->entity->label(), $labels);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates two content and two config test entities.
|
||||
*
|
||||
* @return array
|
||||
* An array of entity objects.
|
||||
*/
|
||||
protected function getTestEntities(): array {
|
||||
$storage = \Drupal::entityTypeManager()->getStorage('config_test');
|
||||
$config_entity_1 = $storage->create(['id' => $this->randomMachineName(), 'label' => $this->randomMachineName()]);
|
||||
$config_entity_1->save();
|
||||
$config_entity_2 = $storage->create(['id' => $this->randomMachineName(), 'label' => $this->randomMachineName()]);
|
||||
$config_entity_2->save();
|
||||
|
||||
$content_entity_1 = EntityTest::create(['name' => $this->randomMachineName()]);
|
||||
$content_entity_1->save();
|
||||
$content_entity_2 = EntityTest::create(['name' => $this->randomMachineName()]);
|
||||
$content_entity_2->save();
|
||||
|
||||
return [
|
||||
'config' => [
|
||||
$config_entity_1,
|
||||
$config_entity_2,
|
||||
],
|
||||
'content' => [
|
||||
$content_entity_1,
|
||||
$content_entity_2,
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,85 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\field\Functional\EntityReference;
|
||||
|
||||
use Drupal\Core\Entity\Entity\EntityFormDisplay;
|
||||
use Drupal\Core\Entity\Entity\EntityViewDisplay;
|
||||
use Drupal\Tests\BrowserTestBase;
|
||||
use Drupal\Tests\field\Traits\EntityReferenceFieldCreationTrait;
|
||||
|
||||
/**
|
||||
* Tests possible XSS security issues in entity references.
|
||||
*
|
||||
* @group entity_reference
|
||||
*/
|
||||
class EntityReferenceXSSTest extends BrowserTestBase {
|
||||
|
||||
use EntityReferenceFieldCreationTrait;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = ['node'];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $defaultTheme = 'stark';
|
||||
|
||||
/**
|
||||
* Tests markup is escaped in the entity reference select and label formatter.
|
||||
*/
|
||||
public function testEntityReferenceXSS(): void {
|
||||
$this->drupalCreateContentType(['type' => 'article']);
|
||||
|
||||
// Create a node with markup in the title.
|
||||
$node_type_one = $this->drupalCreateContentType();
|
||||
$node = [
|
||||
'type' => $node_type_one->id(),
|
||||
'title' => '<em>I am kitten</em>',
|
||||
];
|
||||
$referenced_node = $this->drupalCreateNode($node);
|
||||
|
||||
$node_type_two = $this->drupalCreateContentType(['name' => '<em>bundle with markup</em>']);
|
||||
$this->drupalCreateNode([
|
||||
'type' => $node_type_two->id(),
|
||||
'title' => 'My bundle has markup',
|
||||
]);
|
||||
|
||||
$this->createEntityReferenceField('node', 'article', 'entity_reference_test', 'Entity Reference test', 'node', 'default', ['target_bundles' => [$node_type_one->id(), $node_type_two->id()]]);
|
||||
|
||||
EntityFormDisplay::load('node.article.default')
|
||||
->setComponent('entity_reference_test', ['type' => 'options_select'])
|
||||
->save();
|
||||
EntityViewDisplay::load('node.article.default')
|
||||
->setComponent('entity_reference_test', ['type' => 'entity_reference_label'])
|
||||
->save();
|
||||
|
||||
// Create a node and reference the node with markup in the title.
|
||||
$this->drupalLogin($this->drupalCreateUser([
|
||||
'create article content',
|
||||
]));
|
||||
$this->drupalGet('node/add/article');
|
||||
$this->assertSession()->assertEscaped($referenced_node->getTitle());
|
||||
$this->assertSession()->assertEscaped($node_type_two->label());
|
||||
|
||||
$edit = [
|
||||
'title[0][value]' => $this->randomString(),
|
||||
'entity_reference_test' => $referenced_node->id(),
|
||||
];
|
||||
$this->submitForm($edit, 'Save');
|
||||
$this->assertSession()->assertEscaped($referenced_node->getTitle());
|
||||
|
||||
// Test the options_buttons type.
|
||||
EntityFormDisplay::load('node.article.default')
|
||||
->setComponent('entity_reference_test', ['type' => 'options_buttons'])
|
||||
->save();
|
||||
$this->drupalGet('node/add/article');
|
||||
$this->assertSession()->assertEscaped($referenced_node->getTitle());
|
||||
// options_buttons does not support optgroups.
|
||||
$this->assertSession()->pageTextNotContains('bundle with markup');
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,131 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\field\Functional\EntityReference\Views;
|
||||
|
||||
use Drupal\Component\Serialization\Json;
|
||||
use Drupal\Component\Utility\Crypt;
|
||||
use Drupal\Component\Utility\Html;
|
||||
use Drupal\Core\Site\Settings;
|
||||
use Drupal\entity_test\EntityTestHelper;
|
||||
use Drupal\Tests\BrowserTestBase;
|
||||
use Drupal\Tests\field\Traits\EntityReferenceFieldCreationTrait;
|
||||
use Drupal\views\Views;
|
||||
|
||||
/**
|
||||
* Tests entity reference selection handler.
|
||||
*
|
||||
* @group entity_reference
|
||||
*/
|
||||
class SelectionTest extends BrowserTestBase {
|
||||
|
||||
use EntityReferenceFieldCreationTrait;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = [
|
||||
'node',
|
||||
'views',
|
||||
'entity_reference_test',
|
||||
'entity_test',
|
||||
];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $defaultTheme = 'stark';
|
||||
|
||||
/**
|
||||
* An array of node titles, keyed by content type and node ID.
|
||||
*
|
||||
* @var \Drupal\node\NodeInterface[]
|
||||
*/
|
||||
protected $nodes = [];
|
||||
|
||||
/**
|
||||
* The handler settings for the entity reference field.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $handlerSettings;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp(): void {
|
||||
parent::setUp();
|
||||
|
||||
// Create content types and nodes.
|
||||
$type1 = $this->drupalCreateContentType()->id();
|
||||
$type2 = $this->drupalCreateContentType()->id();
|
||||
// Add some characters that should be escaped but not double escaped.
|
||||
$node1 = $this->drupalCreateNode(['type' => $type1, 'title' => 'Test first node &<>']);
|
||||
$node2 = $this->drupalCreateNode(['type' => $type1, 'title' => 'Test second node &&&']);
|
||||
$node3 = $this->drupalCreateNode(['type' => $type2, 'title' => 'Test third node <span />']);
|
||||
|
||||
foreach ([$node1, $node2, $node3] as $node) {
|
||||
$this->nodes[$node->id()] = $node;
|
||||
}
|
||||
|
||||
// Ensure the bundle to which the field is attached actually exists, or we
|
||||
// will get config validation errors.
|
||||
EntityTestHelper::createBundle('test_bundle');
|
||||
|
||||
// Create an entity reference field.
|
||||
$handler_settings = [
|
||||
'view' => [
|
||||
'view_name' => 'test_entity_reference',
|
||||
'display_name' => 'entity_reference_1',
|
||||
],
|
||||
];
|
||||
$this->handlerSettings = $handler_settings;
|
||||
$this->createEntityReferenceField('entity_test', 'test_bundle', 'test_field', $this->randomString(), 'node', 'views', $handler_settings);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that the Views selection handles the views output properly.
|
||||
*/
|
||||
public function testAutocompleteOutput(): void {
|
||||
// Reset any internal static caching.
|
||||
\Drupal::service('entity_type.manager')->getStorage('node')->resetCache();
|
||||
|
||||
$view = Views::getView('test_entity_reference');
|
||||
$view->setDisplay();
|
||||
|
||||
// Enable the display of the 'type' field so we can test that the output
|
||||
// does not contain only the entity label.
|
||||
$fields = $view->displayHandlers->get('entity_reference_1')->getOption('fields');
|
||||
$fields['type']['exclude'] = FALSE;
|
||||
$view->displayHandlers->get('entity_reference_1')->setOption('fields', $fields);
|
||||
$view->save();
|
||||
|
||||
// Prepare the selection settings key needed by the entity reference
|
||||
// autocomplete route.
|
||||
$target_type = 'node';
|
||||
$selection_handler = 'views';
|
||||
$selection_settings = $this->handlerSettings;
|
||||
$selection_settings_key = Crypt::hmacBase64(serialize($selection_settings) . $target_type . $selection_handler, Settings::getHashSalt());
|
||||
\Drupal::keyValue('entity_autocomplete')->set($selection_settings_key, $selection_settings);
|
||||
|
||||
$result = Json::decode($this->drupalGet('entity_reference_autocomplete/' . $target_type . '/' . $selection_handler . '/' . $selection_settings_key, ['query' => ['q' => 't']]));
|
||||
|
||||
$expected = [
|
||||
0 => [
|
||||
'value' => $this->nodes[1]->bundle() . ': ' . $this->nodes[1]->label() . ' (' . $this->nodes[1]->id() . ')',
|
||||
'label' => '<span class="views-field views-field-type"><span class="field-content">' . $this->nodes[1]->bundle() . '</span></span>: <span class="views-field views-field-title"><span class="field-content">' . Html::escape($this->nodes[1]->label()) . '</span></span>',
|
||||
],
|
||||
1 => [
|
||||
'value' => $this->nodes[2]->bundle() . ': ' . $this->nodes[2]->label() . ' (' . $this->nodes[2]->id() . ')',
|
||||
'label' => '<span class="views-field views-field-type"><span class="field-content">' . $this->nodes[2]->bundle() . '</span></span>: <span class="views-field views-field-title"><span class="field-content">' . Html::escape($this->nodes[2]->label()) . '</span></span>',
|
||||
],
|
||||
2 => [
|
||||
'value' => $this->nodes[3]->bundle() . ': ' . $this->nodes[3]->label() . ' (' . $this->nodes[3]->id() . ')',
|
||||
'label' => '<span class="views-field views-field-type"><span class="field-content">' . $this->nodes[3]->bundle() . '</span></span>: <span class="views-field views-field-title"><span class="field-content">' . Html::escape($this->nodes[3]->label()) . '</span></span>',
|
||||
],
|
||||
];
|
||||
$this->assertEquals($expected, $result, 'The autocomplete result of the Views entity reference selection handler contains the proper output.');
|
||||
}
|
||||
|
||||
}
|
||||
101
web/core/modules/field/tests/src/Functional/FieldAccessTest.php
Normal file
101
web/core/modules/field/tests/src/Functional/FieldAccessTest.php
Normal file
@ -0,0 +1,101 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\field\Functional;
|
||||
|
||||
use Drupal\field\Entity\FieldConfig;
|
||||
use Drupal\field\Entity\FieldStorageConfig;
|
||||
|
||||
/**
|
||||
* Tests Field access.
|
||||
*
|
||||
* @group field
|
||||
*/
|
||||
class FieldAccessTest extends FieldTestBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = ['node', 'field_test'];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $defaultTheme = 'stark';
|
||||
|
||||
/**
|
||||
* Node entity to use in this test.
|
||||
*
|
||||
* @var \Drupal\node\Entity\Node
|
||||
*/
|
||||
protected $node;
|
||||
|
||||
/**
|
||||
* Field value to test display on nodes.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $testViewFieldValue;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp(): void {
|
||||
parent::setUp();
|
||||
|
||||
$web_user = $this->drupalCreateUser(['view test_view_field content']);
|
||||
$this->drupalLogin($web_user);
|
||||
|
||||
// Create content type.
|
||||
$content_type_info = $this->drupalCreateContentType();
|
||||
$content_type = $content_type_info->id();
|
||||
|
||||
$field_storage = [
|
||||
'field_name' => 'test_view_field',
|
||||
'entity_type' => 'node',
|
||||
'type' => 'text',
|
||||
];
|
||||
FieldStorageConfig::create($field_storage)->save();
|
||||
$field = [
|
||||
'field_name' => $field_storage['field_name'],
|
||||
'entity_type' => 'node',
|
||||
'bundle' => $content_type,
|
||||
];
|
||||
FieldConfig::create($field)->save();
|
||||
|
||||
// Assign display properties for the 'default' and 'teaser' view modes.
|
||||
foreach (['default', 'teaser'] as $view_mode) {
|
||||
\Drupal::service('entity_display.repository')
|
||||
->getViewDisplay('node', $content_type, $view_mode)
|
||||
->setComponent($field_storage['field_name'])
|
||||
->save();
|
||||
}
|
||||
|
||||
// Create test node.
|
||||
$this->testViewFieldValue = 'This is some text';
|
||||
$settings = [];
|
||||
$settings['type'] = $content_type;
|
||||
$settings['title'] = 'Field view access test';
|
||||
$settings['test_view_field'] = [['value' => $this->testViewFieldValue]];
|
||||
$this->node = $this->drupalCreateNode($settings);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that hook_entity_field_access() is called.
|
||||
*/
|
||||
public function testFieldAccess(): void {
|
||||
|
||||
// Assert the text is visible.
|
||||
$this->drupalGet('node/' . $this->node->id());
|
||||
$this->assertSession()->pageTextContains($this->testViewFieldValue);
|
||||
|
||||
// Assert the text is not visible for anonymous users.
|
||||
// The field_test module implements hook_entity_field_access() which will
|
||||
// specifically target the 'test_view_field' field.
|
||||
$this->drupalLogout();
|
||||
$this->drupalGet('node/' . $this->node->id());
|
||||
$this->assertSession()->pageTextNotContains($this->testViewFieldValue);
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,98 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\field\Functional;
|
||||
|
||||
use Drupal\field\Entity\FieldConfig;
|
||||
use Drupal\field\Entity\FieldStorageConfig;
|
||||
use Drupal\Tests\BrowserTestBase;
|
||||
|
||||
/**
|
||||
* Tests the default value callback.
|
||||
*
|
||||
* @group field
|
||||
*/
|
||||
class FieldDefaultValueCallbackTest extends BrowserTestBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = ['node', 'field_test', 'field_ui'];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $defaultTheme = 'stark';
|
||||
|
||||
/**
|
||||
* The field name.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $fieldName;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp(): void {
|
||||
parent::setUp();
|
||||
|
||||
$this->fieldName = 'field_test';
|
||||
|
||||
// Create Article node types.
|
||||
if ($this->profile != 'standard') {
|
||||
$this->drupalCreateContentType([
|
||||
'type' => 'article',
|
||||
'name' => 'Article',
|
||||
]);
|
||||
}
|
||||
|
||||
$this->drupalLogin($this->drupalCreateUser([
|
||||
'administer node fields',
|
||||
]));
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the default value callback functionality for fields.
|
||||
*/
|
||||
public function testDefaultValueCallbackForm(): void {
|
||||
// Create a field and storage for checking.
|
||||
FieldStorageConfig::create([
|
||||
'field_name' => $this->fieldName,
|
||||
'entity_type' => 'node',
|
||||
'type' => 'text',
|
||||
])->save();
|
||||
/** @var \Drupal\field\Entity\FieldConfig $field_config */
|
||||
$field_config = FieldConfig::create([
|
||||
'entity_type' => 'node',
|
||||
'field_name' => $this->fieldName,
|
||||
'bundle' => 'article',
|
||||
]);
|
||||
$field_config->save();
|
||||
|
||||
// Check that the default field form is visible when no callback is set.
|
||||
$this->drupalGet('/admin/structure/types/manage/article/fields/node.article.field_test');
|
||||
$this->assertSession()->fieldValueEquals('default_value_input[field_test][0][value]', '');
|
||||
|
||||
// Set a different field value, it should be on the field.
|
||||
$default_value = $this->randomString();
|
||||
$field_config->setDefaultValue([['value' => $default_value]])->save();
|
||||
$this->drupalGet('/admin/structure/types/manage/article/fields/node.article.field_test');
|
||||
$this->assertSession()->fieldValueEquals('default_value_input[field_test][0][value]', $default_value);
|
||||
|
||||
// Set a different field value to the field directly, instead of an array.
|
||||
$default_value = $this->randomString();
|
||||
$field_config->setDefaultValue($default_value)->save();
|
||||
$this->drupalGet('/admin/structure/types/manage/article/fields/node.article.field_test');
|
||||
$this->assertSession()->fieldValueEquals('default_value_input[field_test][0][value]', $default_value);
|
||||
|
||||
// Set a default value callback instead, and the default field form should
|
||||
// not be visible.
|
||||
$field_config->setDefaultValueCallback('\Drupal\field_test\FieldDefaultValueCallbackProvider::calculateDefaultValue')->save();
|
||||
$this->drupalGet('/admin/structure/types/manage/article/fields/node.article.field_test');
|
||||
$this->assertSession()->fieldNotExists('default_value_input[field_test][0][value]');
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,68 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\field\Functional;
|
||||
|
||||
use Drupal\Tests\BrowserTestBase;
|
||||
|
||||
/**
|
||||
* Tests help display for the Field module.
|
||||
*
|
||||
* @group field
|
||||
*/
|
||||
class FieldHelpTest extends BrowserTestBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = ['field', 'help'];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $defaultTheme = 'stark';
|
||||
|
||||
/**
|
||||
* The admin user that will be created.
|
||||
*
|
||||
* @var \Drupal\user\Entity\User|false
|
||||
*/
|
||||
protected $adminUser;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp(): void {
|
||||
parent::setUp();
|
||||
|
||||
// Create the admin user.
|
||||
$this->adminUser = $this->drupalCreateUser([
|
||||
'access help pages',
|
||||
'view the administration theme',
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the Field module's help page.
|
||||
*/
|
||||
public function testFieldHelp(): void {
|
||||
// Log in the admin user.
|
||||
$this->drupalLogin($this->adminUser);
|
||||
|
||||
// Visit the Help page and make sure no warnings or notices are thrown.
|
||||
$this->drupalGet('admin/help/field');
|
||||
|
||||
// Enable the Options, Email and Field API Test modules.
|
||||
\Drupal::service('module_installer')->install(['options', 'field_test']);
|
||||
|
||||
$this->drupalGet('admin/help/field');
|
||||
$this->assertSession()->linkExists('Options', 0, 'Options module is listed on the Field help page.');
|
||||
// Verify that modules with field types that do not implement hook_help are
|
||||
// listed.
|
||||
$this->assertSession()->pageTextContains('Field API Test');
|
||||
$this->assertSession()->linkNotExists('Field API Test', 'Modules with field types that do not implement hook_help are not linked.');
|
||||
$this->assertSession()->linkNotExists('Link', 'Modules that have not been installed, are not listed.');
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,145 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\field\Functional;
|
||||
|
||||
use Drupal\entity_test\Entity\EntityTest;
|
||||
use Drupal\field\Entity\FieldConfig;
|
||||
use Drupal\field\Entity\FieldStorageConfig;
|
||||
|
||||
/**
|
||||
* Tests deleting field storage when a module in uninstalled through the UI.
|
||||
*
|
||||
* @group field
|
||||
* @see \Drupal\field\ConfigImporterFieldPurger
|
||||
* @see field_config_import_steps_alter()
|
||||
* @see field_form_config_admin_import_form_alter()
|
||||
*/
|
||||
class FieldImportDeleteUninstallUiTest extends FieldTestBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = [
|
||||
'entity_test',
|
||||
'telephone',
|
||||
'config',
|
||||
'filter',
|
||||
'datetime',
|
||||
];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $defaultTheme = 'stark';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp(): void {
|
||||
parent::setUp();
|
||||
|
||||
$this->drupalLogin($this->drupalCreateUser(['synchronize configuration']));
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests deleting field storages and fields as part of config import.
|
||||
*/
|
||||
public function testImportDeleteUninstall(): void {
|
||||
// Create a telephone field.
|
||||
$field_storage = FieldStorageConfig::create([
|
||||
'field_name' => 'field_tel',
|
||||
'entity_type' => 'entity_test',
|
||||
'type' => 'telephone',
|
||||
]);
|
||||
$field_storage->save();
|
||||
FieldConfig::create([
|
||||
'field_storage' => $field_storage,
|
||||
'bundle' => 'entity_test',
|
||||
])->save();
|
||||
|
||||
// Create a text field.
|
||||
$date_field_storage = FieldStorageConfig::create([
|
||||
'field_name' => 'field_date',
|
||||
'entity_type' => 'entity_test',
|
||||
'type' => 'datetime',
|
||||
]);
|
||||
$date_field_storage->save();
|
||||
FieldConfig::create([
|
||||
'field_storage' => $date_field_storage,
|
||||
'bundle' => 'entity_test',
|
||||
])->save();
|
||||
|
||||
// Create an entity which has values for the telephone and text field.
|
||||
$entity = EntityTest::create();
|
||||
$value = '+0123456789';
|
||||
$entity->field_tel = $value;
|
||||
$entity->field_date = time();
|
||||
$entity->name->value = $this->randomMachineName();
|
||||
$entity->save();
|
||||
|
||||
// Delete the text field before exporting configuration so that we can test
|
||||
// that deleted fields that are provided by modules that will be uninstalled
|
||||
// are also purged and that the UI message includes such fields.
|
||||
$date_field_storage->delete();
|
||||
|
||||
// Verify entity has been created properly.
|
||||
$id = $entity->id();
|
||||
$entity = EntityTest::load($id);
|
||||
$this->assertEquals($value, $entity->field_tel->value);
|
||||
$this->assertEquals($value, $entity->field_tel[0]->value);
|
||||
|
||||
$active = $this->container->get('config.storage');
|
||||
$sync = $this->container->get('config.storage.sync');
|
||||
$this->copyConfig($active, $sync);
|
||||
|
||||
// Stage uninstall of the Telephone module.
|
||||
$core_extension = $this->config('core.extension')->get();
|
||||
unset($core_extension['module']['telephone']);
|
||||
$sync->write('core.extension', $core_extension);
|
||||
|
||||
// Stage the field deletion including its dependencies.
|
||||
$sync->delete('field.storage.entity_test.field_tel');
|
||||
$sync->delete('field.field.entity_test.entity_test.field_tel');
|
||||
$sync->delete('core.entity_form_display.entity_test.entity_test.default');
|
||||
$this->drupalGet('admin/config/development/configuration');
|
||||
// Test that the message for one field being purged during a configuration
|
||||
// synchronization is correct.
|
||||
$this->assertSession()->pageTextContains('This synchronization will delete data from the field entity_test.field_tel.');
|
||||
|
||||
// Stage an uninstall of the datetime module to test the message for
|
||||
// multiple fields.
|
||||
unset($core_extension['module']['datetime']);
|
||||
$sync->write('core.extension', $core_extension);
|
||||
|
||||
$this->drupalGet('admin/config/development/configuration');
|
||||
$this->assertSession()->pageTextContains('This synchronization will delete data from the fields: entity_test.field_tel, entity_test.field_date.');
|
||||
|
||||
// This will purge all the data, delete the field and uninstall the
|
||||
// Telephone and Text modules.
|
||||
$this->submitForm([], 'Import all');
|
||||
$this->assertSession()->pageTextNotContains('Field data will be deleted by this synchronization.');
|
||||
$this->rebuildContainer();
|
||||
$this->assertFalse(\Drupal::moduleHandler()->moduleExists('telephone'));
|
||||
$this->assertNull(\Drupal::service('entity.repository')->loadEntityByUuid('field_storage_config', $field_storage->uuid()), 'The telephone field has been deleted by the configuration synchronization');
|
||||
$deleted_storages = \Drupal::state()->get('field.storage.deleted', []);
|
||||
$this->assertFalse(isset($deleted_storages[$field_storage->uuid()]), 'Telephone field has been completed removed from the system.');
|
||||
$this->assertFalse(isset($deleted_storages[$field_storage->uuid()]), 'Text field has been completed removed from the system.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests if the synchronization form is available when the core.extension.yml is missing.
|
||||
*/
|
||||
public function testSynchronizeForm(): void {
|
||||
$sync = $this->container->get('config.storage.sync');
|
||||
$this->copyConfig($this->container->get('config.storage'), $sync);
|
||||
|
||||
$sync->delete('core.extension');
|
||||
$this->drupalGet('admin/config/development/configuration');
|
||||
$assertSession = $this->assertSession();
|
||||
$this->assertSession()->elementExists('css', 'input[value="Import all"]')->click();
|
||||
$assertSession->pageTextContains('The core.extension configuration does not exist.');
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,68 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\field\Functional;
|
||||
|
||||
use Drupal\Core\Entity\EntityInterface;
|
||||
use Drupal\Core\Language\LanguageInterface;
|
||||
use Drupal\Tests\BrowserTestBase;
|
||||
|
||||
/**
|
||||
* Parent class for Field API tests.
|
||||
*/
|
||||
abstract class FieldTestBase extends BrowserTestBase {
|
||||
|
||||
/**
|
||||
* Generate random values for a field_test field.
|
||||
*
|
||||
* @param int $cardinality
|
||||
* Number of values to generate.
|
||||
*
|
||||
* @return array
|
||||
* An array of random values, in the format expected for field values.
|
||||
*/
|
||||
public function _generateTestFieldValues($cardinality) {
|
||||
$values = [];
|
||||
for ($i = 0; $i < $cardinality; $i++) {
|
||||
// field_test fields treat 0 as 'empty value'.
|
||||
$values[$i]['value'] = mt_rand(1, 127);
|
||||
}
|
||||
return $values;
|
||||
}
|
||||
|
||||
/**
|
||||
* Assert that a field has the expected values in an entity.
|
||||
*
|
||||
* This function only checks a single column in the field values.
|
||||
*
|
||||
* @param \Drupal\Core\Entity\EntityInterface $entity
|
||||
* The entity to test.
|
||||
* @param string $field_name
|
||||
* The name of the field to test.
|
||||
* @param array $expected_values
|
||||
* The array of expected values.
|
||||
* @param string $langcode
|
||||
* (Optional) The language code for the values. Defaults to
|
||||
* \Drupal\Core\Language\LanguageInterface::LANGCODE_DEFAULT.
|
||||
* @param string $column
|
||||
* (Optional) The name of the column to check. Defaults to 'value'.
|
||||
*/
|
||||
public function assertFieldValues(EntityInterface $entity, $field_name, $expected_values, $langcode = LanguageInterface::LANGCODE_DEFAULT, $column = 'value') {
|
||||
// Re-load the entity to make sure we have the latest changes.
|
||||
$storage = $this->container->get('entity_type.manager')
|
||||
->getStorage($entity->getEntityTypeId());
|
||||
$storage->resetCache([$entity->id()]);
|
||||
$e = $storage->load($entity->id());
|
||||
|
||||
$field = $values = $e->getTranslation($langcode)->$field_name;
|
||||
// Filter out empty values so that they don't mess with the assertions.
|
||||
$field->filterEmptyItems();
|
||||
$values = $field->getValue();
|
||||
$this->assertSameSize($expected_values, $values, 'Expected number of values were saved.');
|
||||
foreach ($expected_values as $key => $value) {
|
||||
$this->assertEquals($value, $values[$key][$column], "Value $value was saved correctly.");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
642
web/core/modules/field/tests/src/Functional/FormTest.php
Normal file
642
web/core/modules/field/tests/src/Functional/FormTest.php
Normal file
@ -0,0 +1,642 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\field\Functional;
|
||||
|
||||
use Drupal\Component\Utility\Html;
|
||||
use Drupal\Core\Entity\Entity\EntityFormDisplay;
|
||||
use Drupal\Core\Field\FieldStorageDefinitionInterface;
|
||||
use Drupal\Core\Form\FormState;
|
||||
use Drupal\entity_test\Entity\EntityTest;
|
||||
use Drupal\entity_test\Entity\EntityTestBaseFieldDisplay;
|
||||
use Drupal\entity_test\EntityTestHelper;
|
||||
use Drupal\field\Entity\FieldConfig;
|
||||
use Drupal\field\Entity\FieldStorageConfig;
|
||||
|
||||
/**
|
||||
* Tests field form handling.
|
||||
*
|
||||
* @group field
|
||||
*/
|
||||
class FormTest extends FieldTestBase {
|
||||
|
||||
/**
|
||||
* Modules to install.
|
||||
*
|
||||
* Locale is installed so that TranslatableMarkup actually does something.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected static $modules = [
|
||||
'field_test',
|
||||
'options',
|
||||
'entity_test',
|
||||
'locale',
|
||||
'field_ui',
|
||||
];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $defaultTheme = 'stark';
|
||||
|
||||
/**
|
||||
* An array of values defining a field single.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $fieldStorageSingle;
|
||||
|
||||
/**
|
||||
* An array of values defining a field with unlimited cardinality.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $fieldStorageUnlimited;
|
||||
|
||||
/**
|
||||
* An array of values defining a field.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $field;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp(): void {
|
||||
parent::setUp();
|
||||
|
||||
$web_user = $this->drupalCreateUser([
|
||||
'view test entity',
|
||||
'administer entity_test content',
|
||||
'administer entity_test fields',
|
||||
]);
|
||||
$this->drupalLogin($web_user);
|
||||
|
||||
$this->fieldStorageSingle = [
|
||||
'field_name' => 'field_single',
|
||||
'entity_type' => 'entity_test',
|
||||
'type' => 'test_field',
|
||||
];
|
||||
$this->fieldStorageUnlimited = [
|
||||
'field_name' => 'field_unlimited',
|
||||
'entity_type' => 'entity_test',
|
||||
'type' => 'test_field',
|
||||
'cardinality' => FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED,
|
||||
];
|
||||
|
||||
$this->field = [
|
||||
'entity_type' => 'entity_test',
|
||||
'bundle' => 'entity_test',
|
||||
'label' => $this->randomMachineName() . '_label',
|
||||
'description' => '[site:name]_description',
|
||||
'weight' => mt_rand(0, 127),
|
||||
'settings' => [
|
||||
'test_field_setting' => $this->randomMachineName(),
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the single-value field form functionality.
|
||||
*/
|
||||
public function testFieldFormSingle(): void {
|
||||
$field_storage = $this->fieldStorageSingle;
|
||||
$field_name = $field_storage['field_name'];
|
||||
$this->field['field_name'] = $field_name;
|
||||
FieldStorageConfig::create($field_storage)->save();
|
||||
FieldConfig::create($this->field)->save();
|
||||
\Drupal::service('entity_display.repository')
|
||||
->getFormDisplay($this->field['entity_type'], $this->field['bundle'])
|
||||
->setComponent($field_name)
|
||||
->save();
|
||||
|
||||
// Display creation form.
|
||||
$this->drupalGet('entity_test/add');
|
||||
|
||||
// Create token value expected for description.
|
||||
$token_description = Html::escape($this->config('system.site')->get('name')) . '_description';
|
||||
$this->assertSession()->pageTextContains($token_description);
|
||||
$this->assertSession()->fieldValueEquals("{$field_name}[0][value]", '');
|
||||
// Verify that no extraneous widget is displayed.
|
||||
$this->assertSession()->fieldNotExists("{$field_name}[1][value]");
|
||||
|
||||
// Check that hook_field_widget_single_element_form_alter() does not believe
|
||||
// this is the default value form.
|
||||
$this->assertSession()->pageTextNotContains('From hook_field_widget_single_element_form_alter(): Default form is true.');
|
||||
// Check that hook_field_widget_single_element_form_alter() does not believe
|
||||
// this is the default value form.
|
||||
$this->assertSession()->pageTextNotContains('From hook_field_widget_complete_form_alter(): Default form is true.');
|
||||
|
||||
// Submit with invalid value (field-level validation).
|
||||
$edit = [
|
||||
"{$field_name}[0][value]" => -1,
|
||||
];
|
||||
$this->submitForm($edit, 'Save');
|
||||
$this->assertSession()->pageTextContains("{$this->field['label']} does not accept the value -1.");
|
||||
// @todo check that the correct field is flagged for error.
|
||||
|
||||
// Create an entity
|
||||
$value = mt_rand(1, 127);
|
||||
$edit = [
|
||||
"{$field_name}[0][value]" => $value,
|
||||
];
|
||||
$this->submitForm($edit, 'Save');
|
||||
preg_match('|entity_test/manage/(\d+)|', $this->getUrl(), $match);
|
||||
$id = $match[1];
|
||||
$this->assertSession()->pageTextContains('entity_test ' . $id . ' has been created.');
|
||||
$entity = EntityTest::load($id);
|
||||
$this->assertEquals($value, $entity->{$field_name}->value, 'Field value was saved');
|
||||
|
||||
// Display edit form.
|
||||
$this->drupalGet('entity_test/manage/' . $id . '/edit');
|
||||
// Check that the widget is displayed with the correct default value.
|
||||
$this->assertSession()->fieldValueEquals("{$field_name}[0][value]", $value);
|
||||
// Verify that no extraneous widget is displayed.
|
||||
$this->assertSession()->fieldNotExists("{$field_name}[1][value]");
|
||||
|
||||
// Update the entity.
|
||||
$value = mt_rand(1, 127);
|
||||
$edit = [
|
||||
"{$field_name}[0][value]" => $value,
|
||||
];
|
||||
$this->submitForm($edit, 'Save');
|
||||
$this->assertSession()->pageTextContains('entity_test ' . $id . ' has been updated.');
|
||||
$entity = EntityTest::load($id);
|
||||
$this->assertEquals($value, $entity->{$field_name}->value, 'Field value was updated');
|
||||
|
||||
// Empty the field.
|
||||
$value = '';
|
||||
$edit = [
|
||||
"{$field_name}[0][value]" => $value,
|
||||
];
|
||||
$this->drupalGet('entity_test/manage/' . $id . '/edit');
|
||||
$this->submitForm($edit, 'Save');
|
||||
$this->assertSession()->pageTextContains('entity_test ' . $id . ' has been updated.');
|
||||
$entity = EntityTest::load($id);
|
||||
$this->assertTrue($entity->{$field_name}->isEmpty(), 'Field was emptied');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests field widget default values on entity forms.
|
||||
*/
|
||||
public function testFieldFormDefaultValue(): void {
|
||||
$field_storage = $this->fieldStorageSingle;
|
||||
$field_name = $field_storage['field_name'];
|
||||
$this->field['field_name'] = $field_name;
|
||||
$default = rand(1, 127);
|
||||
$this->field['default_value'] = [['value' => $default]];
|
||||
FieldStorageConfig::create($field_storage)->save();
|
||||
FieldConfig::create($this->field)->save();
|
||||
\Drupal::service('entity_display.repository')
|
||||
->getFormDisplay($this->field['entity_type'], $this->field['bundle'])
|
||||
->setComponent($field_name)
|
||||
->save();
|
||||
|
||||
// Display creation form.
|
||||
$this->drupalGet('entity_test/add');
|
||||
// Test that the default value is displayed correctly.
|
||||
$this->assertSession()->fieldValueEquals("{$field_name}[0][value]", $default);
|
||||
|
||||
// Try to submit an empty value.
|
||||
$edit = [
|
||||
"{$field_name}[0][value]" => '',
|
||||
];
|
||||
$this->submitForm($edit, 'Save');
|
||||
preg_match('|entity_test/manage/(\d+)|', $this->getUrl(), $match);
|
||||
$id = $match[1];
|
||||
$this->assertSession()->pageTextContains('entity_test ' . $id . ' has been created.');
|
||||
$entity = EntityTest::load($id);
|
||||
$this->assertTrue($entity->{$field_name}->isEmpty(), 'Field is now empty.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the required single-value field form.
|
||||
*/
|
||||
public function testFieldFormSingleRequired(): void {
|
||||
$field_storage = $this->fieldStorageSingle;
|
||||
$field_name = $field_storage['field_name'];
|
||||
$this->field['field_name'] = $field_name;
|
||||
$this->field['required'] = TRUE;
|
||||
FieldStorageConfig::create($field_storage)->save();
|
||||
FieldConfig::create($this->field)->save();
|
||||
\Drupal::service('entity_display.repository')
|
||||
->getFormDisplay($this->field['entity_type'], $this->field['bundle'])
|
||||
->setComponent($field_name)
|
||||
->save();
|
||||
|
||||
// Submit with missing required value.
|
||||
$edit = [];
|
||||
$this->drupalGet('entity_test/add');
|
||||
$this->submitForm($edit, 'Save');
|
||||
$this->assertSession()->pageTextContains("{$this->field['label']} field is required.");
|
||||
|
||||
// Create an entity
|
||||
$value = mt_rand(1, 127);
|
||||
$edit = [
|
||||
"{$field_name}[0][value]" => $value,
|
||||
];
|
||||
$this->submitForm($edit, 'Save');
|
||||
preg_match('|entity_test/manage/(\d+)|', $this->getUrl(), $match);
|
||||
$id = $match[1];
|
||||
$this->assertSession()->pageTextContains('entity_test ' . $id . ' has been created.');
|
||||
$entity = EntityTest::load($id);
|
||||
$this->assertEquals($value, $entity->{$field_name}->value, 'Field value was saved');
|
||||
|
||||
// Edit with missing required value.
|
||||
$value = '';
|
||||
$edit = [
|
||||
"{$field_name}[0][value]" => $value,
|
||||
];
|
||||
$this->drupalGet('entity_test/manage/' . $id . '/edit');
|
||||
$this->submitForm($edit, 'Save');
|
||||
$this->assertSession()->pageTextContains("{$this->field['label']} field is required.");
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the unlimited-value field form.
|
||||
*/
|
||||
public function testFieldFormUnlimited(): void {
|
||||
$field_storage = $this->fieldStorageUnlimited;
|
||||
$field_name = $field_storage['field_name'];
|
||||
$this->field['field_name'] = $field_name;
|
||||
FieldStorageConfig::create($field_storage)->save();
|
||||
FieldConfig::create($this->field)->save();
|
||||
\Drupal::service('entity_display.repository')
|
||||
->getFormDisplay($this->field['entity_type'], $this->field['bundle'])
|
||||
->setComponent($field_name)
|
||||
->save();
|
||||
|
||||
// Verify that only one "Default value" field
|
||||
// exists on the Manage field display.
|
||||
$this->drupalGet("entity_test/structure/entity_test/fields/entity_test.entity_test.{$field_name}");
|
||||
$this->assertSession()->elementsCount('xpath', "//table[@id='field-unlimited-values']/tbody/tr//input[contains(@class, 'form-text')]", 1);
|
||||
|
||||
// Display creation form -> 1 widget.
|
||||
$this->drupalGet('entity_test/add');
|
||||
$this->assertSession()->fieldValueEquals("{$field_name}[0][value]", '');
|
||||
// Verify that no extraneous widget is displayed.
|
||||
$this->assertSession()->fieldNotExists("{$field_name}[1][value]");
|
||||
|
||||
// Check if aria-describedby attribute is placed on multiple value widgets.
|
||||
$this->assertSession()->elementAttributeContains('xpath', '//table[@id="field-unlimited-values"]', 'aria-describedby', 'edit-field-unlimited--description');
|
||||
|
||||
// Press 'add more' button -> 2 widgets.
|
||||
$this->submitForm([], 'Add another item');
|
||||
$this->assertSession()->fieldValueEquals("{$field_name}[0][value]", '');
|
||||
$this->assertSession()->fieldValueEquals("{$field_name}[1][value]", '');
|
||||
// Verify that no extraneous widget is displayed.
|
||||
$this->assertSession()->fieldNotExists("{$field_name}[2][value]");
|
||||
// @todo check that non-field inputs are preserved ('title'), etc.
|
||||
|
||||
// Yet another time so that we can play with more values -> 3 widgets.
|
||||
$this->submitForm([], 'Add another item');
|
||||
|
||||
// Prepare values and weights.
|
||||
$count = 3;
|
||||
$delta_range = $count - 1;
|
||||
$values = $weights = $pattern = $expected_values = [];
|
||||
$edit = [];
|
||||
for ($delta = 0; $delta <= $delta_range; $delta++) {
|
||||
// Assign unique random values and weights.
|
||||
do {
|
||||
$value = mt_rand(1, 127);
|
||||
} while (in_array($value, $values));
|
||||
do {
|
||||
$weight = mt_rand(-$delta_range, $delta_range);
|
||||
} while (in_array($weight, $weights));
|
||||
$edit["{$field_name}[$delta][value]"] = $value;
|
||||
$edit["{$field_name}[$delta][_weight]"] = $weight;
|
||||
// We'll need three slightly different formats to check the values.
|
||||
$values[$delta] = $value;
|
||||
$weights[$delta] = $weight;
|
||||
$field_values[$weight]['value'] = (string) $value;
|
||||
$pattern[$weight] = "<input [^>]*value=\"$value\" [^>]*";
|
||||
}
|
||||
|
||||
// Press 'add more' button -> 4 widgets
|
||||
$this->submitForm($edit, 'Add another item');
|
||||
for ($delta = 0; $delta <= $delta_range; $delta++) {
|
||||
$this->assertSession()->fieldValueEquals("{$field_name}[$delta][value]", $values[$delta]);
|
||||
$this->assertSession()->fieldValueEquals("{$field_name}[$delta][_weight]", $weights[$delta]);
|
||||
}
|
||||
ksort($pattern);
|
||||
$pattern = implode('.*', array_values($pattern));
|
||||
// Verify that the widgets are displayed in the correct order.
|
||||
$this->assertSession()->responseMatches("|$pattern|s");
|
||||
$this->assertSession()->fieldValueEquals("{$field_name}[$delta][value]", '');
|
||||
$this->assertSession()->fieldValueEquals("{$field_name}[$delta][_weight]", $delta);
|
||||
// Verify that no extraneous widget is displayed.
|
||||
$this->assertSession()->fieldNotExists("{$field_name}[" . ($delta + 1) . '][value]');
|
||||
|
||||
// Submit the form and create the entity.
|
||||
$this->submitForm($edit, 'Save');
|
||||
preg_match('|entity_test/manage/(\d+)|', $this->getUrl(), $match);
|
||||
$id = $match[1];
|
||||
$this->assertSession()->pageTextContains('entity_test ' . $id . ' has been created.');
|
||||
$entity = EntityTest::load($id);
|
||||
ksort($field_values);
|
||||
$field_values = array_values($field_values);
|
||||
$this->assertSame($field_values, $entity->{$field_name}->getValue(), 'Field values were saved in the correct order');
|
||||
|
||||
// Display edit form: check that the expected number of widgets is
|
||||
// displayed, with correct values change values, reorder, leave an empty
|
||||
// value in the middle.
|
||||
// Submit: check that the entity is updated with correct values
|
||||
// Re-submit: check that the field can be emptied.
|
||||
|
||||
// Test with several multiple fields in a form
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the position of the required label.
|
||||
*/
|
||||
public function testFieldFormUnlimitedRequired(): void {
|
||||
$field_name = $this->fieldStorageUnlimited['field_name'];
|
||||
$this->field['field_name'] = $field_name;
|
||||
$this->field['required'] = TRUE;
|
||||
FieldStorageConfig::create($this->fieldStorageUnlimited)->save();
|
||||
FieldConfig::create($this->field)->save();
|
||||
\Drupal::service('entity_display.repository')
|
||||
->getFormDisplay($this->field['entity_type'], $this->field['bundle'])
|
||||
->setComponent($field_name)
|
||||
->save();
|
||||
|
||||
// Display creation form -> 1 widget.
|
||||
$this->drupalGet('entity_test/add');
|
||||
// Check that the Required symbol is present for the label of the field
|
||||
// with unlimited cardinality.
|
||||
$this->assertSession()->elementAttributeContains('xpath', "//h4[contains(@class, 'label') and contains(text(), '{$this->field['label']}')]", 'class', 'js-form-required');
|
||||
// Check that the label of the field input is visually hidden and contains
|
||||
// the field title and an indication of the delta for a11y.
|
||||
$this->assertSession()->elementExists('xpath', "//label[@for='edit-field-unlimited-0-value' and contains(@class, 'visually-hidden') and contains(text(), '{$this->field['label']} (value 1)')]");
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests widget handling of multiple required radios.
|
||||
*/
|
||||
public function testFieldFormMultivalueWithRequiredRadio(): void {
|
||||
/** @var \Drupal\Core\Entity\EntityDisplayRepositoryInterface $display_repository */
|
||||
$display_repository = \Drupal::service('entity_display.repository');
|
||||
|
||||
// Create a multivalue test field.
|
||||
$field_storage = $this->fieldStorageUnlimited;
|
||||
$field_name = $field_storage['field_name'];
|
||||
$this->field['field_name'] = $field_name;
|
||||
FieldStorageConfig::create($field_storage)->save();
|
||||
FieldConfig::create($this->field)->save();
|
||||
$display_repository->getFormDisplay($this->field['entity_type'], $this->field['bundle'])
|
||||
->setComponent($field_name)
|
||||
->save();
|
||||
|
||||
// Add a required radio field.
|
||||
FieldStorageConfig::create([
|
||||
'field_name' => 'required_radio_test',
|
||||
'entity_type' => 'entity_test',
|
||||
'type' => 'list_string',
|
||||
'settings' => [
|
||||
'allowed_values' => ['yes' => 'yes', 'no' => 'no'],
|
||||
],
|
||||
])->save();
|
||||
$field = [
|
||||
'field_name' => 'required_radio_test',
|
||||
'entity_type' => 'entity_test',
|
||||
'bundle' => 'entity_test',
|
||||
'required' => TRUE,
|
||||
];
|
||||
FieldConfig::create($field)->save();
|
||||
$display_repository->getFormDisplay($field['entity_type'], $field['bundle'])
|
||||
->setComponent($field['field_name'], [
|
||||
'type' => 'options_buttons',
|
||||
])
|
||||
->save();
|
||||
|
||||
// Display creation form.
|
||||
$this->drupalGet('entity_test/add');
|
||||
|
||||
// Press the 'Add more' button.
|
||||
$this->submitForm([], 'Add another item');
|
||||
|
||||
// Verify that no error is thrown by the radio element.
|
||||
$this->assertSession()->elementNotExists('xpath', '//div[contains(@class, "error")]');
|
||||
|
||||
// Verify that the widget is added.
|
||||
$this->assertSession()->fieldValueEquals("{$field_name}[0][value]", '');
|
||||
$this->assertSession()->fieldValueEquals("{$field_name}[1][value]", '');
|
||||
// Verify that no extraneous widget is displayed.
|
||||
$this->assertSession()->fieldNotExists("{$field_name}[2][value]");
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests fields with no 'edit' access.
|
||||
*/
|
||||
public function testFieldFormAccess(): void {
|
||||
/** @var \Drupal\Core\Entity\EntityDisplayRepositoryInterface $display_repository */
|
||||
$display_repository = \Drupal::service('entity_display.repository');
|
||||
|
||||
$entity_type = 'entity_test_rev';
|
||||
// Create a "regular" field.
|
||||
$field_storage = $this->fieldStorageSingle;
|
||||
$field_storage['entity_type'] = $entity_type;
|
||||
$field_name = $field_storage['field_name'];
|
||||
$field = $this->field;
|
||||
$field['field_name'] = $field_name;
|
||||
$field['entity_type'] = $entity_type;
|
||||
$field['bundle'] = $entity_type;
|
||||
FieldStorageConfig::create($field_storage)->save();
|
||||
FieldConfig::create($field)->save();
|
||||
$display_repository->getFormDisplay($entity_type, $entity_type)
|
||||
->setComponent($field_name)
|
||||
->save();
|
||||
|
||||
// Create a field with no edit access. See
|
||||
// field_test_entity_field_access().
|
||||
$field_storage_no_access = [
|
||||
'field_name' => 'field_no_edit_access',
|
||||
'entity_type' => $entity_type,
|
||||
'type' => 'test_field',
|
||||
];
|
||||
$field_name_no_access = $field_storage_no_access['field_name'];
|
||||
$field_no_access = [
|
||||
'field_name' => $field_name_no_access,
|
||||
'entity_type' => $entity_type,
|
||||
'bundle' => $entity_type,
|
||||
'default_value' => [0 => ['value' => 99]],
|
||||
];
|
||||
FieldStorageConfig::create($field_storage_no_access)->save();
|
||||
FieldConfig::create($field_no_access)->save();
|
||||
$display_repository->getFormDisplay($field_no_access['entity_type'], $field_no_access['bundle'])
|
||||
->setComponent($field_name_no_access)
|
||||
->save();
|
||||
|
||||
// Test that the form structure includes full information for each delta
|
||||
// apart from #access.
|
||||
$entity = $this->container->get('entity_type.manager')
|
||||
->getStorage($entity_type)
|
||||
->create(['id' => 0, 'revision_id' => 0]);
|
||||
|
||||
$display = $display_repository->getFormDisplay($entity_type, $entity_type);
|
||||
$form = [];
|
||||
$form_state = new FormState();
|
||||
$display->buildForm($entity, $form, $form_state);
|
||||
|
||||
$this->assertFalse($form[$field_name_no_access]['#access'], 'Field #access is FALSE for the field without edit access.');
|
||||
|
||||
// Display creation form.
|
||||
$this->drupalGet($entity_type . '/add');
|
||||
// Check that the widget is not displayed if field access is denied.
|
||||
$this->assertSession()->fieldNotExists("{$field_name_no_access}[0][value]");
|
||||
|
||||
// Create entity.
|
||||
$edit = [
|
||||
"{$field_name}[0][value]" => 1,
|
||||
];
|
||||
$this->submitForm($edit, 'Save');
|
||||
preg_match("|$entity_type/manage/(\d+)|", $this->getUrl(), $match);
|
||||
$id = $match[1];
|
||||
|
||||
// Check that the default value was saved.
|
||||
$storage = $this->container->get('entity_type.manager')
|
||||
->getStorage($entity_type);
|
||||
$entity = $storage->load($id);
|
||||
$this->assertEquals(99, $entity->{$field_name_no_access}->value, 'Default value was saved for the field with no edit access.');
|
||||
$this->assertEquals(1, $entity->{$field_name}->value, 'Entered value vas saved for the field with edit access.');
|
||||
|
||||
// Create a new revision.
|
||||
$edit = [
|
||||
"{$field_name}[0][value]" => 2,
|
||||
'revision' => TRUE,
|
||||
];
|
||||
$this->drupalGet($entity_type . '/manage/' . $id . '/edit');
|
||||
$this->submitForm($edit, 'Save');
|
||||
|
||||
// Check that the new revision has the expected values.
|
||||
$entity = $storage->load($id);
|
||||
$this->assertEquals(99, $entity->{$field_name_no_access}->value, 'New revision has the expected value for the field with no edit access.');
|
||||
$this->assertEquals(2, $entity->{$field_name}->value, 'New revision has the expected value for the field with edit access.');
|
||||
|
||||
// Check that the revision is also saved in the revisions table.
|
||||
/** @var \Drupal\Core\Entity\RevisionableStorageInterface $storage */
|
||||
$storage = $this->container->get('entity_type.manager')
|
||||
->getStorage($entity_type);
|
||||
$entity = $storage->loadRevision($entity->getRevisionId());
|
||||
$this->assertEquals(99, $entity->{$field_name_no_access}->value, 'New revision has the expected value for the field with no edit access.');
|
||||
$this->assertEquals(2, $entity->{$field_name}->value, 'New revision has the expected value for the field with edit access.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests hiding a field in a form.
|
||||
*/
|
||||
public function testHiddenField(): void {
|
||||
$entity_type = 'entity_test_rev';
|
||||
$field_storage = $this->fieldStorageSingle;
|
||||
$field_storage['entity_type'] = $entity_type;
|
||||
$field_name = $field_storage['field_name'];
|
||||
$this->field['field_name'] = $field_name;
|
||||
$this->field['default_value'] = [0 => ['value' => 99]];
|
||||
$this->field['entity_type'] = $entity_type;
|
||||
$this->field['bundle'] = $entity_type;
|
||||
FieldStorageConfig::create($field_storage)->save();
|
||||
$this->field = FieldConfig::create($this->field);
|
||||
$this->field->save();
|
||||
// We explicitly do not assign a widget in a form display, so the field
|
||||
// stays hidden in forms.
|
||||
|
||||
// Display the entity creation form.
|
||||
$this->drupalGet($entity_type . '/add');
|
||||
|
||||
// Create an entity and test that the default value is assigned correctly to
|
||||
// the field that uses the hidden widget.
|
||||
$this->assertSession()->fieldNotExists("{$field_name}[0][value]");
|
||||
$this->submitForm([], 'Save');
|
||||
preg_match('|' . $entity_type . '/manage/(\d+)|', $this->getUrl(), $match);
|
||||
$id = $match[1];
|
||||
$this->assertSession()->pageTextContains('entity_test_rev ' . $id . ' has been created.');
|
||||
$storage = $this->container->get('entity_type.manager')
|
||||
->getStorage($entity_type);
|
||||
|
||||
$entity = $storage->load($id);
|
||||
$this->assertEquals(99, $entity->{$field_name}->value, 'Default value was saved');
|
||||
|
||||
// Update the field to remove the default value, and switch to the default
|
||||
// widget.
|
||||
$this->field->setDefaultValue([]);
|
||||
$this->field->save();
|
||||
\Drupal::service('entity_display.repository')
|
||||
->getFormDisplay($entity_type, $this->field->getTargetBundle())
|
||||
->setComponent($this->field->getName(), [
|
||||
'type' => 'test_field_widget',
|
||||
])
|
||||
->save();
|
||||
|
||||
// Display edit form.
|
||||
$this->drupalGet($entity_type . '/manage/' . $id . '/edit');
|
||||
$this->assertSession()->fieldValueEquals("{$field_name}[0][value]", 99);
|
||||
|
||||
// Update the entity.
|
||||
$value = mt_rand(1, 127);
|
||||
$edit = ["{$field_name}[0][value]" => $value];
|
||||
$this->submitForm($edit, 'Save');
|
||||
$this->assertSession()->pageTextContains('entity_test_rev ' . $id . ' has been updated.');
|
||||
$entity = $storage->load($id);
|
||||
$this->assertEquals($value, $entity->{$field_name}->value, 'Field value was updated');
|
||||
|
||||
// Set the field back to hidden.
|
||||
\Drupal::service('entity_display.repository')
|
||||
->getFormDisplay($entity_type, $this->field->getTargetBundle())
|
||||
->removeComponent($this->field->getName())
|
||||
->save();
|
||||
|
||||
// Create a new revision.
|
||||
$edit = ['revision' => TRUE];
|
||||
$this->drupalGet($entity_type . '/manage/' . $id . '/edit');
|
||||
$this->submitForm($edit, 'Save');
|
||||
|
||||
// Check that the expected value has been carried over to the new revision.
|
||||
$storage->resetCache([$id]);
|
||||
$entity = $storage->load($id);
|
||||
$this->assertEquals($value, $entity->{$field_name}->value, 'New revision has the expected value for the field with the Hidden widget');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the form display of the label for multi-value fields.
|
||||
*/
|
||||
public function testLabelOnMultiValueFields(): void {
|
||||
$user = $this->drupalCreateUser(['administer entity_test content']);
|
||||
$this->drupalLogin($user);
|
||||
|
||||
// Ensure that the 'bar' bundle exists, to avoid config validation errors.
|
||||
EntityTestHelper::createBundle('bar', entity_type: 'entity_test_base_field_display');
|
||||
|
||||
FieldStorageConfig::create([
|
||||
'entity_type' => 'entity_test_base_field_display',
|
||||
'field_name' => 'foo',
|
||||
'type' => 'text',
|
||||
'cardinality' => FieldStorageConfig::CARDINALITY_UNLIMITED,
|
||||
])->save();
|
||||
FieldConfig::create([
|
||||
'entity_type' => 'entity_test_base_field_display',
|
||||
'bundle' => 'bar',
|
||||
'field_name' => 'foo',
|
||||
// Set a dangerous label to test XSS filtering.
|
||||
'label' => "<script>alert('a configurable field');</script>",
|
||||
])->save();
|
||||
EntityFormDisplay::create([
|
||||
'targetEntityType' => 'entity_test_base_field_display',
|
||||
'bundle' => 'bar',
|
||||
'mode' => 'default',
|
||||
])->setComponent('foo', ['type' => 'text_textfield'])->enable()->save();
|
||||
|
||||
$entity = EntityTestBaseFieldDisplay::create(['type' => 'bar']);
|
||||
$entity->save();
|
||||
|
||||
$this->drupalGet('entity_test_base_field_display/manage/' . $entity->id());
|
||||
$this->assertSession()->statusCodeEquals(200);
|
||||
$this->assertSession()->pageTextContains('A field with multiple values');
|
||||
// Test if labels were XSS filtered.
|
||||
$this->assertSession()->assertEscaped("<script>alert('a configurable field');</script>");
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,117 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\field\Functional\FunctionalString;
|
||||
|
||||
use Drupal\entity_test\Entity\EntityTest;
|
||||
use Drupal\field\Entity\FieldConfig;
|
||||
use Drupal\field\Entity\FieldStorageConfig;
|
||||
use Drupal\Tests\BrowserTestBase;
|
||||
|
||||
/**
|
||||
* Tests the creation of string fields.
|
||||
*
|
||||
* @group text
|
||||
*/
|
||||
class StringFieldTest extends BrowserTestBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = ['entity_test', 'file'];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $defaultTheme = 'stark';
|
||||
|
||||
/**
|
||||
* A user without any special permissions.
|
||||
*
|
||||
* @var \Drupal\user\UserInterface
|
||||
*/
|
||||
protected $webUser;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp(): void {
|
||||
parent::setUp();
|
||||
|
||||
$this->webUser = $this->drupalCreateUser([
|
||||
'view test entity',
|
||||
'administer entity_test content',
|
||||
'access content',
|
||||
]);
|
||||
$this->drupalLogin($this->webUser);
|
||||
}
|
||||
|
||||
// Test fields.
|
||||
|
||||
/**
|
||||
* Tests widgets.
|
||||
*/
|
||||
public function testTextfieldWidgets(): void {
|
||||
$this->_testTextfieldWidgets('string', 'string_textfield');
|
||||
$this->_testTextfieldWidgets('string_long', 'string_textarea');
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function for testTextfieldWidgets().
|
||||
*/
|
||||
public function _testTextfieldWidgets($field_type, $widget_type): void {
|
||||
// Create a field.
|
||||
$field_name = $this->randomMachineName();
|
||||
$field_storage = FieldStorageConfig::create([
|
||||
'field_name' => $field_name,
|
||||
'entity_type' => 'entity_test',
|
||||
'type' => $field_type,
|
||||
]);
|
||||
$field_storage->save();
|
||||
FieldConfig::create([
|
||||
'field_storage' => $field_storage,
|
||||
'bundle' => 'entity_test',
|
||||
'label' => $this->randomMachineName() . '_label',
|
||||
])->save();
|
||||
|
||||
/** @var \Drupal\Core\Entity\EntityDisplayRepositoryInterface $display_repository */
|
||||
$display_repository = \Drupal::service('entity_display.repository');
|
||||
|
||||
$display_repository->getFormDisplay('entity_test', 'entity_test')
|
||||
->setComponent($field_name, [
|
||||
'type' => $widget_type,
|
||||
'settings' => [
|
||||
'placeholder' => 'A placeholder on ' . $widget_type,
|
||||
],
|
||||
])
|
||||
->save();
|
||||
$display_repository->getViewDisplay('entity_test', 'entity_test', 'full')
|
||||
->setComponent($field_name)
|
||||
->save();
|
||||
|
||||
// Display creation form.
|
||||
$this->drupalGet('entity_test/add');
|
||||
$this->assertSession()->fieldValueEquals("{$field_name}[0][value]", '');
|
||||
$this->assertSession()->fieldNotExists("{$field_name}[0][format]");
|
||||
$this->assertSession()->responseContains('placeholder="A placeholder on ' . $widget_type . '"');
|
||||
|
||||
// Submit with some value.
|
||||
$value = $this->randomMachineName();
|
||||
$edit = [
|
||||
"{$field_name}[0][value]" => $value,
|
||||
];
|
||||
$this->submitForm($edit, 'Save');
|
||||
preg_match('|entity_test/manage/(\d+)|', $this->getUrl(), $match);
|
||||
$id = $match[1];
|
||||
$this->assertSession()->pageTextContains('entity_test ' . $id . ' has been created.');
|
||||
|
||||
// Display the entity.
|
||||
$entity = EntityTest::load($id);
|
||||
$display = $display_repository->getViewDisplay($entity->getEntityTypeId(), $entity->bundle(), 'full');
|
||||
$content = $display->build($entity);
|
||||
$rendered_entity = \Drupal::service('renderer')->renderRoot($content);
|
||||
$this->assertStringContainsString($value, (string) $rendered_entity);
|
||||
}
|
||||
|
||||
}
|
||||
14
web/core/modules/field/tests/src/Functional/GenericTest.php
Normal file
14
web/core/modules/field/tests/src/Functional/GenericTest.php
Normal file
@ -0,0 +1,14 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\field\Functional;
|
||||
|
||||
use Drupal\Tests\system\Functional\Module\GenericModuleTestBase;
|
||||
|
||||
/**
|
||||
* Generic module test for field.
|
||||
*
|
||||
* @group field
|
||||
*/
|
||||
class GenericTest extends GenericModuleTestBase {}
|
||||
@ -0,0 +1,246 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\field\Functional;
|
||||
|
||||
use Drupal\Core\Entity\Entity\EntityFormDisplay;
|
||||
use Drupal\entity_test\Entity\EntityTest;
|
||||
use Drupal\entity_test\Entity\EntityTestBaseFieldDisplay;
|
||||
use Drupal\entity_test\EntityTestHelper;
|
||||
use Drupal\field\Entity\FieldConfig;
|
||||
use Drupal\field\Entity\FieldStorageConfig;
|
||||
|
||||
/**
|
||||
* Tests field form handling.
|
||||
*
|
||||
* @group field
|
||||
*/
|
||||
class MultipleWidgetFormTest extends FieldTestBase {
|
||||
|
||||
/**
|
||||
* Modules to install.
|
||||
*
|
||||
* Locale is installed so that TranslatableMarkup actually does something.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected static $modules = [
|
||||
'field_test',
|
||||
'options',
|
||||
'entity_test',
|
||||
'locale',
|
||||
'field_ui',
|
||||
];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $defaultTheme = 'stark';
|
||||
|
||||
/**
|
||||
* An array of values defining a field multiple.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $fieldStorageMultiple;
|
||||
|
||||
/**
|
||||
* An array of values defining a field.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $field;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp(): void {
|
||||
parent::setUp();
|
||||
|
||||
$web_user = $this->drupalCreateUser([
|
||||
'view test entity',
|
||||
'administer entity_test content',
|
||||
'administer entity_test fields',
|
||||
]);
|
||||
$this->drupalLogin($web_user);
|
||||
|
||||
$this->fieldStorageMultiple = [
|
||||
'field_name' => 'field_multiple',
|
||||
'entity_type' => 'entity_test',
|
||||
'type' => 'test_field',
|
||||
'cardinality' => 4,
|
||||
];
|
||||
|
||||
$this->field = [
|
||||
'entity_type' => 'entity_test',
|
||||
'bundle' => 'entity_test',
|
||||
'label' => $this->randomMachineName() . '_label',
|
||||
'description' => '[site:name]_description',
|
||||
'weight' => mt_rand(0, 127),
|
||||
'settings' => [
|
||||
'test_field_setting' => $this->randomMachineName(),
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests widgets handling multiple values.
|
||||
*/
|
||||
public function testFieldFormMultipleWidget(): void {
|
||||
// Create a field with fixed cardinality, configure the form to use a
|
||||
// "multiple" widget.
|
||||
$field_storage = $this->fieldStorageMultiple;
|
||||
$field_name = $field_storage['field_name'];
|
||||
$this->field['field_name'] = $field_name;
|
||||
FieldStorageConfig::create($field_storage)->save();
|
||||
FieldConfig::create($this->field)->save();
|
||||
$form = \Drupal::service('entity_display.repository')->getFormDisplay($this->field['entity_type'], $this->field['bundle'], 'default')
|
||||
->setComponent($field_name, [
|
||||
'type' => 'test_field_widget_multiple',
|
||||
]);
|
||||
$form->save();
|
||||
$session = $this->assertSession();
|
||||
|
||||
// Display creation form.
|
||||
$this->drupalGet('entity_test/add');
|
||||
$this->assertSession()->fieldValueEquals($field_name, '');
|
||||
|
||||
// Create entity with three values.
|
||||
$edit = [
|
||||
$field_name => '1, 2, 3',
|
||||
];
|
||||
$this->submitForm($edit, 'Save');
|
||||
preg_match('|entity_test/manage/(\d+)|', $this->getUrl(), $match);
|
||||
$id = $match[1];
|
||||
|
||||
// Check that the values were saved.
|
||||
$entity_init = EntityTest::load($id);
|
||||
$this->assertFieldValues($entity_init, $field_name, [1, 2, 3]);
|
||||
|
||||
// Display the form, check that the values are correctly filled in.
|
||||
$this->drupalGet('entity_test/manage/' . $id . '/edit');
|
||||
$this->assertSession()->fieldValueEquals($field_name, '1, 2, 3');
|
||||
|
||||
// Submit the form with more values than the field accepts.
|
||||
$edit = [$field_name => '1, 2, 3, 4, 5'];
|
||||
$this->submitForm($edit, 'Save');
|
||||
$this->assertSession()->pageTextContains('this field cannot hold more than 4 values');
|
||||
// Check that the field values were not submitted.
|
||||
$this->assertFieldValues($entity_init, $field_name, [1, 2, 3]);
|
||||
|
||||
// Check that Attributes are rendered on the multivalue container if it is
|
||||
// a multiple widget form.
|
||||
$form->setComponent($field_name, [
|
||||
'type' => 'entity_reference_autocomplete',
|
||||
])
|
||||
->save();
|
||||
$this->drupalGet('entity_test/manage/' . $id . '/edit');
|
||||
$name = str_replace('_', '-', $field_name);
|
||||
$session->responseContains('data-drupal-selector="edit-' . $name . '"');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the form display of the label for multi-value fields.
|
||||
*/
|
||||
public function testLabelOnMultiValueFields(): void {
|
||||
$user = $this->drupalCreateUser(['administer entity_test content']);
|
||||
$this->drupalLogin($user);
|
||||
|
||||
// Ensure that the 'bar' bundle exists, to avoid config validation errors.
|
||||
EntityTestHelper::createBundle('bar', entity_type: 'entity_test_base_field_display');
|
||||
|
||||
FieldStorageConfig::create([
|
||||
'entity_type' => 'entity_test_base_field_display',
|
||||
'field_name' => 'foo',
|
||||
'type' => 'text',
|
||||
'cardinality' => FieldStorageConfig::CARDINALITY_UNLIMITED,
|
||||
])->save();
|
||||
FieldConfig::create([
|
||||
'entity_type' => 'entity_test_base_field_display',
|
||||
'bundle' => 'bar',
|
||||
'field_name' => 'foo',
|
||||
// Set a dangerous label to test XSS filtering.
|
||||
'label' => "<script>alert('a configurable field');</script>",
|
||||
])->save();
|
||||
EntityFormDisplay::create([
|
||||
'targetEntityType' => 'entity_test_base_field_display',
|
||||
'bundle' => 'bar',
|
||||
'mode' => 'default',
|
||||
])->setComponent('foo', ['type' => 'text_textfield'])->enable()->save();
|
||||
|
||||
$entity = EntityTestBaseFieldDisplay::create(['type' => 'bar']);
|
||||
$entity->save();
|
||||
|
||||
$this->drupalGet('entity_test_base_field_display/manage/' . $entity->id());
|
||||
$this->assertSession()->statusCodeEquals(200);
|
||||
$this->assertSession()->pageTextContains('A field with multiple values');
|
||||
// Test if labels were XSS filtered.
|
||||
$this->assertSession()->assertEscaped("<script>alert('a configurable field');</script>");
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests hook_field_widget_complete_form_alter().
|
||||
*/
|
||||
public function testFieldFormMultipleWidgetAlter(): void {
|
||||
$this->widgetAlterTest('hook_field_widget_complete_form_alter', 'test_field_widget_multiple');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests hook_field_widget_complete_form_alter() with single value elements.
|
||||
*/
|
||||
public function testFieldFormMultipleWidgetAlterSingleValues(): void {
|
||||
$this->widgetAlterTest('hook_field_widget_complete_form_alter', 'test_field_widget_multiple_single_value');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests hook_field_widget_complete_WIDGET_TYPE_form_alter().
|
||||
*/
|
||||
public function testFieldFormMultipleWidgetTypeAlter(): void {
|
||||
$this->widgetAlterTest('hook_field_widget_complete_WIDGET_TYPE_form_alter', 'test_field_widget_multiple');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests hook_field_widget_complete_WIDGET_TYPE_form_alter() with single value elements.
|
||||
*/
|
||||
public function testFieldFormMultipleWidgetTypeAlterSingleValues(): void {
|
||||
$this->widgetAlterTest('hook_field_widget_complete_WIDGET_TYPE_form_alter', 'test_field_widget_multiple_single_value');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests widget alter hooks for a given hook name.
|
||||
*/
|
||||
protected function widgetAlterTest($hook, $widget) {
|
||||
// Create a field with fixed cardinality, configure the form to use a
|
||||
// "multiple" widget.
|
||||
$field_storage = $this->fieldStorageMultiple;
|
||||
$field_name = $field_storage['field_name'];
|
||||
$this->field['field_name'] = $field_name;
|
||||
FieldStorageConfig::create($field_storage)->save();
|
||||
FieldConfig::create($this->field)->save();
|
||||
|
||||
// Set a flag in state so that the hook implementations will run.
|
||||
\Drupal::state()->set("field_test.widget_alter_test", [
|
||||
'hook' => $hook,
|
||||
'field_name' => $field_name,
|
||||
'widget' => $widget,
|
||||
]);
|
||||
\Drupal::service('entity_display.repository')->getFormDisplay($this->field['entity_type'], $this->field['bundle'], 'default')
|
||||
->setComponent($field_name, [
|
||||
'type' => $widget,
|
||||
])
|
||||
->save();
|
||||
|
||||
// We need to rebuild hook information after setting the component through
|
||||
// the API.
|
||||
$this->rebuildAll();
|
||||
|
||||
$this->drupalGet('entity_test/add');
|
||||
$this->assertSession()->pageTextMatchesCount(1, '/From ' . $hook . '.* prefix on ' . $field_name . ' parent element\./');
|
||||
if ($widget === 'test_field_widget_multiple_single_value') {
|
||||
$suffix_text = "From $hook(): suffix on $field_name child element.";
|
||||
$this->assertEquals($field_storage['cardinality'], substr_count($this->getTextContent(), $suffix_text), "'$suffix_text' was found {$field_storage['cardinality']} times using widget $widget");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
231
web/core/modules/field/tests/src/Functional/NestedFormTest.php
Normal file
231
web/core/modules/field/tests/src/Functional/NestedFormTest.php
Normal file
@ -0,0 +1,231 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\field\Functional;
|
||||
|
||||
use Drupal\Core\Field\FieldStorageDefinitionInterface;
|
||||
use Drupal\field\Entity\FieldConfig;
|
||||
use Drupal\field\Entity\FieldStorageConfig;
|
||||
|
||||
/**
|
||||
* Tests field elements in nested forms.
|
||||
*
|
||||
* @group field
|
||||
*/
|
||||
class NestedFormTest extends FieldTestBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = ['field_test', 'entity_test'];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $defaultTheme = 'stark';
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected array $fieldStorageSingle;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected array $fieldStorageUnlimited;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected array $field;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp(): void {
|
||||
parent::setUp();
|
||||
|
||||
$web_user = $this->drupalCreateUser([
|
||||
'view test entity',
|
||||
'administer entity_test content',
|
||||
]);
|
||||
$this->drupalLogin($web_user);
|
||||
|
||||
$this->fieldStorageSingle = [
|
||||
'field_name' => 'field_single',
|
||||
'entity_type' => 'entity_test',
|
||||
'type' => 'test_field',
|
||||
];
|
||||
$this->fieldStorageUnlimited = [
|
||||
'field_name' => 'field_unlimited',
|
||||
'entity_type' => 'entity_test',
|
||||
'type' => 'test_field',
|
||||
'cardinality' => FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED,
|
||||
];
|
||||
|
||||
$this->field = [
|
||||
'entity_type' => 'entity_test',
|
||||
'bundle' => 'entity_test',
|
||||
'label' => $this->randomMachineName() . '_label',
|
||||
'description' => '[site:name]_description',
|
||||
'weight' => mt_rand(0, 127),
|
||||
'settings' => [
|
||||
'test_field_setting' => $this->randomMachineName(),
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests Field API form integration within a subform.
|
||||
*/
|
||||
public function testNestedFieldForm(): void {
|
||||
/** @var \Drupal\Core\Entity\EntityDisplayRepositoryInterface $display_repository */
|
||||
$display_repository = \Drupal::service('entity_display.repository');
|
||||
|
||||
// Add two fields on the 'entity_test'
|
||||
FieldStorageConfig::create($this->fieldStorageSingle)->save();
|
||||
FieldStorageConfig::create($this->fieldStorageUnlimited)->save();
|
||||
$this->field['field_name'] = 'field_single';
|
||||
$this->field['label'] = 'Single field';
|
||||
FieldConfig::create($this->field)->save();
|
||||
$display_repository->getFormDisplay($this->field['entity_type'], $this->field['bundle'])
|
||||
->setComponent($this->field['field_name'])
|
||||
->save();
|
||||
$this->field['field_name'] = 'field_unlimited';
|
||||
$this->field['label'] = 'Unlimited field';
|
||||
FieldConfig::create($this->field)->save();
|
||||
$display_repository->getFormDisplay($this->field['entity_type'], $this->field['bundle'])
|
||||
->setComponent($this->field['field_name'])
|
||||
->save();
|
||||
|
||||
// Create two entities.
|
||||
$entity_type = 'entity_test';
|
||||
$storage = $this->container->get('entity_type.manager')
|
||||
->getStorage($entity_type);
|
||||
|
||||
$entity_1 = $storage->create(['id' => 1]);
|
||||
$entity_1->enforceIsNew();
|
||||
$entity_1->field_single->value = 1;
|
||||
$entity_1->field_unlimited->value = 2;
|
||||
$entity_1->save();
|
||||
|
||||
$entity_2 = $storage->create(['id' => 2]);
|
||||
$entity_2->enforceIsNew();
|
||||
$entity_2->field_single->value = 10;
|
||||
$entity_2->field_unlimited->value = 11;
|
||||
$entity_2->save();
|
||||
|
||||
// Display the 'combined form'.
|
||||
$this->drupalGet('test-entity/nested/1/2');
|
||||
$this->assertSession()->fieldValueEquals('field_single[0][value]', 1);
|
||||
$this->assertSession()->fieldValueEquals('field_unlimited[0][value]', 2);
|
||||
$this->assertSession()->fieldValueEquals('entity_2[field_single][0][value]', 10);
|
||||
$this->assertSession()->fieldValueEquals('entity_2[field_unlimited][0][value]', 11);
|
||||
|
||||
// Submit the form and check that the entities are updated accordingly.
|
||||
$edit = [
|
||||
'field_single[0][value]' => 1,
|
||||
'field_unlimited[0][value]' => 2,
|
||||
'field_unlimited[1][value]' => 3,
|
||||
'entity_2[field_single][0][value]' => 11,
|
||||
'entity_2[field_unlimited][0][value]' => 12,
|
||||
'entity_2[field_unlimited][1][value]' => 13,
|
||||
];
|
||||
$this->submitForm($edit, 'Save');
|
||||
$entity_1 = $storage->load(1);
|
||||
$entity_2 = $storage->load(2);
|
||||
$this->assertFieldValues($entity_1, 'field_single', [1]);
|
||||
$this->assertFieldValues($entity_1, 'field_unlimited', [2, 3]);
|
||||
$this->assertFieldValues($entity_2, 'field_single', [11]);
|
||||
$this->assertFieldValues($entity_2, 'field_unlimited', [12, 13]);
|
||||
|
||||
// Submit invalid values and check that errors are reported on the
|
||||
// correct widgets.
|
||||
$edit = [
|
||||
'field_unlimited[1][value]' => -1,
|
||||
];
|
||||
$this->drupalGet('test-entity/nested/1/2');
|
||||
$this->submitForm($edit, 'Save');
|
||||
$this->assertSession()->pageTextContains("Unlimited field does not accept the value -1.");
|
||||
// Entity 1: check that the error was flagged on the correct element.
|
||||
$error_field = $this->assertSession()->fieldExists('edit-field-unlimited-1-value');
|
||||
$this->assertTrue($error_field->hasClass('error'));
|
||||
$edit = [
|
||||
'entity_2[field_unlimited][1][value]' => -1,
|
||||
];
|
||||
$this->drupalGet('test-entity/nested/1/2');
|
||||
$this->submitForm($edit, 'Save');
|
||||
$this->assertSession()->pageTextContains("Unlimited field does not accept the value -1.");
|
||||
// Entity 2: check that the error was flagged on the correct element.
|
||||
$error_field = $this->assertSession()->fieldExists('edit-entity-2-field-unlimited-1-value');
|
||||
$this->assertTrue($error_field->hasClass('error'));
|
||||
|
||||
// Test that reordering works on both entities.
|
||||
$edit = [
|
||||
'field_unlimited[0][_weight]' => 0,
|
||||
'field_unlimited[1][_weight]' => -1,
|
||||
'entity_2[field_unlimited][0][_weight]' => 0,
|
||||
'entity_2[field_unlimited][1][_weight]' => -1,
|
||||
];
|
||||
$this->drupalGet('test-entity/nested/1/2');
|
||||
$this->submitForm($edit, 'Save');
|
||||
$this->assertFieldValues($entity_1, 'field_unlimited', [3, 2]);
|
||||
$this->assertFieldValues($entity_2, 'field_unlimited', [13, 12]);
|
||||
|
||||
// Test the 'add more' buttons.
|
||||
// 'Add more' button in the first entity:
|
||||
$this->drupalGet('test-entity/nested/1/2');
|
||||
$this->submitForm([], 'field_unlimited_add_more');
|
||||
$this->assertSession()->fieldValueEquals('field_unlimited[0][value]', 3);
|
||||
$this->assertSession()->fieldValueEquals('field_unlimited[1][value]', 2);
|
||||
$this->assertSession()->fieldValueEquals('field_unlimited[2][value]', '');
|
||||
$this->assertSession()->fieldValueEquals('field_unlimited[3][value]', '');
|
||||
// 'Add more' button in the first entity (changing field values):
|
||||
$edit = [
|
||||
'entity_2[field_unlimited][0][value]' => 13,
|
||||
'entity_2[field_unlimited][1][value]' => 14,
|
||||
'entity_2[field_unlimited][2][value]' => 15,
|
||||
];
|
||||
$this->submitForm($edit, 'entity_2_field_unlimited_add_more');
|
||||
$this->assertSession()->fieldValueEquals('entity_2[field_unlimited][0][value]', 13);
|
||||
$this->assertSession()->fieldValueEquals('entity_2[field_unlimited][1][value]', 14);
|
||||
$this->assertSession()->fieldValueEquals('entity_2[field_unlimited][2][value]', 15);
|
||||
$this->assertSession()->fieldValueEquals('entity_2[field_unlimited][3][value]', '');
|
||||
// Save the form and check values are saved correctly.
|
||||
$this->submitForm([], 'Save');
|
||||
$this->assertFieldValues($entity_1, 'field_unlimited', [3, 2]);
|
||||
$this->assertFieldValues($entity_2, 'field_unlimited', [13, 14, 15]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests entity level validation within subforms.
|
||||
*/
|
||||
public function testNestedEntityFormEntityLevelValidation(): void {
|
||||
// Create two entities.
|
||||
$storage = $this->container->get('entity_type.manager')
|
||||
->getStorage('entity_test_constraints');
|
||||
|
||||
$entity_1 = $storage->create();
|
||||
$entity_1->save();
|
||||
|
||||
$entity_2 = $storage->create();
|
||||
$entity_2->save();
|
||||
|
||||
$page = $this->getSession()->getPage();
|
||||
$assert_session = $this->assertSession();
|
||||
|
||||
// Display the 'combined form'.
|
||||
$this->drupalGet("test-entity-constraints/nested/{$entity_1->id()}/{$entity_2->id()}");
|
||||
$assert_session->hiddenFieldValueEquals('entity_2[changed]', (string) \Drupal::time()->getRequestTime());
|
||||
|
||||
// Submit the form and check that the entities are updated accordingly.
|
||||
$assert_session->hiddenFieldExists('entity_2[changed]')
|
||||
->setValue(\Drupal::time()->getRequestTime() - 86400);
|
||||
$page->pressButton('Save');
|
||||
|
||||
$elements = $this->cssSelect('.entity-2.error');
|
||||
$this->assertCount(1, $elements, 'The whole nested entity form has been correctly flagged with an error class.');
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,461 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\field\Functional\Number;
|
||||
|
||||
use Drupal\field\Entity\FieldConfig;
|
||||
use Drupal\field\Entity\FieldStorageConfig;
|
||||
use Drupal\field\FieldConfigInterface;
|
||||
use Drupal\Tests\BrowserTestBase;
|
||||
|
||||
/**
|
||||
* Tests the creation of numeric fields.
|
||||
*
|
||||
* @group field
|
||||
*/
|
||||
class NumberFieldTest extends BrowserTestBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = ['node', 'entity_test', 'field_ui'];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $defaultTheme = 'stark';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp(): void {
|
||||
parent::setUp();
|
||||
$this->drupalLogin($this->drupalCreateUser([
|
||||
'view test entity',
|
||||
'administer entity_test content',
|
||||
'administer content types',
|
||||
'administer node fields',
|
||||
'administer node display',
|
||||
'bypass node access',
|
||||
'administer entity_test fields',
|
||||
]));
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests decimal field.
|
||||
*/
|
||||
public function testNumberDecimalField(): void {
|
||||
// Create a field with settings to validate.
|
||||
$field_name = $this->randomMachineName();
|
||||
FieldStorageConfig::create([
|
||||
'field_name' => $field_name,
|
||||
'entity_type' => 'entity_test',
|
||||
'type' => 'decimal',
|
||||
'settings' => ['precision' => 8, 'scale' => 4],
|
||||
])->save();
|
||||
FieldConfig::create([
|
||||
'field_name' => $field_name,
|
||||
'entity_type' => 'entity_test',
|
||||
'bundle' => 'entity_test',
|
||||
])->save();
|
||||
|
||||
/** @var \Drupal\Core\Entity\EntityDisplayRepositoryInterface $display_repository */
|
||||
$display_repository = \Drupal::service('entity_display.repository');
|
||||
|
||||
$display_repository->getFormDisplay('entity_test', 'entity_test')
|
||||
->setComponent($field_name, [
|
||||
'type' => 'number',
|
||||
'settings' => [
|
||||
'placeholder' => '0.00',
|
||||
],
|
||||
])
|
||||
->save();
|
||||
$display_repository->getViewDisplay('entity_test', 'entity_test')
|
||||
->setComponent($field_name, [
|
||||
'type' => 'number_decimal',
|
||||
])
|
||||
->save();
|
||||
|
||||
// Display creation form.
|
||||
$this->drupalGet('entity_test/add');
|
||||
$this->assertSession()->fieldValueEquals("{$field_name}[0][value]", '');
|
||||
$this->assertSession()->responseContains('placeholder="0.00"');
|
||||
|
||||
// Submit a signed decimal value within the allowed precision and scale.
|
||||
$value = '-1234.5678';
|
||||
$edit = [
|
||||
"{$field_name}[0][value]" => $value,
|
||||
];
|
||||
$this->submitForm($edit, 'Save');
|
||||
preg_match('|entity_test/manage/(\d+)|', $this->getUrl(), $match);
|
||||
$id = $match[1];
|
||||
$this->assertSession()->pageTextContains('entity_test ' . $id . ' has been created.');
|
||||
$this->assertSession()->responseContains($value);
|
||||
|
||||
// Try to create entries with more than one decimal separator; assert fail.
|
||||
$wrong_entries = [
|
||||
'3.14.159',
|
||||
'0..45469',
|
||||
'..4589',
|
||||
'6.459.52',
|
||||
'6.3..25',
|
||||
];
|
||||
|
||||
foreach ($wrong_entries as $wrong_entry) {
|
||||
$this->drupalGet('entity_test/add');
|
||||
$edit = [
|
||||
"{$field_name}[0][value]" => $wrong_entry,
|
||||
];
|
||||
$this->submitForm($edit, 'Save');
|
||||
$this->assertSession()->pageTextContains("{$field_name} must be a number.");
|
||||
}
|
||||
|
||||
// Try to create entries with minus sign not in the first position.
|
||||
$wrong_entries = [
|
||||
'3-3',
|
||||
'4-',
|
||||
'1.3-',
|
||||
'1.2-4',
|
||||
'-10-10',
|
||||
];
|
||||
|
||||
foreach ($wrong_entries as $wrong_entry) {
|
||||
$this->drupalGet('entity_test/add');
|
||||
$edit = [
|
||||
"{$field_name}[0][value]" => $wrong_entry,
|
||||
];
|
||||
$this->submitForm($edit, 'Save');
|
||||
$this->assertSession()->pageTextContains("{$field_name} must be a number.");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests integer field.
|
||||
*/
|
||||
public function testNumberIntegerField(): void {
|
||||
$minimum = rand(-4000, -2000);
|
||||
$maximum = rand(2000, 4000);
|
||||
|
||||
// Create a field with settings to validate.
|
||||
$field_name = $this->randomMachineName();
|
||||
$storage = FieldStorageConfig::create([
|
||||
'field_name' => $field_name,
|
||||
'entity_type' => 'entity_test',
|
||||
'type' => 'integer',
|
||||
]);
|
||||
$storage->save();
|
||||
|
||||
FieldConfig::create([
|
||||
'field_name' => $field_name,
|
||||
'entity_type' => 'entity_test',
|
||||
'bundle' => 'entity_test',
|
||||
'settings' => [
|
||||
'min' => $minimum,
|
||||
'max' => $maximum,
|
||||
'prefix' => 'ThePrefix',
|
||||
],
|
||||
])->save();
|
||||
|
||||
/** @var \Drupal\Core\Entity\EntityDisplayRepositoryInterface $display_repository */
|
||||
$display_repository = \Drupal::service('entity_display.repository');
|
||||
|
||||
$display_repository->getFormDisplay('entity_test', 'entity_test')
|
||||
->setComponent($field_name, [
|
||||
'type' => 'number',
|
||||
'settings' => [
|
||||
'placeholder' => '4',
|
||||
],
|
||||
])
|
||||
->save();
|
||||
$display_repository->getViewDisplay('entity_test', 'entity_test')
|
||||
->setComponent($field_name, [
|
||||
'type' => 'number_integer',
|
||||
'settings' => [
|
||||
'prefix_suffix' => FALSE,
|
||||
],
|
||||
])
|
||||
->save();
|
||||
|
||||
// Check the storage schema.
|
||||
$expected = [
|
||||
'columns' => [
|
||||
'value' => [
|
||||
'type' => 'int',
|
||||
'unsigned' => '',
|
||||
'size' => 'normal',
|
||||
],
|
||||
],
|
||||
'unique keys' => [],
|
||||
'indexes' => [],
|
||||
'foreign keys' => [],
|
||||
];
|
||||
$this->assertEquals($expected, $storage->getSchema());
|
||||
|
||||
// Display creation form.
|
||||
$this->drupalGet('entity_test/add');
|
||||
$this->assertSession()->fieldValueEquals("{$field_name}[0][value]", '');
|
||||
$this->assertSession()->responseContains('placeholder="4"');
|
||||
|
||||
// Submit a valid integer
|
||||
$value = rand($minimum, $maximum);
|
||||
$edit = [
|
||||
"{$field_name}[0][value]" => $value,
|
||||
];
|
||||
$this->submitForm($edit, 'Save');
|
||||
preg_match('|entity_test/manage/(\d+)|', $this->getUrl(), $match);
|
||||
$id = $match[1];
|
||||
$this->assertSession()->pageTextContains('entity_test ' . $id . ' has been created.');
|
||||
|
||||
// Try to set a value below the minimum value
|
||||
$this->drupalGet('entity_test/add');
|
||||
$edit = [
|
||||
"{$field_name}[0][value]" => $minimum - 1,
|
||||
];
|
||||
$this->submitForm($edit, 'Save');
|
||||
$this->assertSession()->pageTextContains("{$field_name} must be higher than or equal to {$minimum}.");
|
||||
|
||||
// Try to set a decimal value
|
||||
$this->drupalGet('entity_test/add');
|
||||
$edit = [
|
||||
"{$field_name}[0][value]" => 1.5,
|
||||
];
|
||||
$this->submitForm($edit, 'Save');
|
||||
$this->assertSession()->pageTextContains("{$field_name} is not a valid number.");
|
||||
|
||||
// Try to set a value above the maximum value
|
||||
$this->drupalGet('entity_test/add');
|
||||
$edit = [
|
||||
"{$field_name}[0][value]" => $maximum + 1,
|
||||
];
|
||||
$this->submitForm($edit, 'Save');
|
||||
$this->assertSession()->pageTextContains("{$field_name} must be lower than or equal to {$maximum}.");
|
||||
|
||||
// Try to set a wrong integer value.
|
||||
$this->drupalGet('entity_test/add');
|
||||
$edit = [
|
||||
"{$field_name}[0][value]" => '20-40',
|
||||
];
|
||||
$this->submitForm($edit, 'Save');
|
||||
$this->assertSession()->pageTextContains("{$field_name} must be a number.");
|
||||
|
||||
// Test with valid entries.
|
||||
$valid_entries = [
|
||||
'-1234',
|
||||
'0',
|
||||
'1234',
|
||||
];
|
||||
|
||||
foreach ($valid_entries as $valid_entry) {
|
||||
$this->drupalGet('entity_test/add');
|
||||
$edit = [
|
||||
"{$field_name}[0][value]" => $valid_entry,
|
||||
];
|
||||
$this->submitForm($edit, 'Save');
|
||||
preg_match('|entity_test/manage/(\d+)|', $this->getUrl(), $match);
|
||||
$id = $match[1];
|
||||
$this->assertSession()->pageTextContains('entity_test ' . $id . ' has been created.');
|
||||
$this->assertSession()->responseContains($valid_entry);
|
||||
// Verify that the "content" attribute is not present since the Prefix is
|
||||
// not being displayed.
|
||||
$this->assertSession()->elementNotExists('xpath', '//div[@content="' . $valid_entry . '"]');
|
||||
}
|
||||
|
||||
// Test for the content attribute when a Prefix is displayed. Presumably
|
||||
// this also tests for the attribute when a Suffix is displayed.
|
||||
$display_repository->getViewDisplay('entity_test', 'entity_test')
|
||||
->setComponent($field_name, [
|
||||
'type' => 'number_integer',
|
||||
'settings' => [
|
||||
'prefix_suffix' => TRUE,
|
||||
],
|
||||
])
|
||||
->save();
|
||||
$integer_value = '123';
|
||||
$this->drupalGet('entity_test/add');
|
||||
$edit = [
|
||||
"{$field_name}[0][value]" => $integer_value,
|
||||
];
|
||||
$this->submitForm($edit, 'Save');
|
||||
preg_match('|entity_test/manage/(\d+)|', $this->getUrl(), $match);
|
||||
$id = $match[1];
|
||||
$this->assertSession()->pageTextContains('entity_test ' . $id . ' has been created.');
|
||||
$this->drupalGet('entity_test/' . $id);
|
||||
// Verify that the "content" attribute has been set to the value of the
|
||||
// field, and the prefix is being displayed.
|
||||
$this->assertSession()->elementTextContains('xpath', '//div[@content="' . $integer_value . '"]', 'ThePrefix' . $integer_value);
|
||||
|
||||
$field_configuration_url = 'entity_test/structure/entity_test/fields/entity_test.entity_test.' . $field_name;
|
||||
$this->drupalGet($field_configuration_url);
|
||||
|
||||
// Tests Number validation messages.
|
||||
$edit = [
|
||||
'settings[min]' => 10,
|
||||
'settings[max]' => 8,
|
||||
];
|
||||
$this->submitForm($edit, 'Save settings');
|
||||
$this->assertSession()->pageTextContains("The minimum value must be less than or equal to {$edit['settings[max]']}.");
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests float field.
|
||||
*/
|
||||
public function testNumberFloatField(): void {
|
||||
// Create a field with settings to validate.
|
||||
$field_name = $this->randomMachineName();
|
||||
FieldStorageConfig::create([
|
||||
'field_name' => $field_name,
|
||||
'entity_type' => 'entity_test',
|
||||
'type' => 'float',
|
||||
])->save();
|
||||
|
||||
FieldConfig::create([
|
||||
'field_name' => $field_name,
|
||||
'entity_type' => 'entity_test',
|
||||
'bundle' => 'entity_test',
|
||||
])->save();
|
||||
|
||||
/** @var \Drupal\Core\Entity\EntityDisplayRepositoryInterface $display_repository */
|
||||
$display_repository = \Drupal::service('entity_display.repository');
|
||||
|
||||
$display_repository->getFormDisplay('entity_test', 'entity_test')
|
||||
->setComponent($field_name, [
|
||||
'type' => 'number',
|
||||
'settings' => [
|
||||
'placeholder' => '0.00',
|
||||
],
|
||||
])
|
||||
->save();
|
||||
|
||||
$display_repository->getViewDisplay('entity_test', 'entity_test')
|
||||
->setComponent($field_name, [
|
||||
'type' => 'number_decimal',
|
||||
])
|
||||
->save();
|
||||
|
||||
// Display creation form.
|
||||
$this->drupalGet('entity_test/add');
|
||||
$this->assertSession()->fieldValueEquals("{$field_name}[0][value]", '');
|
||||
$this->assertSession()->responseContains('placeholder="0.00"');
|
||||
|
||||
// Submit a signed decimal value within the allowed precision and scale.
|
||||
$value = -1234.5678;
|
||||
$edit = [
|
||||
"{$field_name}[0][value]" => $value,
|
||||
];
|
||||
$this->submitForm($edit, 'Save');
|
||||
preg_match('|entity_test/manage/(\d+)|', $this->getUrl(), $match);
|
||||
$id = $match[1];
|
||||
$this->assertSession()->pageTextContains('entity_test ' . $id . ' has been created.');
|
||||
|
||||
// Ensure that the 'number_decimal' formatter displays the number with the
|
||||
// expected rounding.
|
||||
$this->drupalGet('entity_test/' . $id);
|
||||
$this->assertSession()->responseContains(round($value, 2));
|
||||
|
||||
// Try to create entries with more than one decimal separator; assert fail.
|
||||
$wrong_entries = [
|
||||
'3.14.159',
|
||||
'0..45469',
|
||||
'..4589',
|
||||
'6.459.52',
|
||||
'6.3..25',
|
||||
];
|
||||
|
||||
foreach ($wrong_entries as $wrong_entry) {
|
||||
$this->drupalGet('entity_test/add');
|
||||
$edit = [
|
||||
"{$field_name}[0][value]" => $wrong_entry,
|
||||
];
|
||||
$this->submitForm($edit, 'Save');
|
||||
$this->assertSession()->pageTextContains("{$field_name} must be a number.");
|
||||
}
|
||||
|
||||
// Try to create entries with minus sign not in the first position.
|
||||
$wrong_entries = [
|
||||
'3-3',
|
||||
'4-',
|
||||
'1.3-',
|
||||
'1.2-4',
|
||||
'-10-10',
|
||||
];
|
||||
|
||||
foreach ($wrong_entries as $wrong_entry) {
|
||||
$this->drupalGet('entity_test/add');
|
||||
$edit = [
|
||||
"{$field_name}[0][value]" => $wrong_entry,
|
||||
];
|
||||
$this->submitForm($edit, 'Save');
|
||||
$this->assertSession()->pageTextContains("{$field_name} must be a number.");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests setting minimum values through the interface.
|
||||
*/
|
||||
public function testMinimumValues(): void {
|
||||
// Create a float field.
|
||||
$field_name = $this->randomMachineName();
|
||||
FieldStorageConfig::create([
|
||||
'field_name' => $field_name,
|
||||
'entity_type' => 'entity_test',
|
||||
'type' => 'float',
|
||||
])->save();
|
||||
|
||||
$field = FieldConfig::create([
|
||||
'field_name' => $field_name,
|
||||
'entity_type' => 'entity_test',
|
||||
'bundle' => 'entity_test',
|
||||
]);
|
||||
$field->save();
|
||||
|
||||
// Set the minimum value to a float value.
|
||||
$this->assertSetMinimumValue($field, 0.0001);
|
||||
// Set the minimum value to an integer value.
|
||||
$this->assertSetMinimumValue($field, 1);
|
||||
|
||||
// Create a decimal field.
|
||||
$field_name = $this->randomMachineName();
|
||||
FieldStorageConfig::create([
|
||||
'field_name' => $field_name,
|
||||
'entity_type' => 'entity_test',
|
||||
'type' => 'decimal',
|
||||
])->save();
|
||||
|
||||
$field = FieldConfig::create([
|
||||
'field_name' => $field_name,
|
||||
'entity_type' => 'entity_test',
|
||||
'bundle' => 'entity_test',
|
||||
]);
|
||||
$field->save();
|
||||
|
||||
// Set the minimum value to a decimal value.
|
||||
$this->assertSetMinimumValue($field, 0.1);
|
||||
// Set the minimum value to an integer value.
|
||||
$this->assertSetMinimumValue($field, 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function to set the minimum value of a field.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
public function assertSetMinimumValue(FieldConfigInterface $field, $minimum_value): void {
|
||||
$field_configuration_url = 'entity_test/structure/entity_test/fields/entity_test.entity_test.' . $field->getName();
|
||||
|
||||
// Set the minimum value.
|
||||
$edit = [
|
||||
'settings[min]' => $minimum_value,
|
||||
];
|
||||
$this->drupalGet($field_configuration_url);
|
||||
$this->submitForm($edit, 'Save settings');
|
||||
// Check if an error message is shown.
|
||||
$this->assertSession()->pageTextNotContains("Minimum is not a valid number.");
|
||||
// Check if a success message is shown.
|
||||
$this->assertSession()->pageTextContains("Saved {$field->getLabel()} configuration.");
|
||||
// Check if the minimum value was actually set.
|
||||
$this->drupalGet($field_configuration_url);
|
||||
$this->assertSession()->fieldValueEquals('edit-settings-min', $minimum_value);
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,142 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\field\Functional;
|
||||
|
||||
use Drupal\field\Entity\FieldConfig;
|
||||
use Drupal\field\Entity\FieldStorageConfig;
|
||||
use Drupal\Tests\BrowserTestBase;
|
||||
use Drupal\Tests\Traits\Core\CronRunTrait;
|
||||
|
||||
/**
|
||||
* Tests the behavior of a field module after being disabled and re-enabled.
|
||||
*
|
||||
* @group field
|
||||
*/
|
||||
class ReEnableModuleFieldTest extends BrowserTestBase {
|
||||
|
||||
use CronRunTrait;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = [
|
||||
'field',
|
||||
'node',
|
||||
// We use telephone module instead of test_field because test_field is
|
||||
// hidden and does not display on the admin/modules page.
|
||||
'telephone',
|
||||
];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $defaultTheme = 'stark';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp(): void {
|
||||
parent::setUp();
|
||||
|
||||
$this->drupalCreateContentType(['type' => 'article']);
|
||||
$this->drupalLogin($this->drupalCreateUser([
|
||||
'create article content',
|
||||
'edit own article content',
|
||||
]));
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the behavior of a field module after being disabled and re-enabled.
|
||||
*
|
||||
* @see field_system_info_alter()
|
||||
*/
|
||||
public function testReEnabledField(): void {
|
||||
// Add a telephone field to the article content type.
|
||||
$field_storage = FieldStorageConfig::create([
|
||||
'field_name' => 'field_telephone',
|
||||
'entity_type' => 'node',
|
||||
'type' => 'telephone',
|
||||
]);
|
||||
$field_storage->save();
|
||||
FieldConfig::create([
|
||||
'field_storage' => $field_storage,
|
||||
'bundle' => 'article',
|
||||
'label' => 'Telephone Number',
|
||||
])->save();
|
||||
|
||||
/** @var \Drupal\Core\Entity\EntityDisplayRepositoryInterface $display_repository */
|
||||
$display_repository = \Drupal::service('entity_display.repository');
|
||||
$display_repository->getFormDisplay('node', 'article')
|
||||
->setComponent('field_telephone', [
|
||||
'type' => 'telephone_default',
|
||||
'settings' => [
|
||||
'placeholder' => '123-456-7890',
|
||||
],
|
||||
])
|
||||
->save();
|
||||
|
||||
$display_repository->getViewDisplay('node', 'article')
|
||||
->setComponent('field_telephone', [
|
||||
'type' => 'telephone_link',
|
||||
'weight' => 1,
|
||||
])
|
||||
->save();
|
||||
|
||||
// Display the article node form and verify the telephone widget is present.
|
||||
$this->drupalGet('node/add/article');
|
||||
$this->assertSession()->fieldValueEquals("field_telephone[0][value]", '');
|
||||
|
||||
// Submit an article node with a telephone field so data exist for the
|
||||
// field.
|
||||
$edit = [
|
||||
'title[0][value]' => $this->randomMachineName(),
|
||||
'field_telephone[0][value]' => "123456789",
|
||||
];
|
||||
$this->submitForm($edit, 'Save');
|
||||
$this->assertSession()->responseContains('<a href="tel:123456789">');
|
||||
|
||||
// Test that the module can't be uninstalled from the UI while there is data
|
||||
// for its fields.
|
||||
$admin_user = $this->drupalCreateUser([
|
||||
'access administration pages',
|
||||
'administer modules',
|
||||
]);
|
||||
$this->drupalLogin($admin_user);
|
||||
$this->drupalGet('admin/modules/uninstall');
|
||||
$this->assertSession()->pageTextContains("The Telephone number field type is used in the following field: node.field_telephone");
|
||||
|
||||
// Add another telephone field to a different entity type in order to test
|
||||
// the message for the case when multiple fields are blocking the
|
||||
// uninstallation of a module.
|
||||
$field_storage2 = FieldStorageConfig::create([
|
||||
'field_name' => 'field_telephone_2',
|
||||
'entity_type' => 'user',
|
||||
'type' => 'telephone',
|
||||
]);
|
||||
$field_storage2->save();
|
||||
FieldConfig::create([
|
||||
'field_storage' => $field_storage2,
|
||||
'bundle' => 'user',
|
||||
'label' => 'User Telephone Number',
|
||||
])->save();
|
||||
|
||||
$this->drupalGet('admin/modules/uninstall');
|
||||
$this->assertSession()->pageTextContains("The Telephone number field type is used in the following fields: node.field_telephone, user.field_telephone_2");
|
||||
|
||||
// Delete both fields.
|
||||
$field_storage->delete();
|
||||
$field_storage2->delete();
|
||||
|
||||
$this->drupalGet('admin/modules/uninstall');
|
||||
$this->assertSession()->pageTextContains('Uninstall');
|
||||
$this->assertSession()->pageTextContains('Fields pending deletion');
|
||||
$this->cronRun();
|
||||
$this->drupalGet('admin/modules/uninstall');
|
||||
$this->assertSession()->pageTextContains('Uninstall');
|
||||
$this->assertSession()->pageTextNotContains("The Telephone number field type is used in the following field: node.field_telephone");
|
||||
$this->assertSession()->pageTextNotContains('Fields pending deletion');
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,31 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\field\Functional\Rest;
|
||||
|
||||
use Drupal\Tests\rest\Functional\AnonResourceTestTrait;
|
||||
|
||||
/**
|
||||
* @group rest
|
||||
*/
|
||||
class FieldConfigJsonAnonTest extends FieldConfigResourceTestBase {
|
||||
|
||||
use AnonResourceTestTrait;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $format = 'json';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $mimeType = 'application/json';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $defaultTheme = 'stark';
|
||||
|
||||
}
|
||||
@ -0,0 +1,41 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\field\Functional\Rest;
|
||||
|
||||
use Drupal\Tests\rest\Functional\BasicAuthResourceTestTrait;
|
||||
|
||||
/**
|
||||
* @group rest
|
||||
*/
|
||||
class FieldConfigJsonBasicAuthTest extends FieldConfigResourceTestBase {
|
||||
|
||||
use BasicAuthResourceTestTrait;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = ['basic_auth'];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $defaultTheme = 'stark';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $format = 'json';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $mimeType = 'application/json';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $auth = 'basic_auth';
|
||||
|
||||
}
|
||||
@ -0,0 +1,36 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\field\Functional\Rest;
|
||||
|
||||
use Drupal\Tests\rest\Functional\CookieResourceTestTrait;
|
||||
|
||||
/**
|
||||
* @group rest
|
||||
*/
|
||||
class FieldConfigJsonCookieTest extends FieldConfigResourceTestBase {
|
||||
|
||||
use CookieResourceTestTrait;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $format = 'json';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $mimeType = 'application/json';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $auth = 'cookie';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $defaultTheme = 'stark';
|
||||
|
||||
}
|
||||
@ -0,0 +1,123 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\field\Functional\Rest;
|
||||
|
||||
use Drupal\field\Entity\FieldConfig;
|
||||
use Drupal\field\Entity\FieldStorageConfig;
|
||||
use Drupal\node\Entity\NodeType;
|
||||
use Drupal\Tests\rest\Functional\EntityResource\ConfigEntityResourceTestBase;
|
||||
|
||||
/**
|
||||
* Resource test base for the FieldConfig entity.
|
||||
*/
|
||||
abstract class FieldConfigResourceTestBase extends ConfigEntityResourceTestBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = ['field', 'field_ui', 'node'];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $entityTypeId = 'field_config';
|
||||
|
||||
/**
|
||||
* @var \Drupal\field\FieldConfigInterface
|
||||
*/
|
||||
protected $entity;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUpAuthorization($method) {
|
||||
$this->grantPermissionsToTestedRole(['administer node fields']);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function createEntity() {
|
||||
$camelids = NodeType::create([
|
||||
'name' => 'Camelids',
|
||||
'type' => 'camelids',
|
||||
]);
|
||||
$camelids->save();
|
||||
|
||||
$field_storage = FieldStorageConfig::create([
|
||||
'field_name' => 'field_llama',
|
||||
'entity_type' => 'node',
|
||||
'type' => 'text',
|
||||
]);
|
||||
$field_storage->save();
|
||||
|
||||
$entity = FieldConfig::create([
|
||||
'field_storage' => $field_storage,
|
||||
'bundle' => 'camelids',
|
||||
]);
|
||||
$entity->save();
|
||||
|
||||
return $entity;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function getExpectedNormalizedEntity() {
|
||||
return [
|
||||
'bundle' => 'camelids',
|
||||
'default_value' => [],
|
||||
'default_value_callback' => '',
|
||||
'dependencies' => [
|
||||
'config' => [
|
||||
'field.storage.node.field_llama',
|
||||
'node.type.camelids',
|
||||
],
|
||||
'module' => [
|
||||
'text',
|
||||
],
|
||||
],
|
||||
'description' => '',
|
||||
'entity_type' => 'node',
|
||||
'field_name' => 'field_llama',
|
||||
'field_type' => 'text',
|
||||
'id' => 'node.camelids.field_llama',
|
||||
'label' => 'field_llama',
|
||||
'langcode' => 'en',
|
||||
'required' => FALSE,
|
||||
'settings' => [
|
||||
'allowed_formats' => [],
|
||||
],
|
||||
'status' => TRUE,
|
||||
'translatable' => TRUE,
|
||||
'uuid' => $this->entity->uuid(),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function getNormalizedPostEntity() {
|
||||
// @todo Update in https://www.drupal.org/node/2300677.
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function getExpectedCacheContexts() {
|
||||
return [
|
||||
'user.permissions',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function getExpectedUnauthorizedAccessMessage($method) {
|
||||
return "The 'administer node fields' permission is required.";
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,33 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\field\Functional\Rest;
|
||||
|
||||
use Drupal\Tests\rest\Functional\AnonResourceTestTrait;
|
||||
use Drupal\Tests\rest\Functional\EntityResource\XmlEntityNormalizationQuirksTrait;
|
||||
|
||||
/**
|
||||
* @group rest
|
||||
*/
|
||||
class FieldConfigXmlAnonTest extends FieldConfigResourceTestBase {
|
||||
|
||||
use AnonResourceTestTrait;
|
||||
use XmlEntityNormalizationQuirksTrait;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $format = 'xml';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $mimeType = 'text/xml; charset=UTF-8';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $defaultTheme = 'stark';
|
||||
|
||||
}
|
||||
@ -0,0 +1,43 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\field\Functional\Rest;
|
||||
|
||||
use Drupal\Tests\rest\Functional\BasicAuthResourceTestTrait;
|
||||
use Drupal\Tests\rest\Functional\EntityResource\XmlEntityNormalizationQuirksTrait;
|
||||
|
||||
/**
|
||||
* @group rest
|
||||
*/
|
||||
class FieldConfigXmlBasicAuthTest extends FieldConfigResourceTestBase {
|
||||
|
||||
use BasicAuthResourceTestTrait;
|
||||
use XmlEntityNormalizationQuirksTrait;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = ['basic_auth'];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $defaultTheme = 'stark';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $format = 'xml';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $mimeType = 'text/xml; charset=UTF-8';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $auth = 'basic_auth';
|
||||
|
||||
}
|
||||
@ -0,0 +1,38 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\field\Functional\Rest;
|
||||
|
||||
use Drupal\Tests\rest\Functional\CookieResourceTestTrait;
|
||||
use Drupal\Tests\rest\Functional\EntityResource\XmlEntityNormalizationQuirksTrait;
|
||||
|
||||
/**
|
||||
* @group rest
|
||||
*/
|
||||
class FieldConfigXmlCookieTest extends FieldConfigResourceTestBase {
|
||||
|
||||
use CookieResourceTestTrait;
|
||||
use XmlEntityNormalizationQuirksTrait;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $format = 'xml';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $mimeType = 'text/xml; charset=UTF-8';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $auth = 'cookie';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $defaultTheme = 'stark';
|
||||
|
||||
}
|
||||
@ -0,0 +1,31 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\field\Functional\Rest;
|
||||
|
||||
use Drupal\Tests\rest\Functional\AnonResourceTestTrait;
|
||||
|
||||
/**
|
||||
* @group rest
|
||||
*/
|
||||
class FieldStorageConfigJsonAnonTest extends FieldStorageConfigResourceTestBase {
|
||||
|
||||
use AnonResourceTestTrait;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $format = 'json';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $mimeType = 'application/json';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $defaultTheme = 'stark';
|
||||
|
||||
}
|
||||
@ -0,0 +1,41 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\field\Functional\Rest;
|
||||
|
||||
use Drupal\Tests\rest\Functional\BasicAuthResourceTestTrait;
|
||||
|
||||
/**
|
||||
* @group rest
|
||||
*/
|
||||
class FieldStorageConfigJsonBasicAuthTest extends FieldStorageConfigResourceTestBase {
|
||||
|
||||
use BasicAuthResourceTestTrait;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = ['basic_auth'];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $defaultTheme = 'stark';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $format = 'json';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $mimeType = 'application/json';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $auth = 'basic_auth';
|
||||
|
||||
}
|
||||
@ -0,0 +1,36 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\field\Functional\Rest;
|
||||
|
||||
use Drupal\Tests\rest\Functional\CookieResourceTestTrait;
|
||||
|
||||
/**
|
||||
* @group rest
|
||||
*/
|
||||
class FieldStorageConfigJsonCookieTest extends FieldStorageConfigResourceTestBase {
|
||||
|
||||
use CookieResourceTestTrait;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $format = 'json';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $mimeType = 'application/json';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $auth = 'cookie';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $defaultTheme = 'stark';
|
||||
|
||||
}
|
||||
@ -0,0 +1,97 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\field\Functional\Rest;
|
||||
|
||||
use Drupal\field\Entity\FieldStorageConfig;
|
||||
use Drupal\Tests\rest\Functional\EntityResource\ConfigEntityResourceTestBase;
|
||||
|
||||
/**
|
||||
* Resource test base for the FieldStorageConfig entity.
|
||||
*/
|
||||
abstract class FieldStorageConfigResourceTestBase extends ConfigEntityResourceTestBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = ['field_ui', 'node'];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $entityTypeId = 'field_storage_config';
|
||||
|
||||
/**
|
||||
* @var \Drupal\field\FieldConfigStorage
|
||||
*/
|
||||
protected $entity;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUpAuthorization($method) {
|
||||
$this->grantPermissionsToTestedRole(['administer node fields']);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function createEntity() {
|
||||
$field_storage = FieldStorageConfig::create([
|
||||
'field_name' => 'true_llama',
|
||||
'entity_type' => 'node',
|
||||
'type' => 'boolean',
|
||||
]);
|
||||
$field_storage->save();
|
||||
return $field_storage;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function getExpectedNormalizedEntity() {
|
||||
return [
|
||||
'cardinality' => 1,
|
||||
'custom_storage' => FALSE,
|
||||
'dependencies' => [
|
||||
'module' => ['node'],
|
||||
],
|
||||
'entity_type' => 'node',
|
||||
'field_name' => 'true_llama',
|
||||
'id' => 'node.true_llama',
|
||||
'indexes' => [],
|
||||
'langcode' => 'en',
|
||||
'locked' => FALSE,
|
||||
'module' => 'core',
|
||||
'persist_with_no_fields' => FALSE,
|
||||
'settings' => [],
|
||||
'status' => TRUE,
|
||||
'translatable' => TRUE,
|
||||
'type' => 'boolean',
|
||||
'uuid' => $this->entity->uuid(),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function getNormalizedPostEntity() {
|
||||
// @todo Update in https://www.drupal.org/node/2300677.
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function getExpectedUnauthorizedAccessMessage($method) {
|
||||
switch ($method) {
|
||||
case 'GET':
|
||||
return "The 'administer node fields' permission is required.";
|
||||
|
||||
default:
|
||||
return parent::getExpectedUnauthorizedAccessMessage($method);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,33 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\field\Functional\Rest;
|
||||
|
||||
use Drupal\Tests\rest\Functional\AnonResourceTestTrait;
|
||||
use Drupal\Tests\rest\Functional\EntityResource\XmlEntityNormalizationQuirksTrait;
|
||||
|
||||
/**
|
||||
* @group rest
|
||||
*/
|
||||
class FieldStorageConfigXmlAnonTest extends FieldStorageConfigResourceTestBase {
|
||||
|
||||
use AnonResourceTestTrait;
|
||||
use XmlEntityNormalizationQuirksTrait;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $format = 'xml';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $mimeType = 'text/xml; charset=UTF-8';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $defaultTheme = 'stark';
|
||||
|
||||
}
|
||||
@ -0,0 +1,43 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\field\Functional\Rest;
|
||||
|
||||
use Drupal\Tests\rest\Functional\BasicAuthResourceTestTrait;
|
||||
use Drupal\Tests\rest\Functional\EntityResource\XmlEntityNormalizationQuirksTrait;
|
||||
|
||||
/**
|
||||
* @group rest
|
||||
*/
|
||||
class FieldStorageConfigXmlBasicAuthTest extends FieldStorageConfigResourceTestBase {
|
||||
|
||||
use BasicAuthResourceTestTrait;
|
||||
use XmlEntityNormalizationQuirksTrait;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = ['basic_auth'];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $defaultTheme = 'stark';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $format = 'xml';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $mimeType = 'text/xml; charset=UTF-8';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $auth = 'basic_auth';
|
||||
|
||||
}
|
||||
@ -0,0 +1,38 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\field\Functional\Rest;
|
||||
|
||||
use Drupal\Tests\rest\Functional\CookieResourceTestTrait;
|
||||
use Drupal\Tests\rest\Functional\EntityResource\XmlEntityNormalizationQuirksTrait;
|
||||
|
||||
/**
|
||||
* @group rest
|
||||
*/
|
||||
class FieldStorageConfigXmlCookieTest extends FieldStorageConfigResourceTestBase {
|
||||
|
||||
use CookieResourceTestTrait;
|
||||
use XmlEntityNormalizationQuirksTrait;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $format = 'xml';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $mimeType = 'text/xml; charset=UTF-8';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $auth = 'cookie';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $defaultTheme = 'stark';
|
||||
|
||||
}
|
||||
@ -0,0 +1,152 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\field\Functional;
|
||||
|
||||
use Drupal\field\Entity\FieldStorageConfig;
|
||||
use Drupal\field\Entity\FieldConfig;
|
||||
use Drupal\field_test\FieldTestHelper;
|
||||
use Drupal\language\Entity\ConfigurableLanguage;
|
||||
|
||||
/**
|
||||
* Tests multilingual fields logic that require a full environment.
|
||||
*
|
||||
* @group field
|
||||
*/
|
||||
class TranslationWebTest extends FieldTestBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = ['language', 'field_test', 'entity_test'];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $defaultTheme = 'stark';
|
||||
|
||||
/**
|
||||
* The name of the field to use in this test.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $fieldName;
|
||||
|
||||
/**
|
||||
* The name of the entity type to use in this test.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $entityTypeId = 'entity_test_mulrev';
|
||||
|
||||
/**
|
||||
* The field storage to use in this test.
|
||||
*
|
||||
* @var \Drupal\field\Entity\FieldStorageConfig
|
||||
*/
|
||||
protected $fieldStorage;
|
||||
|
||||
/**
|
||||
* The field to use in this test.
|
||||
*
|
||||
* @var \Drupal\field\Entity\FieldConfig
|
||||
*/
|
||||
protected $field;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp(): void {
|
||||
parent::setUp();
|
||||
|
||||
$this->fieldName = $this->randomMachineName() . '_field_name';
|
||||
|
||||
$field_storage = [
|
||||
'field_name' => $this->fieldName,
|
||||
'entity_type' => $this->entityTypeId,
|
||||
'type' => 'test_field',
|
||||
'cardinality' => 4,
|
||||
];
|
||||
FieldStorageConfig::create($field_storage)->save();
|
||||
$this->fieldStorage = FieldStorageConfig::load($this->entityTypeId . '.' . $this->fieldName);
|
||||
|
||||
$field = [
|
||||
'field_storage' => $this->fieldStorage,
|
||||
'bundle' => $this->entityTypeId,
|
||||
];
|
||||
FieldConfig::create($field)->save();
|
||||
$this->field = FieldConfig::load($this->entityTypeId . '.' . $field['bundle'] . '.' . $this->fieldName);
|
||||
|
||||
\Drupal::service('entity_display.repository')
|
||||
->getFormDisplay($this->entityTypeId, $this->entityTypeId)
|
||||
->setComponent($this->fieldName)
|
||||
->save();
|
||||
|
||||
for ($i = 0; $i < 3; ++$i) {
|
||||
ConfigurableLanguage::create([
|
||||
'id' => 'l' . $i,
|
||||
'label' => $this->randomString(),
|
||||
])->save();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests field translations when creating a new revision.
|
||||
*/
|
||||
public function testFieldFormTranslationRevisions(): void {
|
||||
$web_user = $this->drupalCreateUser([
|
||||
'view test entity',
|
||||
'administer entity_test content',
|
||||
]);
|
||||
$this->drupalLogin($web_user);
|
||||
|
||||
// Prepare the field translations.
|
||||
FieldTestHelper::entityInfoTranslatable($this->entityTypeId, TRUE);
|
||||
$entity = $this->container->get('entity_type.manager')
|
||||
->getStorage($this->entityTypeId)
|
||||
->create();
|
||||
$available_langcodes = array_flip(array_keys($this->container->get('language_manager')->getLanguages()));
|
||||
$field_name = $this->fieldStorage->getName();
|
||||
|
||||
// Store the field translations.
|
||||
ksort($available_langcodes);
|
||||
$entity->langcode->value = key($available_langcodes);
|
||||
foreach ($available_langcodes as $langcode => $value) {
|
||||
$translation = $entity->hasTranslation($langcode) ? $entity->getTranslation($langcode) : $entity->addTranslation($langcode);
|
||||
$translation->{$field_name}->value = $value + 1;
|
||||
}
|
||||
$entity->save();
|
||||
|
||||
// Create a new revision.
|
||||
$edit = [
|
||||
"{$field_name}[0][value]" => $entity->{$field_name}->value,
|
||||
'revision' => TRUE,
|
||||
];
|
||||
$this->drupalGet($this->entityTypeId . '/manage/' . $entity->id() . '/edit');
|
||||
$this->submitForm($edit, 'Save');
|
||||
|
||||
// Check translation revisions.
|
||||
$this->checkTranslationRevisions($entity->id(), $entity->getRevisionId(), $available_langcodes);
|
||||
$this->checkTranslationRevisions($entity->id(), $entity->getRevisionId() + 1, $available_langcodes);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests translation revisions.
|
||||
*
|
||||
* Check if the field translation attached to the entity revision identified
|
||||
* by the passed arguments were correctly stored.
|
||||
*/
|
||||
private function checkTranslationRevisions($id, $revision_id, $available_langcodes): void {
|
||||
$field_name = $this->fieldStorage->getName();
|
||||
/** @var \Drupal\Core\Entity\RevisionableStorageInterface $storage */
|
||||
$storage = $this->container->get('entity_type.manager')
|
||||
->getStorage($this->entityTypeId);
|
||||
$entity = $storage->loadRevision($revision_id);
|
||||
foreach ($available_langcodes as $langcode => $value) {
|
||||
$passed = $entity->getTranslation($langcode)->{$field_name}->value == $value + 1;
|
||||
$this->assertTrue($passed, "The $langcode translation for revision {$entity->getRevisionId()} was correctly stored");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,87 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\field\Functional\Views;
|
||||
|
||||
use Drupal\field\Entity\FieldConfig;
|
||||
use Drupal\node\Entity\NodeType;
|
||||
use Drupal\Tests\views\Functional\ViewTestBase;
|
||||
use Drupal\field\Entity\FieldStorageConfig;
|
||||
|
||||
/**
|
||||
* Provides some helper methods for testing fieldapi integration into views.
|
||||
*
|
||||
* @todo Test on a generic entity not on a node. What has to be tested:
|
||||
* - Make sure that every wanted field is added to the according entity type.
|
||||
* - Make sure the joins are done correctly.
|
||||
* - Use basic fields and make sure that the full wanted object is built.
|
||||
* - Use relationships between different entity types, for example node and
|
||||
* the node author(user).
|
||||
*/
|
||||
abstract class FieldTestBase extends ViewTestBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = ['node', 'field_test_views'];
|
||||
|
||||
/**
|
||||
* Stores the field definitions used by the test.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public $fieldStorages;
|
||||
|
||||
/**
|
||||
* Stores the fields of the field storage.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public $fields;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp($import_test_views = TRUE, $modules = ['field_test_views']): void {
|
||||
parent::setUp($import_test_views, $modules);
|
||||
|
||||
// Ensure the page node type exists.
|
||||
NodeType::create([
|
||||
'type' => 'page',
|
||||
'name' => 'page',
|
||||
])->save();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets up field storages for testing.
|
||||
*/
|
||||
public function setUpFieldStorages($amount = 3, $type = 'string') {
|
||||
// Create three fields.
|
||||
$field_names = [];
|
||||
for ($i = 0; $i < $amount; $i++) {
|
||||
$field_names[$i] = 'field_name_' . $i;
|
||||
$this->fieldStorages[$i] = FieldStorageConfig::create([
|
||||
'field_name' => $field_names[$i],
|
||||
'entity_type' => 'node',
|
||||
'type' => $type,
|
||||
]);
|
||||
$this->fieldStorages[$i]->save();
|
||||
}
|
||||
return $field_names;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets up fields for a given bundle.
|
||||
*/
|
||||
public function setUpFields($bundle = 'page') {
|
||||
foreach ($this->fieldStorages as $key => $field_storage) {
|
||||
$this->fields[$key] = FieldConfig::create([
|
||||
'field_storage' => $field_storage,
|
||||
'bundle' => $bundle,
|
||||
]);
|
||||
$this->fields[$key]->save();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,166 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\field\Functional\Views;
|
||||
|
||||
use Drupal\field\Entity\FieldConfig;
|
||||
use Drupal\field\Entity\FieldStorageConfig;
|
||||
use Drupal\views\Views;
|
||||
|
||||
/**
|
||||
* Tests the UI of the field handler.
|
||||
*
|
||||
* @group field
|
||||
* @see \Drupal\field\Plugin\views\field\Field
|
||||
*/
|
||||
class FieldUITest extends FieldTestBase {
|
||||
|
||||
/**
|
||||
* Views used by this test.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public static $testViews = ['test_view_fieldapi'];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = ['views_ui'];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $defaultTheme = 'stark';
|
||||
|
||||
/**
|
||||
* A user with the 'administer views' permission.
|
||||
*
|
||||
* @var \Drupal\user\UserInterface
|
||||
*/
|
||||
protected $account;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp($import_test_views = TRUE, $modules = ['field_test_views']): void {
|
||||
parent::setUp($import_test_views, $modules);
|
||||
|
||||
$this->account = $this->drupalCreateUser(['administer views']);
|
||||
$this->drupalLogin($this->account);
|
||||
|
||||
$this->setUpFieldStorages(1, 'text');
|
||||
$this->setUpFields();
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests basic field handler settings in the UI.
|
||||
*/
|
||||
public function testHandlerUI(): void {
|
||||
$url = "admin/structure/views/nojs/handler/test_view_fieldapi/default/field/field_name_0";
|
||||
$this->drupalGet($url);
|
||||
|
||||
// Tests the available formatter options.
|
||||
$options = $this->assertSession()->selectExists('edit-options-type')->findAll('css', 'option');
|
||||
$options = array_map(function ($item) {
|
||||
return $item->getValue();
|
||||
}, $options);
|
||||
$this->assertEqualsCanonicalizing(['text_default', 'text_trimmed'], $options);
|
||||
|
||||
$this->submitForm(['options[type]' => 'text_trimmed'], 'Apply');
|
||||
|
||||
$this->drupalGet($url);
|
||||
$this->assertTrue($this->assertSession()->optionExists('edit-options-type', 'text_trimmed')->isSelected());
|
||||
|
||||
$random_number = rand(100, 400);
|
||||
$this->submitForm(['options[settings][trim_length]' => $random_number], 'Apply');
|
||||
$this->drupalGet($url);
|
||||
$this->assertSession()->fieldValueEquals('options[settings][trim_length]', $random_number);
|
||||
|
||||
// Save the view and test whether the settings are saved.
|
||||
$this->drupalGet('admin/structure/views/view/test_view_fieldapi');
|
||||
$this->submitForm([], 'Save');
|
||||
$view = Views::getView('test_view_fieldapi');
|
||||
$view->initHandlers();
|
||||
$this->assertEquals('text_trimmed', $view->field['field_name_0']->options['type']);
|
||||
$this->assertEquals($random_number, $view->field['field_name_0']->options['settings']['trim_length']);
|
||||
|
||||
// Now change the formatter back to 'default' which doesn't have any
|
||||
// settings. We want to ensure that the settings are empty then.
|
||||
$edit['options[type]'] = 'text_default';
|
||||
$this->drupalGet('admin/structure/views/nojs/handler/test_view_fieldapi/default/field/field_name_0');
|
||||
$this->submitForm($edit, 'Apply');
|
||||
$this->drupalGet('admin/structure/views/view/test_view_fieldapi');
|
||||
$this->submitForm([], 'Save');
|
||||
$view = Views::getView('test_view_fieldapi');
|
||||
$view->initHandlers();
|
||||
$this->assertEquals('text_default', $view->field['field_name_0']->options['type']);
|
||||
$this->assertEquals([], $view->field['field_name_0']->options['settings']);
|
||||
|
||||
// Ensure that the view depends on the field storage.
|
||||
$dependencies = \Drupal::service('config.manager')->findConfigEntityDependencies('config', [$this->fieldStorages[0]->getConfigDependencyName()]);
|
||||
$this->assertTrue(isset($dependencies['views.view.test_view_fieldapi']), 'The view is dependent on the field storage.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the basic field handler form when aggregation is enabled.
|
||||
*/
|
||||
public function testHandlerUIAggregation(): void {
|
||||
// Enable aggregation.
|
||||
$edit = ['group_by' => '1'];
|
||||
$this->drupalGet('admin/structure/views/nojs/display/test_view_fieldapi/default/group_by');
|
||||
$this->submitForm($edit, 'Apply');
|
||||
|
||||
$url = "admin/structure/views/nojs/handler/test_view_fieldapi/default/field/field_name_0";
|
||||
$this->drupalGet($url);
|
||||
$this->assertSession()->statusCodeEquals(200);
|
||||
|
||||
// Test the click sort column options.
|
||||
// Tests the available formatter options.
|
||||
$options = $this->assertSession()->selectExists('edit-options-click-sort-column')->findAll('css', 'option');
|
||||
$options = array_map(function ($item) {
|
||||
return $item->getValue();
|
||||
}, $options);
|
||||
$this->assertEqualsCanonicalizing(['format', 'value'], $options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests adding a boolean field filter handler.
|
||||
*/
|
||||
public function testBooleanFilterHandler(): void {
|
||||
// Create a boolean field.
|
||||
$field_name = 'field_boolean';
|
||||
$field_storage = FieldStorageConfig::create([
|
||||
'field_name' => $field_name,
|
||||
'entity_type' => 'node',
|
||||
'type' => 'boolean',
|
||||
]);
|
||||
$field_storage->save();
|
||||
$field = FieldConfig::create([
|
||||
'field_storage' => $field_storage,
|
||||
'bundle' => 'page',
|
||||
]);
|
||||
$field->save();
|
||||
|
||||
$url = "admin/structure/views/nojs/add-handler/test_view_fieldapi/default/filter";
|
||||
$this->drupalGet($url);
|
||||
$this->submitForm([
|
||||
'name[node__' . $field_name . '.' . $field_name . '_value]' => TRUE,
|
||||
], 'Add and configure filter criteria');
|
||||
$this->assertSession()->statusCodeEquals(200);
|
||||
// Verify that using a boolean field as a filter also results in using the
|
||||
// boolean plugin.
|
||||
$this->assertSession()->elementTextEquals('xpath', '//label[@for="edit-options-value-1"]', 'True');
|
||||
$this->assertSession()->elementTextEquals('xpath', '//label[@for="edit-options-value-0"]', 'False');
|
||||
|
||||
// Expose the filter and see if the 'Any' option is added and if we can save
|
||||
// it.
|
||||
$this->submitForm([], 'Expose filter');
|
||||
$this->assertSession()->elementTextEquals('xpath', '//label[@for="edit-options-value-all"]', '- Any -');
|
||||
$this->submitForm(['options[value]' => 'All', 'options[expose][required]' => FALSE], 'Apply');
|
||||
$this->submitForm([], 'Save');
|
||||
$this->drupalGet('/admin/structure/views/nojs/handler/test_view_fieldapi/default/filter/field_boolean_value');
|
||||
$this->assertSession()->checkboxChecked('edit-options-value-all');
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,134 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\field\FunctionalJavascript\Boolean;
|
||||
|
||||
use Drupal\field\Entity\FieldConfig;
|
||||
use Drupal\field\Entity\FieldStorageConfig;
|
||||
use Drupal\FunctionalJavascriptTests\WebDriverTestBase;
|
||||
|
||||
/**
|
||||
* Tests the Boolean field formatter settings.
|
||||
*
|
||||
* @group field
|
||||
*/
|
||||
class BooleanFormatterSettingsTest extends WebDriverTestBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = ['field', 'field_ui', 'text', 'node', 'user'];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $defaultTheme = 'stark';
|
||||
|
||||
/**
|
||||
* The name of the entity bundle that is created in the test.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $bundle;
|
||||
|
||||
/**
|
||||
* The name of the Boolean field to use for testing.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $fieldName;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp(): void {
|
||||
parent::setUp();
|
||||
|
||||
// Create a content type. Use Node because it has Field UI pages that work.
|
||||
$type_name = $this->randomMachineName(8) . '_test';
|
||||
$type = $this->drupalCreateContentType(['name' => $type_name, 'type' => $type_name]);
|
||||
$this->bundle = $type->id();
|
||||
|
||||
$admin_user = $this->drupalCreateUser([
|
||||
'access content',
|
||||
'administer content types',
|
||||
'administer node fields',
|
||||
'administer node display',
|
||||
'bypass node access',
|
||||
'administer nodes',
|
||||
]);
|
||||
$this->drupalLogin($admin_user);
|
||||
|
||||
$this->fieldName = $this->randomMachineName(8);
|
||||
|
||||
$field_storage = FieldStorageConfig::create([
|
||||
'field_name' => $this->fieldName,
|
||||
'entity_type' => 'node',
|
||||
'type' => 'boolean',
|
||||
]);
|
||||
$field_storage->save();
|
||||
|
||||
$instance = FieldConfig::create([
|
||||
'field_storage' => $field_storage,
|
||||
'bundle' => $this->bundle,
|
||||
'label' => $this->randomMachineName(),
|
||||
]);
|
||||
$instance->save();
|
||||
|
||||
$display = \Drupal::service('entity_display.repository')->getViewDisplay('node', $this->bundle)
|
||||
->setComponent($this->fieldName, [
|
||||
'type' => 'boolean',
|
||||
'settings' => [],
|
||||
]);
|
||||
$display->save();
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the formatter settings page for the Boolean formatter.
|
||||
*/
|
||||
public function testBooleanFormatterSettings(): void {
|
||||
// List the options we expect to see on the settings form. Omit the one
|
||||
// with the Unicode check/x characters, which does not appear to work
|
||||
// well in BrowserTestBase.
|
||||
$options = [
|
||||
'Yes / No',
|
||||
'True / False',
|
||||
'On / Off',
|
||||
'Enabled / Disabled',
|
||||
'1 / 0',
|
||||
'Custom',
|
||||
];
|
||||
|
||||
// For several different values of the field settings, test that the
|
||||
// options, including default, are shown correctly.
|
||||
$settings = [
|
||||
['Yes', 'No'],
|
||||
['On', 'Off'],
|
||||
['TRUE', 'FALSE'],
|
||||
];
|
||||
|
||||
$assert_session = $this->assertSession();
|
||||
foreach ($settings as $values) {
|
||||
// Set up the field settings.
|
||||
$this->drupalGet('admin/structure/types/manage/' . $this->bundle . '/fields/node.' . $this->bundle . '.' . $this->fieldName);
|
||||
$this->submitForm([
|
||||
'settings[on_label]' => $values[0],
|
||||
'settings[off_label]' => $values[1],
|
||||
], 'Save settings');
|
||||
|
||||
$assert_session->waitForText('Saved ' . $this->fieldName . ' configuration.');
|
||||
// Open the Manage Display page and trigger the field settings form.
|
||||
$this->drupalGet('admin/structure/types/manage/' . $this->bundle . '/display');
|
||||
$this->getSession()->getPage()->pressButton($this->fieldName . '_settings_edit');
|
||||
$assert_session->waitForElement('css', '.ajax-new-content');
|
||||
|
||||
// Test that the settings options are present in the correct format.
|
||||
foreach ($options as $string) {
|
||||
$assert_session->pageTextContains($string);
|
||||
}
|
||||
$assert_session->pageTextContains("Field settings ({$values[0]} / {$values[1]})");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,380 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\field\FunctionalJavascript\EntityReference;
|
||||
|
||||
use Behat\Mink\Element\NodeElement;
|
||||
use Drupal\Core\Url;
|
||||
use Drupal\FunctionalJavascriptTests\WebDriverTestBase;
|
||||
use Drupal\Tests\field_ui\Traits\FieldUiJSTestTrait;
|
||||
use Drupal\Tests\field_ui\Traits\FieldUiTestTrait;
|
||||
use Drupal\field\Entity\FieldConfig;
|
||||
use Drupal\field\Entity\FieldStorageConfig;
|
||||
|
||||
/**
|
||||
* Tests for the administrative UI.
|
||||
*
|
||||
* @group entity_reference
|
||||
*/
|
||||
class EntityReferenceAdminTest extends WebDriverTestBase {
|
||||
|
||||
use FieldUiTestTrait;
|
||||
use FieldUiJSTestTrait;
|
||||
|
||||
/**
|
||||
* Modules to install.
|
||||
*
|
||||
* Enable path module to ensure that the selection handler does not fail for
|
||||
* entities with a path field.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected static $modules = [
|
||||
'node',
|
||||
'block',
|
||||
'field_ui',
|
||||
'path',
|
||||
'taxonomy',
|
||||
'block',
|
||||
'views_ui',
|
||||
];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $defaultTheme = 'stark';
|
||||
|
||||
/**
|
||||
* The name of the content type created for testing purposes.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $type;
|
||||
|
||||
/**
|
||||
* Name of a second content type to be used as a target of entity references.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $targetType;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp(): void {
|
||||
parent::setUp();
|
||||
$this->drupalPlaceBlock('system_breadcrumb_block');
|
||||
$this->drupalPlaceBlock('local_actions_block');
|
||||
|
||||
// Create a content type, with underscores.
|
||||
$type_name = $this->randomMachineName(8) . '_test';
|
||||
$type = $this->drupalCreateContentType(['name' => $type_name, 'type' => $type_name]);
|
||||
$this->type = $type->id();
|
||||
|
||||
// Create a second content type, to be a target for entity reference fields.
|
||||
$type_name = $this->randomMachineName(8) . '_test';
|
||||
$type = $this->drupalCreateContentType(['name' => $type_name, 'type' => $type_name]);
|
||||
$this->targetType = $type->id();
|
||||
|
||||
// Change the title field label.
|
||||
$fields = \Drupal::service('entity_field.manager')
|
||||
->getFieldDefinitions('node', $type->id());
|
||||
$fields['title']->getConfig($type->id())
|
||||
->setLabel($type->id() . ' title')->save();
|
||||
|
||||
// Add text field to the second content type.
|
||||
FieldStorageConfig::create([
|
||||
'field_name' => 'field_text',
|
||||
'entity_type' => 'node',
|
||||
'type' => 'text',
|
||||
'entity_types' => ['node'],
|
||||
])->save();
|
||||
FieldConfig::create([
|
||||
'label' => 'Text Field',
|
||||
'field_name' => 'field_text',
|
||||
'entity_type' => 'node',
|
||||
'bundle' => $this->targetType,
|
||||
'settings' => [],
|
||||
'required' => FALSE,
|
||||
])->save();
|
||||
|
||||
// Create test user.
|
||||
$admin_user = $this->drupalCreateUser([
|
||||
'access content',
|
||||
'administer node fields',
|
||||
'administer node display',
|
||||
'administer views',
|
||||
'create ' . $this->type . ' content',
|
||||
'edit own ' . $this->type . ' content',
|
||||
]);
|
||||
$this->drupalLogin($admin_user);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the Entity Reference Admin UI.
|
||||
*/
|
||||
public function testFieldAdminHandler(): void {
|
||||
$bundle_path = 'admin/structure/types/manage/' . $this->type;
|
||||
|
||||
$page = $this->getSession()->getPage();
|
||||
/** @var \Drupal\FunctionalJavascriptTests\JSWebAssert $assert_session */
|
||||
$assert_session = $this->assertSession();
|
||||
|
||||
// First step: 'Add new field' on the 'Manage fields' page.
|
||||
$this->drupalGet($bundle_path . '/fields');
|
||||
$this->clickLink('Create a new field');
|
||||
$this->assertSession()->assertWaitOnAjaxRequest();
|
||||
// Check if the commonly referenced entity types appear in the list.
|
||||
$this->clickLink('Reference');
|
||||
$this->assertSession()->assertWaitOnAjaxRequest();
|
||||
$this->assertTrue($assert_session->waitForText('Choose a field type'));
|
||||
$this->assertSession()->elementExists('css', "[name='field_options_wrapper'][value='field_ui:entity_reference:node']");
|
||||
$this->assertSession()->elementExists('css', "[name='field_options_wrapper'][value='field_ui:entity_reference:user']");
|
||||
|
||||
$this->assertSession()->buttonExists('Change field type')->press();
|
||||
$this->assertSession()->assertWaitOnAjaxRequest();
|
||||
$this->fieldUIAddNewFieldJS(NULL, 'test', 'Test', 'entity_reference', FALSE);
|
||||
|
||||
// Node should be selected by default.
|
||||
$this->assertSession()->fieldValueEquals('field_storage[subform][settings][target_type]', 'node');
|
||||
|
||||
// Check that all entity types can be referenced.
|
||||
$this->assertFieldSelectOptions('field_storage[subform][settings][target_type]', array_keys(\Drupal::entityTypeManager()->getDefinitions()));
|
||||
|
||||
// The base handler should be selected by default.
|
||||
$this->assertSession()->fieldValueEquals('settings[handler]', 'default:node');
|
||||
|
||||
// The base handler settings should be displayed.
|
||||
$entity_type_id = 'node';
|
||||
// Check that the type label is correctly displayed.
|
||||
$assert_session->pageTextContains('Content type');
|
||||
// Check that sort options are not yet visible.
|
||||
$sort_by = $page->findField('settings[handler_settings][sort][field]');
|
||||
$this->assertNotEmpty($sort_by);
|
||||
$this->assertFalse($sort_by->isVisible(), 'The "sort by" options are hidden.');
|
||||
$bundles = $this->container->get('entity_type.bundle.info')->getBundleInfo($entity_type_id);
|
||||
foreach ($bundles as $bundle_name => $bundle_info) {
|
||||
$this->assertSession()->fieldExists('settings[handler_settings][target_bundles][' . $bundle_name . ']');
|
||||
}
|
||||
|
||||
reset($bundles);
|
||||
|
||||
// Initially, no bundles are selected so no sort options are available.
|
||||
$this->assertFieldSelectOptions('settings[handler_settings][sort][field]', ['_none']);
|
||||
|
||||
// Select this bundle so that standard sort options are available.
|
||||
$page->findField('settings[handler_settings][target_bundles][' . $this->type . ']')->setValue($this->type);
|
||||
$assert_session->assertWaitOnAjaxRequest();
|
||||
// Test that a non-translatable base field is a sort option.
|
||||
$assert_session->optionExists('settings[handler_settings][sort][field]', 'nid');
|
||||
// Test that a translatable base field is a sort option.
|
||||
$assert_session->optionExists('settings[handler_settings][sort][field]', 'title');
|
||||
// Test that a configurable field is a sort option.
|
||||
$assert_session->optionExists('settings[handler_settings][sort][field]', 'body.value');
|
||||
// Test that a field not on this bundle is not a sort option.
|
||||
$assert_session->optionNotExists('settings[handler_settings][sort][field]', 'field_text.value');
|
||||
// Test that the title option appears once, with the default label.
|
||||
$title_options = $sort_by->findAll('xpath', 'option[@value="title"]');
|
||||
$this->assertEquals(1, count($title_options));
|
||||
$this->assertEquals('Title', $title_options[0]->getText());
|
||||
|
||||
// Also select the target bundle so that field_text is also available.
|
||||
$page->findField('settings[handler_settings][target_bundles][' . $this->targetType . ']')->setValue($this->targetType);
|
||||
$assert_session->assertWaitOnAjaxRequest();
|
||||
$assert_session->optionExists('settings[handler_settings][sort][field]', 'nid');
|
||||
$assert_session->optionExists('settings[handler_settings][sort][field]', 'title');
|
||||
$assert_session->optionExists('settings[handler_settings][sort][field]', 'body.value');
|
||||
$assert_session->optionExists('settings[handler_settings][sort][field]', 'field_text.value');
|
||||
|
||||
// Select only the target bundle. The options should be the same.
|
||||
$page->findField('settings[handler_settings][target_bundles][' . $this->type . ']')->uncheck();
|
||||
$assert_session->assertWaitOnAjaxRequest();
|
||||
$assert_session->optionExists('settings[handler_settings][sort][field]', 'nid');
|
||||
$assert_session->optionExists('settings[handler_settings][sort][field]', 'title');
|
||||
$assert_session->optionExists('settings[handler_settings][sort][field]', 'body.value');
|
||||
$assert_session->optionExists('settings[handler_settings][sort][field]', 'field_text.value');
|
||||
// Exception: the title option has a different label.
|
||||
$title_options = $sort_by->findAll('xpath', 'option[@value="title"]');
|
||||
$this->assertEquals(1, count($title_options));
|
||||
$this->assertEquals($this->targetType . ' title', $title_options[0]->getText());
|
||||
|
||||
// Test the sort settings.
|
||||
// Option 0: no sort.
|
||||
$this->assertSession()->fieldValueEquals('settings[handler_settings][sort][field]', '_none');
|
||||
$sort_direction = $page->findField('settings[handler_settings][sort][direction]');
|
||||
$this->assertFalse($sort_direction->isVisible());
|
||||
// Option 1: sort by field.
|
||||
$sort_by->setValue('nid');
|
||||
$assert_session->assertWaitOnAjaxRequest();
|
||||
$this->assertTrue($sort_direction->isVisible());
|
||||
$this->assertSession()->fieldValueEquals('settings[handler_settings][sort][direction]', 'ASC');
|
||||
|
||||
// Test that the sort-by options are sorted.
|
||||
$labels = array_map(function (NodeElement $element) {
|
||||
return $element->getText();
|
||||
}, $sort_by->findAll('xpath', 'option'));
|
||||
for ($i = count($labels) - 1, $sorted = TRUE; $i > 0; --$i) {
|
||||
if ($labels[$i - 1] > $labels[$i]) {
|
||||
$sorted = FALSE;
|
||||
break;
|
||||
}
|
||||
}
|
||||
$this->assertTrue($sorted, 'The "sort by" options are sorted.');
|
||||
|
||||
// Set back to no sort.
|
||||
$sort_by->setValue('_none');
|
||||
$assert_session->assertWaitOnAjaxRequest();
|
||||
$this->assertFalse($sort_direction->isVisible());
|
||||
|
||||
// Sort by nid, then select no bundles. The sort fields and sort direction
|
||||
// should not display.
|
||||
$sort_by->setValue('nid');
|
||||
$assert_session->assertWaitOnAjaxRequest();
|
||||
foreach ($bundles as $bundle_name => $bundle_info) {
|
||||
$this->assertSession()->fieldExists('settings[handler_settings][target_bundles][' . $bundle_name . ']');
|
||||
$checkbox = $page->findField('settings[handler_settings][target_bundles][' . $bundle_name . ']');
|
||||
if ($checkbox->isChecked()) {
|
||||
$checkbox->uncheck();
|
||||
$assert_session->assertWaitOnAjaxRequest();
|
||||
}
|
||||
}
|
||||
$this->assertFalse($sort_by->isVisible(), 'The "sort by" options are hidden.');
|
||||
$this->assertFalse($sort_direction->isVisible());
|
||||
|
||||
// Select a bundle and check the same two fields.
|
||||
$page->findField('settings[handler_settings][target_bundles][' . $this->targetType . ']')->setValue($this->targetType);
|
||||
$assert_session->assertWaitOnAjaxRequest();
|
||||
$this->assertTrue($sort_by->isVisible(), 'The "sort by" options are visible.');
|
||||
$assert_session->optionExists('settings[handler_settings][sort][field]', 'field_text.value');
|
||||
|
||||
// Un-select the bundle and check the same two fields.
|
||||
$page->findField('settings[handler_settings][target_bundles][' . $this->targetType . ']')->uncheck();
|
||||
$assert_session->assertWaitOnAjaxRequest();
|
||||
$this->assertFalse($sort_by->isVisible(), 'The "sort by" options are hidden yet again.');
|
||||
$this->assertFieldSelectOptions('settings[handler_settings][sort][field]', ['_none']);
|
||||
|
||||
// Third step: confirm.
|
||||
$page->findField('settings[handler_settings][target_bundles][' . $this->targetType . ']')->setValue($this->targetType);
|
||||
$assert_session->assertWaitOnAjaxRequest();
|
||||
// @todo remove after https://www.drupal.org/i/3395590 has been fixed.
|
||||
$this->getSession()->resizeWindow(1200, 1500);
|
||||
usleep(5000);
|
||||
|
||||
$page->find('css', '.ui-dialog-buttonset')->pressButton('Save');
|
||||
|
||||
$this->assertTrue($assert_session->waitForText('Saved Test configuration.'));
|
||||
|
||||
// Check that the field appears in the overview form.
|
||||
$this->assertSession()->elementTextContains('xpath', '//table[@id="field-overview"]//tr[@id="field-test"]/td[1]', "Test");
|
||||
|
||||
// Check that the field settings form can be submitted again, even when the
|
||||
// field is required.
|
||||
// The first 'Edit' link is for the Body field.
|
||||
$this->drupalGet($bundle_path . '/fields');
|
||||
$this->clickLink('Edit', 1);
|
||||
|
||||
$assert_session->assertExpectedAjaxRequest(1);
|
||||
$this->assertNotNull($button_set = $assert_session->waitForElement('css', '.ui-dialog-buttonset'));
|
||||
$button_set->pressButton('Save settings');
|
||||
$this->assertTrue($assert_session->waitForText('Saved Test configuration.'));
|
||||
|
||||
// Switch the target type to 'taxonomy_term' and check that the settings
|
||||
// specific to its selection handler are displayed.
|
||||
$field_name = 'node.' . $this->type . '.field_test';
|
||||
$this->drupalGet($bundle_path . '/fields/' . $field_name);
|
||||
$this->assertTrue($assert_session->waitForText('These settings apply to the Test field everywhere it is used.'));
|
||||
$page->findField('field_storage[subform][settings][target_type]')->setValue('taxonomy_term');
|
||||
$this->assertSession()->assertWaitOnAjaxRequest();
|
||||
$this->assertSession()->fieldExists('settings[handler_settings][auto_create]');
|
||||
$this->assertSession()->fieldValueEquals('settings[handler]', 'default:taxonomy_term');
|
||||
|
||||
// Switch the target type to 'user' and check that the settings specific to
|
||||
// its selection handler are displayed.
|
||||
$field_name = 'node.' . $this->type . '.field_test';
|
||||
$this->drupalGet($bundle_path . '/fields/' . $field_name);
|
||||
$target_type_input = $assert_session->fieldExists('field_storage[subform][settings][target_type]');
|
||||
$target_type_input->setValue('user');
|
||||
$assert_session->assertWaitOnAjaxRequest();
|
||||
$this->assertSession()->fieldValueEquals('settings[handler_settings][filter][type]', '_none');
|
||||
$this->assertSession()->fieldValueEquals('settings[handler_settings][sort][field]', '_none');
|
||||
$assert_session->optionNotExists('settings[handler_settings][sort][field]', 'nid');
|
||||
$assert_session->optionExists('settings[handler_settings][sort][field]', 'uid');
|
||||
|
||||
// Check that sort direction is visible only when a sort field is selected.
|
||||
$sort_direction = $page->findField('settings[handler_settings][sort][direction]');
|
||||
$this->assertFalse($sort_direction->isVisible());
|
||||
$sort_by->setValue('name');
|
||||
$assert_session->assertWaitOnAjaxRequest();
|
||||
$this->assertTrue($sort_direction->isVisible());
|
||||
|
||||
// Switch the target type to 'node'.
|
||||
$field_name = 'node.' . $this->type . '.field_test';
|
||||
$this->drupalGet($bundle_path . '/fields/' . $field_name);
|
||||
$page->findField('field_storage[subform][settings][target_type]')->setValue('node');
|
||||
|
||||
// Try to select the views handler.
|
||||
$this->drupalGet($bundle_path . '/fields/' . $field_name);
|
||||
$page->findField('settings[handler]')->setValue('views');
|
||||
$create = Url::fromRoute('views_ui.add')->toString();
|
||||
$existing = Url::fromRoute('entity.view.collection')->toString();
|
||||
$views_text = 'No eligible views were found. <a href="' . $create . '">Create a view</a> with an <em>Entity Reference</em> display, or add such a display to an <a href="' . $existing . '">existing view</a>.';
|
||||
$assert_session->waitForElement('xpath', '//a[contains(text(), "Create a view")]');
|
||||
$assert_session->responseContains($views_text);
|
||||
|
||||
$this->submitForm([], 'Save settings');
|
||||
// If no eligible view is available we should see a message.
|
||||
$this->assertTrue($assert_session->waitForText('The views entity selection mode requires a view.'));
|
||||
|
||||
// Enable the entity_reference_test module which creates an eligible view.
|
||||
$this->container->get('module_installer')
|
||||
->install(['entity_reference_test']);
|
||||
$this->resetAll();
|
||||
$this->drupalGet($bundle_path . '/fields/' . $field_name);
|
||||
$page->findField('settings[handler]')->setValue('views');
|
||||
$assert_session
|
||||
->waitForField('settings[handler_settings][view][view_and_display]')
|
||||
->setValue('test_entity_reference:entity_reference_1');
|
||||
$this->submitForm([], 'Save settings');
|
||||
$this->assertTrue($assert_session->waitForText('Saved Test configuration.'));
|
||||
|
||||
// Switch the target type to 'entity_test'.
|
||||
$this->drupalGet($bundle_path . '/fields/' . $field_name);
|
||||
$page->findField('field_storage[subform][settings][target_type]')->setValue('entity_test');
|
||||
$assert_session->assertExpectedAjaxRequest(1);
|
||||
$page->findField('settings[handler]')->setValue('views');
|
||||
$assert_session->waitForField('settings[handler_settings][view][view_and_display]');
|
||||
$page
|
||||
->findField('settings[handler_settings][view][view_and_display]')
|
||||
->selectOption('test_entity_reference_entity_test:entity_reference_1');
|
||||
$this->assertSession()->fieldExists('required')->check();
|
||||
$this->submitForm([], 'Save settings');
|
||||
$this->assertTrue($assert_session->waitForText('Saved Test configuration.'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a select element contains the specified options.
|
||||
*
|
||||
* @param string $name
|
||||
* The field name.
|
||||
* @param array $expected_options
|
||||
* An array of expected options.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
protected function assertFieldSelectOptions(string $name, array $expected_options): void {
|
||||
$field = $this->assertSession()->selectExists($name);
|
||||
$options = $field->findAll('xpath', 'option');
|
||||
$optgroups = $field->findAll('xpath', 'optgroup');
|
||||
$nested_options = [];
|
||||
foreach ($optgroups as $optgroup) {
|
||||
$nested_options[] = $optgroup->findAll('xpath', 'option');
|
||||
}
|
||||
$options = array_merge($options, ...$nested_options);
|
||||
array_walk($options, function (NodeElement &$option) {
|
||||
$option = $option->getAttribute('value');
|
||||
});
|
||||
$this->assertEqualsCanonicalizing($expected_options, $options);
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,213 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\field\FunctionalJavascript;
|
||||
|
||||
use Drupal\Core\Entity\Entity\EntityFormDisplay;
|
||||
use Drupal\Core\Field\FieldStorageDefinitionInterface;
|
||||
use Drupal\field\Entity\FieldConfig;
|
||||
use Drupal\field\Entity\FieldStorageConfig;
|
||||
use Drupal\FunctionalJavascriptTests\WebDriverTestBase;
|
||||
|
||||
/**
|
||||
* Tests widget form for a multiple value field.
|
||||
*
|
||||
* @group field
|
||||
*/
|
||||
class MultipleValueWidgetTest extends WebDriverTestBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = ['field_test', 'entity_test'];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $defaultTheme = 'stark';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp(): void {
|
||||
parent::setUp();
|
||||
|
||||
$account = $this->drupalCreateUser([
|
||||
'view test entity',
|
||||
'administer entity_test content',
|
||||
]);
|
||||
$this->drupalLogin($account);
|
||||
|
||||
$field = [
|
||||
'field_name' => 'field_unlimited',
|
||||
'entity_type' => 'entity_test',
|
||||
'bundle' => 'entity_test',
|
||||
'label' => $this->randomMachineName() . '_label',
|
||||
'description' => '[site:name]_description',
|
||||
'weight' => mt_rand(0, 127),
|
||||
'settings' => [
|
||||
'test_field_setting' => $this->randomMachineName(),
|
||||
],
|
||||
];
|
||||
|
||||
FieldStorageConfig::create([
|
||||
'field_name' => 'field_unlimited',
|
||||
'entity_type' => 'entity_test',
|
||||
'type' => 'test_field',
|
||||
'cardinality' => FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED,
|
||||
])->save();
|
||||
FieldConfig::create($field)->save();
|
||||
|
||||
$entity_form_display = EntityFormDisplay::load($field['entity_type'] . '.' . $field['bundle'] . '.default');
|
||||
$entity_form_display->setComponent($field['field_name'])->save();
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the 'Add more' functionality.
|
||||
*/
|
||||
public function testFieldMultipleValueWidget(): void {
|
||||
$this->drupalGet('entity_test/add');
|
||||
|
||||
$assert_session = $this->assertSession();
|
||||
$page = $this->getSession()->getPage();
|
||||
$add_more_button = $page->findButton('field_unlimited_add_more');
|
||||
|
||||
// First set a value on the first input field.
|
||||
$field_0 = $page->findField('field_unlimited[0][value]');
|
||||
$field_0->setValue('1');
|
||||
|
||||
$field_0_remove_button = $page->findButton('field_unlimited_0_remove_button');
|
||||
$this->assertNotEmpty($field_0_remove_button, 'First field has a remove button.');
|
||||
|
||||
// Add another item.
|
||||
$add_more_button->click();
|
||||
$field_1 = $assert_session->waitForField('field_unlimited[1][value]');
|
||||
$this->assertNotEmpty($field_1, 'Successfully added another item.');
|
||||
|
||||
$field_1_remove_button = $page->findButton('field_unlimited_1_remove_button');
|
||||
$this->assertNotEmpty($field_1_remove_button, 'Also second field has a remove button.');
|
||||
|
||||
// Validate the value of the first field has not changed.
|
||||
$this->assertEquals('1', $field_0->getValue(), 'Value for the first item has not changed.');
|
||||
|
||||
// Validate the value of the second item is empty.
|
||||
$this->assertEmpty($field_1->getValue(), 'Value for the second item is currently empty.');
|
||||
|
||||
// Add another item.
|
||||
$add_more_button->click();
|
||||
$field_2 = $assert_session->waitForField('field_unlimited[2][value]');
|
||||
$this->assertNotEmpty($field_2, 'Successfully added another item.');
|
||||
|
||||
// Set values for the 2nd and 3rd fields to validate dragging.
|
||||
$field_1->setValue('2');
|
||||
$field_2->setValue('3');
|
||||
|
||||
$field_weight_0 = $page->findField('field_unlimited[0][_weight]');
|
||||
$field_weight_1 = $page->findField('field_unlimited[1][_weight]');
|
||||
$field_weight_2 = $page->findField('field_unlimited[2][_weight]');
|
||||
|
||||
// Assert starting situation matches expectations.
|
||||
$this->assertGreaterThan($field_weight_0->getValue(), $field_weight_1->getValue());
|
||||
$this->assertGreaterThan($field_weight_1->getValue(), $field_weight_2->getValue());
|
||||
|
||||
// Drag the first row after the third row.
|
||||
$dragged = $field_0->find('xpath', 'ancestor::tr[contains(@class, "draggable")]//a[starts-with(@class, "tabledrag-handle")]');
|
||||
$target = $field_2->find('xpath', 'ancestor::tr[contains(@class, "draggable")]');
|
||||
$dragged->dragTo($target);
|
||||
|
||||
// Assert the order of items is updated correctly after dragging.
|
||||
$this->assertGreaterThan($field_weight_2->getValue(), $field_weight_0->getValue());
|
||||
$this->assertGreaterThan($field_weight_1->getValue(), $field_weight_2->getValue());
|
||||
|
||||
// Validate the order of items conforms to the last drag action after a
|
||||
// updating the form via the server.
|
||||
$add_more_button->click();
|
||||
$field_3 = $assert_session->waitForField('field_unlimited[3][value]');
|
||||
$this->assertNotEmpty($field_3);
|
||||
$this->assertGreaterThan($field_weight_2->getValue(), $field_weight_0->getValue());
|
||||
$this->assertGreaterThan($field_weight_1->getValue(), $field_weight_2->getValue());
|
||||
|
||||
// Validate no extraneous widget is displayed.
|
||||
$element = $page->findField('field_unlimited[4][value]');
|
||||
$this->assertEmpty($element);
|
||||
|
||||
// Test removing items/values.
|
||||
$field_0_remove_button->click();
|
||||
$this->assertSession()->assertWaitOnAjaxRequest();
|
||||
// Test the updated widget.
|
||||
// First item is the initial second item.
|
||||
$this->assertEquals('2', $field_0->getValue(), 'Value for the first item has changed.');
|
||||
// We do not have the initial first item anymore.
|
||||
$this->assertEmpty($field_2->getValue(), 'Value for the third item is currently empty.');
|
||||
$element = $page->findField('field_unlimited[3][value]');
|
||||
$this->assertEmpty($element);
|
||||
|
||||
// We can also remove empty items.
|
||||
$field_2_remove_button = $page->findButton('field_unlimited_2_remove_button');
|
||||
$field_2_remove_button->click();
|
||||
$this->assertSession()->assertWaitOnAjaxRequest();
|
||||
$element = $page->findField('field_unlimited[2][value]');
|
||||
$this->assertEmpty($element, 'Empty field also removed.');
|
||||
|
||||
// Assert that the wrapper exists and isn't nested.
|
||||
$this->assertSession()->elementsCount('css', '[data-drupal-selector="edit-field-unlimited-wrapper"]', 1);
|
||||
|
||||
// Test removing items/values on saved entities resets to initial value.
|
||||
$this->submitForm([], 'Save');
|
||||
$field_2_remove_button->click();
|
||||
$this->assertSession()->assertWaitOnAjaxRequest();
|
||||
$field_1_remove_button->click();
|
||||
$this->assertSession()->assertWaitOnAjaxRequest();
|
||||
$field_0_remove_button->click();
|
||||
$this->assertSession()->assertWaitOnAjaxRequest();
|
||||
$add_more_button->click();
|
||||
$this->assertSession()->assertWaitOnAjaxRequest();
|
||||
$this->assertSame('', $field_0->getValue());
|
||||
$add_more_button->click();
|
||||
$this->assertSession()->assertWaitOnAjaxRequest();
|
||||
$this->assertSame('', $field_1->getValue());
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that no validation occurs on field on "Add more" click.
|
||||
*/
|
||||
public function testFieldMultipleValueWidgetAddMoreNoValidation(): void {
|
||||
// Set unlimited field to be required.
|
||||
$field_name = 'field_unlimited';
|
||||
$field = FieldConfig::loadByName('entity_test', 'entity_test', $field_name);
|
||||
$field->setRequired(TRUE);
|
||||
$field->save();
|
||||
|
||||
$this->drupalGet('entity_test/add');
|
||||
$assert_session = $this->assertSession();
|
||||
$page = $this->getSession()->getPage();
|
||||
|
||||
// Add another item with the first item being empty, even though the field
|
||||
// is required.
|
||||
$add_more_button = $page->findButton('field_unlimited_add_more');
|
||||
$add_more_button->click();
|
||||
$field_1 = $assert_session->waitForField('field_unlimited[1][value]');
|
||||
$this->assertNotEmpty($field_1, 'Successfully added another item.');
|
||||
// Confirm the new item has focus.
|
||||
$this->assertHasFocusByAttribute('name', 'field_unlimited[1][value]');
|
||||
// The first item should not be in error state.
|
||||
$assert_session->elementNotExists('css', 'input[name="field_unlimited[0][value]"].error');
|
||||
}
|
||||
|
||||
/**
|
||||
* Asserts an element specified by an attribute value has focus.
|
||||
*
|
||||
* @param string $name
|
||||
* The attribute name.
|
||||
* @param string $value
|
||||
* The attribute value.
|
||||
*
|
||||
* @todo Replace with assertHasFocus() in https://drupal.org/i/3041768.
|
||||
*/
|
||||
private function assertHasFocusByAttribute(string $name, string $value): void {
|
||||
$active_element = $this->getSession()->evaluateScript('document.activeElement');
|
||||
$this->assertSame($value, $active_element->attribute($name));
|
||||
}
|
||||
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user