Commit 97823b5a authored by alexpott's avatar alexpott
Browse files

Issue #2429191 by claudiu.cristea, amateescu, yched, nlisgo, Berdir, alexpott,...

Issue #2429191 by claudiu.cristea, amateescu, yched, nlisgo, Berdir, alexpott, klausi, Wim Leers, xjm, catch: Deprecate entity_reference.module and move its functionality to core
parent 02bd75b1
......@@ -323,10 +323,6 @@ Email module
Editor module
- Wim Leers 'Wim Leers' https://www.drupal.org/u/wim-leers
Entity Reference module
- Amitai Burstein 'Amitaibu' https://www.drupal.org/u/amitaibu
- Andrei Mateescu 'amateescu' https://www.drupal.org/u/amateescu
Field UI module
- Yves Chedemois 'yched' https://www.drupal.org/u/yched
- Kristof De Jaeger 'swentel' https://www.drupal.org/u/swentel
......
......@@ -12,6 +12,7 @@
use Drupal\Core\Database\Query\SelectInterface;
use Drupal\Core\Entity\EntityManagerInterface;
use Drupal\Core\Extension\ModuleHandlerInterface;
use Drupal\Core\Field\Plugin\Field\FieldType\EntityReferenceItem;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Entity\EntityReferenceSelection\SelectionInterface;
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
......@@ -135,7 +136,7 @@ public function buildConfigurationForm(array $form, FormStateInterface $form_sta
'#required' => TRUE,
'#size' => 6,
'#multiple' => TRUE,
'#element_validate' => array('_entity_reference_element_validate_filter'),
'#element_validate' => [[get_class($this), 'elementValidateFilter']],
);
}
else {
......@@ -182,7 +183,7 @@ public function buildConfigurationForm(array $form, FormStateInterface $form_sta
$form['sort']['settings'] = array(
'#type' => 'container',
'#attributes' => array('class' => array('entity_reference-settings')),
'#process' => array('_entity_reference_form_process_merge_parent'),
'#process' => [[EntityReferenceItem::class, 'formProcessMergeParent']],
);
if ($selection_handler_settings['sort']['field'] != '_none') {
......@@ -225,6 +226,14 @@ public function validateConfigurationForm(array &$form, FormStateInterface $form
*/
public function submitConfigurationForm(array &$form, FormStateInterface $form_state) { }
/**
* Form element validation handler; Filters the #value property of an element.
*/
public static function elementValidateFilter(&$element, FormStateInterface $form_state) {
$element['#value'] = array_filter($element['#value']);
$form_state->setValueForElement($element, $element['#value']);
}
/**
* {@inheritdoc}
*/
......@@ -320,8 +329,7 @@ protected function buildEntityQuery($match = NULL, $match_operator = 'CONTAINS')
// Add entity-access tag.
$query->addTag($target_type . '_access');
// Add the Selection handler for
// entity_reference_query_entity_reference_alter().
// Add the Selection handler for system_query_entity_reference_alter().
$query->addTag('entity_reference');
$query->addMetaData('entity_reference_selection_handler', $this);
......
......@@ -7,15 +7,24 @@
namespace Drupal\Core\Field\Plugin\Field\FieldType;
use Drupal\Component\Utility\Html;
use Drupal\Component\Utility\NestedArray;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Entity\EntityTypeInterface;
use Drupal\Core\Entity\TypedData\EntityDataDefinition;
use Drupal\Core\Field\FieldDefinitionInterface;
use Drupal\Core\Field\FieldItemBase;
use Drupal\Core\Field\FieldStorageDefinitionInterface;
use Drupal\Core\Field\PreconfiguredFieldUiOptionsInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Form\OptGroup;
use Drupal\Core\Render\Element;
use Drupal\Core\Session\AccountInterface;
use Drupal\Core\StringTranslation\TranslatableMarkup;
use Drupal\Core\TypedData\DataReferenceDefinition;
use Drupal\Core\TypedData\DataReferenceTargetDefinition;
use Drupal\Core\TypedData\OptionsProviderInterface;
use Drupal\Core\Validation\Plugin\Validation\Constraint\AllowedValuesConstraint;
/**
* Defines the 'entity_reference' entity field type.
......@@ -28,14 +37,15 @@
* label = @Translation("Entity reference"),
* description = @Translation("An entity field containing an entity reference."),
* category = @Translation("Reference"),
* no_ui = TRUE,
* default_widget = "entity_reference_autocomplete",
* default_formatter = "entity_reference_label",
* list_class = "\Drupal\Core\Field\EntityReferenceFieldItemList",
* default_widget = "entity_reference_autocomplete",
* default_formatter = "entity_reference_label",
* constraints = {"ValidReference" = {}}
* )
*/
class EntityReferenceItem extends FieldItemBase {
class EntityReferenceItem extends FieldItemBase implements OptionsProviderInterface, PreconfiguredFieldUiOptionsInterface {
/**
* {@inheritdoc}
......@@ -148,6 +158,13 @@ public static function schema(FieldStorageDefinitionInterface $field_definition)
*/
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]);
}
}
list($current_handler) = explode(':', $this->getSetting('handler'), 2);
if ($current_handler === 'default') {
$handler_settings = $this->getSetting('handler_settings');
......@@ -280,6 +297,103 @@ public static function generateSampleValue(FieldDefinitionInterface $field_defin
}
}
/**
* {@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', ...) and not
// entity type specific plugins (e.g. 'default:node', 'default:user',
// ...).
if (array_key_exists($selection_group_id, $selection_plugins[$selection_group_id])) {
$handlers_options[$selection_group_id] = Html::escape($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] = Html::escape($selection_plugins[$selection_group_id][$selection_group_plugin]['base_plugin_label']);
}
}
$form = array(
'#type' => 'container',
'#process' => array(array(get_class($this), 'fieldSettingsAjaxProcess')),
'#element_validate' => array(array(get_class($this), 'fieldSettingsFormValidate')),
);
$form['handler'] = array(
'#type' => 'details',
'#title' => t('Reference type'),
'#open' => TRUE,
'#tree' => TRUE,
'#process' => array(array(get_class($this), 'formProcessMergeParent')),
);
$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(array(get_class($this), 'settingsAjaxSubmit')),
);
$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);
}
/**
* Determines whether the item holds an unsaved entity.
*
......@@ -393,4 +507,143 @@ public static function onDependencyRemoval(FieldDefinitionInterface $field_defin
return $changed;
}
/**
* {@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) {
// The label does not need sanitizing since it is used as an optgroup
// which is only supported by select elements and auto-escaped.
$bundle_label = (string) $bundles[$bundle]['label'];
$return[$bundle_label] = $entity_ids;
}
return count($return) == 1 ? reset($return) : $return;
}
/**
* Render API callback: Processes the field settings form and allows access to
* the form state.
*
* @see static::fieldSettingsForm()
*/
public static function fieldSettingsAjaxProcess($form, FormStateInterface $form_state) {
static::fieldSettingsAjaxProcessElement($form, $form);
return $form;
}
/**
* Adds entity_reference specific properties to AJAX form elements from the
* field settings form.
*
* @see static::fieldSettingsAjaxProcess()
*/
public static function fieldSettingsAjaxProcessElement(&$element, $main_form) {
if (!empty($element['#ajax'])) {
$element['#ajax'] = array(
'callback' => array(get_called_class(), 'settingsAjax'),
'wrapper' => $main_form['#id'],
'element' => $main_form['#array_parents'],
);
}
foreach (Element::children($element) as $key) {
static::fieldSettingsAjaxProcessElement($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()
*/
public static function formProcessMergeParent($element) {
$parents = $element['#parents'];
array_pop($parents);
$element['#parents'] = $parents;
return $element;
}
/**
* Ajax callback for the handler settings form.
*
* @see static::fieldSettingsForm()
*/
public static function settingsAjax($form, FormStateInterface $form_state) {
return NestedArray::getValue($form, $form_state->getTriggeringElement()['#ajax']['element']);
}
/**
* Submit handler for the non-JS case.
*
* @see static::fieldSettingsForm()
*/
public static function settingsAjaxSubmit($form, FormStateInterface $form_state) {
$form_state->setRebuild();
}
/**
* {@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;
}
}
......@@ -18,6 +18,7 @@
* label = @Translation("Check boxes/radio buttons"),
* field_types = {
* "boolean",
* "entity_reference",
* "list_integer",
* "list_float",
* "list_string",
......
......@@ -18,6 +18,7 @@
* id = "options_select",
* label = @Translation("Select list"),
* field_types = {
* "entity_reference",
* "list_integer",
* "list_float",
* "list_string"
......
......@@ -6,6 +6,5 @@ version: VERSION
core: 8.x
configure: aggregator.admin_settings
dependencies:
- entity_reference
- file
- options
......@@ -5,7 +5,6 @@ dependencies:
- core.entity_view_mode.aggregator_item.summary
module:
- aggregator
- entity_reference
id: aggregator_item.aggregator_item.summary
targetEntityType: aggregator_item
bundle: aggregator_item
......
......@@ -24,7 +24,7 @@ class AggregatorTitleTest extends KernelTestBase {
*
* @var array
*/
public static $modules = array('file', 'field', 'options', 'aggregator', 'entity_reference');
public static $modules = ['file', 'field', 'options', 'aggregator'];
/**
* The field name that is tested.
......
......@@ -5,7 +5,6 @@ dependencies:
- field.field.node.book.body
- node.type.book
module:
- entity_reference
- text
id: node.book.default
targetEntityType: node
......
......@@ -23,7 +23,7 @@ class BookUninstallTest extends KernelTestBase {
*
* @var array
*/
public static $modules = array('system', 'user', 'field', 'filter', 'text', 'entity_reference', 'node', 'book');
public static $modules = ['system', 'user', 'field', 'filter', 'text', 'node', 'book'];
/**
* {@inheritdoc}
......
......@@ -32,7 +32,7 @@ class ConfigImportRecreateTest extends KernelTestBase {
*
* @var array
*/
public static $modules = array('system', 'field', 'text', 'user', 'node', 'entity_reference');
public static $modules = ['system', 'field', 'text', 'user', 'node'];
protected function setUp() {
parent::setUp();
......
......@@ -34,7 +34,7 @@ class ConfigImportRenameValidationTest extends KernelTestBase {
*
* @var array
*/
public static $modules = array('system', 'user', 'node', 'field', 'text', 'config_test', 'entity_reference');
public static $modules = ['system', 'user', 'node', 'field', 'text', 'config_test'];
/**
* {@inheritdoc}
......
......@@ -589,7 +589,7 @@ public function testUnmetDependency() {
$error_log = $this->configImporter->getErrors();
$expected = [
'Unable to install the <em class="placeholder">unknown_module</em> module since it does not exist.',
'Unable to install the <em class="placeholder">Book</em> module since it requires the <em class="placeholder">Node, Text, Field, Filter, User, Entity Reference</em> modules.',
'Unable to install the <em class="placeholder">Book</em> module since it requires the <em class="placeholder">Node, Text, Field, Filter, User</em> modules.',
'Unable to install the <em class="placeholder">unknown_theme</em> theme since it does not exist.',
'Unable to install the <em class="placeholder">Bartik</em> theme since it requires the <em class="placeholder">Classy</em> theme.',
'Configuration <em class="placeholder">config_test.dynamic.dotted.config</em> depends on the <em class="placeholder">unknown</em> configuration that will not exist after import.',
......
# 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'
name: 'Entity Reference'
type: module
description: 'Provides a field that can reference other entities.'
description: 'Deprecated. All the functionality has been moved to Core.'
package: Field types
version: VERSION
core: 8.x
dependencies:
- field
hidden: true
......@@ -2,205 +2,6 @@
/**
* @file
* Provides a field that can reference other entities.
* Deprecated. All its functionality has been moved to Core. This empty module
* will be removed in Drupal 9.0.x.
*/
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\FieldConfig;
/**
* 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 the <a href=":er_do">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_form_FORM_ID_alter() for 'field_ui_field_storage_add_form'.
*/
function entity_reference_form_field_ui_field_storage_add_form_alter(array &$form) {
$optgroup = (string) t('Reference');
// Move the "Entity reference" option to the end of the list and rename it to
// "Other".
unset($form['add']['new_storage_type']['#options'][$optgroup]['entity_reference']);
$form['add']['new_storage_type']['#options'][$optgroup]['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_setti