first commit

This commit is contained in:
2020-06-08 23:57:36 +02:00
commit 6277454f7a
16057 changed files with 1715382 additions and 0 deletions
@@ -0,0 +1,37 @@
<?php
/**
* @file
* Contains SQL necessary to add a new component for an email field/widget to
* the 'node.article.default' entity form display.
*/
use Drupal\Core\Database\Database;
$connection = Database::getConnection();
$config = $connection->select('config', 'c')
->fields('c')
->condition('collection', '')
->condition('name', 'core.entity_form_display.node.article.default')
->execute()
->fetchAssoc();
$data = unserialize($config['data']);
// Manually add a new component that simulates an email field using the default
// email widget.
$data['content']['field_email_2578741'] = [
'weight' => 20,
'settings' => [
'placeholder' => '',
],
'third_party_settings' => [],
'type' => 'email_default',
];
$connection->update('config')
->fields(['data' => serialize($data)])
->condition('collection', '')
->condition('name', 'core.entity_form_display.node.article.default')
->execute();
@@ -0,0 +1,26 @@
<?php
/**
* @file
* Provides necessary database additions for testing
* field_post_update_remove_handler_submit_setting()
*/
use Drupal\Core\Database\Database;
$connection = Database::getConnection();
$config = unserialize($connection->select('config', 'c')
->fields('c', ['data'])
->condition('collection', '')
->condition('name', 'field.field.node.article.field_tags')
->execute()
->fetchField());
$config['settings']['handler_submit'] = 'Change handler';
$connection->update('config')
->fields(['data' => serialize($config)])
->condition('collection', '')
->condition('name', 'field.field.node.article.field_tags')
->execute();
@@ -0,0 +1,203 @@
<?php
// @codingStandardsIgnoreFile
/**
* @file
* Contains SQL necessary to add a deleted field to the node entity type.
*/
use Drupal\Core\Database\Database;
$connection = Database::getConnection();
// Add the field schema data and the deleted field definitions.
$connection->insert('key_value')
->fields([
'collection',
'name',
'value',
])
->values([
'collection' => 'entity.storage_schema.sql',
'name' => 'node.field_schema_data.field_test',
'value' => 'a:2:{s:16:"node__field_test";a:4:{s:11:"description";s:39:"Data storage for node field field_test.";s:6:"fields";a:7:{s:6:"bundle";a:5:{s:4:"type";s:13:"varchar_ascii";s:6:"length";i:128;s:8:"not null";b:1;s:7:"default";s:0:"";s:11:"description";s:88:"The field instance bundle to which this row belongs, used when deleting a field instance";}s:7:"deleted";a:5:{s:4:"type";s:3:"int";s:4:"size";s:4:"tiny";s:8:"not null";b:1;s:7:"default";i:0;s:11:"description";s:60:"A boolean indicating whether this data item has been deleted";}s:9:"entity_id";a:4:{s:4:"type";s:3:"int";s:8:"unsigned";b:1;s:8:"not null";b:1;s:11:"description";s:38:"The entity id this data is attached to";}s:11:"revision_id";a:4:{s:4:"type";s:3:"int";s:8:"unsigned";b:1;s:8:"not null";b:1;s:11:"description";s:47:"The entity revision id this data is attached to";}s:8:"langcode";a:5:{s:4:"type";s:13:"varchar_ascii";s:6:"length";i:32;s:8:"not null";b:1;s:7:"default";s:0:"";s:11:"description";s:37:"The language code for this data item.";}s:5:"delta";a:4:{s:4:"type";s:3:"int";s:8:"unsigned";b:1;s:8:"not null";b:1;s:11:"description";s:67:"The sequence number for this data item, used for multi-value fields";}s:16:"field_test_value";a:3:{s:4:"type";s:7:"varchar";s:6:"length";i:254;s:8:"not null";b:1;}}s:11:"primary key";a:4:{i:0;s:9:"entity_id";i:1;s:7:"deleted";i:2;s:5:"delta";i:3;s:8:"langcode";}s:7:"indexes";a:2:{s:6:"bundle";a:1:{i:0;s:6:"bundle";}s:11:"revision_id";a:1:{i:0;s:11:"revision_id";}}}s:25:"node_revision__field_test";a:4:{s:11:"description";s:51:"Revision archive storage for node field field_test.";s:6:"fields";a:7:{s:6:"bundle";a:5:{s:4:"type";s:13:"varchar_ascii";s:6:"length";i:128;s:8:"not null";b:1;s:7:"default";s:0:"";s:11:"description";s:88:"The field instance bundle to which this row belongs, used when deleting a field instance";}s:7:"deleted";a:5:{s:4:"type";s:3:"int";s:4:"size";s:4:"tiny";s:8:"not null";b:1;s:7:"default";i:0;s:11:"description";s:60:"A boolean indicating whether this data item has been deleted";}s:9:"entity_id";a:4:{s:4:"type";s:3:"int";s:8:"unsigned";b:1;s:8:"not null";b:1;s:11:"description";s:38:"The entity id this data is attached to";}s:11:"revision_id";a:4:{s:4:"type";s:3:"int";s:8:"unsigned";b:1;s:8:"not null";b:1;s:11:"description";s:47:"The entity revision id this data is attached to";}s:8:"langcode";a:5:{s:4:"type";s:13:"varchar_ascii";s:6:"length";i:32;s:8:"not null";b:1;s:7:"default";s:0:"";s:11:"description";s:37:"The language code for this data item.";}s:5:"delta";a:4:{s:4:"type";s:3:"int";s:8:"unsigned";b:1;s:8:"not null";b:1;s:11:"description";s:67:"The sequence number for this data item, used for multi-value fields";}s:16:"field_test_value";a:3:{s:4:"type";s:7:"varchar";s:6:"length";i:254;s:8:"not null";b:1;}}s:11:"primary key";a:5:{i:0;s:9:"entity_id";i:1;s:11:"revision_id";i:2;s:7:"deleted";i:3;s:5:"delta";i:4;s:8:"langcode";}s:7:"indexes";a:2:{s:6:"bundle";a:1:{i:0;s:6:"bundle";}s:11:"revision_id";a:1:{i:0;s:11:"revision_id";}}}}',
])
->values([
'collection' => 'state',
'name' => 'field.field.deleted',
'value' => 'a:1:{s:36:"5d0d9870-560b-46c4-b838-0dcded0502dd";a:18:{s:4:"uuid";s:36:"5d0d9870-560b-46c4-b838-0dcded0502dd";s:8:"langcode";s:2:"en";s:6:"status";b:1;s:12:"dependencies";a:1:{s:6:"config";a:2:{i:0;s:29:"field.storage.node.field_test";i:1;s:17:"node.type.article";}}s:2:"id";s:23:"node.article.field_test";s:10:"field_name";s:10:"field_test";s:11:"entity_type";s:4:"node";s:6:"bundle";s:7:"article";s:5:"label";s:4:"Test";s:11:"description";s:0:"";s:8:"required";b:0;s:12:"translatable";b:0;s:13:"default_value";a:0:{}s:22:"default_value_callback";s:0:"";s:8:"settings";a:0:{}s:10:"field_type";s:5:"email";s:7:"deleted";b:1;s:18:"field_storage_uuid";s:36:"ce93d7c2-1da7-4a2c-9e6d-b4925e3b129f";}}',
])
->values([
'collection' => 'state',
'name' => 'field.storage.deleted',
'value' => 'a:1:{s:36:"ce93d7c2-1da7-4a2c-9e6d-b4925e3b129f";a:18:{s:4:"uuid";s:36:"ce93d7c2-1da7-4a2c-9e6d-b4925e3b129f";s:8:"langcode";s:2:"en";s:6:"status";b:1;s:12:"dependencies";a:1:{s:6:"module";a:1:{i:0;s:4:"node";}}s:2:"id";s:15:"node.field_test";s:10:"field_name";s:10:"field_test";s:11:"entity_type";s:4:"node";s:4:"type";s:5:"email";s:8:"settings";a:0:{}s:6:"module";s:4:"core";s:6:"locked";b:0;s:11:"cardinality";i:1;s:12:"translatable";b:1;s:7:"indexes";a:0:{}s:22:"persist_with_no_fields";b:0;s:14:"custom_storage";b:0;s:7:"deleted";b:1;s:7:"bundles";a:0:{}}}',
])
->execute();
// Create and populate the deleted field tables.
// @see \Drupal\Core\Entity\Sql\DefaultTableMapping::getDedicatedDataTableName()
$deleted_field_data_table_name = "field_deleted_data_" . substr(hash('sha256', 'ce93d7c2-1da7-4a2c-9e6d-b4925e3b129f'), 0, 10);
$connection->schema()->createTable($deleted_field_data_table_name, array(
'fields' => array(
'bundle' => array(
'type' => 'varchar_ascii',
'not null' => TRUE,
'length' => '128',
'default' => '',
),
'deleted' => array(
'type' => 'int',
'not null' => TRUE,
'size' => 'tiny',
'default' => '0',
),
'entity_id' => array(
'type' => 'int',
'not null' => TRUE,
'size' => 'normal',
'unsigned' => TRUE,
),
'revision_id' => array(
'type' => 'int',
'not null' => TRUE,
'size' => 'normal',
'unsigned' => TRUE,
),
'langcode' => array(
'type' => 'varchar_ascii',
'not null' => TRUE,
'length' => '32',
'default' => '',
),
'delta' => array(
'type' => 'int',
'not null' => TRUE,
'size' => 'normal',
'unsigned' => TRUE,
),
'field_test_value' => array(
'type' => 'varchar',
'not null' => TRUE,
'length' => '254',
),
),
'primary key' => array(
'entity_id',
'deleted',
'delta',
'langcode',
),
'indexes' => array(
'bundle' => array(
'bundle',
),
'revision_id' => array(
'revision_id',
),
),
));
$connection->insert($deleted_field_data_table_name)
->fields(array(
'bundle',
'deleted',
'entity_id',
'revision_id',
'langcode',
'delta',
'field_test_value',
))
->values(array(
'bundle' => 'article',
'deleted' => '1',
'entity_id' => '1',
'revision_id' => '1',
'langcode' => 'en',
'delta' => '0',
'field_test_value' => 'test@test.com',
))
->execute();
// @see \Drupal\Core\Entity\Sql\DefaultTableMapping::getDedicatedDataTableName()
$deleted_field_revision_table_name = "field_deleted_revision_" . substr(hash('sha256', 'ce93d7c2-1da7-4a2c-9e6d-b4925e3b129f'), 0, 10);
$connection->schema()->createTable($deleted_field_revision_table_name, array(
'fields' => array(
'bundle' => array(
'type' => 'varchar_ascii',
'not null' => TRUE,
'length' => '128',
'default' => '',
),
'deleted' => array(
'type' => 'int',
'not null' => TRUE,
'size' => 'tiny',
'default' => '0',
),
'entity_id' => array(
'type' => 'int',
'not null' => TRUE,
'size' => 'normal',
'unsigned' => TRUE,
),
'revision_id' => array(
'type' => 'int',
'not null' => TRUE,
'size' => 'normal',
'unsigned' => TRUE,
),
'langcode' => array(
'type' => 'varchar_ascii',
'not null' => TRUE,
'length' => '32',
'default' => '',
),
'delta' => array(
'type' => 'int',
'not null' => TRUE,
'size' => 'normal',
'unsigned' => TRUE,
),
'field_test_value' => array(
'type' => 'varchar',
'not null' => TRUE,
'length' => '254',
),
),
'primary key' => array(
'entity_id',
'revision_id',
'deleted',
'delta',
'langcode',
),
'indexes' => array(
'bundle' => array(
'bundle',
),
'revision_id' => array(
'revision_id',
),
),
));
$connection->insert($deleted_field_revision_table_name)
->fields(array(
'bundle',
'deleted',
'entity_id',
'revision_id',
'langcode',
'delta',
'field_test_value',
))
->values(array(
'bundle' => 'article',
'deleted' => '1',
'entity_id' => '1',
'revision_id' => '1',
'langcode' => 'en',
'delta' => '0',
'field_test_value' => 'test@test.com',
))
->execute();
@@ -0,0 +1,95 @@
<?php
/**
* @file
* Contains database additions to drupal-8.bare.standard.php.gz for testing the
* upgrade path of https://www.drupal.org/node/2429191.
*/
use Drupal\Core\Database\Database;
use Drupal\Core\Serialization\Yaml;
use Drupal\field\Entity\FieldStorageConfig;
$connection = Database::getConnection();
// Configuration for a View with an "entity_reference selection" display.
$config = Yaml::decode(file_get_contents(__DIR__ . '/views.view.entity_reference_plugins_2429191.yml'));
$connection->insert('config')
->fields([
'collection',
'name',
'data',
])
->values([
'collection' => '',
'name' => 'views.view.' . $config['id'],
'data' => serialize($config),
])
->execute();
// Configuration for an entity_reference field storage using the View for
// selection.
$field_ref_views_select_2429191 = Yaml::decode(file_get_contents(__DIR__ . '/field.storage.node.field_ref_views_select_2429191.yml'));
// Configuration for an entity_reference field storage using the auto-create
// feature.
$field_ref_autocreate_2412569 = Yaml::decode(file_get_contents(__DIR__ . '/field.storage.node.field_ref_autocreate_2412569.yml'));
$connection->insert('config')
->fields([
'collection',
'name',
'data',
])
->values([
'collection' => '',
'name' => 'field.storage.' . $field_ref_views_select_2429191['id'],
'data' => serialize($field_ref_views_select_2429191),
])
->values([
'collection' => '',
'name' => 'field.storage.' . $field_ref_autocreate_2412569['id'],
'data' => serialize($field_ref_autocreate_2412569),
])
->execute();
// We need to Update the registry of "last installed" field definitions.
$installed = $connection->select('key_value')
->fields('key_value', ['value'])
->condition('collection', 'entity.definitions.installed')
->condition('name', 'node.field_storage_definitions')
->execute()
->fetchField();
$installed = unserialize($installed);
$installed['field_ref_views_select_2429191'] = new FieldStorageConfig($field_ref_views_select_2429191);
$installed['field_ref_autocreate_2412569'] = new FieldStorageConfig($field_ref_autocreate_2412569);
$connection->update('key_value')
->condition('collection', 'entity.definitions.installed')
->condition('name', 'node.field_storage_definitions')
->fields([
'value' => serialize($installed),
])
->execute();
// Configuration for an entity_reference field using the View for selection.
$field_ref_views_select_2429191 = Yaml::decode(file_get_contents(__DIR__ . '/field.field.node.article.field_ref_views_select_2429191.yml'));
// Configuration for an entity_reference field using the auto-create feature.
$field_ref_autocreate_2412569 = Yaml::decode(file_get_contents(__DIR__ . '/field.field.node.article.field_ref_autocreate_2412569.yml'));
$connection->insert('config')
->fields([
'collection',
'name',
'data',
])
->values([
'collection' => '',
'name' => 'field.field.' . $field_ref_views_select_2429191['id'],
'data' => serialize($field_ref_views_select_2429191),
])
->values([
'collection' => '',
'name' => 'field.field.' . $field_ref_autocreate_2412569['id'],
'data' => serialize($field_ref_autocreate_2412569),
])
->execute();
@@ -0,0 +1,29 @@
uuid: d6deba8d-073a-4572-a000-ee2a2de94de2
langcode: en
status: true
dependencies:
config:
- field.storage.node.field_ref_autocreate_2412569
- node.type.article
- taxonomy.vocabulary.tags
- taxonomy.vocabulary.test
id: node.article.field_ref_autocreate_2412569
field_name: field_ref_autocreate_2412569
entity_type: node
bundle: article
label: 'Ref Autocreate 2412569'
description: ''
required: false
translatable: false
default_value: { }
default_value_callback: ''
settings:
handler: 'default:taxonomy_term'
handler_settings:
target_bundles:
tags: tags
test: test
sort:
field: _none
auto_create: true
field_type: entity_reference
@@ -0,0 +1,27 @@
uuid: ac77b88d-dfa0-4b07-b700-ba50cb51f72a
langcode: en
status: true
dependencies:
config:
- field.storage.node.field_ref_views_select_2429191
- node.type.article
module:
- entity_reference
id: node.article.field_ref_views_select_2429191
field_name: field_ref_views_select_2429191
entity_type: node
bundle: article
label: reference_views_select
description: ''
required: false
translatable: false
default_value: { }
default_value_callback: ''
settings:
handler: views
handler_settings:
view:
view_name: entity_reference_plugins_2429191
display_name: entity_reference_1
arguments: { }
field_type: entity_reference
@@ -0,0 +1,20 @@
uuid: 5e4095a3-8f89-4d7a-b222-34bf5240b646
langcode: en
status: true
dependencies:
module:
- node
- taxonomy
id: node.field_ref_autocreate_2412569
field_name: field_ref_autocreate_2412569
entity_type: node
type: entity_reference
settings:
target_type: taxonomy_term
module: core
locked: false
cardinality: 1
translatable: true
indexes: { }
persist_with_no_fields: false
custom_storage: true
@@ -0,0 +1,20 @@
uuid: 7f5e9177-56b3-4b84-a936-54bfd4d4c078
langcode: en
status: true
dependencies:
module:
- entity_reference
- node
id: node.field_ref_views_select_2429191
field_name: field_ref_views_select_2429191
entity_type: node
type: entity_reference
settings:
target_type: node
module: entity_reference
locked: false
cardinality: 1
translatable: true
indexes: { }
persist_with_no_fields: false
custom_storage: true
@@ -0,0 +1,213 @@
uuid: 8b3d9ae1-c8f9-4219-af35-53f687e6081b
langcode: en
status: true
dependencies:
module:
- entity_reference
- node
- user
id: entity_reference_plugins_2429191
label: test
module: views
description: ''
tag: ''
base_table: node_field_data
base_field: nid
display:
default:
display_plugin: default
id: default
display_title: Master
position: 0
display_options:
access:
type: perm
options:
perm: 'access content'
cache:
type: tag
options: { }
query:
type: views_query
options:
disable_sql_rewrite: false
distinct: false
replica: false
query_comment: ''
query_tags: { }
exposed_form:
type: basic
options:
submit_button: Apply
reset_button: false
reset_button_label: Reset
exposed_sorts_label: 'Sort by'
expose_sort_order: true
sort_asc_label: Asc
sort_desc_label: Desc
pager:
type: full
options:
items_per_page: 10
offset: 0
id: 0
total_pages: null
expose:
items_per_page: false
items_per_page_label: 'Items per page'
items_per_page_options: '5, 10, 25, 50'
items_per_page_options_all: false
items_per_page_options_all_label: '- All -'
offset: false
offset_label: Offset
tags:
previous: ' previous'
next: 'next '
first: '« first'
last: 'last »'
quantity: 9
style:
type: default
options:
grouping: { }
row_class: ''
default_row_class: true
uses_fields: false
row:
type: fields
options:
inline: { }
separator: ''
hide_empty: false
default_field_elements: true
fields:
title:
id: title
table: node_field_data
field: title
entity_type: node
entity_field: title
label: ''
alter:
alter_text: false
make_link: false
absolute: false
trim: false
word_boundary: false
ellipsis: false
strip_tags: false
html: false
hide_empty: false
empty_zero: false
settings:
link_to_entity: true
plugin_id: field
relationship: none
group_type: group
admin_label: ''
exclude: false
element_type: ''
element_class: ''
element_label_type: ''
element_label_class: ''
element_label_colon: true
element_wrapper_type: ''
element_wrapper_class: ''
element_default_classes: true
empty: ''
hide_alter_empty: true
click_sort_column: value
type: string
group_column: value
group_columns: { }
group_rows: true
delta_limit: 0
delta_offset: 0
delta_reversed: false
delta_first_last: false
multi_type: separator
separator: ', '
field_api_classes: false
filters:
status:
value: true
table: node_field_data
field: status
plugin_id: boolean
entity_type: node
entity_field: status
id: status
expose:
operator: ''
group: 1
title:
id: title
table: node_field_data
field: title
relationship: none
group_type: group
admin_label: ''
operator: contains
value: foo
group: 1
exposed: false
expose:
operator_id: ''
label: ''
description: ''
multiple: false
remember: false
entity_type: node
entity_field: title
plugin_id: string
sorts:
created:
id: created
table: node_field_data
field: created
order: DESC
entity_type: node
entity_field: created
plugin_id: date
relationship: none
group_type: group
admin_label: ''
exposed: false
expose:
label: ''
granularity: second
header: { }
footer: { }
empty: { }
relationships: { }
arguments: { }
display_extenders: { }
cache_metadata:
max-age: -1
contexts:
- 'languages:language_content'
- 'languages:language_interface'
- url.query_args
- 'user.node_grants:view'
- user.permissions
tags: { }
entity_reference_1:
display_plugin: entity_reference
id: entity_reference_1
display_title: 'Entity Reference'
position: 1
display_options:
display_extenders: { }
style:
type: entity_reference
options:
search_fields:
title: title
cache_metadata:
max-age: -1
contexts:
- 'languages:language_content'
- 'languages:language_interface'
- 'user.node_grants:view'
- user.permissions
tags: { }
@@ -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,8 @@
name: 'Field Plugins Test'
type: module
description: 'Support module for the field and entity display tests.'
core: 8.x
package: Testing
version: VERSION
dependencies:
- drupal:text
@@ -0,0 +1,24 @@
<?php
namespace Drupal\field_plugins_test\Plugin\Field\FieldFormatter;
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 = @Translation("Test Trimmed"),
* field_types = {
* "text",
* "text_long",
* "text_with_summary"
* },
* quickedit = {
* "editor" = "form"
* }
* )
*/
class TestTextTrimmedFormatter extends TextTrimmedFormatter {
}
@@ -0,0 +1,20 @@
<?php
namespace Drupal\field_plugins_test\Plugin\Field\FieldWidget;
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 = @Translation("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.color:
type: mapping
label: 'Field test entity display color 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,34 @@
<?php
/**
* @file
* Defines an entity type.
*/
/**
* Implements hook_entity_type_alter().
*/
function field_test_entity_type_alter(array &$entity_types) {
/** @var $entity_types \Drupal\Core\Entity\EntityTypeInterface[] */
foreach (field_test_entity_info_translatable() as $entity_type => $translatable) {
$entity_types[$entity_type]->set('translatable', $translatable);
}
}
/**
* Helper function to enable entity translations.
*/
function field_test_entity_info_translatable($entity_type_id = NULL, $translatable = NULL) {
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;
}
@@ -0,0 +1,62 @@
<?php
/**
* @file
* Defines a field type and its formatters and widgets.
*/
use Drupal\Core\Entity\FieldableEntityInterface;
use Drupal\Core\Access\AccessResult;
use Drupal\Core\Entity\Exception\FieldStorageDefinitionUpdateForbiddenException;
use Drupal\Core\Field\FieldDefinitionInterface;
use Drupal\Core\Field\FieldItemListInterface;
use Drupal\Core\Session\AccountInterface;
use Drupal\field\FieldStorageConfigInterface;
/**
* Implements hook_field_widget_info_alter().
*/
function field_test_field_widget_info_alter(&$info) {
$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().
*/
function field_test_field_storage_config_update_forbid(FieldStorageConfigInterface $field_storage, FieldStorageConfigInterface $prior_field_storage) {
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'");
}
}
/**
* Sample 'default value' callback.
*/
function field_test_default_value(FieldableEntityInterface $entity, FieldDefinitionInterface $definition) {
return [['value' => 99]];
}
/**
* Implements hook_entity_field_access().
*/
function field_test_entity_field_access($operation, FieldDefinitionInterface $field_definition, AccountInterface $account, FieldItemListInterface $items = NULL) {
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,8 @@
name: 'Field API Test'
type: module
description: 'Support module for the Field API tests.'
core: 8.x
package: Testing
version: VERSION
dependencies:
- drupal:entity_test
@@ -0,0 +1,218 @@
<?php
/**
* @file
* Helper module for the Field API tests.
*
* The module defines
* - an entity type (field_test.entity.inc)
* - a field type and its formatters and widgets (field_test.field.inc)
* - a field storage backend (field_test.storage.inc)
*
* The main field_test.module file implements generic hooks and provides some
* test helper functions
*/
use Drupal\Core\Entity\EntityTypeInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Render\Element;
use Drupal\field\FieldStorageConfigInterface;
require_once __DIR__ . '/field_test.entity.inc';
require_once __DIR__ . '/field_test.field.inc';
/**
* 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 field_test_memorize() here are ignored
*
* // turn on memorization
* field_test_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 = field_test_memorize();
*
* // make sure hook_field_storage_config_create() is invoked correctly
* assertEqual(count($mem['field_test_field_storage_config_create']), 1);
* assertEqual($mem['field_test_field_storage_config_create'][0], array($field));
* @endcode
*
* @param $key
* The key under which to store to $value, or NULL as described above.
* @param $value
* A value to store for $key.
* @return
* An array mapping each $key to an array of each $value passed in
* for that key.
*/
function field_test_memorize($key = NULL, $value = NULL) {
static $memorize;
if (!isset($key)) {
$return = $memorize;
$memorize = [];
return $return;
}
if (is_array($memorize)) {
$memorize[$key][] = $value;
}
}
/**
* Memorize calls to field_test_field_storage_config_create().
*/
function field_test_field_storage_config_create(FieldStorageConfigInterface $field_storage) {
$args = func_get_args();
field_test_memorize(__FUNCTION__, $args);
}
/**
* Implements hook_entity_display_build_alter().
*/
function field_test_entity_display_build_alter(&$output, $context) {
$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_form_alter().
*/
function field_test_field_widget_form_alter(&$element, FormStateInterface $form_state, $context) {
// 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_form_alter(): Default form is true.');
}
}
/**
* Implements hook_field_widget_multivalue_form_alter().
*/
function field_test_field_widget_multivalue_form_alter(array &$elements, FormStateInterface $form_state, array $context) {
_field_test_alter_widget("hook_field_widget_multivalue_form_alter", $elements, $form_state, $context);
}
/**
* Implements hook_field_widget_multivalue_WIDGET_TYPE_form_alter().
*/
function field_test_field_widget_multivalue_test_field_widget_multiple_form_alter(array &$elements, FormStateInterface $form_state, array $context) {
_field_test_alter_widget("hook_field_widget_multivalue_WIDGET_TYPE_form_alter", $elements, $form_state, $context);
}
/**
* Implements hook_field_widget_multivalue_WIDGET_TYPE_form_alter().
*/
function field_test_field_widget_multivalue_test_field_widget_multiple_single_value_form_alter(array &$elements, FormStateInterface $form_state, array $context) {
_field_test_alter_widget("hook_field_widget_multivalue_WIDGET_TYPE_form_alter", $elements, $form_state, $context);
}
/**
* Sets up alterations for widget alter tests.
*
* @see \Drupal\field\Tests\FormTest::widgetAlterTest()
*/
function _field_test_alter_widget($hook, array &$elements, FormStateInterface $form_state, array $context) {
// 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.";
}
}
}
/**
* Implements hook_query_TAG_alter() for tag 'efq_table_prefixing_test'.
*
* @see \Drupal\system\Tests\Entity\EntityFieldQueryTest::testTablePrefixing()
*/
function field_test_query_efq_table_prefixing_test_alter(&$query) {
// 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()
*/
function field_test_query_efq_metadata_test_alter(&$query) {
global $efq_test_metadata;
$efq_test_metadata = $query->getMetadata('foo');
}
/**
* Implements hook_entity_extra_field_info_alter().
*/
function field_test_entity_extra_field_info_alter(&$info) {
// Remove all extra fields from the 'no_fields' content type;
unset($info['node']['no_fields']);
}
/**
* Implements hook_entity_bundle_field_info_alter().
*/
function field_test_entity_bundle_field_info_alter(&$fields, EntityTypeInterface $entity_type, $bundle) {
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' => t('%name does not accept the value @value.', ['%name' => $field_name, '@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().
*/
function field_test_field_ui_preconfigured_options_alter(array &$options, $field_type) {
if ($field_type === 'test_field_with_preconfigured_options') {
$options['custom_options']['entity_view_display']['settings'] = [
'test_formatter_setting_multiple' => 'altered dummy test string',
];
}
}
@@ -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,17 @@
<?php
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,109 @@
<?php
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' => 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' => 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,40 @@
<?php
namespace Drupal\field_test\Plugin\Field\FieldFormatter;
use Drupal\Core\Field\FieldDefinitionInterface;
use Drupal\Core\Field\FieldItemListInterface;
use Drupal\Core\Field\FormatterBase;
/**
* 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 = @Translation("Applicable"),
* description = @Translation("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,70 @@
<?php
namespace Drupal\field_test\Plugin\Field\FieldFormatter;
use Drupal\Core\Field\FormatterBase;
use Drupal\Core\Field\FieldItemListInterface;
use Drupal\Core\Form\FormStateInterface;
/**
* Plugin implementation of the 'field_test_default' formatter.
*
* @FieldFormatter(
* id = "field_test_default",
* label = @Translation("Default"),
* description = @Translation("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' => t('Setting'),
'#type' => 'textfield',
'#size' => 20,
'#default_value' => $this->getSetting('test_formatter_setting'),
'#required' => TRUE,
];
return $element;
}
/**
* {@inheritdoc}
*/
public function settingsSummary() {
$summary = [];
$summary[] = 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,51 @@
<?php
namespace Drupal\field_test\Plugin\Field\FieldFormatter;
use Drupal\Core\Field\FieldItemListInterface;
use Drupal\Core\Field\FormatterBase;
/**
* Plugin implementation of the 'field_empty_test' formatter.
*
* @FieldFormatter(
* id = "field_empty_test",
* label = @Translation("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,73 @@
<?php
namespace Drupal\field_test\Plugin\Field\FieldFormatter;
use Drupal\Core\Field\FormatterBase;
use Drupal\Core\Field\FieldItemListInterface;
use Drupal\Core\Form\FormStateInterface;
/**
* Plugin implementation of the 'field_empty_setting' formatter.
*
* @FieldFormatter(
* id = "field_empty_setting",
* label = @Translation("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' => 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[] = 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,75 @@
<?php
namespace Drupal\field_test\Plugin\Field\FieldFormatter;
use Drupal\Core\Field\FormatterBase;
use Drupal\Core\Field\FieldItemListInterface;
use Drupal\Core\Form\FormStateInterface;
/**
* Plugin implementation of the 'field_test_multiple' formatter.
*
* @FieldFormatter(
* id = "field_test_multiple",
* label = @Translation("Multiple"),
* description = @Translation("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' => t('Setting'),
'#type' => 'textfield',
'#size' => 20,
'#default_value' => $this->getSetting('test_formatter_setting_multiple'),
'#required' => TRUE,
];
return $element;
}
/**
* {@inheritdoc}
*/
public function settingsSummary() {
$summary = [];
$summary[] = 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,36 @@
<?php
namespace Drupal\field_test\Plugin\Field\FieldFormatter;
use Drupal\Core\Field\FieldItemListInterface;
use Drupal\Core\Field\FormatterBase;
/**
* Plugin implementation of the 'field_no_settings' formatter.
*
* @FieldFormatter(
* id = "field_no_settings",
* label = @Translation("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,83 @@
<?php
namespace Drupal\field_test\Plugin\Field\FieldFormatter;
use Drupal\Core\Field\FormatterBase;
use Drupal\Core\Field\FieldItemListInterface;
use Drupal\Core\Form\FormStateInterface;
/**
* Plugin implementation of the 'field_test_with_prepare_view' formatter.
*
* @FieldFormatter(
* id = "field_test_with_prepare_view",
* label = @Translation("With prepare step"),
* description = @Translation("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' => t('Setting'),
'#type' => 'textfield',
'#size' => 20,
'#default_value' => $this->getSetting('test_formatter_setting_additional'),
'#required' => TRUE,
];
return $element;
}
/**
* {@inheritdoc}
*/
public function settingsSummary() {
$summary = [];
$summary[] = 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,19 @@
<?php
namespace Drupal\field_test\Plugin\Field\FieldType;
/**
* Defines the 'hidden_test' entity field item.
*
* @FieldType(
* id = "hidden_test_field",
* label = @Translation("Hidden from UI test field"),
* description = @Translation("Dummy hidden field type used for tests."),
* no_ui = TRUE,
* default_widget = "test_field_widget",
* default_formatter = "field_test_default"
* )
*/
class HiddenTestItem extends TestItem {
}
@@ -0,0 +1,173 @@
<?php
namespace Drupal\field_test\Plugin\Field\FieldType;
use Drupal\Core\Field\FieldStorageDefinitionInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\TypedData\DataDefinition;
use Drupal\Core\Field\FieldItemBase;
/**
* Defines the 'test_field' entity field item.
*
* @FieldType(
* id = "test_field",
* label = @Translation("Test field"),
* description = @Translation("Dummy field type used for tests."),
* 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(t('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['test_field_storage_setting'] = [
'#type' => 'textfield',
'#title' => t('Field test field storage setting'),
'#default_value' => $this->getSetting('test_field_storage_setting'),
'#required' => FALSE,
'#description' => t('A dummy form element to simulate field storage setting.'),
];
return $form;
}
/**
* {@inheritdoc}
*/
public function fieldSettingsForm(array $form, FormStateInterface $form_state) {
$form['test_field_setting'] = [
'#type' => 'textfield',
'#title' => t('Field test field setting'),
'#default_value' => $this->getSetting('test_field_setting'),
'#required' => FALSE,
'#description' => t('A dummy form element to simulate field setting.'),
];
return $form;
}
/**
* {@inheritdoc}
*/
public function delete() {
// Reports that delete() method is executed for testing purposes.
field_test_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' => 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,32 @@
<?php
namespace Drupal\field_test\Plugin\Field\FieldType;
use Drupal\Core\Field\FieldDefinitionInterface;
/**
* Defines the 'test_field_with_dependencies' entity field item.
*
* @FieldType(
* id = "test_field_with_dependencies",
* label = @Translation("Test field with dependencies"),
* description = @Translation("Dummy field type used for tests."),
* default_widget = "test_field_widget",
* default_formatter = "field_test_default",
* config_dependencies = {
* "module" = {
* "test_module"
* }
* }
* )
*/
class TestItemWithDependencies extends TestItem {
/**
* {@inheritdoc}
*/
public static function calculateDependencies(FieldDefinitionInterface $field_definition) {
return ['content' => ['node:article:uuid']];
}
}
@@ -0,0 +1,51 @@
<?php
namespace Drupal\field_test\Plugin\Field\FieldType;
use Drupal\Core\Field\FieldStorageDefinitionInterface;
use Drupal\Core\Field\PreconfiguredFieldUiOptionsInterface;
/**
* Defines the 'test_field_with_preconfigured_options' entity field item.
*
* @FieldType(
* id = "test_field_with_preconfigured_options",
* label = @Translation("Test field with preconfigured options"),
* description = @Translation("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' => t('All custom options'),
'category' => t('Custom category'),
'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,49 @@
<?php
namespace Drupal\field_test\Plugin\Field\FieldType;
use Drupal\Core\Field\FieldStorageDefinitionInterface;
use Drupal\Core\TypedData\DataDefinition;
use Drupal\Core\Field\FieldItemBase;
/**
* Defines the 'test_object_field' entity field item.
*
* @FieldType(
* id = "test_object_field",
* label = @Translation("Test object field"),
* description = @Translation("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,116 @@
<?php
namespace Drupal\field_test\Plugin\Field\FieldWidget;
use Drupal\Core\Field\FieldItemListInterface;
use Drupal\Core\Field\WidgetBase;
use Drupal\Core\Form\FormStateInterface;
use Symfony\Component\Validator\ConstraintViolationInterface;
/**
* Plugin implementation of the 'test_field_widget' widget.
*
* @FieldWidget(
* id = "test_field_widget",
* label = @Translation("Test widget"),
* field_types = {
* "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' => t('Field test field widget setting'),
'#description' => 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[] = 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' => isset($items[$delta]->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,30 @@
<?php
namespace Drupal\field_test\Plugin\Field\FieldWidget;
use Drupal\Core\Field\FieldItemListInterface;
use Drupal\Core\Form\FormStateInterface;
/**
* Plugin implementation of the 'test_field_widget_multilingual' widget.
*
* @FieldWidget(
* id = "test_field_widget_multilingual",
* label = @Translation("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,104 @@
<?php
namespace Drupal\field_test\Plugin\Field\FieldWidget;
use Drupal\Core\Field\FieldDefinitionInterface;
use Drupal\Core\Field\FieldItemListInterface;
use Drupal\Core\Field\WidgetBase;
use Drupal\Core\Form\FormStateInterface;
use Symfony\Component\Validator\ConstraintViolationInterface;
/**
* 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 = @Translation("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' => t('Field test field widget setting'),
'#description' => 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[] = 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' => [[get_class($this), '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);
}
/**
* {@inheritdoc}
* Used in \Drupal\field\Tests\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,22 @@
<?php
namespace Drupal\field_test\Plugin\Field\FieldWidget;
/**
* 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 = @Translation("Test widget - multiple - single value"),
* multiple_values = FALSE,
* weight = 10
* )
*/
class TestFieldWidgetMultipleSingleValues extends TestFieldWidgetMultiple {
}
@@ -0,0 +1,32 @@
<?php
namespace Drupal\field_test\Plugin\Validation\Constraint;
use Symfony\Component\Validator\Constraints\NotEqualTo;
/**
* Checks if a value is not equal.
*
* @Constraint(
* id = "TestField",
* label = @Translation("Test Field", context = "Validation"),
* type = { "integer" }
* )
*/
class TestFieldConstraint extends NotEqualTo {
/**
* {@inheritdoc}
*/
public function getRequiredOptions() {
return ['value'];
}
/**
* {@inheritdoc}
*/
public function validatedBy() {
return '\Symfony\Component\Validator\Constraints\NotEqualToValidator';
}
}
@@ -0,0 +1,8 @@
name: 'Boolean field Test'
type: module
description: 'Support module for the field and entity display tests.'
core: 8.x
package: Testing
version: VERSION
dependencies:
- drupal:field
@@ -0,0 +1,18 @@
<?php
/**
* @file
* Module for testing denying access to boolean fields.
*/
use Drupal\Core\Access\AccessResult;
use Drupal\Core\Field\FieldDefinitionInterface;
use Drupal\Core\Field\FieldItemListInterface;
use Drupal\Core\Session\AccountInterface;
/**
* Implements hook_entity_field_access().
*/
function field_test_boolean_access_denied_entity_field_access($operation, FieldDefinitionInterface $field_definition, AccountInterface $account, FieldItemListInterface $items = NULL) {
return AccessResult::forbiddenIf($field_definition->getName() === \Drupal::state()->get('field.test_boolean_field_access_field'));
}
@@ -0,0 +1,15 @@
id: entity_test.entity_test.field_test_import
langcode: en
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: { }
field_type: text
dependencies:
config:
- field.storage.entity_test.field_test_import
@@ -0,0 +1,15 @@
id: entity_test.entity_test.field_test_import_2
langcode: en
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: { }
field_type: text
dependencies:
config:
- field.storage.entity_test.field_test_import_2
@@ -0,0 +1,15 @@
id: entity_test.test_bundle.field_test_import_2
langcode: en
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: { }
field_type: text
dependencies:
config:
- field.storage.entity_test.field_test_import_2
@@ -0,0 +1,20 @@
id: entity_test.field_test_import
langcode: en
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
dependencies:
module:
- entity_test
- text
persist_with_no_fields: false
custom_storage: false
@@ -0,0 +1,20 @@
id: entity_test.field_test_import_2
langcode: en
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
dependencies:
module:
- entity_test
- text
persist_with_no_fields: false
custom_storage: false
@@ -0,0 +1,6 @@
name: 'Field API configuration tests'
type: module
description: 'Support module for the Field API configuration tests.'
core: 8.x
package: Testing
version: VERSION
@@ -0,0 +1,16 @@
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: { }
field_type: text
dependencies:
config:
- field.storage.entity_test.field_test_import_sync
@@ -0,0 +1,16 @@
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: { }
field_type: text
dependencies:
config:
- field.storage.entity_test.field_test_import_sync_2
@@ -0,0 +1,16 @@
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: { }
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,8 @@
name: 'Field test views'
type: module
description: 'Provides default views for views field tests.'
package: Testing
version: VERSION
core: 8.x
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: Master
id: default
position: 0
@@ -0,0 +1,63 @@
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
display_plugin: default
display_title: Master
id: default
position: 0
@@ -0,0 +1,15 @@
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
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
@@ -0,0 +1,9 @@
name: 'Field Third Party Settings Test'
type: module
description: 'Support module for the Field API tests.'
core: 8.x
package: Testing
version: VERSION
dependencies:
- drupal:entity_test
- drupal:field_test
@@ -0,0 +1,51 @@
<?php
/**
* @file
* Test module.
*/
use Drupal\Core\Field\FieldDefinitionInterface;
use Drupal\Core\Field\FormatterInterface;
use Drupal\Core\Field\WidgetInterface;
use Drupal\Core\Form\FormStateInterface;
/**
* Implements hook_field_widget_third_party_settings_form().
*/
function field_third_party_test_field_widget_third_party_settings_form(WidgetInterface $plugin, FieldDefinitionInterface $field_definition, $form_mode, $form, FormStateInterface $form_state) {
$element['field_test_widget_third_party_settings_form'] = [
'#type' => 'textfield',
'#title' => 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_settings_summary_alter().
*/
function field_third_party_test_field_widget_settings_summary_alter(&$summary, $context) {
$summary[] = 'field_test_field_widget_settings_summary_alter';
return $summary;
}
/**
* Implements hook_field_formatter_third_party_settings_form().
*/
function field_third_party_test_field_formatter_third_party_settings_form(FormatterInterface $plugin, FieldDefinitionInterface $field_definition, $view_mode, $form, FormStateInterface $form_state) {
$element['field_test_field_formatter_third_party_settings_form'] = [
'#type' => 'textfield',
'#title' => 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_settings_summary_alter().
*/
function field_third_party_test_field_formatter_settings_summary_alter(&$summary, $context) {
$summary[] = 'field_test_field_formatter_settings_summary_alter';
return $summary;
}
@@ -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,9 @@
name: 'Field Timestamp Test'
type: module
description: 'Support module for the Timestamp field item test.'
core: 8.x
package: Testing
version: VERSION
dependencies:
- drupal:entity_test
- drupal:field
@@ -0,0 +1,253 @@
<?php
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 {
/**
* Modules to enable.
*
* @var array
*/
public static $modules = [
'entity_test',
'field_ui',
'options',
'field_test_boolean_access_denied',
];
/**
* {@inheritdoc}
*/
protected $defaultTheme = 'classy';
/**
* 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() {
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() {
$on = $this->randomMachineName();
$off = $this->randomMachineName();
$label = $this->randomMachineName();
// Create a field with settings to validate.
$field_name = mb_strtolower($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->assertFieldByName("{$field_name}[value]", '', 'Widget found.');
$this->assertText($this->field->label(), 'Uses field label by default.');
$this->assertNoRaw($on, 'Does not use the "On" label.');
// Submit and ensure it is accepted.
$edit = [
"{$field_name}[value]" => 1,
];
$this->drupalPostForm(NULL, $edit, t('Save'));
preg_match('|entity_test/manage/(\d+)|', $this->getUrl(), $match);
$id = $match[1];
$this->assertText(t('entity_test @id has been created.', ['@id' => $id]));
// Verify that boolean value is displayed.
$entity = EntityTest::load($id);
$this->drupalGet($entity->toUrl());
$this->assertRaw('<div class="field__item">' . $on . '</div>');
// 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->assertFieldByName("{$field_name}[value]", '', 'Widget found.');
$this->assertRaw($on);
$this->assertNoText($this->field->label());
// Test if we can change the on label.
$on = $this->randomMachineName();
$edit = [
'settings[on_label]' => $on,
];
$this->drupalPostForm('entity_test/structure/entity_test/fields/entity_test.entity_test.' . $field_name, $edit, t('Save settings'));
// Check if we see the updated labels in the creation form.
$this->drupalGet('entity_test/add');
$this->assertRaw($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->drupalPostForm(NULL, [], $field_name . "_settings_edit");
$this->assertText(
'Use field label instead of the "On" label as the label.',
t('Display setting checkbox available.')
);
// Enable setting.
$edit = ['fields[' . $field_name . '][settings_edit_form][settings][display_label]' => 1];
$this->drupalPostForm(NULL, $edit, $field_name . "_plugin_settings_update");
$this->drupalPostForm(NULL, NULL, 'Save');
// Go again to the form display page and check if the setting
// is stored and has the expected effect.
$this->drupalGet($fieldEditUrl);
$this->assertText('Use field label: Yes', 'Checking the display settings checkbox updated the value.');
$this->drupalPostForm(NULL, [], $field_name . "_settings_edit");
$this->assertText(
'Use field label instead of the "On" label as the label.',
t('Display setting checkbox is available')
);
$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->assertFieldById('edit-settings-on-label', $on);
$this->assertFieldById('edit-settings-off-label', $off);
}
/**
* Test field access.
*/
public function testFormAccess() {
$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->assertFieldByName("{$field_name}[value]");
// Should be posted OK.
$this->drupalPostForm(NULL, [], t('Save'));
preg_match('|entity_test/manage/(\d+)|', $this->getUrl(), $match);
$id = $match[1];
$this->assertText(t('entity_test @id has been created.', ['@id' => $id]));
// 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->assertNoFieldByName("{$field_name}[value]");
// Should still be able to post the form.
$this->drupalPostForm(NULL, [], t('Save'));
preg_match('|entity_test/manage/(\d+)|', $this->getUrl(), $match);
$id = $match[1];
$this->assertText(t('entity_test @id has been created.', ['@id' => $id]));
}
}
@@ -0,0 +1,134 @@
<?php
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 {
/**
* Modules to enable.
*
* @var array
*/
public 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() {
parent::setUp();
// Create a content type. Use Node because it has Field UI pages that work.
$type_name = mb_strtolower($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 = mb_strtolower($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() {
// 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 WebTestBase.
$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->drupalPostForm(NULL, [
'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->drupalPostForm(NULL, [], $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(t('Field settings (@on_label / @off_label)', ['@on_label' => $values[0], '@off_label' => $values[1]]));
// Test that the settings summary are present in the correct format.
$this->drupalGet('admin/structure/types/manage/' . $this->bundle . '/display');
$result = $this->xpath('//div[contains(@class, :class) and contains(text(), :text)]', [
':class' => 'field-plugin-summary',
':text' => (string) t('Display: @true_label / @false_label', ['@true_label' => $values[0], '@false_label' => $values[1]]),
]);
$this->assertCount(1, $result, "Boolean formatter settings summary exist.");
}
}
}
@@ -0,0 +1,114 @@
<?php
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 {
/**
* Modules to enable.
*
* @var array
*/
public 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;
protected function setUp() {
parent::setUp();
$this->drupalLogin($this->drupalCreateUser([
'view test entity',
'administer entity_test content',
'administer content types',
]));
}
/**
* Tests email field.
*/
public function testEmailField() {
// Create a field with settings to validate.
$field_name = mb_strtolower($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->assertFieldByName("{$field_name}[0][value]", '', 'Widget found.');
$this->assertRaw('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->drupalPostForm(NULL, $edit, t('Save'));
preg_match('|entity_test/manage/(\d+)|', $this->getUrl(), $match);
$id = $match[1];
$this->assertText(t('entity_test @id has been created.', ['@id' => $id]));
$this->assertRaw($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);
}
}
@@ -0,0 +1,410 @@
<?php
namespace Drupal\Tests\field\Functional\EntityReference;
use Behat\Mink\Element\NodeElement;
use Drupal\Core\Field\FieldStorageDefinitionInterface;
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
*/
public 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() {
parent::setUp();
$this->drupalPlaceBlock('system_breadcrumb_block');
// Create a content type, with underscores.
$type_name = strtolower($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() {
$bundle_path = 'admin/structure/types/manage/' . $this->type;
// Create a new view and display it as a 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->drupalPostForm('admin/structure/views/add', $edit, t('Save and edit'));
$this->drupalPostForm(NULL, [], t('Duplicate as Entity Reference'));
$this->clickLink(t('Settings'));
$edit = [
'style_options[search_fields][title]' => 'title',
];
$this->drupalPostForm(NULL, $edit, t('Apply'));
// Set sort to NID ascending.
$edit = [
'name[node_field_data.nid]' => 1,
];
$this->drupalPostForm('admin/structure/views/nojs/add-handler/node_test_view/entity_reference_1/sort', $edit, t('Add and configure sort criteria'));
$this->drupalPostForm(NULL, NULL, t('Apply'));
$this->drupalPostForm('admin/structure/views/view/node_test_view/edit/entity_reference_1', [], t('Save'));
$this->clickLink(t('Settings'));
// Create a test entity reference field.
$field_name = 'test_entity_ref_field';
$edit = [
'new_storage_type' => 'field_ui:entity_reference:node',
'label' => 'Test Entity Reference Field',
'field_name' => $field_name,
];
$this->drupalPostForm($bundle_path . '/fields/add-field', $edit, t('Save and continue'));
// Set to unlimited.
$edit = [
'cardinality' => FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED,
];
$this->drupalPostForm(NULL, $edit, t('Save field settings'));
// Add the view to the test field.
$edit = [
'settings[handler]' => 'views',
];
$this->drupalPostForm(NULL, $edit, t('Change handler'));
$edit = [
'required' => FALSE,
'settings[handler_settings][view][view_and_display]' => 'node_test_view:entity_reference_1',
];
$this->drupalPostForm(NULL, $edit, t('Save settings'));
// 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);
$result = $this->xpath('//input[@name="field_test_entity_ref_field[0][target_id]" and contains(@data-autocomplete-path, "/entity_reference_autocomplete/node/views/")]');
$target_url = $this->getAbsoluteUrl($result[0]->getAttribute('data-autocomplete-path'));
$this->drupalGet($target_url, ['query' => ['q' => 'Foo']]);
$this->assertRaw($node1->getTitle() . ' (' . $node1->id() . ')');
$this->assertRaw($node2->getTitle() . ' (' . $node2->id() . ')');
// Try to add a new node, fill the entity reference field and submit the
// form.
$this->drupalPostForm('node/add/' . $this->type, [], t('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->drupalPostForm(NULL, $edit, t('Save'));
$this->assertSession()->statusCodeEquals(200);
$edit = [
'title[0][value]' => 'Example',
'field_test_entity_ref_field[0][target_id]' => 'Test',
];
$this->drupalPostForm('node/add/' . $this->type, $edit, t('Save'));
// Assert that entity reference autocomplete field is validated.
$this->assertText(t('There are no entities matching "@entity"', ['@entity' => 'Test']));
$edit = [
'title[0][value]' => 'Test',
'field_test_entity_ref_field[0][target_id]' => $node1->getTitle(),
];
$this->drupalPostForm('node/add/' . $this->type, $edit, t('Save'));
// Assert the results multiple times to avoid sorting problem of nodes with
// the same title.
$this->assertText(t('Multiple entities match this reference;'));
$this->assertText(t("@node1", ['@node1' => $node1->getTitle() . ' (' . $node1->id() . ')']));
$this->assertText(t("@node2", ['@node2' => $node2->getTitle() . ' (' . $node2->id() . ')']));
$this->assertText(t('Specify the one you want by appending the id in parentheses, like "@example".', ['@example' => $node2->getTitle() . ' (' . $node2->id() . ')']));
$edit = [
'title[0][value]' => 'Test',
'field_test_entity_ref_field[0][target_id]' => $node1->getTitle() . ' (' . $node1->id() . ')',
];
$this->drupalPostForm('node/add/' . $this->type, $edit, t('Save'));
$this->assertLink($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 . '/storage');
$edit = [
'cardinality' => -1,
];
$this->drupalPostForm(NULL, $edit, t('Save field settings'));
$this->drupalGet($bundle_path . '/fields/' . $field_path);
$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 = [
// This must be set before new entities will be auto-created.
'settings[handler_settings][auto_create]' => 1,
];
$this->drupalPostForm(NULL, $edit, t('Save settings'));
$this->drupalGet($bundle_path . '/fields/' . $field_path);
$edit = [
// A term that doesn't yet exist.
'default_value_input[field_' . $taxonomy_term_field_name . '][0][target_id]' => $term_name,
];
$this->drupalPostForm(NULL, $edit, t('Save settings'));
// 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() {
// 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 when the field has
* multiple target bundles and is set to auto-create the target entity.
*/
public function testMultipleTargetBundles() {
/** @var \Drupal\taxonomy\Entity\Vocabulary[] $vocabularies */
$vocabularies = [];
for ($i = 0; $i < 2; $i++) {
$vid = mb_strtolower($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->assertNoFieldByName('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->drupalPostForm($path, $edit, 'Save settings');
$this->drupalGet($path);
// Expect a select element with the two vocabularies as options.
$this->assertFieldByXPath("//select[@name='settings[handler_settings][auto_create_bundle]']/option[@value='" . $vocabularies[0]->id() . "']");
$this->assertFieldByXPath("//select[@name='settings[handler_settings][auto_create_bundle]']/option[@value='" . $vocabularies[1]->id() . "']");
$edit = [
'settings[handler_settings][auto_create]' => TRUE,
'settings[handler_settings][auto_create_bundle]' => $vocabularies[1]->id(),
];
$this->drupalPostForm(NULL, $edit, t('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->assertEqual($field_config->getSetting('handler_settings')['auto_create_bundle'], $vocabularies[1]->id());
// 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->assertIdentical($field_config->getSetting('handler_settings')['auto_create_bundle'], $vocabularies[1]->id());
// 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->assertSame(FALSE, $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 = strtolower($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, NULL, '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.
*/
protected function assertFieldSelectOptions($name, array $expected_options) {
$xpath = $this->buildXPathQuery('//select[@name=:name]', [':name' => $name]);
$fields = $this->xpath($xpath);
if ($fields) {
$field = $fields[0];
$options = $field->findAll('xpath', 'option');
array_walk($options, function (NodeElement &$option) {
$option = $option->getValue();
});
sort($options);
sort($expected_options);
$this->assertIdentical($options, $expected_options);
}
else {
$this->fail('Unable to find field ' . $name);
}
}
}
@@ -0,0 +1,237 @@
<?php
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\EntityReferenceTestTrait;
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 EntityReferenceTestTrait;
public static $modules = ['node', 'taxonomy'];
/**
* {@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;
protected function setUp() {
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 that the autocomplete input element appears and the creation of a new
* entity.
*/
public function testAutoCreate() {
$this->drupalGet('node/add/' . $this->referencingType);
$this->assertFieldByXPath('//input[@id="edit-test-field-0-target-id" and contains(@class, "form-autocomplete")]', NULL, 'The autocomplete input element appears.');
$new_title = $this->randomMachineName();
// Assert referenced node does not exist.
$base_query = \Drupal::entityQuery('node');
$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->drupalPostForm("node/add/$this->referencingType", $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')
->condition('type', $this->referencingType)
->execute();
$referencing_nid = key($result);
$referencing_node = Node::load($referencing_nid);
$this->assertEqual($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->assertText($referencing_node->label(), 'Referencing node label found.');
$this->assertText($referenced_node->label(), 'Referenced node label found.');
}
/**
* Tests if an entity reference field having multiple target bundles is
* storing the auto-created entity in the right destination.
*/
public function testMultipleTargetBundles() {
/** @var \Drupal\taxonomy\Entity\Vocabulary[] $vocabularies */
$vocabularies = [];
for ($i = 0; $i < 2; $i++) {
$vid = mb_strtolower($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 = mb_strtolower($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);
/** @var \Drupal\Core\Entity\Display\EntityFormDisplayInterface $fd */
\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->drupalPostForm('node/add/' . $this->referencingType, $edit, 'Save');
/** @var \Drupal\taxonomy\Entity\Term $term */
$term = taxonomy_term_load_multiple_by_name($term_name);
$term = reset($term);
// The new term is expected to be stored in the second vocabulary.
$this->assertEqual($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->drupalPostForm('node/add/' . $this->referencingType, $edit, 'Save');
/** @var \Drupal\taxonomy\Entity\Term $term */
$term = taxonomy_term_load_multiple_by_name($term_name);
$term = reset($term);
// The second term is expected to be stored in the first vocabulary.
$this->assertEqual($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.
// 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);
}
}
@@ -0,0 +1,162 @@
<?php
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;
/**
* Modules to install.
*
* @var array
*/
public 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;
protected function setUp() {
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() {
// Create a node to be referenced.
$referenced_node = $this->drupalCreateNode(['type' => 'referenced_content']);
$field_name = mb_strtolower($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 = [
'default_value_input[' . $field_name . '][0][target_id]' => $referenced_node->getTitle() . ' (' . $referenced_node->id() . ')',
];
$this->drupalPostForm('admin/structure/types/manage/reference_content/fields/node.reference_content.' . $field_name, $field_edit, t('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->assertRaw('name="default_value_input[' . $field_name . '][0][target_id]" value="' . $referenced_node->getTitle() . ' (' . $referenced_node->id() . ')', 'The default value is selected in instance settings page');
// 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->assertEqual($config_entity['default_value'][0]['target_uuid'], $referenced_node->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->assertEqual([$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->assertEqual($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() {
// 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 = mb_strtolower($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 = [
'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->drupalPostForm('admin/structure/types/manage/reference_content/fields/node.reference_content.' . $field_name, $field_edit, t('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,357 @@
<?php
namespace Drupal\Tests\field\Functional\EntityReference;
use Drupal\field\Entity\FieldConfig;
use Drupal\language\Entity\ConfigurableLanguage;
use Drupal\Core\Field\FieldStorageDefinitionInterface;
use Drupal\Tests\BrowserTestBase;
use Drupal\field\Entity\FieldStorageConfig;
/**
* Tests the translation of entity reference field display on nodes.
*
* @group entity_reference
*/
class EntityReferenceFieldTranslatedReferenceViewTest extends BrowserTestBase {
/**
* 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;
/**
* An user with permission to edit the referrer entity.
*
* @var \Drupal\user\UserInterface
*/
protected $webUser;
/**
* Modules to enable.
*
* @var array
*/
public static $modules = [
'language',
'content_translation',
'node',
];
/**
* {@inheritdoc}
*/
protected $defaultTheme = 'stark';
protected function setUp() {
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() {
// Create a translated referrer entity.
$this->referrerEntity = $this->createReferrerEntity();
$this->assertEntityReferenceDisplay();
$this->assertEntityReferenceFormDisplay();
// Disable translation for referrer content type.
$this->drupalLogin($this->rootUser);
$this->drupalPostForm('admin/config/regional/content-language', ['settings[node][referrer][translatable]' => FALSE], t('Save configuration'));
$this->drupalLogout();
// Create a referrer entity without translation.
$this->referrerEntity = $this->createReferrerEntity(FALSE);
$this->assertEntityReferenceDisplay();
$this->assertEntityReferenceFormDisplay();
}
/**
* Assert entity reference display.
*/
protected function assertEntityReferenceDisplay() {
$url = $this->referrerEntity->toUrl();
$translation_url = $this->referrerEntity->toUrl('canonical', ['language' => ConfigurableLanguage::load($this->translateToLangcode)]);
$this->drupalGet($url);
$this->assertText($this->labelOfNotTranslatedReference, 'The label of not translated reference is displayed.');
$this->assertText($this->originalLabel, 'The default label of translated reference is displayed.');
$this->assertNoText($this->translatedLabel, 'The translated label of translated reference is not displayed.');
$this->drupalGet($translation_url);
$this->assertText($this->labelOfNotTranslatedReference, 'The label of not translated reference is displayed.');
$this->assertNoText($this->originalLabel, 'The default label of translated reference is not displayed.');
$this->assertText($this->translatedLabel, 'The translated label of translated reference is displayed.');
}
/**
* Assert entity reference form display.
*/
protected function assertEntityReferenceFormDisplay() {
$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() {
ConfigurableLanguage::createFromLangcode($this->translateToLangcode)->save();
}
/**
* Creates a test subject contents, with translation.
*/
protected function createContent() {
$this->referencedEntityWithTranslation = $this->createReferencedEntityWithTranslation();
$this->referencedEntityWithoutTranslation = $this->createNotTranslatedReferencedEntity();
}
/**
* Enables translations where it needed.
*/
protected function enableTranslation() {
// Enable translation for the entity types and ensure the change is picked
// up.
\Drupal::service('content_translation.manager')->setEnabled($this->testEntityTypeName, $this->referrerType->id(), TRUE);
\Drupal::service('content_translation.manager')->setEnabled($this->testEntityTypeName, $this->referencedType->id(), TRUE);
\Drupal::service('router.builder')->rebuild();
}
/**
* Adds term reference field for the article content type.
*/
protected function setUpEntityReferenceField() {
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() {
$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,139 @@
<?php
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;
public static $modules = ['entity_reference', '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;
protected function setUp() {
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() {
$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->drupalPostForm('node/add/' . $this->referencingType, $edit, 'Upload');
$this->assertSession()->statusCodeEquals(200);
$edit = [
'title[0][value]' => $this->randomMachineName(),
'test_field[0][target_id]' => $this->nodeId,
];
$this->drupalPostForm(NULL, $edit, 'Save');
$this->assertSession()->statusCodeEquals(200);
}
}
@@ -0,0 +1,237 @@
<?php
namespace Drupal\Tests\field\Functional\EntityReference;
use Drupal\Component\Render\FormattableMarkup;
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\EntityReferenceTestTrait;
/**
* Tests various Entity reference UI components.
*
* @group entity_reference
*/
class EntityReferenceIntegrationTest extends BrowserTestBase {
use AssertConfigEntityImportTrait;
use EntityReferenceTestTrait;
/**
* 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;
/**
* Modules to install.
*
* @var array
*/
public static $modules = ['config_test', 'entity_test', 'field_ui'];
/**
* {@inheritdoc}
*/
protected $defaultTheme = 'stark';
/**
* {@inheritdoc}
*/
protected function setUp() {
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() {
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 a ' (entity_id)' suffix.
$this->fieldName . '[1][target_id]' => $referenced_entities[1]->label(),
];
$this->drupalPostForm($this->entityType . '/add', $edit, t('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->assertFieldByName($this->fieldName . '[0][target_id]', $referenced_entities[0]->label() . ' (' . $referenced_entities[0]->id() . ')');
$this->assertFieldByName($this->fieldName . '[1][target_id]', $referenced_entities[1]->label() . ' (' . $referenced_entities[1]->id() . ')');
$this->drupalPostForm(NULL, [], t('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 a ' (entity_id)' suffix.
$target_id .= ', ' . $referenced_entities[1]->label();
$edit = [
'name[0][value]' => $entity_name,
$this->fieldName . '[target_id]' => $target_id,
];
$this->drupalPostForm($this->entityType . '/add', $edit, t('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->assertFieldByName($this->fieldName . '[target_id]', $target_id . ' (' . $referenced_entities[1]->id() . ')');
$this->drupalPostForm(NULL, [], t('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->drupalPostForm($this->entityType . '/manage/' . $entity->id() . '/edit', [], t('Save'));
$this->assertFieldValues($entity_name, $referenced_entities);
}
// 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 = [
'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->drupalPostForm($this->entityType . '/structure/' . $this->bundle . '/fields/' . $this->entityType . '.' . $this->bundle . '.' . $this->fieldName, $field_edit, t('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], new FormattableMarkup('Expected @type dependency @name found', ['@type' => $key, '@name' => $referenced_entities[0]->getConfigDependencyName()]));
// 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]), new FormattableMarkup('@type dependency @name does not exist.', ['@type' => $key, '@name' => $referenced_entities[0]->getConfigDependencyName()]));
}
}
/**
* 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.
*/
protected function assertFieldValues($entity_name, $referenced_entities) {
$entity = current($this->container->get('entity_type.manager')->getStorage(
$this->entityType)->loadByProperties(['name' => $entity_name]));
$this->assertNotEmpty($entity, new FormattableMarkup('%entity_type: Entity found in the database.', ['%entity_type' => $this->entityType]));
$this->assertEqual($entity->{$this->fieldName}->target_id, $referenced_entities[0]->id());
$this->assertEqual($entity->{$this->fieldName}->entity->id(), $referenced_entities[0]->id());
$this->assertEqual($entity->{$this->fieldName}->entity->label(), $referenced_entities[0]->label());
$this->assertEqual($entity->{$this->fieldName}[1]->target_id, $referenced_entities[1]->id());
$this->assertEqual($entity->{$this->fieldName}[1]->entity->id(), $referenced_entities[1]->id());
$this->assertEqual($entity->{$this->fieldName}[1]->entity->label(), $referenced_entities[1]->label());
}
/**
* Creates two content and two config test entities.
*
* @return array
* An array of entity objects.
*/
protected function getTestEntities() {
$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,83 @@
<?php
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\EntityReferenceTestTrait;
/**
* Tests possible XSS security issues in entity references.
*
* @group entity_reference
*/
class EntityReferenceXSSTest extends BrowserTestBase {
use EntityReferenceTestTrait;
/**
* Modules to enable.
*
* @var array
*/
protected static $modules = ['node'];
/**
* {@inheritdoc}
*/
protected $defaultTheme = 'stark';
/**
* Tests markup is escaped in the entity reference select and label formatter.
*/
public function testEntityReferenceXSS() {
$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->rootUser);
$this->drupalGet('node/add/article');
$this->assertEscaped($referenced_node->getTitle());
$this->assertEscaped($node_type_two->label());
$edit = [
'title[0][value]' => $this->randomString(),
'entity_reference_test' => $referenced_node->id(),
];
$this->drupalPostForm(NULL, $edit, 'Save');
$this->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->assertEscaped($referenced_node->getTitle());
// options_buttons does not support optgroups.
$this->assertNoText('bundle with markup');
}
}
@@ -0,0 +1,124 @@
<?php
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\Tests\BrowserTestBase;
use Drupal\Tests\field\Traits\EntityReferenceTestTrait;
use Drupal\views\Views;
/**
* Tests entity reference selection handler.
*
* @group entity_reference
*/
class SelectionTest extends BrowserTestBase {
use EntityReferenceTestTrait;
/**
* {@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() {
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;
}
// 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() {
// 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->assertEqual($result, $expected, 'The autocomplete result of the Views entity reference selection handler contains the proper output.');
}
}
@@ -0,0 +1,98 @@
<?php
namespace Drupal\Tests\field\Functional;
use Drupal\field\Entity\FieldConfig;
use Drupal\field\Entity\FieldStorageConfig;
/**
* Tests Field access.
*
* @group field
*/
class FieldAccessTest extends FieldTestBase {
/**
* Modules to enable.
*
* @var array
*/
public 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;
protected function setUp() {
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);
}
/**
* Test that hook_entity_field_access() is called.
*/
public function testFieldAccess() {
// Assert the text is visible.
$this->drupalGet('node/' . $this->node->id());
$this->assertText($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->assertNoText($this->testViewFieldValue);
}
}
@@ -0,0 +1,94 @@
<?php
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 {
/**
* Modules to enable.
*
* @var array
*/
public static $modules = ['node', 'field_test', 'field_ui'];
/**
* {@inheritdoc}
*/
protected $defaultTheme = 'stark';
/**
* The field name.
*
* @var string
*/
private $fieldName;
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
$this->fieldName = 'field_test';
// Create Article node types.
if ($this->profile != 'standard') {
$this->drupalCreateContentType([
'type' => 'article',
'name' => 'Article',
]);
}
}
public function testDefaultValueCallbackForm() {
// Create a field and storage for checking.
/** @var \Drupal\field\Entity\FieldStorageConfig $field_storage */
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();
$this->drupalLogin($this->rootUser);
// 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->assertFieldByName('default_value_input[field_test][0][value]', NULL, 'The default field form is visible.');
// 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->assertFieldByName('default_value_input[field_test][0][value]', $default_value, 'The default field form is visible.');
// 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->assertFieldByName('default_value_input[field_test][0][value]', $default_value, 'The default field form is visible.');
// 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->assertNoFieldByName('default_value_input[field_test][0][value]', 'Calculated default value', 'The default field form is not visible when a callback is defined.');
}
}
@@ -0,0 +1,64 @@
<?php
namespace Drupal\Tests\field\Functional;
use Drupal\Tests\BrowserTestBase;
/**
* Tests help display for the Field module.
*
* @group field
*/
class FieldHelpTest extends BrowserTestBase {
/**
* Modules to enable.
*
* @var array
*/
public static $modules = ['field', 'help'];
/**
* {@inheritdoc}
*/
protected $defaultTheme = 'stark';
// Tests field help implementation without optional core modules enabled.
protected $profile = 'minimal';
/**
* The admin user that will be created.
*/
protected $adminUser;
protected function setUp() {
parent::setUp();
// Create the admin user.
$this->adminUser = $this->drupalCreateUser(['access administration pages', 'view the administration theme']);
}
/**
* Test the Field module's help page.
*/
public function testFieldHelp() {
// 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->resetAll();
\Drupal::service('plugin.manager.field.widget')->clearCachedDefinitions();
\Drupal::service('plugin.manager.field.field_type')->clearCachedDefinitions();
$this->drupalGet('admin/help/field');
$this->assertLink('Options', 0, 'Options module is listed on the Field help page.');
$this->assertText('Field API Test', 'Modules with field types that do not implement hook_help are listed.');
$this->assertNoLink('Field API Test', 'Modules with field types that do not implement hook_help are not linked.');
$this->assertNoLink('Link', 'Modules that have not been installed, are not listed.');
}
}
@@ -0,0 +1,129 @@
<?php
namespace Drupal\Tests\field\Functional;
use Drupal\entity_test\Entity\EntityTest;
use Drupal\field\Entity\FieldConfig;
use Drupal\field\Entity\FieldStorageConfig;
/**
* Delete field storages and fields during config synchronization and uninstall
* module that provides the field type 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 {
/**
* Modules to enable.
*
* @var array
*/
public static $modules = [
'entity_test',
'telephone',
'config',
'filter',
'datetime',
];
/**
* {@inheritdoc}
*/
protected $defaultTheme = 'stark';
protected function setUp() {
parent::setUp();
$this->drupalLogin($this->drupalCreateUser(['synchronize configuration']));
}
/**
* Tests deleting field storages and fields as part of config import.
*/
public function testImportDeleteUninstall() {
// 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->assertEqual($entity->field_tel->value, $value);
$this->assertEqual($entity->field_tel[0]->value, $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->assertText('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->assertText('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->drupalPostForm(NULL, [], t('Import all'));
$this->assertNoText('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.');
}
}
@@ -0,0 +1,66 @@
<?php
namespace Drupal\Tests\field\Functional;
use Drupal\Component\Render\FormattableMarkup;
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 $cardinality
* Number of values to generate.
* @return
* 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 $field_name
* The name of the field to test
* @param $expected_values
* The array of expected values.
* @param $langcode
* (Optional) The language code for the values. Defaults to
* \Drupal\Core\Language\LanguageInterface::LANGCODE_DEFAULT.
* @param $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->assertEqual(count($values), count($expected_values), 'Expected number of values were saved.');
foreach ($expected_values as $key => $value) {
$this->assertEqual($values[$key][$column], $value, new FormattableMarkup('Value @value was saved correctly.', ['@value' => $value]));
}
}
}
@@ -0,0 +1,739 @@
<?php
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\field\Entity\FieldConfig;
use Drupal\field\Entity\FieldStorageConfig;
/**
* Tests field form handling.
*
* @group field
*/
class FormTest extends FieldTestBase {
/**
* Modules to enable.
*
* Locale is installed so that TranslatableMarkup actually does something.
*
* @var array
*/
public static $modules = [
'node',
'field_test',
'options',
'entity_test',
'locale',
];
/**
* {@inheritdoc}
*/
protected $defaultTheme = 'stark';
/**
* An array of values defining a field single.
*
* @var array
*/
protected $fieldStorageSingle;
/**
* An array of values defining a field multiple.
*
* @var array
*/
protected $fieldStorageMultiple;
/**
* 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;
protected function setUp() {
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->fieldStorageMultiple = [
'field_name' => 'field_multiple',
'entity_type' => 'entity_test',
'type' => 'test_field',
'cardinality' => 4,
];
$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(),
],
];
}
public function testFieldFormSingle() {
$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->assertText($token_description, 'Token replacement for description is displayed');
$this->assertFieldByName("{$field_name}[0][value]", '', 'Widget is displayed');
$this->assertNoField("{$field_name}[1][value]", 'No extraneous widget is displayed');
// Check that hook_field_widget_form_alter() does not believe this is the
// default value form.
$this->assertNoText('From hook_field_widget_form_alter(): Default form is true.', 'Not default value form in hook_field_widget_form_alter().');
// Check that hook_field_widget_form_alter() does not believe this is the
// default value form.
$this->assertNoText('From hook_field_widget_multivalue_form_alter(): Default form is true.', 'Not default value form in hook_field_widget_form_alter().');
// Submit with invalid value (field-level validation).
$edit = [
"{$field_name}[0][value]" => -1,
];
$this->drupalPostForm(NULL, $edit, t('Save'));
$this->assertRaw(t('%name does not accept the value -1.', ['%name' => $this->field['label']]), 'Field validation fails with invalid input.');
// 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->drupalPostForm(NULL, $edit, t('Save'));
preg_match('|entity_test/manage/(\d+)|', $this->getUrl(), $match);
$id = $match[1];
$this->assertText(t('entity_test @id has been created.', ['@id' => $id]), 'Entity was created');
$entity = EntityTest::load($id);
$this->assertEqual($entity->{$field_name}->value, $value, 'Field value was saved');
// Display edit form.
$this->drupalGet('entity_test/manage/' . $id . '/edit');
$this->assertFieldByName("{$field_name}[0][value]", $value, 'Widget is displayed with the correct default value');
$this->assertNoField("{$field_name}[1][value]", 'No extraneous widget is displayed');
// Update the entity.
$value = mt_rand(1, 127);
$edit = [
"{$field_name}[0][value]" => $value,
];
$this->drupalPostForm(NULL, $edit, t('Save'));
$this->assertText(t('entity_test @id has been updated.', ['@id' => $id]), 'Entity was updated');
$this->container->get('entity_type.manager')->getStorage('entity_test')->resetCache([$id]);
$entity = EntityTest::load($id);
$this->assertEqual($entity->{$field_name}->value, $value, 'Field value was updated');
// Empty the field.
$value = '';
$edit = [
"{$field_name}[0][value]" => $value,
];
$this->drupalPostForm('entity_test/manage/' . $id . '/edit', $edit, t('Save'));
$this->assertText(t('entity_test @id has been updated.', ['@id' => $id]), 'Entity was updated');
$this->container->get('entity_type.manager')->getStorage('entity_test')->resetCache([$id]);
$entity = EntityTest::load($id);
$this->assertTrue($entity->{$field_name}->isEmpty(), 'Field was emptied');
}
/**
* Tests field widget default values on entity forms.
*/
public function testFieldFormDefaultValue() {
$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->assertFieldByXpath("//input[@name='{$field_name}[0][value]' and @value='$default']");
// Try to submit an empty value.
$edit = [
"{$field_name}[0][value]" => '',
];
$this->drupalPostForm(NULL, $edit, t('Save'));
preg_match('|entity_test/manage/(\d+)|', $this->getUrl(), $match);
$id = $match[1];
$this->assertText(t('entity_test @id has been created.', ['@id' => $id]), 'Entity was created.');
$entity = EntityTest::load($id);
$this->assertTrue($entity->{$field_name}->isEmpty(), 'Field is now empty.');
}
public function testFieldFormSingleRequired() {
$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->drupalPostForm('entity_test/add', $edit, t('Save'));
$this->assertRaw(t('@name field is required.', ['@name' => $this->field['label']]), 'Required field with no value fails validation');
// Create an entity
$value = mt_rand(1, 127);
$edit = [
"{$field_name}[0][value]" => $value,
];
$this->drupalPostForm(NULL, $edit, t('Save'));
preg_match('|entity_test/manage/(\d+)|', $this->getUrl(), $match);
$id = $match[1];
$this->assertText(t('entity_test @id has been created.', ['@id' => $id]), 'Entity was created');
$entity = EntityTest::load($id);
$this->assertEqual($entity->{$field_name}->value, $value, 'Field value was saved');
// Edit with missing required value.
$value = '';
$edit = [
"{$field_name}[0][value]" => $value,
];
$this->drupalPostForm('entity_test/manage/' . $id . '/edit', $edit, t('Save'));
$this->assertRaw(t('@name field is required.', ['@name' => $this->field['label']]), 'Required field with no value fails validation');
}
public function testFieldFormUnlimited() {
$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();
// Display creation form -> 1 widget.
$this->drupalGet('entity_test/add');
$this->assertFieldByName("{$field_name}[0][value]", '', 'Widget 1 is displayed');
$this->assertNoField("{$field_name}[1][value]", 'No extraneous widget is displayed');
// Check if aria-describedby attribute is placed on multiple value widgets.
$elements = $this->xpath('//table[@id="field-unlimited-values" and @aria-describedby="edit-field-unlimited--description"]');
$this->assertTrue(isset($elements[0]), t('aria-describedby attribute is properly placed on multiple value widgets.'));
// Press 'add more' button -> 2 widgets.
$this->drupalPostForm(NULL, [], t('Add another item'));
$this->assertFieldByName("{$field_name}[0][value]", '', 'Widget 1 is displayed');
$this->assertFieldByName("{$field_name}[1][value]", '', 'New widget is displayed');
$this->assertNoField("{$field_name}[2][value]", 'No extraneous widget is displayed');
// TODO : check that non-field inputs are preserved ('title'), etc.
// Yet another time so that we can play with more values -> 3 widgets.
$this->drupalPostForm(NULL, [], t('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->drupalPostForm(NULL, $edit, t('Add another item'));
for ($delta = 0; $delta <= $delta_range; $delta++) {
$this->assertFieldByName("{$field_name}[$delta][value]", $values[$delta], "Widget $delta is displayed and has the right value");
$this->assertFieldByName("{$field_name}[$delta][_weight]", $weights[$delta], "Widget $delta has the right weight");
}
ksort($pattern);
$pattern = implode('.*', array_values($pattern));
$this->assertPattern("|$pattern|s", 'Widgets are displayed in the correct order');
$this->assertFieldByName("{$field_name}[$delta][value]", '', "New widget is displayed");
$this->assertFieldByName("{$field_name}[$delta][_weight]", $delta, "New widget has the right weight");
$this->assertNoField("{$field_name}[" . ($delta + 1) . '][value]', 'No extraneous widget is displayed');
// Submit the form and create the entity.
$this->drupalPostForm(NULL, $edit, t('Save'));
preg_match('|entity_test/manage/(\d+)|', $this->getUrl(), $match);
$id = $match[1];
$this->assertText(t('entity_test @id has been created.', ['@id' => $id]), 'Entity was created');
$entity = EntityTest::load($id);
ksort($field_values);
$field_values = array_values($field_values);
$this->assertIdentical($entity->{$field_name}->getValue(), $field_values, '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() {
$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 multifield label.
$element = $this->xpath('//h4[contains(@class, "label") and contains(@class, "js-form-required") and contains(text(), :value)]', [':value' => $this->field['label']]);
$this->assertTrue(isset($element[0]), 'Required symbol added field label.');
// Check that the label of the field input is visually hidden and contains
// the field title and an indication of the delta for a11y.
$element = $this->xpath('//label[@for=:for and contains(@class, "visually-hidden") and contains(text(), :value)]', [':for' => 'edit-field-unlimited-0-value', ':value' => $this->field['label'] . ' (value 1)']);
$this->assertTrue(isset($element[0]), 'Required symbol not added for field input.');
}
/**
* Tests widget handling of multiple required radios.
*/
public function testFieldFormMultivalueWithRequiredRadio() {
/** @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->drupalPostForm(NULL, [], t('Add another item'));
// Verify that no error is thrown by the radio element.
$this->assertNoFieldByXpath('//div[contains(@class, "error")]', FALSE, 'No error message is displayed.');
// Verify that the widget is added.
$this->assertFieldByName("{$field_name}[0][value]", '', 'Widget 1 is displayed');
$this->assertFieldByName("{$field_name}[1][value]", '', 'New widget is displayed');
$this->assertNoField("{$field_name}[2][value]", 'No extraneous widget is displayed');
}
/**
* Tests widgets handling multiple values.
*/
public function testFieldFormMultipleWidget() {
// 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->assertFieldByName($field_name, '', 'Widget is displayed.');
// Create entity with three values.
$edit = [
$field_name => '1, 2, 3',
];
$this->drupalPostForm(NULL, $edit, t('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->assertFieldByName($field_name, '1, 2, 3', 'Widget is displayed.');
// Submit the form with more values than the field accepts.
$edit = [$field_name => '1, 2, 3, 4, 5'];
$this->drupalPostForm(NULL, $edit, t('Save'));
$this->assertRaw('this field cannot hold more than 4 values', 'Form validation failed.');
// 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 fields with no 'edit' access.
*/
public function testFieldFormAccess() {
/** @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');
$this->assertNoFieldByName("{$field_name_no_access}[0][value]", '', 'Widget is not displayed if field access is denied.');
// Create entity.
$edit = [
"{$field_name}[0][value]" => 1,
];
$this->drupalPostForm(NULL, $edit, t('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->assertEqual($entity->$field_name_no_access->value, 99, 'Default value was saved for the field with no edit access.');
$this->assertEqual($entity->$field_name->value, 1, 'Entered value vas saved for the field with edit access.');
// Create a new revision.
$edit = [
"{$field_name}[0][value]" => 2,
'revision' => TRUE,
];
$this->drupalPostForm($entity_type . '/manage/' . $id . '/edit', $edit, t('Save'));
// Check that the new revision has the expected values.
$storage->resetCache([$id]);
$entity = $storage->load($id);
$this->assertEqual($entity->$field_name_no_access->value, 99, 'New revision has the expected value for the field with no edit access.');
$this->assertEqual($entity->$field_name->value, 2, 'New revision has the expected value for the field with edit access.');
// Check that the revision is also saved in the revisions table.
$entity = $this->container->get('entity_type.manager')
->getStorage($entity_type)
->loadRevision($entity->getRevisionId());
$this->assertEqual($entity->$field_name_no_access->value, 99, 'New revision has the expected value for the field with no edit access.');
$this->assertEqual($entity->$field_name->value, 2, 'New revision has the expected value for the field with edit access.');
}
/**
* Tests hiding a field in a form.
*/
public function testHiddenField() {
$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->assertNoField("{$field_name}[0][value]", 'The field does not appear in the form');
$this->drupalPostForm(NULL, [], t('Save'));
preg_match('|' . $entity_type . '/manage/(\d+)|', $this->getUrl(), $match);
$id = $match[1];
$this->assertText(t('entity_test_rev @id has been created.', ['@id' => $id]), 'Entity was created');
$storage = $this->container->get('entity_type.manager')
->getStorage($entity_type);
$entity = $storage->load($id);
$this->assertEqual($entity->{$field_name}->value, 99, '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->assertFieldByName("{$field_name}[0][value]", 99, 'Widget is displayed with the correct default value');
// Update the entity.
$value = mt_rand(1, 127);
$edit = ["{$field_name}[0][value]" => $value];
$this->drupalPostForm(NULL, $edit, t('Save'));
$this->assertText(t('entity_test_rev @id has been updated.', ['@id' => $id]), 'Entity was updated');
$storage->resetCache([$id]);
$entity = $storage->load($id);
$this->assertEqual($entity->{$field_name}->value, $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->drupalPostForm($entity_type . '/manage/' . $id . '/edit', $edit, t('Save'));
// Check that the expected value has been carried over to the new revision.
$storage->resetCache([$id]);
$entity = $storage->load($id);
$this->assertEqual($entity->{$field_name}->value, $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() {
$user = $this->drupalCreateUser(['administer entity_test content']);
$this->drupalLogin($user);
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->assertText('A field with multiple values');
// Test if labels were XSS filtered.
$this->assertEscaped("<script>alert('a configurable field');</script>");
}
/**
* Tests hook_field_widget_multivalue_form_alter().
*/
public function testFieldFormMultipleWidgetAlter() {
$this->widgetAlterTest('hook_field_widget_multivalue_form_alter', 'test_field_widget_multiple');
}
/**
* Tests hook_field_widget_multivalue_form_alter() with single value elements.
*/
public function testFieldFormMultipleWidgetAlterSingleValues() {
$this->widgetAlterTest('hook_field_widget_multivalue_form_alter', 'test_field_widget_multiple_single_value');
}
/**
* Tests hook_field_widget_multivalue_WIDGET_TYPE_form_alter().
*/
public function testFieldFormMultipleWidgetTypeAlter() {
$this->widgetAlterTest('hook_field_widget_multivalue_WIDGET_TYPE_form_alter', 'test_field_widget_multiple');
}
/**
* Tests hook_field_widget_multivalue_WIDGET_TYPE_form_alter() with single value elements.
*/
public function testFieldFormMultipleWidgetTypeAlterSingleValues() {
$this->widgetAlterTest('hook_field_widget_multivalue_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->assertUniqueText("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->assertEqual($field_storage['cardinality'], substr_count($this->getTextContent(), $suffix_text), "'$suffix_text' was found {$field_storage['cardinality']} times using widget $widget");
}
}
}
@@ -0,0 +1,35 @@
<?php
namespace Drupal\Tests\field\Functional\Hal;
use Drupal\Tests\field\Functional\Rest\FieldConfigResourceTestBase;
use Drupal\Tests\rest\Functional\AnonResourceTestTrait;
/**
* @group hal
*/
class FieldConfigHalJsonAnonTest extends FieldConfigResourceTestBase {
use AnonResourceTestTrait;
/**
* {@inheritdoc}
*/
public static $modules = ['hal'];
/**
* {@inheritdoc}
*/
protected $defaultTheme = 'stark';
/**
* {@inheritdoc}
*/
protected static $format = 'hal_json';
/**
* {@inheritdoc}
*/
protected static $mimeType = 'application/hal+json';
}
@@ -0,0 +1,40 @@
<?php
namespace Drupal\Tests\field\Functional\Hal;
use Drupal\Tests\field\Functional\Rest\FieldConfigResourceTestBase;
use Drupal\Tests\rest\Functional\BasicAuthResourceTestTrait;
/**
* @group hal
*/
class FieldConfigHalJsonBasicAuthTest extends FieldConfigResourceTestBase {
use BasicAuthResourceTestTrait;
/**
* {@inheritdoc}
*/
public static $modules = ['hal', 'basic_auth'];
/**
* {@inheritdoc}
*/
protected $defaultTheme = 'stark';
/**
* {@inheritdoc}
*/
protected static $format = 'hal_json';
/**
* {@inheritdoc}
*/
protected static $mimeType = 'application/hal+json';
/**
* {@inheritdoc}
*/
protected static $auth = 'basic_auth';
}
@@ -0,0 +1,40 @@
<?php
namespace Drupal\Tests\field\Functional\Hal;
use Drupal\Tests\field\Functional\Rest\FieldConfigResourceTestBase;
use Drupal\Tests\rest\Functional\CookieResourceTestTrait;
/**
* @group hal
*/
class FieldConfigHalJsonCookieTest extends FieldConfigResourceTestBase {
use CookieResourceTestTrait;
/**
* {@inheritdoc}
*/
public static $modules = ['hal'];
/**
* {@inheritdoc}
*/
protected $defaultTheme = 'stark';
/**
* {@inheritdoc}
*/
protected static $format = 'hal_json';
/**
* {@inheritdoc}
*/
protected static $mimeType = 'application/hal+json';
/**
* {@inheritdoc}
*/
protected static $auth = 'cookie';
}
@@ -0,0 +1,35 @@
<?php
namespace Drupal\Tests\field\Functional\Hal;
use Drupal\Tests\field\Functional\Rest\FieldStorageConfigResourceTestBase;
use Drupal\Tests\rest\Functional\AnonResourceTestTrait;
/**
* @group hal
*/
class FieldStorageConfigHalJsonAnonTest extends FieldStorageConfigResourceTestBase {
use AnonResourceTestTrait;
/**
* {@inheritdoc}
*/
public static $modules = ['hal'];
/**
* {@inheritdoc}
*/
protected $defaultTheme = 'stark';
/**
* {@inheritdoc}
*/
protected static $format = 'hal_json';
/**
* {@inheritdoc}
*/
protected static $mimeType = 'application/hal+json';
}
@@ -0,0 +1,40 @@
<?php
namespace Drupal\Tests\field\Functional\Hal;
use Drupal\Tests\field\Functional\Rest\FieldStorageConfigResourceTestBase;
use Drupal\Tests\rest\Functional\BasicAuthResourceTestTrait;
/**
* @group hal
*/
class FieldStorageConfigHalJsonBasicAuthTest extends FieldStorageConfigResourceTestBase {
use BasicAuthResourceTestTrait;
/**
* {@inheritdoc}
*/
public static $modules = ['hal', 'basic_auth'];
/**
* {@inheritdoc}
*/
protected $defaultTheme = 'stark';
/**
* {@inheritdoc}
*/
protected static $format = 'hal_json';
/**
* {@inheritdoc}
*/
protected static $mimeType = 'application/hal+json';
/**
* {@inheritdoc}
*/
protected static $auth = 'basic_auth';
}
@@ -0,0 +1,40 @@
<?php
namespace Drupal\Tests\field\Functional\Hal;
use Drupal\Tests\field\Functional\Rest\FieldStorageConfigResourceTestBase;
use Drupal\Tests\rest\Functional\CookieResourceTestTrait;
/**
* @group hal
*/
class FieldStorageConfigHalJsonCookieTest extends FieldStorageConfigResourceTestBase {
use CookieResourceTestTrait;
/**
* {@inheritdoc}
*/
public static $modules = ['hal'];
/**
* {@inheritdoc}
*/
protected $defaultTheme = 'stark';
/**
* {@inheritdoc}
*/
protected static $format = 'hal_json';
/**
* {@inheritdoc}
*/
protected static $mimeType = 'application/hal+json';
/**
* {@inheritdoc}
*/
protected static $auth = 'cookie';
}
@@ -0,0 +1,205 @@
<?php
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 {
/**
* Modules to enable.
*
* @var array
*/
public static $modules = ['field_test', 'entity_test'];
/**
* {@inheritdoc}
*/
protected $defaultTheme = 'stark';
protected function setUp() {
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() {
/** @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 = 0;
$entity_1->field_unlimited->value = 1;
$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->assertFieldByName('field_single[0][value]', 0, 'Entity 1: field_single value appears correctly is the form.');
$this->assertFieldByName('field_unlimited[0][value]', 1, 'Entity 1: field_unlimited value 0 appears correctly is the form.');
$this->assertFieldByName('entity_2[field_single][0][value]', 10, 'Entity 2: field_single value appears correctly is the form.');
$this->assertFieldByName('entity_2[field_unlimited][0][value]', 11, 'Entity 2: field_unlimited value 0 appears correctly is the form.');
// 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->drupalPostForm(NULL, $edit, t('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->drupalPostForm('test-entity/nested/1/2', $edit, t('Save'));
$this->assertRaw(t('%label does not accept the value -1', ['%label' => 'Unlimited field']), 'Entity 1: the field validation error was reported.');
$error_field = $this->xpath('//input[@id=:id and contains(@class, "error")]', [':id' => 'edit-field-unlimited-1-value']);
$this->assertCount(1, $error_field, 'Entity 1: the error was flagged on the correct element.');
$edit = [
'entity_2[field_unlimited][1][value]' => -1,
];
$this->drupalPostForm('test-entity/nested/1/2', $edit, t('Save'));
$this->assertRaw(t('%label does not accept the value -1', ['%label' => 'Unlimited field']), 'Entity 2: the field validation error was reported.');
$error_field = $this->xpath('//input[@id=:id and contains(@class, "error")]', [':id' => 'edit-entity-2-field-unlimited-1-value']);
$this->assertCount(1, $error_field, 'Entity 2: the error was flagged on the correct element.');
// 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->drupalPostForm('test-entity/nested/1/2', $edit, t('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->drupalPostForm(NULL, [], 'field_unlimited_add_more');
$this->assertFieldByName('field_unlimited[0][value]', 3, 'Entity 1: field_unlimited value 0 appears correctly is the form.');
$this->assertFieldByName('field_unlimited[1][value]', 2, 'Entity 1: field_unlimited value 1 appears correctly is the form.');
$this->assertFieldByName('field_unlimited[2][value]', '', 'Entity 1: field_unlimited value 2 appears correctly is the form.');
$this->assertFieldByName('field_unlimited[3][value]', '', 'Entity 1: an empty widget was added for field_unlimited value 3.');
// '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->drupalPostForm(NULL, $edit, 'entity_2_field_unlimited_add_more');
$this->assertFieldByName('entity_2[field_unlimited][0][value]', 13, 'Entity 2: field_unlimited value 0 appears correctly is the form.');
$this->assertFieldByName('entity_2[field_unlimited][1][value]', 14, 'Entity 2: field_unlimited value 1 appears correctly is the form.');
$this->assertFieldByName('entity_2[field_unlimited][2][value]', 15, 'Entity 2: field_unlimited value 2 appears correctly is the form.');
$this->assertFieldByName('entity_2[field_unlimited][3][value]', '', 'Entity 2: an empty widget was added for field_unlimited value 3.');
// Save the form and check values are saved correctly.
$this->drupalPostForm(NULL, [], t('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() {
// 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]', REQUEST_TIME);
// Submit the form and check that the entities are updated accordingly.
$assert_session->hiddenFieldExists('entity_2[changed]')
->setValue(REQUEST_TIME - 86400);
$page->pressButton(t('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,443 @@
<?php
namespace Drupal\Tests\field\Functional\Number;
use Drupal\field\Entity\FieldConfig;
use Drupal\field\Entity\FieldStorageConfig;
use Drupal\Tests\BrowserTestBase;
/**
* Tests the creation of numeric fields.
*
* @group field
*/
class NumberFieldTest extends BrowserTestBase {
/**
* Modules to enable.
*
* @var array
*/
public static $modules = ['node', 'entity_test', 'field_ui'];
/**
* {@inheritdoc}
*/
protected $defaultTheme = 'stark';
protected function setUp() {
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',
]));
}
/**
* Test decimal field.
*/
public function testNumberDecimalField() {
// Create a field with settings to validate.
$field_name = mb_strtolower($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->assertFieldByName("{$field_name}[0][value]", '', 'Widget is displayed');
$this->assertRaw('placeholder="0.00"');
// Submit a signed decimal value within the allowed precision and scale.
$value = '-1234.5678';
$edit = [
"{$field_name}[0][value]" => $value,
];
$this->drupalPostForm(NULL, $edit, t('Save'));
preg_match('|entity_test/manage/(\d+)|', $this->getUrl(), $match);
$id = $match[1];
$this->assertText(t('entity_test @id has been created.', ['@id' => $id]), 'Entity was created');
$this->assertRaw($value, 'Value is displayed.');
// 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->drupalPostForm(NULL, $edit, t('Save'));
$this->assertRaw(t('%name must be a number.', ['%name' => $field_name]), 'Correctly failed to save decimal value with more than one decimal point.');
}
// 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->drupalPostForm(NULL, $edit, t('Save'));
$this->assertRaw(t('%name must be a number.', ['%name' => $field_name]), 'Correctly failed to save decimal value with minus sign in the wrong position.');
}
}
/**
* Test integer field.
*/
public function testNumberIntegerField() {
$minimum = rand(-4000, -2000);
$maximum = rand(2000, 4000);
// Create a field with settings to validate.
$field_name = mb_strtolower($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->assertEqual($storage->getSchema(), $expected);
// Display creation form.
$this->drupalGet('entity_test/add');
$this->assertFieldByName("{$field_name}[0][value]", '', 'Widget is displayed');
$this->assertRaw('placeholder="4"');
// Submit a valid integer
$value = rand($minimum, $maximum);
$edit = [
"{$field_name}[0][value]" => $value,
];
$this->drupalPostForm(NULL, $edit, t('Save'));
preg_match('|entity_test/manage/(\d+)|', $this->getUrl(), $match);
$id = $match[1];
$this->assertText(t('entity_test @id has been created.', ['@id' => $id]), 'Entity was created');
// Try to set a value below the minimum value
$this->drupalGet('entity_test/add');
$edit = [
"{$field_name}[0][value]" => $minimum - 1,
];
$this->drupalPostForm(NULL, $edit, t('Save'));
$this->assertRaw(t('%name must be higher than or equal to %minimum.', ['%name' => $field_name, '%minimum' => $minimum]), 'Correctly failed to save integer value less than minimum allowed value.');
// Try to set a decimal value
$this->drupalGet('entity_test/add');
$edit = [
"{$field_name}[0][value]" => 1.5,
];
$this->drupalPostForm(NULL, $edit, t('Save'));
$this->assertRaw(t('%name is not a valid number.', ['%name' => $field_name]), 'Correctly failed to save decimal value to integer field.');
// Try to set a value above the maximum value
$this->drupalGet('entity_test/add');
$edit = [
"{$field_name}[0][value]" => $maximum + 1,
];
$this->drupalPostForm(NULL, $edit, t('Save'));
$this->assertRaw(t('%name must be lower than or equal to %maximum.', ['%name' => $field_name, '%maximum' => $maximum]), 'Correctly failed to save integer value greater than maximum allowed value.');
// Try to set a wrong integer value.
$this->drupalGet('entity_test/add');
$edit = [
"{$field_name}[0][value]" => '20-40',
];
$this->drupalPostForm(NULL, $edit, t('Save'));
$this->assertRaw(t('%name must be a number.', ['%name' => $field_name]), 'Correctly failed to save wrong integer value.');
// 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->drupalPostForm(NULL, $edit, t('Save'));
preg_match('|entity_test/manage/(\d+)|', $this->getUrl(), $match);
$id = $match[1];
$this->assertText(t('entity_test @id has been created.', ['@id' => $id]), 'Entity was created');
$this->assertRaw($valid_entry, 'Value is displayed.');
$this->assertNoFieldByXpath('//div[@content="' . $valid_entry . '"]', NULL, 'The "content" attribute is not present since the Prefix is not being displayed');
}
// 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->drupalPostForm(NULL, $edit, t('Save'));
preg_match('|entity_test/manage/(\d+)|', $this->getUrl(), $match);
$id = $match[1];
$this->assertText(t('entity_test @id has been created.', ['@id' => $id]), 'Entity was created');
$this->drupalGet('entity_test/' . $id);
$this->assertFieldByXPath('//div[@content="' . $integer_value . '"]', 'ThePrefix' . $integer_value, 'The "content" attribute has been set to the value of the field, and the prefix is being displayed.');
}
/**
* Test float field.
*/
public function testNumberFloatField() {
// Create a field with settings to validate.
$field_name = mb_strtolower($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->assertFieldByName("{$field_name}[0][value]", '', 'Widget is displayed');
$this->assertRaw('placeholder="0.00"');
// Submit a signed decimal value within the allowed precision and scale.
$value = '-1234.5678';
$edit = [
"{$field_name}[0][value]" => $value,
];
$this->drupalPostForm(NULL, $edit, t('Save'));
preg_match('|entity_test/manage/(\d+)|', $this->getUrl(), $match);
$id = $match[1];
$this->assertText(t('entity_test @id has been created.', ['@id' => $id]), 'Entity was created');
// Ensure that the 'number_decimal' formatter displays the number with the
// expected rounding.
$this->drupalGet('entity_test/' . $id);
$this->assertRaw(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->drupalPostForm(NULL, $edit, t('Save'));
$this->assertRaw(t('%name must be a number.', ['%name' => $field_name]), 'Correctly failed to save float value with more than one decimal point.');
}
// 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->drupalPostForm(NULL, $edit, t('Save'));
$this->assertRaw(t('%name must be a number.', ['%name' => $field_name]), 'Correctly failed to save float value with minus sign in the wrong position.');
}
}
/**
* Tests setting the minimum value of a float field through the interface.
*/
public function testCreateNumberFloatField() {
// Create a float field.
$field_name = mb_strtolower($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);
}
/**
* Tests setting the minimum value of a decimal field through the interface.
*/
public function testCreateNumberDecimalField() {
// Create a decimal field.
$field_name = mb_strtolower($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.
*/
public function assertSetMinimumValue($field, $minimum_value) {
$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->drupalPostForm($field_configuration_url, $edit, t('Save settings'));
// Check if an error message is shown.
$this->assertNoRaw(t('%name is not a valid number.', ['%name' => t('Minimum')]), 'Saved ' . gettype($minimum_value) . ' value as minimal value on a ' . $field->getType() . ' field');
// Check if a success message is shown.
$this->assertRaw(t('Saved %label configuration.', ['%label' => $field->getLabel()]));
// Check if the minimum value was actually set.
$this->drupalGet($field_configuration_url);
$this->assertFieldById('edit-settings-min', $minimum_value, 'Minimal ' . gettype($minimum_value) . ' value was set on a ' . $field->getType() . ' field.');
}
}
@@ -0,0 +1,133 @@
<?php
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;
/**
* Modules to enable.
*
* @var array
*/
public 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';
protected function setUp() {
parent::setUp();
$this->drupalCreateContentType(['type' => 'article']);
$this->drupalLogin($this->drupalCreateUser([
'create article content',
'edit own article content',
]));
}
/**
* Test the behavior of a field module after being disabled and re-enabled.
*
* @see field_system_info_alter()
*/
public function testReEnabledField() {
// 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->assertFieldByName("field_telephone[0][value]", '', 'Widget found.');
// 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->drupalPostForm(NULL, $edit, t('Save'));
$this->assertRaw('<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->assertText("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->assertText("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->assertText('Fields pending deletion');
$this->cronRun();
$this->assertNoText("The Telephone number field type is used in the following field: node.field_telephone");
$this->assertNoText('Fields pending deletion');
}
}
@@ -0,0 +1,29 @@
<?php
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,39 @@
<?php
namespace Drupal\Tests\field\Functional\Rest;
use Drupal\Tests\rest\Functional\BasicAuthResourceTestTrait;
/**
* @group rest
*/
class FieldConfigJsonBasicAuthTest extends FieldConfigResourceTestBase {
use BasicAuthResourceTestTrait;
/**
* {@inheritdoc}
*/
public 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,34 @@
<?php
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,119 @@
<?php
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\EntityResourceTestBase;
abstract class FieldConfigResourceTestBase extends EntityResourceTestBase {
/**
* {@inheritdoc}
*/
public static $modules = ['field', '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' => [],
'status' => TRUE,
'translatable' => TRUE,
'uuid' => $this->entity->uuid(),
];
}
/**
* {@inheritdoc}
*/
protected function getNormalizedPostEntity() {
// @todo Update in https://www.drupal.org/node/2300677.
}
/**
* {@inheritdoc}
*/
protected function getExpectedCacheContexts() {
return [
'user.permissions',
];
}
/**
* {@inheritdoc}
*/
protected function getExpectedUnauthorizedAccessMessage($method) {
if ($this->config('rest.settings')->get('bc_entity_resource_permissions')) {
return parent::getExpectedUnauthorizedAccessMessage($method);
}
return "The 'administer node fields' permission is required.";
}
}
@@ -0,0 +1,31 @@
<?php
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,41 @@
<?php
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}
*/
public 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,36 @@
<?php
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,29 @@
<?php
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,39 @@
<?php
namespace Drupal\Tests\field\Functional\Rest;
use Drupal\Tests\rest\Functional\BasicAuthResourceTestTrait;
/**
* @group rest
*/
class FieldStorageConfigJsonBasicAuthTest extends FieldStorageConfigResourceTestBase {
use BasicAuthResourceTestTrait;
/**
* {@inheritdoc}
*/
public 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,34 @@
<?php
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,104 @@
<?php
namespace Drupal\Tests\field\Functional\Rest;
use Drupal\field\Entity\FieldStorageConfig;
use Drupal\Tests\rest\Functional\EntityResource\EntityResourceTestBase;
abstract class FieldStorageConfigResourceTestBase extends EntityResourceTestBase {
/**
* {@inheritdoc}
*/
public static $modules = ['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.
}
/**
* {@inheritdoc}
*/
protected function getExpectedUnauthorizedAccessMessage($method) {
if ($this->config('rest.settings')->get('bc_entity_resource_permissions')) {
return parent::getExpectedUnauthorizedAccessMessage($method);
}
switch ($method) {
case 'GET':
return "The 'administer node fields' permission is required.";
default:
return parent::getExpectedUnauthorizedAccessMessage($method);
}
}
/**
* {@inheritdoc}
*/
protected function getExpectedCacheContexts() {
return [
'user.permissions',
];
}
}
@@ -0,0 +1,31 @@
<?php
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,41 @@
<?php
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}
*/
public 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';
}

Some files were not shown because too many files have changed in this diff Show More