Drupal 8.0.0 beta 12. More info: https://www.drupal.org/node/2514176

This commit is contained in:
Pantheon Automation 2015-08-17 17:00:26 -07:00 committed by Greg Anderson
commit 9921556621
13277 changed files with 1459781 additions and 0 deletions

View file

@ -0,0 +1,20 @@
# Schema for the views plugins of the Entity Reference module.
views.display.entity_reference:
type: views_display
label: 'Entity Reference'
views.row.entity_reference:
type: views.row.fields
label: 'Entity Reference inline fields'
views.style.entity_reference:
type: views_style
label: 'Entity Reference list'
mapping:
search_fields:
type: sequence
label: 'Search fields'
sequence:
type: string
label: 'Search field'

View file

@ -0,0 +1,8 @@
name: 'Entity Reference'
type: module
description: 'Provides a field that can reference other entities.'
package: Field types
version: VERSION
core: 8.x
dependencies:
- field

View file

@ -0,0 +1,212 @@
<?php
/**
* @file
* Provides a field that can reference other entities.
*/
use Drupal\Component\Utility\NestedArray;
use Drupal\Core\Database\Query\AlterableInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Render\Element;
use Drupal\Core\Routing\RouteMatchInterface;
use Drupal\field\Entity\FieldStorageConfig;
use Drupal\field\Entity\FieldConfig;
use Drupal\field\FieldStorageConfigInterface;
use Drupal\field\FieldConfigInterface;
/**
* Implements hook_help().
*/
function entity_reference_help($route_name, RouteMatchInterface $route_match) {
switch ($route_name) {
case 'help.page.entity_reference':
$output = '';
$output .= '<h3>' . t('About') . '</h3>';
$output .= '<p>' . t('The Entity Reference module allows you to create fields that contain links to other entities (such as content items, taxonomy terms, etc.) within the site. This allows you, for example, to include a link to a user within a content item. For more information, see <a href="!er_do">the online documentation for the Entity Reference module</a> and the <a href="!field_help">Field module help page</a>.', array('!field_help' => \Drupal::url('help.page', array('name' => 'field')), '!er_do' => 'https://www.drupal.org/documentation/modules/entityreference')) . '</p>';
$output .= '<h3>' . t('Uses') . '</h3>';
$output .= '<dl>';
$output .= '<dt>' . t('Managing and displaying entity reference fields') . '</dt>';
$output .= '<dd>' . t('The <em>settings</em> and the <em>display</em> of the entity reference field can be configured separately. See the <a href="!field_ui">Field UI help</a> for more information on how to manage fields and their display.', array('!field_ui' => (\Drupal::moduleHandler()->moduleExists('field_ui')) ? \Drupal::url('help.page', array('name' => 'field_ui')) : '#')) . '</dd>';
$output .= '<dt>' . t('Selecting reference type') . '</dt>';
$output .= '<dd>' . t('In the field settings you can select which type of item you want to create a reference to.') . '</dd>';
$output .= '<dt>' . t('Filtering and sorting reference fields') . '</dt>';
$output .= '<dd>' . t('Depending on the chosen entity type, additional filtering and sorting options are available for the list of entities that can be referred to, in the field settings. For example, the list of users can be filtered by role and sorted by name or ID.') . '</dd>';
$output .= '<dt>' . t('Displaying a reference') . '</dt>';
$output .= '<dd>' . t('An entity reference can be displayed as a simple label with or without a link to the entity. Alternatively, the referenced entity can be displayed as a teaser (or any other available view mode) inside the referencing entity. Certain entity types may provide additional display options. You can configure how the entity reference is displayed on the <em>Manage display</em> page for the entity.') . '</dd>';
$output .= '<dt>' . t('Configuring form displays') . '</dt>';
$output .= '<dd>' . t('Reference fields have several widgets available on the <em>Manage form display</em> page:');
$output .= '<ul>';
$output .= '<li>' . t('The <em>Check boxes/radio buttons</em> widget displays the existing entities for the entity type as check boxes or radio buttons based on the <em>Allowed number of values</em> set for the field.') . '</li>';
$output .= '<li>' . t('The <em>Select list</em> widget displays the existing entities in a drop-down list or scrolling list box based on the <em>Allowed number of values</em> setting for the field.') . '</li>';
$output .= '<li>' . t('The <em>Autocomplete</em> widget displays text fields in which users can type entity labels based on the <em>Allowed number of values</em>. The widget can be configured to display all entities that contain the typed characters or restricted to those starting with those characters.') . '</li>';
$output .= '<li>' . t('The <em>Autocomplete (Tags style)</em> widget displays a multi-text field in which users can type in a comma-separated list of entity labels.') . '</li>';
$output .= '</ul>';
$output .= '</dl>';
return $output;
}
}
/**
* Implements hook_field_info_alter().
*/
function entity_reference_field_info_alter(&$info) {
// Make the entity reference field configurable.
$info['entity_reference']['no_ui'] = FALSE;
$info['entity_reference']['class'] = '\Drupal\entity_reference\ConfigurableEntityReferenceItem';
$info['entity_reference']['list_class'] = '\Drupal\Core\Field\EntityReferenceFieldItemList';
$info['entity_reference']['default_widget'] = 'entity_reference_autocomplete';
$info['entity_reference']['default_formatter'] = 'entity_reference_label';
$info['entity_reference']['provider'] = 'entity_reference';
}
/**
* Implements hook_field_widget_info_alter().
*/
function entity_reference_field_widget_info_alter(&$info) {
if (isset($info['options_select'])) {
$info['options_select']['field_types'][] = 'entity_reference';
}
if (isset($info['options_buttons'])) {
$info['options_buttons']['field_types'][] = 'entity_reference';
}
}
/**
* Implements hook_ENTITY_TYPE_update() for 'field_storage_config'.
*
* Reset the instance handler settings, when the target type is changed.
*/
function entity_reference_field_storage_config_update(FieldStorageConfigInterface $field_storage) {
if ($field_storage->getType() != 'entity_reference') {
// Only act on entity reference fields.
return;
}
if ($field_storage->isSyncing()) {
// Don't change anything during a configuration sync.
return;
}
if ($field_storage->getSetting('target_type') == $field_storage->original->getSetting('target_type')) {
// Target type didn't change.
return;
}
foreach ($field_storage->getBundles() as $bundle) {
$field = FieldConfig::loadByName($field_storage->getTargetEntityTypeId(), $bundle, $field_storage->getName());
$field->setSetting('handler_settings', []);
$field->save();
}
}
/**
* Implements hook_ENTITY_TYPE_presave() for 'field_config'.
*
* Determine the selection handler plugin ID for an entity reference field.
*/
function entity_reference_field_config_presave(FieldConfigInterface $field) {
if ($field->getType() != 'entity_reference') {
// Only act on entity reference fields.
return;
}
if ($field->isSyncing()) {
// Don't change anything during a configuration sync.
return;
}
$target_type = $field->getFieldStorageDefinition()->getSetting('target_type');
$selection_manager = \Drupal::service('plugin.manager.entity_reference_selection');
list($current_handler) = explode(':', $field->getSetting('handler'), 2);
$field->setSetting('handler', $selection_manager->getPluginId($target_type, $current_handler));
}
/**
* Implements hook_form_FORM_ID_alter() for 'field_ui_field_storage_add_form'.
*/
function entity_reference_form_field_ui_field_storage_add_form_alter(array &$form) {
// Move the "Entity reference" option to the end of the list and rename it to
// "Other".
unset($form['add']['new_storage_type']['#options'][t('Reference')]['entity_reference']);
$form['add']['new_storage_type']['#options'][t('Reference')]['entity_reference'] = t('Other…');
}
/**
* Render API callback: Processes the field settings form and allows access to
* the form state.
*
* @see entity_reference_field_field_settings_form()
*/
function _entity_reference_field_field_settings_ajax_process($form, FormStateInterface $form_state) {
_entity_reference_field_field_settings_ajax_process_element($form, $form);
return $form;
}
/**
* Adds entity_reference specific properties to AJAX form elements from the
* field settings form.
*
* @see _entity_reference_field_field_settings_ajax_process()
*/
function _entity_reference_field_field_settings_ajax_process_element(&$element, $main_form) {
if (!empty($element['#ajax'])) {
$element['#ajax'] = array(
'callback' => 'entity_reference_settings_ajax',
'wrapper' => $main_form['#id'],
'element' => $main_form['#array_parents'],
);
}
foreach (Element::children($element) as $key) {
_entity_reference_field_field_settings_ajax_process_element($element[$key], $main_form);
}
}
/**
* Render API callback: Moves entity_reference specific Form API elements
* (i.e. 'handler_settings') up a level for easier processing by the validation
* and submission handlers.
*
* @see _entity_reference_field_settings_process()
*/
function _entity_reference_form_process_merge_parent($element) {
$parents = $element['#parents'];
array_pop($parents);
$element['#parents'] = $parents;
return $element;
}
/**
* Form element validation handler; Filters the #value property of an element.
*/
function _entity_reference_element_validate_filter(&$element, FormStateInterface $form_state) {
$element['#value'] = array_filter($element['#value']);
$form_state->setValueForElement($element, $element['#value']);
}
/**
* Ajax callback for the handler settings form.
*
* @see entity_reference_field_field_settings_form()
*/
function entity_reference_settings_ajax($form, FormStateInterface $form_state) {
return NestedArray::getValue($form, $form_state->getTriggeringElement()['#ajax']['element']);
}
/**
* Submit handler for the non-JS case.
*
* @see entity_reference_field_field_settings_form()
*/
function entity_reference_settings_ajax_submit($form, FormStateInterface $form_state) {
$form_state->setRebuild();
}
/**
* Implements hook_query_TAG_alter().
*/
function entity_reference_query_entity_reference_alter(AlterableInterface $query) {
$handler = $query->getMetadata('entity_reference_selection_handler');
$handler->entityQueryAlter($query);
}

View file

@ -0,0 +1,73 @@
<?php
/**
* @file
* Provides views data for the entity_reference module.
*/
use Drupal\field\FieldStorageConfigInterface;
/**
* Implements hook_field_views_data().
*/
function entity_reference_field_views_data(FieldStorageConfigInterface $field_storage) {
$data = views_field_default_views_data($field_storage);
$entity_manager = \Drupal::entityManager();
foreach ($data as $table_name => $table_data) {
// Add a relationship to the target entity type.
$target_entity_type_id = $field_storage->getSetting('target_type');
$target_entity_type = $entity_manager->getDefinition($target_entity_type_id);
$entity_type_id = $field_storage->getTargetEntityTypeId();
$entity_type = $entity_manager->getDefinition($entity_type_id);
$target_base_table = $target_entity_type->getDataTable() ?: $target_entity_type->getBaseTable();
$field_name = $field_storage->getName();
// Provide a relationship for the entity type with the entity reference
// field.
$args = array(
'@label' => $target_entity_type->getLabel(),
'@field_name' => $field_name,
);
$data[$table_name][$field_name]['relationship'] = array(
'title' => t('@label referenced from @field_name', $args),
'label' => t('@field_name: @label', $args),
'group' => $entity_type->getLabel(),
'help' => t('Appears in: @bundles.', array('@bundles' => implode(', ', $field_storage->getBundles()))),
'id' => 'standard',
'base' => $target_base_table,
'entity type' => $target_entity_type_id,
'base field' => $target_entity_type->getKey('id'),
'relationship field' => $field_name . '_target_id',
);
// Provide a reverse relationship for the entity type that is referenced by
// the field.
$args['@entity'] = $entity_type->getLabel();
$args['@label'] = $target_entity_type->getLowercaseLabel();
$pseudo_field_name = 'reverse__' . $entity_type_id . '__' . $field_name;
/** @var \Drupal\Core\Entity\Sql\DefaultTableMapping $table_mapping */
$table_mapping = $entity_manager->getStorage($entity_type_id)->getTableMapping();
$data[$target_base_table][$pseudo_field_name]['relationship'] = array(
'title' => t('@entity using @field_name', $args),
'label' => t('@field_name', array('@field_name' => $field_name)),
'group' => $target_entity_type->getLabel(),
'help' => t('Relate each @entity with a @field_name set to the @label.', $args),
'id' => 'entity_reverse',
'base' => $entity_type->getDataTable() ?: $entity_type->getBaseTable(),
'entity_type' => $entity_type_id,
'base field' => $entity_type->getKey('id'),
'field_name' => $field_name,
'field table' => $table_mapping->getDedicatedDataTableName($field_storage),
'field field' => $field_name . '_target_id',
'join_extra' => array(
array(
'field' => 'deleted',
'value' => 0,
'numeric' => TRUE,
),
),
);
}
return $data;
}

View file

@ -0,0 +1,233 @@
<?php
/**
* @file
* Contains \Drupal\entity_reference\ConfigurableEntityReferenceItem.
*/
namespace Drupal\entity_reference;
use Drupal\Component\Utility\SafeMarkup;
use Drupal\Core\Entity\EntityTypeInterface;
use Drupal\Core\Field\Plugin\Field\FieldType\EntityReferenceItem;
use Drupal\Core\Field\PreconfiguredFieldUiOptionsInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Form\OptGroup;
use Drupal\Core\Session\AccountInterface;
use Drupal\Core\TypedData\OptionsProviderInterface;
use Drupal\Core\Validation\Plugin\Validation\Constraint\AllowedValuesConstraint;
/**
* Alternative plugin implementation of the 'entity_reference' field type.
*
* Replaces the Core 'entity_reference' entity field type implementation, this
* supports configurable fields, auto-creation of referenced entities and more.
*
* Required settings are:
* - target_type: The entity type to reference.
*
* @see entity_reference_field_info_alter().
*/
class ConfigurableEntityReferenceItem extends EntityReferenceItem implements OptionsProviderInterface, PreconfiguredFieldUiOptionsInterface {
/**
* {@inheritdoc}
*/
public static function defaultStorageSettings() {
$settings = parent::defaultStorageSettings();
// The target bundle is handled by the 'target_bundles' property in the
// 'handler_settings' instance setting.
unset($settings['target_bundle']);
return $settings;
}
/**
* {@inheritdoc}
*/
public function getPossibleValues(AccountInterface $account = NULL) {
return $this->getSettableValues($account);
}
/**
* {@inheritdoc}
*/
public function getPossibleOptions(AccountInterface $account = NULL) {
return $this->getSettableOptions($account);
}
/**
* {@inheritdoc}
*/
public function getSettableValues(AccountInterface $account = NULL) {
// Flatten options first, because "settable options" may contain group
// arrays.
$flatten_options = OptGroup::flattenOptions($this->getSettableOptions($account));
return array_keys($flatten_options);
}
/**
* {@inheritdoc}
*/
public function getSettableOptions(AccountInterface $account = NULL) {
$field_definition = $this->getFieldDefinition();
if (!$options = \Drupal::service('plugin.manager.entity_reference_selection')->getSelectionHandler($field_definition, $this->getEntity())->getReferenceableEntities()) {
return array();
}
// Rebuild the array by changing the bundle key into the bundle label.
$target_type = $field_definition->getSetting('target_type');
$bundles = \Drupal::entityManager()->getBundleInfo($target_type);
$return = array();
foreach ($options as $bundle => $entity_ids) {
$bundle_label = SafeMarkup::checkPlain($bundles[$bundle]['label']);
$return[$bundle_label] = $entity_ids;
}
return count($return) == 1 ? reset($return) : $return;
}
/**
* {@inheritdoc}
*/
public function getConstraints() {
$constraints = parent::getConstraints();
// Remove the 'AllowedValuesConstraint' validation constraint because entity
// reference fields already use the 'ValidReference' constraint.
foreach ($constraints as $key => $constraint) {
if ($constraint instanceof AllowedValuesConstraint) {
unset($constraints[$key]);
}
}
return $constraints;
}
/**
* {@inheritdoc}
*/
public function storageSettingsForm(array &$form, FormStateInterface $form_state, $has_data) {
$element['target_type'] = array(
'#type' => 'select',
'#title' => t('Type of item to reference'),
'#options' => \Drupal::entityManager()->getEntityTypeLabels(TRUE),
'#default_value' => $this->getSetting('target_type'),
'#required' => TRUE,
'#disabled' => $has_data,
'#size' => 1,
);
return $element;
}
/**
* {@inheritdoc}
*/
public function fieldSettingsForm(array $form, FormStateInterface $form_state) {
$field = $form_state->getFormObject()->getEntity();
// Get all selection plugins for this entity type.
$selection_plugins = \Drupal::service('plugin.manager.entity_reference_selection')->getSelectionGroups($this->getSetting('target_type'));
$handlers_options = array();
foreach (array_keys($selection_plugins) as $selection_group_id) {
// We only display base plugins (e.g., 'default', 'views', etc.) and not
// entity type specific plugins (e.g., 'default:node', 'default:user',
// etc.).
if (array_key_exists($selection_group_id, $selection_plugins[$selection_group_id])) {
$handlers_options[$selection_group_id] = SafeMarkup::checkPlain($selection_plugins[$selection_group_id][$selection_group_id]['label']);
}
elseif (array_key_exists($selection_group_id . ':' . $this->getSetting('target_type'), $selection_plugins[$selection_group_id])) {
$selection_group_plugin = $selection_group_id . ':' . $this->getSetting('target_type');
$handlers_options[$selection_group_plugin] = SafeMarkup::checkPlain($selection_plugins[$selection_group_id][$selection_group_plugin]['base_plugin_label']);
}
}
$form = array(
'#type' => 'container',
'#process' => array(
'_entity_reference_field_field_settings_ajax_process',
),
'#element_validate' => array(array(get_class($this), 'fieldSettingsFormValidate')),
);
$form['handler'] = array(
'#type' => 'details',
'#title' => t('Reference type'),
'#open' => TRUE,
'#tree' => TRUE,
'#process' => array('_entity_reference_form_process_merge_parent'),
);
$form['handler']['handler'] = array(
'#type' => 'select',
'#title' => t('Reference method'),
'#options' => $handlers_options,
'#default_value' => $field->getSetting('handler'),
'#required' => TRUE,
'#ajax' => TRUE,
'#limit_validation_errors' => array(),
);
$form['handler']['handler_submit'] = array(
'#type' => 'submit',
'#value' => t('Change handler'),
'#limit_validation_errors' => array(),
'#attributes' => array(
'class' => array('js-hide'),
),
'#submit' => array('entity_reference_settings_ajax_submit'),
);
$form['handler']['handler_settings'] = array(
'#type' => 'container',
'#attributes' => array('class' => array('entity_reference-settings')),
);
$handler = \Drupal::service('plugin.manager.entity_reference_selection')->getSelectionHandler($field);
$form['handler']['handler_settings'] += $handler->buildConfigurationForm(array(), $form_state);
return $form;
}
/**
* Form element validation handler; Invokes selection plugin's validation.
*
* @param array $form
* The form where the settings form is being included in.
* @param \Drupal\Core\Form\FormStateInterface $form_state
* The form state of the (entire) configuration form.
*/
public static function fieldSettingsFormValidate(array $form, FormStateInterface $form_state) {
$field = $form_state->getFormObject()->getEntity();
$handler = \Drupal::service('plugin.manager.entity_reference_selection')->getSelectionHandler($field);
$handler->validateConfigurationForm($form, $form_state);
}
/**
* {@inheritdoc}
*/
public static function getPreconfiguredOptions() {
$options = array();
// Add all the commonly referenced entity types as distinct pre-configured
// options.
$entity_types = \Drupal::entityManager()->getDefinitions();
$common_references = array_filter($entity_types, function (EntityTypeInterface $entity_type) {
return $entity_type->isCommonReferenceTarget();
});
/** @var \Drupal\Core\Entity\EntityTypeInterface $entity_type */
foreach ($common_references as $entity_type) {
$options[$entity_type->id()] = [
'label' => $entity_type->getLabel(),
'field_storage_config' => [
'settings' => [
'target_type' => $entity_type->id(),
]
]
];
}
return $options;
}
}

View file

@ -0,0 +1,181 @@
<?php
/**
* @file
* Contains \Drupal\entity_reference\Plugin\views\display\EntityReference.
*/
namespace Drupal\entity_reference\Plugin\views\display;
use Drupal\views\Plugin\views\display\DisplayPluginBase;
/**
* The plugin that handles an EntityReference display.
*
* "entity_reference_display" is a custom property, used with
* \Drupal\views\Views::getApplicableViews() to retrieve all views with a
* 'Entity Reference' display.
*
* @ingroup views_display_plugins
*
* @ViewsDisplay(
* id = "entity_reference",
* title = @Translation("Entity Reference"),
* admin = @Translation("Entity Reference Source"),
* help = @Translation("Selects referenceable entities for an entity reference field."),
* theme = "views_view",
* register_theme = FALSE,
* uses_menu_links = FALSE,
* entity_reference_display = TRUE
* )
*/
class EntityReference extends DisplayPluginBase {
/**
* Overrides \Drupal\views\Plugin\views\display\DisplayPluginBase::$useAJAX.
*/
protected $usesAJAX = FALSE;
/**
* Overrides \Drupal\views\Plugin\views\display\DisplayPluginBase::$usesPager.
*/
protected $usesPager = FALSE;
/**
* Overrides \Drupal\views\Plugin\views\display\DisplayPluginBase::$usesAttachments.
*/
protected $usesAttachments = FALSE;
/**
* Overrides \Drupal\views\Plugin\views\display\DisplayPluginBase::defineOptions().
*/
protected function defineOptions() {
$options = parent::defineOptions();
// Force the style plugin to 'entity_reference_style' and the row plugin to
// 'fields'.
$options['style']['contains']['type'] = array('default' => 'entity_reference');
$options['defaults']['default']['style'] = FALSE;
$options['row']['contains']['type'] = array('default' => 'entity_reference');
$options['defaults']['default']['row'] = FALSE;
// Make sure the query is not cached.
$options['defaults']['default']['cache'] = FALSE;
// Set the display title to an empty string (not used in this display type).
$options['title']['default'] = '';
$options['defaults']['default']['title'] = FALSE;
return $options;
}
/**
* Overrides \Drupal\views\Plugin\views\display\DisplayPluginBase::optionsSummary().
*
* Disable 'cache' and 'title' so it won't be changed.
*/
public function optionsSummary(&$categories, &$options) {
parent::optionsSummary($categories, $options);
unset($options['query']);
unset($options['title']);
}
/**
* Overrides \Drupal\views\Plugin\views\display\DisplayPluginBase::getType().
*/
protected function getType() {
return 'entity_reference';
}
/**
* Overrides \Drupal\views\Plugin\views\display\DisplayPluginBase::execute().
*/
public function execute() {
return $this->view->render($this->display['id']);
}
/**
* Overrides \Drupal\views\Plugin\views\display\DisplayPluginBase::render().
*/
public function render() {
if (!empty($this->view->result) && $this->view->style_plugin->evenEmpty()) {
return $this->view->style_plugin->render($this->view->result);
}
return '';
}
/**
* Overrides \Drupal\views\Plugin\views\display\DisplayPluginBase::usesExposed().
*/
public function usesExposed() {
return FALSE;
}
/**
* Overrides \Drupal\views\Plugin\views\display\DisplayPluginBase::query().
*/
public function query() {
if (!empty($this->view->live_preview)) {
return;
}
// Make sure the id field is included in the results.
$id_field = $this->view->storage->get('base_field');
$this->id_field_alias = $this->view->query->addField($this->view->storage->get('base_table'), $id_field);
$options = $this->getOption('entity_reference_options');
// Restrict the autocomplete options based on what's been typed already.
if (isset($options['match'])) {
$style_options = $this->getOption('style');
$value = db_like($options['match']) . '%';
if ($options['match_operator'] != 'STARTS_WITH') {
$value = '%' . $value;
}
// Multiple search fields are OR'd together.
$conditions = db_or();
// Build the condition using the selected search fields.
foreach ($style_options['options']['search_fields'] as $field_alias) {
if (!empty($field_alias)) {
// Get the table and field names for the checked field.
$field = $this->view->query->fields[$this->view->field[$field_alias]->field_alias];
// Add an OR condition for the field.
$conditions->condition($field['table'] . '.' . $field['field'], $value, 'LIKE');
}
}
$this->view->query->addWhere(0, $conditions);
}
// Add an IN condition for validation.
if (!empty($options['ids'])) {
$this->view->query->addWhere(0, $id_field, $options['ids']);
}
$this->view->setItemsPerPage($options['limit']);
}
/**
* Overrides \Drupal\views\Plugin\views\display\DisplayPluginBase::validate().
*/
public function validate() {
$errors = parent::validate();
// Verify that search fields are set up.
$style = $this->getOption('style');
if (!isset($style['options']['search_fields'])) {
$errors[] = $this->t('Display "@display" needs a selected search fields to work properly. See the settings for the Entity Reference list format.', array('@display' => $this->display['display_title']));
}
else {
// Verify that the search fields used actually exist.
$fields = array_keys($this->handlers['field']);
foreach ($style['options']['search_fields'] as $field_alias => $enabled) {
if ($enabled && !in_array($field_alias, $fields)) {
$errors[] = $this->t('Display "@display" uses field %field as search field, but the field is no longer present. See the settings for the Entity Reference list format.', array('@display' => $this->display['display_title'], '%field' => $field_alias));
}
}
}
return $errors;
}
}

View file

@ -0,0 +1,62 @@
<?php
/**
* @file
* Contains \Drupal\entity_reference\Plugin\views\row\EntityReference.
*/
namespace Drupal\entity_reference\Plugin\views\row;
use Drupal\Core\Form\FormStateInterface;
use Drupal\views\Plugin\views\row\Fields;
/**
* EntityReference row plugin.
*
* @ingroup views_row_plugins
*
* @ViewsRow(
* id = "entity_reference",
* title = @Translation("Entity Reference inline fields"),
* help = @Translation("Displays the fields with an optional template."),
* theme = "views_view_fields",
* register_theme = FALSE,
* display_types = {"entity_reference"}
* )
*/
class EntityReference extends Fields {
/**
* Overrides \Drupal\views\Plugin\views\row\Fields::defineOptions().
*/
protected function defineOptions() {
$options = parent::defineOptions();
$options['separator'] = array('default' => '-');
return $options;
}
/**
* Overrides \Drupal\views\Plugin\views\row\Fields::buildOptionsForm().
*/
public function buildOptionsForm(&$form, FormStateInterface $form_state) {
parent::buildOptionsForm($form, $form_state);
// Expand the description of the 'Inline field' checkboxes.
$form['inline']['#description'] .= '<br />' . $this->t("<strong>Note:</strong> In 'Entity Reference' displays, all fields will be displayed inline unless an explicit selection of inline fields is made here." );
}
/**
* {@inheritdoc}
*/
public function preRender($row) {
// Force all fields to be inline by default.
if (empty($this->options['inline'])) {
$fields = $this->view->getHandlers('field', $this->displayHandler->display['id']);
$names = array_keys($fields);
$this->options['inline'] = array_combine($names, $names);
}
return parent::preRender($row);
}
}

View file

@ -0,0 +1,109 @@
<?php
/**
* @file
* Contains \Drupal\entity_reference\Plugin\views\style\EntityReference.
*/
namespace Drupal\entity_reference\Plugin\views\style;
use Drupal\Component\Utility\Xss;
use Drupal\Core\Form\FormStateInterface;
use Drupal\views\Plugin\views\style\StylePluginBase;
/**
* EntityReference style plugin.
*
* @ingroup views_style_plugins
*
* @ViewsStyle(
* id = "entity_reference",
* title = @Translation("Entity Reference list"),
* help = @Translation("Returns results as a PHP array of labels and rendered rows."),
* theme = "views_view_unformatted",
* register_theme = FALSE,
* display_types = {"entity_reference"}
* )
*/
class EntityReference extends StylePluginBase {
/**
* Overrides \Drupal\views\Plugin\views\style\StylePluginBase::usesRowPlugin.
*/
protected $usesRowPlugin = TRUE;
/**
* Overrides \Drupal\views\Plugin\views\style\StylePluginBase::usesFields.
*/
protected $usesFields = TRUE;
/**
* Overrides \Drupal\views\Plugin\views\style\StylePluginBase::usesGrouping.
*/
protected $usesGrouping = FALSE;
/**
* Overrides \Drupal\views\Plugin\views\style\StylePluginBase\StylePluginBase::defineOptions().
*/
protected function defineOptions() {
$options = parent::defineOptions();
$options['search_fields'] = array('default' => NULL);
return $options;
}
/**
* Overrides \Drupal\views\Plugin\views\style\StylePluginBase\StylePluginBase::buildOptionsForm().
*/
public function buildOptionsForm(&$form, FormStateInterface $form_state) {
parent::buildOptionsForm($form, $form_state);
$options = $this->displayHandler->getFieldLabels(TRUE);
$form['search_fields'] = array(
'#type' => 'checkboxes',
'#title' => $this->t('Search fields'),
'#options' => $options,
'#required' => TRUE,
'#default_value' => $this->options['search_fields'],
'#description' => $this->t('Select the field(s) that will be searched when using the autocomplete widget.'),
'#weight' => -3,
);
}
/**
* Overrides \Drupal\views\Plugin\views\style\StylePluginBase\StylePluginBase::render().
*/
public function render() {
if (!empty($this->view->live_preview)) {
return parent::render();
}
// Group the rows according to the grouping field, if specified.
$sets = $this->renderGrouping($this->view->result, $this->options['grouping']);
// Grab the alias of the 'id' field added by
// entity_reference_plugin_display.
$id_field_alias = $this->view->storage->get('base_field');
// @todo We don't display grouping info for now. Could be useful for select
// widget, though.
$results = array();
foreach ($sets as $records) {
foreach ($records as $values) {
$results[$values->{$id_field_alias}] = $this->view->rowPlugin->render($values);
// Sanitize HTML, remove line breaks and extra whitespace.
$results[$values->{$id_field_alias}]['#post_render'][] = function ($html, array $elements) {
return Xss::filterAdmin(preg_replace('/\s\s+/', ' ', str_replace("\n", '', $html)));
};
}
}
return $results;
}
/**
* {@inheritdoc}
*/
public function evenEmpty() {
return TRUE;
}
}

View file

@ -0,0 +1,345 @@
<?php
/**
* @file
* Contains \Drupal\entity_reference\Tests\EntityReferenceAdminTest.
*/
namespace Drupal\entity_reference\Tests;
use Drupal\field_ui\Tests\FieldUiTestTrait;
use Drupal\simpletest\WebTestBase;
use Drupal\taxonomy\Entity\Vocabulary;
/**
* Tests for the administrative UI.
*
* @group entity_reference
*/
class EntityReferenceAdminTest extends WebTestBase {
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 = array('node', 'field_ui', 'entity_reference', 'path', 'taxonomy', 'block', 'views', 'views_ui', 'entity_test');
/**
* 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 test user.
$admin_user = $this->drupalCreateUser(array('access content', 'administer node fields', 'administer node display', 'administer views'));
$this->drupalLogin($admin_user);
// Create a content type, with underscores.
$type_name = strtolower($this->randomMachineName(8)) . '_test';
$type = $this->drupalCreateContentType(array('name' => $type_name, 'type' => $type_name));
$this->type = $type->id();
}
/**
* Tests the Entity Reference Admin UI.
*/
public function testFieldAdminHandler() {
$bundle_path = 'admin/structure/types/manage/' . $this->type;
// First step: 'Add new field' on the 'Manage fields' page.
$this->drupalGet($bundle_path . '/fields/add-field');
// Check if the commonly referenced entity types appear in the list.
$this->assertOption('edit-new-storage-type', 'field_ui:entity_reference:node');
$this->assertOption('edit-new-storage-type', 'field_ui:entity_reference:user');
$this->drupalPostForm(NULL, array(
'label' => 'Test label',
'field_name' => 'test',
'new_storage_type' => 'entity_reference',
), t('Save and continue'));
// Node should be selected by default.
$this->assertFieldByName('settings[target_type]', 'node');
// Check that all entity types can be referenced.
$this->assertFieldSelectOptions('settings[target_type]', array_keys(\Drupal::entityManager()->getDefinitions()));
// Second step: 'Field settings' form.
$this->drupalPostForm(NULL, array(), t('Save field settings'));
// The base handler should be selected by default.
$this->assertFieldByName('settings[handler]', 'default:node');
// The base handler settings should be displayed.
$entity_type_id = 'node';
$bundles = entity_get_bundles($entity_type_id);
foreach ($bundles as $bundle_name => $bundle_info) {
$this->assertFieldByName('settings[handler_settings][target_bundles][' . $bundle_name . ']');
}
reset($bundles);
// Test the sort settings.
// Option 0: no sort.
$this->assertFieldByName('settings[handler_settings][sort][field]', '_none');
$this->assertNoFieldByName('settings[handler_settings][sort][direction]');
// Option 1: sort by field.
$this->drupalPostAjaxForm(NULL, array('settings[handler_settings][sort][field]' => 'nid'), 'settings[handler_settings][sort][field]');
$this->assertFieldByName('settings[handler_settings][sort][direction]', 'ASC');
// Test that a non-translatable base field is a sort option.
$this->assertFieldByXPath("//select[@name='settings[handler_settings][sort][field]']/option[@value='nid']");
// Test that a translatable base field is a sort option.
$this->assertFieldByXPath("//select[@name='settings[handler_settings][sort][field]']/option[@value='title']");
// Test that a configurable field is a sort option.
$this->assertFieldByXPath("//select[@name='settings[handler_settings][sort][field]']/option[@value='body.value']");
// Set back to no sort.
$this->drupalPostAjaxForm(NULL, array('settings[handler_settings][sort][field]' => '_none'), 'settings[handler_settings][sort][field]');
$this->assertNoFieldByName('settings[handler_settings][sort][direction]');
// Third step: confirm.
$this->drupalPostForm(NULL, array(
'required' => '1',
'settings[handler_settings][target_bundles][' . key($bundles) . ']' => key($bundles),
), t('Save settings'));
// Check that the field appears in the overview form.
$this->assertFieldByXPath('//table[@id="field-overview"]//tr[@id="field-test"]/td[1]', 'Test label', 'Field was created and appears in the overview page.');
// Check that the field settings form can be submitted again, even when the
// field is required.
// The first 'Edit' link is for the Body field.
$this->clickLink(t('Edit'), 1);
$this->drupalPostForm(NULL, array(), t('Save settings'));
// Switch the target type to 'taxonomy_term' and check that the settings
// specific to its selection handler are displayed.
$field_name = 'node.' . $this->type . '.field_test';
$edit = array(
'settings[target_type]' => 'taxonomy_term',
);
$this->drupalPostForm($bundle_path . '/fields/' . $field_name . '/storage', $edit, t('Save field settings'));
$this->drupalGet($bundle_path . '/fields/' . $field_name);
$this->assertFieldByName('settings[handler_settings][auto_create]');
// Switch the target type to 'user' and check that the settings specific to
// its selection handler are displayed.
$field_name = 'node.' . $this->type . '.field_test';
$edit = array(
'settings[target_type]' => 'user',
);
$this->drupalPostForm($bundle_path . '/fields/' . $field_name . '/storage', $edit, t('Save field settings'));
$this->drupalGet($bundle_path . '/fields/' . $field_name);
$this->assertFieldByName('settings[handler_settings][filter][type]', '_none');
// Switch the target type to 'node'.
$field_name = 'node.' . $this->type . '.field_test';
$edit = array(
'settings[target_type]' => 'node',
);
$this->drupalPostForm($bundle_path . '/fields/' . $field_name . '/storage', $edit, t('Save field settings'));
// Try to select the views handler.
$edit = array(
'settings[handler]' => 'views',
);
$this->drupalPostAjaxForm($bundle_path . '/fields/' . $field_name, $edit, 'settings[handler]');
$this->assertRaw(t('No eligible views were found. <a href="@create">Create a view</a> with an <em>Entity Reference</em> display, or add such a display to an <a href="@existing">existing view</a>.', array(
'@create' => \Drupal::url('views_ui.add'),
'@existing' => \Drupal::url('entity.view.collection'),
)));
$this->drupalPostForm(NULL, $edit, t('Save settings'));
// If no eligible view is available we should see a message.
$this->assertText('The views entity selection mode requires a view.');
// Enable the entity_reference_test module which creates an eligible view.
$this->container->get('module_installer')->install(array('entity_reference_test'));
$this->resetAll();
$this->drupalGet($bundle_path . '/fields/' . $field_name);
$this->drupalPostAjaxForm($bundle_path . '/fields/' . $field_name, $edit, 'settings[handler]');
$edit = array(
'settings[handler_settings][view][view_and_display]' => 'test_entity_reference:entity_reference_1',
);
$this->drupalPostForm(NULL, $edit, t('Save settings'));
$this->assertResponse(200);
// Switch the target type to 'entity_test'.
$edit = array(
'settings[target_type]' => 'entity_test',
);
$this->drupalPostForm($bundle_path . '/fields/' . $field_name . '/storage', $edit, t('Save field settings'));
$this->drupalGet($bundle_path . '/fields/' . $field_name);
$edit = array(
'settings[handler]' => 'views',
);
$this->drupalPostAjaxForm($bundle_path . '/fields/' . $field_name, $edit, 'settings[handler]');
$edit = array(
'settings[handler_settings][view][view_and_display]' => 'test_entity_reference_entity_test:entity_reference_1',
);
$this->drupalPostForm(NULL, $edit, t('Save settings'));
$this->assertResponse(200);
}
/**
* Tests the formatters for the Entity References
*/
public function testAvailableFormatters() {
// Create a new vocabulary.
Vocabulary::create(array('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]', array(
'entity_reference_label',
'entity_reference_entity_id',
'entity_reference_rss_category',
'entity_reference_entity_view',
'hidden',
));
// 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]', array(
'author',
'entity_reference_entity_id',
'entity_reference_entity_view',
'entity_reference_label',
'hidden',
));
// 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]', array(
'entity_reference_label',
'entity_reference_entity_id',
'entity_reference_entity_view',
'hidden',
));
// 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]', array(
'entity_reference_label',
'entity_reference_entity_id',
'hidden',
));
}
/**
* Creates a new Entity Reference fields with a given target type.
*
* @param $target_type
* The name of the target type
* @param $bundle
* Name of the bundle
* Default = NULL
* @return string
* Returns the generated field name
*/
public function createEntityReferenceField($target_type, $bundle = NULL) {
// 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 = array();
$storage_edit['settings[target_type]'] = $target_type;
if ($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.
*
* @return bool
* TRUE if the assertion succeeded, FALSE otherwise.
*/
protected function assertFieldSelectOptions($name, array $expected_options) {
$xpath = $this->buildXPathQuery('//select[@name=:name]', array(':name' => $name));
$fields = $this->xpath($xpath);
if ($fields) {
$field = $fields[0];
$options = $this->getAllOptionsList($field);
sort($options);
sort($expected_options);
return $this->assertIdentical($options, $expected_options);
}
else {
return $this->fail('Unable to find field ' . $name);
}
}
/**
* Extracts all options from a select element.
*
* @param \SimpleXMLElement $element
* The select element field information.
*
* @return array
* An array of option values as strings.
*/
protected function getAllOptionsList(\SimpleXMLElement $element) {
$options = array();
// Add all options items.
foreach ($element->option as $option) {
$options[] = (string) $option['value'];
}
// Loops trough all the option groups
foreach ($element->optgroup as $optgroup) {
$options = array_merge($this->getAllOptionsList($optgroup), $options);
}
return $options;
}
}

View file

@ -0,0 +1,137 @@
<?php
/**
* @file
* Contains \Drupal\entity_reference\Tests\EntityReferenceAutoCreateTest.
*/
namespace Drupal\entity_reference\Tests;
use Drupal\Core\Field\FieldStorageDefinitionInterface;
use Drupal\simpletest\WebTestBase;
use Drupal\node\Entity\Node;
/**
* Tests creating new entity (e.g. taxonomy-term) from an autocomplete widget.
*
* @group entity_reference
*/
class EntityReferenceAutoCreateTest extends WebTestBase {
public static $modules = array('entity_reference', 'node');
/**
* 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();
entity_create('field_storage_config', array(
'field_name' => 'test_field',
'entity_type' => 'node',
'translatable' => FALSE,
'entity_types' => array(),
'settings' => array(
'target_type' => 'node',
),
'type' => 'entity_reference',
'cardinality' => FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED,
))->save();
entity_create('field_config', array(
'label' => 'Entity reference field',
'field_name' => 'test_field',
'entity_type' => 'node',
'bundle' => $referencing->id(),
'settings' => array(
'handler' => 'default',
'handler_settings' => array(
// Reference a single vocabulary.
'target_bundles' => array(
$referenced->id(),
),
// Enable auto-create.
'auto_create' => TRUE,
),
),
))->save();
entity_get_display('node', $referencing->id(), 'default')
->setComponent('test_field')
->save();
entity_get_form_display('node', $referencing->id(), 'default')
->setComponent('test_field', array(
'type' => 'entity_reference_autocomplete',
))
->save();
}
/**
* Tests that the autocomplete input element appears and the creation of a new
* entity.
*/
public function testAutoCreate() {
$user1 = $this->drupalCreateUser(array('access content', "create $this->referencingType content"));
$this->drupalLogin($user1);
$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->assertFalse($result, 'Referenced node does not exist yet.');
$edit = array(
'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->assertTrue($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.');
}
}

View file

@ -0,0 +1,160 @@
<?php
/**
* @file
* Contains \Drupal\entity_reference\Tests\EntityReferenceFieldDefaultValueTest.
*/
namespace Drupal\entity_reference\Tests;
use Drupal\Component\Utility\Unicode;
use Drupal\config\Tests\SchemaCheckTestTrait;
use Drupal\field\Entity\FieldStorageConfig;
use Drupal\simpletest\WebTestBase;
/**
* Tests entity reference field default values storage in CMI.
*
* @group entity_reference
*/
class EntityReferenceFieldDefaultValueTest extends WebTestBase {
use SchemaCheckTestTrait;
/**
* Modules to install.
*
* @var array
*/
public static $modules = array('entity_reference', 'field_ui', 'node');
/**
* 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(array('type' => 'reference_content'));
$this->drupalCreateContentType(array('type' => 'referenced_content'));
// Create admin user.
$this->adminUser = $this->drupalCreateUser(array('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.
*/
function testEntityReferenceDefaultValue() {
// Create a node to be referenced.
$referenced_node = $this->drupalCreateNode(array('type' => 'referenced_content'));
$field_name = Unicode::strtolower($this->randomMachineName());
$field_storage = entity_create('field_storage_config', array(
'field_name' => $field_name,
'entity_type' => 'node',
'type' => 'entity_reference',
'settings' => array('target_type' => 'node'),
));
$field_storage->save();
$field = entity_create('field_config', array(
'field_storage' => $field_storage,
'bundle' => 'reference_content',
'settings' => array(
'handler' => 'default',
'handler_settings' => array(
'target_bundles' => array('referenced_content'),
'sort' => array('field' => '_none'),
),
),
));
$field->save();
// Set created node as default_value.
$field_edit = array(
'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(array($referenced_node->getConfigDependencyName()), $config_entity['dependencies']['content']);
// Clear field definitions cache in order to avoid stale cache values.
\Drupal::entityManager()->clearCachedFieldDefinitions();
// Create a new node to check that UUID has been converted to numeric ID.
$new_node = entity_create('node', array('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()
*/
function testEntityReferenceDefaultConfigValue() {
// Create a node to be referenced.
$referenced_node_type = $this->drupalCreateContentType(array('type' => 'referenced_config_to_delete'));
$referenced_node_type2 = $this->drupalCreateContentType(array('type' => 'referenced_config_to_preserve'));
$field_name = Unicode::strtolower($this->randomMachineName());
$field_storage = entity_create('field_storage_config', array(
'field_name' => $field_name,
'entity_type' => 'node',
'type' => 'entity_reference',
'settings' => array('target_type' => 'node_type'),
'cardinality' => FieldStorageConfig::CARDINALITY_UNLIMITED,
));
$field_storage->save();
$field = entity_create('field_config', array(
'field_storage' => $field_storage,
'bundle' => 'reference_content',
'settings' => array(
'handler' => 'default',
'handler_settings' => array(
'sort' => array('field' => '_none'),
),
),
));
$field->save();
// Set created node as default_value.
$field_edit = array(
'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->assertTrue(in_array($referenced_node_type->getConfigDependencyName(), $config_entity['dependencies']['config'], TRUE), 'The node type referenced_config_to_delete is a dependency of the field.');
$this->assertTrue(in_array($referenced_node_type2->getConfigDependencyName(), $config_entity['dependencies']['config'], TRUE), '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->assertFalse(in_array($referenced_node_type->getConfigDependencyName(), $config_entity['dependencies']['config'], TRUE), 'The node type referenced_config_to_delete not a dependency of the field.');
$this->assertTrue(in_array($referenced_node_type2->getConfigDependencyName(), $config_entity['dependencies']['config'], TRUE), 'The node type referenced_config_to_preserve is a dependency of the field.');
}
}

View file

@ -0,0 +1,301 @@
<?php
/**
* @file
* Contains \Drupal\entity_reference\Tests\EntityReferenceFieldTranslatedReferenceViewTest.
*/
namespace Drupal\entity_reference\Tests;
use Drupal\language\Entity\ConfigurableLanguage;
use Drupal\Core\Field\FieldStorageDefinitionInterface;
use Drupal\simpletest\WebTestBase;
/**
* Tests the translation of entity reference field display on nodes.
*
* @group entity_reference
*/
class EntityReferenceFieldTranslatedReferenceViewTest extends WebTestBase {
/**
* 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;
/**
* Modules to enable.
*
* @var array
*/
public static $modules = array(
'language',
'content_translation',
'entity_reference',
'node',
);
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();
}
/**
* Tests if the translated entity is displayed in an entity reference field.
*/
public function testTranslatedEntityReferenceDisplay() {
$url = $this->referrerEntity->urlInfo();
$translation_url = $this->referrerEntity->urlInfo('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.');
}
/**
* 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();
$this->referrerEntity = $this->createReferrerEntity();
}
/**
* 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_static_reset();
\Drupal::entityManager()->clearCachedDefinitions();
\Drupal::service('router.builder')->rebuild();
\Drupal::service('entity.definition_update_manager')->applyUpdates();
}
/**
* Adds term reference field for the article content type.
*/
protected function setUpEntityReferenceField() {
entity_create('field_storage_config', array(
'field_name' => $this->referenceFieldName,
'entity_type' => $this->testEntityTypeName,
'type' => 'entity_reference',
'cardinality' => FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED,
'translatable' => FALSE,
'settings' => array(
'allowed_values' => array(
array(
'target_type' => $this->testEntityTypeName,
),
),
),
))->save();
entity_create('field_config', array(
'field_name' => $this->referenceFieldName,
'bundle' => $this->referrerType->id(),
'entity_type' => $this->testEntityTypeName,
))
->save();
entity_get_form_display($this->testEntityTypeName, $this->referrerType->id(), 'default')
->setComponent($this->referenceFieldName, array(
'type' => 'entity_reference_autocomplete',
))
->save();
entity_get_display($this->testEntityTypeName, $this->referrerType->id(), 'default')
->setComponent($this->referenceFieldName, array(
'type' => 'entity_reference_label',
))
->save();
}
/**
* Create content types.
*/
protected function setUpContentTypes() {
$this->referrerType = $this->drupalCreateContentType(array(
'type' => 'referrer',
'name' => 'Referrer',
));
$this->referencedType = $this->drupalCreateContentType(array(
'type' => 'referenced_page',
'name' => 'Referenced Page',
));
}
/**
* Create a referenced entity with a translation.
*/
protected function createReferencedEntityWithTranslation() {
/** @var \Drupal\node\Entity\Node $node */
$node = entity_create($this->testEntityTypeName, array(
'title' => $this->originalLabel,
'type' => $this->referencedType->id(),
'description' => array(
'value' => $this->randomMachineName(),
'format' => 'basic_html',
),
'langcode' => $this->baseLangcode,
));
$node->save();
$node->addTranslation($this->translateToLangcode, array(
'title' => $this->translatedLabel,
));
$node->save();
return $node;
}
/**
* Create the referenced entity.
*/
protected function createNotTranslatedReferencedEntity() {
/** @var \Drupal\node\Entity\Node $node */
$node = entity_create($this->testEntityTypeName, array(
'title' => $this->labelOfNotTranslatedReference,
'type' => $this->referencedType->id(),
'description' => array(
'value' => $this->randomMachineName(),
'format' => 'basic_html',
),
'langcode' => $this->baseLangcode,
));
$node->save();
return $node;
}
/**
* Create the referrer entity.
*/
protected function createReferrerEntity() {
/** @var \Drupal\node\Entity\Node $node */
$node = entity_create($this->testEntityTypeName, array(
'title' => $this->randomMachineName(),
'type' => $this->referrerType->id(),
'description' => array(
'value' => $this->randomMachineName(),
'format' => 'basic_html',
),
$this->referenceFieldName => array(
array('target_id' => $this->referencedEntityWithTranslation->id()),
array('target_id' => $this->referencedEntityWithoutTranslation->id()),
),
'langcode' => $this->baseLangcode,
));
$node->save();
$node->addTranslation($this->translateToLangcode, array());
$node->save();
return $node;
}
}

View file

@ -0,0 +1,224 @@
<?php
/**
* @file
* Contains \Drupal\entity_reference\Tests\EntityReferenceIntegrationTest.
*/
namespace Drupal\entity_reference\Tests;
use Drupal\Component\Utility\SafeMarkup;
use Drupal\config\Tests\AssertConfigEntityImportTrait;
use Drupal\Core\Config\Entity\ConfigEntityBase;
use Drupal\entity_reference\Tests\EntityReferenceTestTrait;
use Drupal\field\Entity\FieldConfig;
use Drupal\simpletest\WebTestBase;
/**
* Tests various Entity reference UI components.
*
* @group entity_reference
*/
class EntityReferenceIntegrationTest extends WebTestBase {
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 = array('config_test', 'entity_test', 'entity_reference', 'field_ui');
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
// Create a test user.
$web_user = $this->drupalCreateUser(array('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', array(), 2);
// Test the default 'entity_reference_autocomplete' widget.
entity_get_form_display($this->entityType, $this->bundle, 'default')->setComponent($this->fieldName)->save();
$entity_name = $this->randomMachineName();
$edit = array(
'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.
$entity = current(entity_load_multiple_by_properties($this->entityType, array('name' => $entity_name)));
$this->drupalGet($this->entityType . '/manage/' . $entity->id());
$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, array(), t('Save'));
$this->assertFieldValues($entity_name, $referenced_entities);
// Test the 'entity_reference_autocomplete_tags' widget.
entity_get_form_display($this->entityType, $this->bundle, 'default')->setComponent($this->fieldName, array(
'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 = array(
'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(entity_load_multiple_by_properties($this->entityType, array('name' => $entity_name)));
$this->drupalGet($this->entityType . '/manage/' . $entity->id());
$this->assertFieldByName($this->fieldName . '[target_id]', $target_id . ' (' . $referenced_entities[1]->id() . ')');
$this->drupalPostForm(NULL, array(), 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 = array('entity_reference_autocomplete', 'entity_reference_autocomplete_tags');
$entity = current(entity_load_multiple_by_properties($this->entityType, array('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) {
entity_get_form_display($this->entityType, $this->bundle, 'default')->setComponent($this->fieldName, array(
'type' => $widget_type,
))->save();
$this->drupalPostForm($this->entityType . '/manage/' . $entity->id(), array(), t('Save'));
$this->assertFieldValues($entity_name, $referenced_entities);
}
// Reset to the default 'entity_reference_autocomplete' widget.
entity_get_form_display($this->entityType, $this->bundle, 'default')->setComponent($this->fieldName)->save();
// Set first entity as the default_value.
$field_edit = array(
'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->assertTrue(in_array($referenced_entities[0]->getConfigDependencyName(), $field->getDependencies()[$key]), SafeMarkup::format('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::entityManager()->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]), SafeMarkup::format('@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(entity_load_multiple_by_properties($this->entityType, array('name' => $entity_name)));
$this->assertTrue($entity, format_string('%entity_type: Entity found in the database.', array('%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() {
$config_entity_1 = entity_create('config_test', array('id' => $this->randomMachineName(), 'label' => $this->randomMachineName()));
$config_entity_1->save();
$config_entity_2 = entity_create('config_test', array('id' => $this->randomMachineName(), 'label' => $this->randomMachineName()));
$config_entity_2->save();
$content_entity_1 = entity_create('entity_test', array('name' => $this->randomMachineName()));
$content_entity_1->save();
$content_entity_2 = entity_create('entity_test', array('name' => $this->randomMachineName()));
$content_entity_2->save();
return array(
'config' => array(
$config_entity_1,
$config_entity_2,
),
'content' => array(
$content_entity_1,
$content_entity_2,
),
);
}
}

View file

@ -0,0 +1,69 @@
<?php
/**
* @file
* Contains \Drupal\entity_reference\Tests\EntityReferenceTestTrait.
*/
namespace Drupal\entity_reference\Tests;
use Drupal\field\Entity\FieldConfig;
use Drupal\field\Entity\FieldStorageConfig;
/**
* Provides common functionality for the EntityReference test classes.
*/
trait EntityReferenceTestTrait {
/**
* Creates a field of an entity reference field storage on the specified bundle.
*
* @param string $entity_type
* The type of entity the field will be attached to.
* @param string $bundle
* The bundle name of the entity the field will be attached to.
* @param string $field_name
* The name of the field; if it already exists, a new instance of the existing
* field will be created.
* @param string $field_label
* The label of the field.
* @param string $target_entity_type
* The type of the referenced entity.
* @param string $selection_handler
* The selection handler used by this field.
* @param array $selection_handler_settings
* An array of settings supported by the selection handler specified above.
* (e.g. 'target_bundles', 'sort', 'auto_create', etc).
* @param int $cardinality
* The cardinality of the field.
*
* @see \Drupal\Core\Entity\Plugin\EntityReferenceSelection\SelectionBase::buildConfigurationForm()
*/
protected function createEntityReferenceField($entity_type, $bundle, $field_name, $field_label, $target_entity_type, $selection_handler = 'default', $selection_handler_settings = array(), $cardinality = 1) {
// Look for or add the specified field to the requested entity bundle.
if (!FieldStorageConfig::loadByName($entity_type, $field_name)) {
FieldStorageConfig::create(array(
'field_name' => $field_name,
'type' => 'entity_reference',
'entity_type' => $entity_type,
'cardinality' => $cardinality,
'settings' => array(
'target_type' => $target_entity_type,
),
))->save();
}
if (!FieldConfig::loadByName($entity_type, $bundle, $field_name)) {
FieldConfig::create(array(
'field_name' => $field_name,
'entity_type' => $entity_type,
'bundle' => $bundle,
'label' => $field_label,
'settings' => array(
'handler' => $selection_handler,
'handler_settings' => $selection_handler_settings,
),
))->save();
}
}
}

View file

@ -0,0 +1,232 @@
<?php
/**
* @file
* Contains \Drupal\entity_reference\Tests\Views\EntityReferenceRelationshipTest.
*/
namespace Drupal\entity_reference\Tests\Views;
use Drupal\entity_reference\Tests\EntityReferenceTestTrait;
use Drupal\entity_test\Entity\EntityTest;
use Drupal\entity_test\Entity\EntityTestMul;
use Drupal\views\Tests\ViewTestData;
use Drupal\views\Tests\ViewUnitTestBase;
use Drupal\views\Views;
/**
* Tests entity reference relationship data.
*
* @group entity_reference
* @see entity_reference_field_views_data()
*/
class EntityReferenceRelationshipTest extends ViewUnitTestBase {
use EntityReferenceTestTrait;
/**
* Views used by this test.
*
* @var array
*/
public static $testViews = array(
'test_entity_reference_entity_test_view',
'test_entity_reference_reverse_entity_test_view',
'test_entity_reference_entity_test_mul_view',
'test_entity_reference_reverse_entity_test_mul_view',
);
/**
* Modules to install.
*
* @var array
*/
public static $modules = array('user', 'field', 'entity_test', 'entity_reference', 'views', 'entity_reference_test_views');
/**
* The entity_test entities used by the test.
*
* @var array
*/
protected $entities = array();
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
$this->installEntitySchema('user');
$this->installEntitySchema('entity_test');
$this->installEntitySchema('entity_test_mul');
// Create reference from entity_test to entity_test_mul.
$this->createEntityReferenceField('entity_test','entity_test','field_test_data','field_test_data','entity_test_mul');
// Create reference from entity_test_mul to entity_test.
$this->createEntityReferenceField('entity_test_mul', 'entity_test_mul', 'field_data_test', 'field_data_test', 'entity_test');
ViewTestData::createTestViews(get_class($this), array('entity_reference_test_views'));
}
/**
* Tests using the views relationship.
*/
public function testNoDataTableRelationship() {
// Create some test entities which link each other.
$referenced_entity = EntityTestMul::create();
$referenced_entity->save();
$entity = EntityTest::create();
$entity->field_test_data->target_id = $referenced_entity->id();
$entity->save();
$this->assertEqual($entity->field_test_data[0]->entity->id(), $referenced_entity->id());
$this->entities[] = $entity;
$entity = EntityTest::create();
$entity->field_test_data->target_id = $referenced_entity->id();
$entity->save();
$this->assertEqual($entity->field_test_data[0]->entity->id(), $referenced_entity->id());
$this->entities[] = $entity;
Views::viewsData()->clear();
// Check the generated views data.
$views_data = Views::viewsData()->get('entity_test__field_test_data');
$this->assertEqual($views_data['field_test_data']['relationship']['id'], 'standard');
$this->assertEqual($views_data['field_test_data']['relationship']['base'], 'entity_test_mul_property_data');
$this->assertEqual($views_data['field_test_data']['relationship']['base field'], 'id');
$this->assertEqual($views_data['field_test_data']['relationship']['relationship field'], 'field_test_data_target_id');
$this->assertEqual($views_data['field_test_data']['relationship']['entity type'], 'entity_test_mul');
// Check the backwards reference.
$views_data = Views::viewsData()->get('entity_test_mul_property_data');
$this->assertEqual($views_data['reverse__entity_test__field_test_data']['relationship']['id'], 'entity_reverse');
$this->assertEqual($views_data['reverse__entity_test__field_test_data']['relationship']['base'], 'entity_test');
$this->assertEqual($views_data['reverse__entity_test__field_test_data']['relationship']['base field'], 'id');
$this->assertEqual($views_data['reverse__entity_test__field_test_data']['relationship']['field table'], 'entity_test__field_test_data');
$this->assertEqual($views_data['reverse__entity_test__field_test_data']['relationship']['field field'], 'field_test_data_target_id');
$this->assertEqual($views_data['reverse__entity_test__field_test_data']['relationship']['field_name'], 'field_test_data');
$this->assertEqual($views_data['reverse__entity_test__field_test_data']['relationship']['entity_type'], 'entity_test');
$this->assertEqual($views_data['reverse__entity_test__field_test_data']['relationship']['join_extra'][0], ['field' => 'deleted', 'value' => 0, 'numeric' => TRUE]);
// Check an actual test view.
$view = Views::getView('test_entity_reference_entity_test_view');
$this->executeView($view);
/** @var \Drupal\views\ResultRow $row */
foreach ($view->result as $index => $row) {
// Check that the actual ID of the entity is the expected one.
$this->assertEqual($row->id, $this->entities[$index]->id());
// Also check that we have the correct result entity.
$this->assertEqual($row->_entity->id(), $this->entities[$index]->id());
// Test the forward relationship.
$this->assertEqual($row->entity_test_mul_property_data_entity_test__field_test_data_i, 1);
// Test that the correct relationship entity is on the row.
$this->assertEqual($row->_relationship_entities['field_test_data']->id(), 1);
$this->assertEqual($row->_relationship_entities['field_test_data']->bundle(), 'entity_test_mul');
}
// Check the backwards reference view.
$view = Views::getView('test_entity_reference_reverse_entity_test_view');
$this->executeView($view);
/** @var \Drupal\views\ResultRow $row */
foreach ($view->result as $index => $row) {
$this->assertEqual($row->id, 1);
$this->assertEqual($row->_entity->id(), 1);
// Test the backwards relationship.
$this->assertEqual($row->field_test_data_entity_test_mul_property_data_id, $this->entities[$index]->id());
// Test that the correct relationship entity is on the row.
$this->assertEqual($row->_relationship_entities['reverse__entity_test__field_test_data']->id(), $this->entities[$index]->id());
$this->assertEqual($row->_relationship_entities['reverse__entity_test__field_test_data']->bundle(), 'entity_test');
}
}
/**
* Tests views data generated for relationship.
*
* @see entity_reference_field_views_data()
*/
public function testDataTableRelationship() {
// Create some test entities which link each other.
$referenced_entity = EntityTest::create();
$referenced_entity->save();
$entity = EntityTestMul::create();
$entity->field_data_test->target_id = $referenced_entity->id();
$entity->save();
$this->assertEqual($entity->field_data_test[0]->entity->id(), $referenced_entity->id());
$this->entities[] = $entity;
$entity = EntityTestMul::create();
$entity->field_data_test->target_id = $referenced_entity->id();
$entity->save();
$this->assertEqual($entity->field_data_test[0]->entity->id(), $referenced_entity->id());
$this->entities[] = $entity;
Views::viewsData()->clear();
// Check the generated views data.
$views_data = Views::viewsData()->get('entity_test_mul__field_data_test');
$this->assertEqual($views_data['field_data_test']['relationship']['id'], 'standard');
$this->assertEqual($views_data['field_data_test']['relationship']['base'], 'entity_test');
$this->assertEqual($views_data['field_data_test']['relationship']['base field'], 'id');
$this->assertEqual($views_data['field_data_test']['relationship']['relationship field'], 'field_data_test_target_id');
$this->assertEqual($views_data['field_data_test']['relationship']['entity type'], 'entity_test');
// Check the backwards reference.
$views_data = Views::viewsData()->get('entity_test');
$this->assertEqual($views_data['reverse__entity_test_mul__field_data_test']['relationship']['id'], 'entity_reverse');
$this->assertEqual($views_data['reverse__entity_test_mul__field_data_test']['relationship']['base'], 'entity_test_mul_property_data');
$this->assertEqual($views_data['reverse__entity_test_mul__field_data_test']['relationship']['base field'], 'id');
$this->assertEqual($views_data['reverse__entity_test_mul__field_data_test']['relationship']['field table'], 'entity_test_mul__field_data_test');
$this->assertEqual($views_data['reverse__entity_test_mul__field_data_test']['relationship']['field field'], 'field_data_test_target_id');
$this->assertEqual($views_data['reverse__entity_test_mul__field_data_test']['relationship']['field_name'], 'field_data_test');
$this->assertEqual($views_data['reverse__entity_test_mul__field_data_test']['relationship']['entity_type'], 'entity_test_mul');
$this->assertEqual($views_data['reverse__entity_test_mul__field_data_test']['relationship']['join_extra'][0], ['field' => 'deleted', 'value' => 0, 'numeric' => TRUE]);
// Check an actual test view.
$view = Views::getView('test_entity_reference_entity_test_mul_view');
$this->executeView($view);
/** @var \Drupal\views\ResultRow $row */
foreach ($view->result as $index => $row) {
// Check that the actual ID of the entity is the expected one.
$this->assertEqual($row->id, $this->entities[$index]->id());
// Also check that we have the correct result entity.
$this->assertEqual($row->_entity->id(), $this->entities[$index]->id());
// Test the forward relationship.
$this->assertEqual($row->entity_test_entity_test_mul__field_data_test_id, 1);
// Test that the correct relationship entity is on the row.
$this->assertEqual($row->_relationship_entities['field_data_test']->id(), 1);
$this->assertEqual($row->_relationship_entities['field_data_test']->bundle(), 'entity_test');
}
// Check the backwards reference view.
$view = Views::getView('test_entity_reference_reverse_entity_test_mul_view');
$this->executeView($view);
/** @var \Drupal\views\ResultRow $row */
foreach ($view->result as $index => $row) {
$this->assertEqual($row->id, 1);
$this->assertEqual($row->_entity->id(), 1);
// Test the backwards relationship.
$this->assertEqual($row->field_data_test_entity_test_id, $this->entities[$index]->id());
// Test that the correct relationship entity is on the row.
$this->assertEqual($row->_relationship_entities['reverse__entity_test_mul__field_data_test']->id(), $this->entities[$index]->id());
$this->assertEqual($row->_relationship_entities['reverse__entity_test_mul__field_data_test']->bundle(), 'entity_test_mul');
}
}
}

View file

@ -0,0 +1,147 @@
<?php
/**
* @file
* Contains \Drupal\entity_reference\Tests\Views\SelectionTest.
*/
namespace Drupal\entity_reference\Tests\Views;
use Drupal\simpletest\WebTestBase;
use Drupal\views\Views;
/**
* Tests entity reference selection handler.
*
* @group entity_reference
*/
class SelectionTest extends WebTestBase {
public static $modules = array('node', 'views', 'entity_reference', 'entity_reference_test', 'entity_test');
/**
* Nodes for testing.
*
* @var array
*/
protected $nodes = array();
/**
* The entity reference field to test.
*
* @var \Drupal\Core\Field\FieldDefinitionInterface
*/
protected $field;
/**
* {@inheritdoc}
*/
public function setUp() {
parent::setUp();
// Create nodes.
$type = $this->drupalCreateContentType()->id();
$node1 = $this->drupalCreateNode(array('type' => $type));
$node2 = $this->drupalCreateNode(array('type' => $type));
$node3 = $this->drupalCreateNode();
foreach (array($node1, $node2, $node3) as $node) {
$this->nodes[$node->getType()][$node->id()] = $node->label();
}
// Create a field.
$field_storage = entity_create('field_storage_config', array(
'field_name' => 'test_field',
'entity_type' => 'entity_test',
'translatable' => FALSE,
'settings' => array(
'target_type' => 'node',
),
'type' => 'entity_reference',
'cardinality' => '1',
));
$field_storage->save();
$field = entity_create('field_config', array(
'field_storage' => $field_storage,
'bundle' => 'test_bundle',
'settings' => array(
'handler' => 'views',
'handler_settings' => array(
'view' => array(
'view_name' => 'test_entity_reference',
'display_name' => 'entity_reference_1',
'arguments' => array(),
),
),
),
));
$field->save();
$this->field = $field;
}
/**
* Confirm the expected results are returned.
*
* @param array $result
* Query results keyed by node type and nid.
*/
protected function assertResults(array $result) {
$success = FALSE;
foreach ($result as $node_type => $values) {
foreach ($values as $nid => $label) {
if (!$success = $this->nodes[$node_type][$nid] == trim(strip_tags($label))) {
// There was some error, so break.
break;
}
}
}
$this->assertTrue($success, 'Views selection handler returned expected values.');
}
/**
* Tests the selection handler.
*/
public function testSelectionHandler() {
// Get values from selection handler.
$handler = $this->container->get('plugin.manager.entity_reference_selection')->getSelectionHandler($this->field);
$result = $handler->getReferenceableEntities();
$this->assertResults($result);
}
/**
* Tests the selection handler with a relationship.
*/
public function testSelectionHandlerRelationship() {
// Add a relationship to the view.
$view = Views::getView('test_entity_reference');
$view->setDisplay();
$view->displayHandlers->get('default')->setOption('relationships', array(
'test_relationship' => array(
'id' => 'uid',
'table' => 'node_field_data',
'field' => 'uid',
),
));
// Add a filter depending on the relationship to the test view.
$view->displayHandlers->get('default')->setOption('filters', array(
'uid' => array(
'id' => 'uid',
'table' => 'users_field_data',
'field' => 'uid',
'relationship' => 'test_relationship',
)
));
// Set view to distinct so only one row per node is returned.
$query_options = $view->display_handler->getOption('query');
$query_options['options']['distinct'] = TRUE;
$view->display_handler->setOption('query', $query_options);
$view->save();
// Get values from the selection handler.
$handler = $this->container->get('plugin.manager.entity_reference_selection')->getSelectionHandler($this->field);
$result = $handler->getReferenceableEntities();
$this->assertResults($result);
}
}

View file

@ -0,0 +1,126 @@
langcode: en
status: true
dependencies:
module:
- entity_reference
- entity_reference_test
- node
- user
id: test_entity_reference
label: 'Entity reference'
module: entity_reference_test
description: ''
tag: ''
base_table: node_field_data
base_field: nid
core: '8'
display:
default:
display_plugin: default
id: default
display_title: Master
position: null
display_options:
access:
type: perm
cache:
type: tag
query:
type: views_query
exposed_form:
type: basic
pager:
type: full
style:
type: default
row:
type: fields
fields:
title:
id: title
table: node_field_data
field: title
relationship: none
group_type: group
admin_label: ''
label: ''
exclude: false
alter:
alter_text: false
text: ''
make_link: false
path: ''
absolute: false
external: false
replace_spaces: false
path_case: none
trim_whitespace: false
alt: ''
rel: ''
link_class: ''
prefix: ''
suffix: ''
target: ''
nl2br: false
max_length: 0
word_boundary: false
ellipsis: false
more_link: false
more_link_text: ''
more_link_path: ''
strip_tags: false
trim: false
preserve_tags: ''
html: false
element_type: ''
element_class: ''
element_label_type: ''
element_label_class: ''
element_label_colon: false
element_wrapper_type: ''
element_wrapper_class: ''
element_default_classes: true
empty: ''
hide_empty: false
empty_zero: false
hide_alter_empty: true
plugin_id: field
entity_type: node
entity_field: title
filters:
status:
value: true
table: node_field_data
field: status
id: status
expose:
operator: ''
group: 1
plugin_id: boolean
entity_type: node
entity_field: status
sorts:
created:
id: created
table: node_field_data
field: created
order: DESC
plugin_id: date
entity_type: node
entity_field: created
entity_reference_1:
display_plugin: entity_reference
id: entity_reference_1
display_title: EntityReference
position: null
display_options:
style:
type: entity_reference
options:
grouping: { }
search_fields:
title: title
pager:
type: none
options:
offset: 0

View file

@ -0,0 +1,121 @@
langcode: en
status: true
dependencies:
module:
- entity_reference
- entity_reference_test
- entity_test
id: test_entity_reference_entity_test
label: 'Entity reference'
module: views
description: ''
tag: ''
base_table: entity_test
base_field: id
core: 8.x
display:
default:
display_plugin: default
id: default
display_title: Master
position: 0
display_options:
access:
type: none
cache:
type: tag
query:
type: views_query
exposed_form:
type: basic
pager:
type: full
style:
type: default
row:
type: fields
fields:
name:
table: entity_test
field: name
id: name
entity_type: null
entity_field: name
plugin_id: field
relationship: none
group_type: group
admin_label: ''
label: ''
exclude: false
alter:
alter_text: false
text: ''
make_link: false
path: ''
absolute: false
external: false
replace_spaces: false
path_case: none
trim_whitespace: false
alt: ''
rel: ''
link_class: ''
prefix: ''
suffix: ''
target: ''
nl2br: false
max_length: 0
word_boundary: true
ellipsis: true
more_link: false
more_link_text: ''
more_link_path: ''
strip_tags: false
trim: false
preserve_tags: ''
html: 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_empty: false
empty_zero: false
hide_alter_empty: true
click_sort_column: value
type: string
settings: { }
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: { }
sorts: { }
header: { }
footer: { }
empty: { }
relationships: { }
arguments: { }
display_extenders: { }
entity_reference_1:
display_plugin: entity_reference
id: entity_reference_1
display_title: EntityReference
position: null
display_options:
display_extenders: { }
style:
type: entity_reference
options:
search_fields:
name: name

View file

@ -0,0 +1,12 @@
name: "Entity Reference Test"
type: module
description: "Support module for the Entity Reference tests."
core: 8.x
package: Testing
version: VERSION
dependencies:
- entity_reference
- node
- user
- views
- entity_test

View file

@ -0,0 +1,42 @@
<?php
/**
* @file
* Helper module for the Entity Reference tests.
*/
use Drupal\Core\Entity\EntityTypeInterface;
use Drupal\Core\Field\BaseFieldDefinition;
/**
* Implements hook_entity_base_field_info().
*/
function entity_reference_test_entity_base_field_info(EntityTypeInterface $entity_type) {
$fields = array();
if ($entity_type->id() === 'entity_test') {
$fields['user_role'] = BaseFieldDefinition::create('entity_reference')
->setLabel(t('User role'))
->setDescription(t('The role of the associated user.'))
->setSetting('target_type', 'user_role')
->setSetting('handler', 'default');
}
return $fields;
}
/**
* Implements hook_entity_base_field_info_alter().
*/
function entity_reference_test_entity_base_field_info_alter(&$fields, EntityTypeInterface $entity_type) {
if ($entity_type->id() === 'entity_test') {
// Allow user_id field to use configurable widget.
$fields['user_id']
->setSetting('handler', 'default')
->setDisplayOptions('form', array(
'type' => 'entity_reference_autocomplete',
'weight' => 0,
))
->setDisplayConfigurable('form', TRUE);
}
}

View file

@ -0,0 +1,9 @@
name: 'Entity reference test views'
type: module
description: 'Provides default views for views entity reference tests.'
package: Testing
version: VERSION
core: 8.x
dependencies:
- entity_reference
- views

View file

@ -0,0 +1,120 @@
langcode: en
status: true
dependencies:
module:
- entity_test
id: test_entity_reference_entity_test_mul_view
label: test_entity_reference_entity_test_mul_view
module: views
description: ''
tag: ''
base_table: entity_test_mul_property_data
base_field: id
core: 8.x
display:
default:
display_plugin: default
id: default
display_title: Master
position: 0
display_options:
access:
type: none
options: { }
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:
id:
id: id
table: entity_test_mul_property_data
field: id
entity_type: entity_test_mul
entity_field: id
plugin_id: field
id_1:
id: id_1
table: entity_test
field: id
entity_type: entity_test
entity_field: id
plugin_id: field
relationship: field_data_test
filters: { }
sorts:
id:
id: id
table: entity_test_mul_property_data
field: id
entity_type: entity_test_mul
entity_field: id
plugin_id: standard
header: { }
footer: { }
empty: { }
relationships:
field_data_test:
id: field_data_test
table: entity_test_mul__field_data_test
field: field_data_test
plugin_id: standard
arguments: { }
display_extenders: { }
cache_metadata:
contexts:
- languages
- 'languages:language_interface'
cacheable: false

View file

@ -0,0 +1,121 @@
langcode: en
status: true
dependencies:
module:
- entity_test
id: test_entity_reference_entity_test_view
label: test_entity_reference_entity_test_view
module: views
description: ''
tag: ''
base_table: entity_test
base_field: id
core: 8.x
display:
default:
display_plugin: default
id: default
display_title: Master
position: 0
display_options:
access:
type: none
options: { }
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:
id:
id: id
table: entity_test
field: id
entity_type: entity_test
entity_field: id
plugin_id: field
id_1:
id: id_1
table: entity_test_mul
field: id
entity_type: entity_test_mul
entity_field: id
plugin_id: field
relationship: field_test_data
filters: { }
sorts:
id:
id: id
table: entity_test
field: id
entity_type: entity_test
entity_field: id
plugin_id: standard
header: { }
footer: { }
empty: { }
relationships:
field_test_data:
id: field_test_data
table: entity_test__field_test_data
field: field_test_data
plugin_id: standard
arguments: { }
display_extenders: { }
cache_metadata:
contexts:
- entity_test_view_grants
- languages
- 'languages:language_interface'
cacheable: false

View file

@ -0,0 +1,130 @@
langcode: en
status: true
dependencies:
module:
- entity_test
id: test_entity_reference_reverse_entity_test_mul_view
label: test_entity_reference_reverse_entity_test_mul_view
module: views
description: ''
tag: ''
base_table: entity_test
base_field: id
core: 8.x
display:
default:
display_plugin: default
id: default
display_title: Master
position: 0
display_options:
access:
type: none
options: { }
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:
id:
id: id
table: entity_test
field: id
entity_type: entity_test
entity_field: id
plugin_id: field
id_1:
id: id_1
table: entity_test_mul_property_data
field: id
entity_type: entity_test_mul
entity_field: id
plugin_id: field
relationship: reverse__entity_test_mul__field_data_test
filters: { }
sorts:
id:
id: id
table: entity_test
field: id
entity_type: entity_test
entity_field: id
plugin_id: standard
id_1:
id: id_1
table: entity_test_mul_property_data
field: id
entity_type: entity_test_mul
entity_field: id
plugin_id: standard
relationship: reverse__entity_test_mul__field_data_test
header: { }
footer: { }
empty: { }
relationships:
reverse__entity_test_mul__field_data_test:
id: reverse__entity_test_mul__field_data_test
table: entity_test
field: reverse__entity_test_mul__field_data_test
entity_type: entity_test
plugin_id: entity_reverse
arguments: { }
display_extenders: { }
cache_metadata:
contexts:
- entity_test_view_grants
- languages
- 'languages:language_interface'
cacheable: false

View file

@ -0,0 +1,129 @@
langcode: en
status: true
dependencies:
module:
- entity_test
id: test_entity_reference_reverse_entity_test_view
label: test_entity_reference_reverse_entity_test_view
module: views
description: ''
tag: ''
base_table: entity_test_mul_property_data
base_field: id
core: 8.x
display:
default:
display_plugin: default
id: default
display_title: Master
position: 0
display_options:
access:
type: none
options: { }
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:
id:
id: id
table: entity_test_mul_property_data
field: id
entity_type: entity_test_mul
entity_field: id
plugin_id: field
id_1:
id: id_1
table: entity_test
field: id
entity_type: entity_test
entity_field: id
plugin_id: field
relationship: reverse__entity_test__field_test_data
filters: { }
sorts:
id:
id: id
table: entity_test_mul_property_data
field: id
entity_type: entity_test_mul
entity_field: id
plugin_id: standard
id_1:
id: id_1
table: entity_test
field: id
entity_type: entity_test
entity_field: id
plugin_id: standard
relationship: reverse__entity_test__field_test_data
header: { }
footer: { }
empty: { }
relationships:
reverse__entity_test__field_test_data:
id: reverse__entity_test__field_test_data
table: entity_test_mul_property_data
field: reverse__entity_test__field_test_data
entity_type: entity_test_mul
plugin_id: entity_reverse
arguments: { }
display_extenders: { }
cache_metadata:
contexts:
- languages
- 'languages:language_interface'
cacheable: false