View source
<?php
namespace Drupal\Core\Field\Plugin\Field\FieldType;
use Drupal\Component\Utility\Html;
use Drupal\Component\Utility\NestedArray;
use Drupal\Core\Entity\ContentEntityStorageInterface;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Entity\EntityTypeInterface;
use Drupal\Core\Entity\FieldableEntityInterface;
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;
class EntityReferenceItem extends FieldItemBase implements OptionsProviderInterface, PreconfiguredFieldUiOptionsInterface {
public static function defaultStorageSettings() {
return [
'target_type' => \Drupal::moduleHandler()
->moduleExists('node') ? 'node' : 'user',
] + parent::defaultStorageSettings();
}
public static function defaultFieldSettings() {
return [
'handler' => 'default',
'handler_settings' => [],
] + parent::defaultFieldSettings();
}
public static function propertyDefinitions(FieldStorageDefinitionInterface $field_definition) {
$settings = $field_definition
->getSettings();
$target_type_info = \Drupal::entityTypeManager()
->getDefinition($settings['target_type']);
$target_id_data_type = 'string';
if ($target_type_info
->entityClassImplements(FieldableEntityInterface::class)) {
$id_definition = \Drupal::service('entity_field.manager')
->getBaseFieldDefinitions($settings['target_type'])[$target_type_info
->getKey('id')];
if ($id_definition
->getType() === 'integer') {
$target_id_data_type = 'integer';
}
}
if ($target_id_data_type === 'integer') {
$target_id_definition = DataReferenceTargetDefinition::create('integer')
->setLabel(new TranslatableMarkup('@label ID', [
'@label' => $target_type_info
->getLabel(),
]))
->setSetting('unsigned', TRUE);
}
else {
$target_id_definition = DataReferenceTargetDefinition::create('string')
->setLabel(new TranslatableMarkup('@label ID', [
'@label' => $target_type_info
->getLabel(),
]));
}
$target_id_definition
->setRequired(TRUE);
$properties['target_id'] = $target_id_definition;
$properties['entity'] = DataReferenceDefinition::create('entity')
->setLabel($target_type_info
->getLabel())
->setDescription(new TranslatableMarkup('The referenced entity'))
->setComputed(TRUE)
->setReadOnly(FALSE)
->setTargetDefinition(EntityDataDefinition::create($settings['target_type']))
->addConstraint('EntityType', $settings['target_type']);
return $properties;
}
public static function mainPropertyName() {
return 'target_id';
}
public static function schema(FieldStorageDefinitionInterface $field_definition) {
$target_type = $field_definition
->getSetting('target_type');
$target_type_info = \Drupal::entityTypeManager()
->getDefinition($target_type);
$properties = static::propertyDefinitions($field_definition)['target_id'];
if ($target_type_info
->entityClassImplements(FieldableEntityInterface::class) && $properties
->getDataType() === 'integer') {
$columns = [
'target_id' => [
'description' => 'The ID of the target entity.',
'type' => 'int',
'unsigned' => TRUE,
],
];
}
else {
$columns = [
'target_id' => [
'description' => 'The ID of the target entity.',
'type' => 'varchar_ascii',
'length' => $target_type_info
->getBundleOf() ? EntityTypeInterface::BUNDLE_MAX_LENGTH : 255,
],
];
}
$schema = [
'columns' => $columns,
'indexes' => [
'target_id' => [
'target_id',
],
],
];
return $schema;
}
public function getConstraints() {
$constraints = parent::getConstraints();
foreach ($constraints as $key => $constraint) {
if ($constraint instanceof AllowedValuesConstraint) {
unset($constraints[$key]);
}
}
return $constraints;
}
public function setValue($values, $notify = TRUE) {
if (isset($values) && !is_array($values)) {
$this
->set('entity', $values, $notify);
}
else {
parent::setValue($values, FALSE);
if (is_array($values) && array_key_exists('target_id', $values) && !isset($values['entity'])) {
$this
->onChange('target_id', FALSE);
}
elseif (is_array($values) && !array_key_exists('target_id', $values) && isset($values['entity'])) {
$this
->onChange('entity', FALSE);
}
elseif (is_array($values) && array_key_exists('target_id', $values) && isset($values['entity'])) {
$entity_id = $this
->get('entity')
->getTargetIdentifier();
if (!$this->entity
->isNew() && $values['target_id'] !== NULL && $entity_id != $values['target_id']) {
throw new \InvalidArgumentException('The target id and entity passed to the entity reference item do not match.');
}
}
if ($notify && $this->parent) {
$this->parent
->onChange($this
->getName());
}
}
}
public function getValue() {
$values = parent::getValue();
if ($this
->hasNewEntity()) {
$values['entity'] = $this->entity;
}
return $values;
}
public function onChange($property_name, $notify = TRUE) {
if ($property_name == 'entity') {
$property = $this
->get('entity');
$target_id = $property
->isTargetNew() ? NULL : $property
->getTargetIdentifier();
$this
->writePropertyValue('target_id', $target_id);
}
elseif ($property_name == 'target_id') {
$this
->writePropertyValue('entity', $this->target_id);
}
parent::onChange($property_name, $notify);
}
public function isEmpty() {
if ($this->target_id !== NULL) {
return FALSE;
}
if ($this->entity && $this->entity instanceof EntityInterface) {
return FALSE;
}
return TRUE;
}
public function preSave() {
if ($this
->hasNewEntity()) {
if ($this->entity
->isNew()) {
$this->entity
->save();
}
$this->target_id = $this->entity
->id();
}
if (!$this
->isEmpty() && $this->target_id === NULL) {
$this->target_id = $this->entity
->id();
}
}
public static function generateSampleValue(FieldDefinitionInterface $field_definition) {
static $recursion_tracker = [];
$manager = \Drupal::service('plugin.manager.entity_reference_selection');
$options = [
'target_type' => $field_definition
->getFieldStorageDefinition()
->getSetting('target_type'),
'handler' => $field_definition
->getSetting('handler'),
'entity' => NULL,
] + $field_definition
->getSetting('handler_settings') ?: [];
$entity_type = \Drupal::entityTypeManager()
->getDefinition($options['target_type']);
$options['sort'] = [
'field' => $entity_type
->getKey('id'),
'direction' => 'DESC',
];
$selection_handler = $manager
->getInstance($options);
if ($referenceable = $selection_handler
->getReferenceableEntities(NULL, 'CONTAINS', 50)) {
$group = array_rand($referenceable);
$values['target_id'] = array_rand($referenceable[$group]);
return $values;
}
$entity_storage = \Drupal::entityTypeManager()
->getStorage($options['target_type']);
if ($entity_storage instanceof ContentEntityStorageInterface) {
$bundle = static::getRandomBundle($entity_type, $options);
$key = $field_definition
->getTargetEntityTypeId() . ':' . $options['target_type'] . ':' . $bundle;
if (isset($recursion_tracker[$key])) {
return [];
}
$recursion_tracker[$key] = TRUE;
$values['entity'] = $entity_storage
->createWithSampleValues($bundle, [
'in_preview' => TRUE,
]);
unset($recursion_tracker[$key]);
return $values;
}
}
protected static function getRandomBundle(EntityTypeInterface $entity_type, array $selection_settings) {
if ($bundle_key = $entity_type
->getKey('bundle')) {
if (!empty($selection_settings['target_bundles'])) {
$bundle_ids = $selection_settings['target_bundles'];
}
else {
$bundle_ids = \Drupal::service('entity_type.bundle.info')
->getBundleInfo($entity_type
->id());
}
return array_rand($bundle_ids);
}
}
public function storageSettingsForm(array &$form, FormStateInterface $form_state, $has_data) {
$element['target_type'] = [
'#type' => 'select',
'#title' => t('Type of item to reference'),
'#options' => \Drupal::service('entity_type.repository')
->getEntityTypeLabels(TRUE),
'#default_value' => $this
->getSetting('target_type'),
'#required' => TRUE,
'#disabled' => $has_data,
'#size' => 1,
];
return $element;
}
public function fieldSettingsForm(array $form, FormStateInterface $form_state) {
$field = $form_state
->getFormObject()
->getEntity();
$selection_plugins = \Drupal::service('plugin.manager.entity_reference_selection')
->getSelectionGroups($this
->getSetting('target_type'));
$handlers_options = [];
foreach (array_keys($selection_plugins) as $selection_group_id) {
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 = [
'#type' => 'container',
'#process' => [
[
get_class($this),
'fieldSettingsAjaxProcess',
],
],
'#element_validate' => [
[
get_class($this),
'fieldSettingsFormValidate',
],
],
];
$form['handler'] = [
'#type' => 'details',
'#title' => t('Reference type'),
'#open' => TRUE,
'#tree' => TRUE,
'#process' => [
[
get_class($this),
'formProcessMergeParent',
],
],
];
$form['handler']['handler'] = [
'#type' => 'select',
'#title' => t('Reference method'),
'#options' => $handlers_options,
'#default_value' => $field
->getSetting('handler'),
'#required' => TRUE,
'#ajax' => TRUE,
'#limit_validation_errors' => [],
];
$form['handler']['handler_submit'] = [
'#type' => 'submit',
'#value' => t('Change handler'),
'#limit_validation_errors' => [],
'#attributes' => [
'class' => [
'js-hide',
],
],
'#submit' => [
[
get_class($this),
'settingsAjaxSubmit',
],
],
];
$form['handler']['handler_settings'] = [
'#type' => 'container',
'#attributes' => [
'class' => [
'entity_reference-settings',
],
],
];
$handler = \Drupal::service('plugin.manager.entity_reference_selection')
->getSelectionHandler($field);
$form['handler']['handler_settings'] += $handler
->buildConfigurationForm([], $form_state);
return $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);
}
public function hasNewEntity() {
return !$this
->isEmpty() && $this->target_id === NULL && $this->entity
->isNew();
}
public static function calculateDependencies(FieldDefinitionInterface $field_definition) {
$dependencies = parent::calculateDependencies($field_definition);
$entity_type_manager = \Drupal::entityTypeManager();
$target_entity_type = $entity_type_manager
->getDefinition($field_definition
->getFieldStorageDefinition()
->getSetting('target_type'));
if ($default_value = $field_definition
->getDefaultValueLiteral()) {
$entity_repository = \Drupal::service('entity.repository');
foreach ($default_value as $value) {
if (is_array($value) && isset($value['target_uuid'])) {
$entity = $entity_repository
->loadEntityByUuid($target_entity_type
->id(), $value['target_uuid']);
if ($entity) {
$dependencies[$target_entity_type
->getConfigDependencyKey()][] = $entity
->getConfigDependencyName();
}
}
}
}
$handler = $field_definition
->getSetting('handler_settings');
if (!empty($handler['target_bundles'])) {
if ($bundle_entity_type_id = $target_entity_type
->getBundleEntityType()) {
if ($storage = $entity_type_manager
->getStorage($bundle_entity_type_id)) {
foreach ($storage
->loadMultiple($handler['target_bundles']) as $bundle) {
$dependencies[$bundle
->getConfigDependencyKey()][] = $bundle
->getConfigDependencyName();
}
}
}
}
return $dependencies;
}
public static function calculateStorageDependencies(FieldStorageDefinitionInterface $field_definition) {
$dependencies = parent::calculateStorageDependencies($field_definition);
$target_entity_type = \Drupal::entityTypeManager()
->getDefinition($field_definition
->getSetting('target_type'));
$dependencies['module'][] = $target_entity_type
->getProvider();
return $dependencies;
}
public static function onDependencyRemoval(FieldDefinitionInterface $field_definition, array $dependencies) {
$changed = parent::onDependencyRemoval($field_definition, $dependencies);
$entity_type_manager = \Drupal::entityTypeManager();
$target_entity_type = $entity_type_manager
->getDefinition($field_definition
->getFieldStorageDefinition()
->getSetting('target_type'));
if ($default_value = $field_definition
->getDefaultValueLiteral()) {
$entity_repository = \Drupal::service('entity.repository');
foreach ($default_value as $key => $value) {
if (is_array($value) && isset($value['target_uuid'])) {
$entity = $entity_repository
->loadEntityByUuid($target_entity_type
->id(), $value['target_uuid']);
if ($entity && isset($dependencies[$entity
->getConfigDependencyKey()][$entity
->getConfigDependencyName()])) {
unset($default_value[$key]);
$changed = TRUE;
}
}
}
if ($changed) {
$field_definition
->setDefaultValue($default_value);
}
}
$bundles_changed = FALSE;
$handler_settings = $field_definition
->getSetting('handler_settings');
if (!empty($handler_settings['target_bundles'])) {
if ($bundle_entity_type_id = $target_entity_type
->getBundleEntityType()) {
if ($storage = $entity_type_manager
->getStorage($bundle_entity_type_id)) {
foreach ($storage
->loadMultiple($handler_settings['target_bundles']) as $bundle) {
if (isset($dependencies[$bundle
->getConfigDependencyKey()][$bundle
->getConfigDependencyName()])) {
unset($handler_settings['target_bundles'][$bundle
->id()]);
$auto_create_bundle = !empty($handler_settings['auto_create_bundle']) ? $handler_settings['auto_create_bundle'] : FALSE;
if ($auto_create_bundle && $auto_create_bundle == $bundle
->id()) {
$handler_settings['auto_create'] = FALSE;
$handler_settings['auto_create_bundle'] = NULL;
}
$bundles_changed = TRUE;
}
}
}
}
}
if ($bundles_changed) {
$field_definition
->setSetting('handler_settings', $handler_settings);
}
$changed |= $bundles_changed;
return $changed;
}
public function getPossibleValues(AccountInterface $account = NULL) {
return $this
->getSettableValues($account);
}
public function getPossibleOptions(AccountInterface $account = NULL) {
return $this
->getSettableOptions($account);
}
public function getSettableValues(AccountInterface $account = NULL) {
$flatten_options = OptGroup::flattenOptions($this
->getSettableOptions($account));
return array_keys($flatten_options);
}
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 [];
}
$target_type = $field_definition
->getSetting('target_type');
$bundles = \Drupal::service('entity_type.bundle.info')
->getBundleInfo($target_type);
$return = [];
foreach ($options as $bundle => $entity_ids) {
$bundle_label = (string) $bundles[$bundle]['label'];
$return[$bundle_label] = $entity_ids;
}
return count($return) == 1 ? reset($return) : $return;
}
public static function fieldSettingsAjaxProcess($form, FormStateInterface $form_state) {
static::fieldSettingsAjaxProcessElement($form, $form);
return $form;
}
public static function fieldSettingsAjaxProcessElement(&$element, $main_form) {
if (!empty($element['#ajax'])) {
$element['#ajax'] = [
'callback' => [
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);
}
}
public static function formProcessMergeParent($element) {
$parents = $element['#parents'];
array_pop($parents);
$element['#parents'] = $parents;
return $element;
}
public static function settingsAjax($form, FormStateInterface $form_state) {
return NestedArray::getValue($form, $form_state
->getTriggeringElement()['#ajax']['element']);
}
public static function settingsAjaxSubmit($form, FormStateInterface $form_state) {
$form_state
->setRebuild();
}
public static function getPreconfiguredOptions() {
$options = [];
$entity_types = \Drupal::entityTypeManager()
->getDefinitions();
$common_references = array_filter($entity_types, function (EntityTypeInterface $entity_type) {
return $entity_type
->isCommonReferenceTarget();
});
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;
}
}