class DefaultSelection

Same name in other branches
  1. 9 core/lib/Drupal/Core/Entity/Plugin/EntityReferenceSelection/DefaultSelection.php \Drupal\Core\Entity\Plugin\EntityReferenceSelection\DefaultSelection
  2. 8.9.x core/lib/Drupal/Core/Entity/Plugin/EntityReferenceSelection/DefaultSelection.php \Drupal\Core\Entity\Plugin\EntityReferenceSelection\DefaultSelection
  3. 10 core/lib/Drupal/Core/Entity/Plugin/EntityReferenceSelection/DefaultSelection.php \Drupal\Core\Entity\Plugin\EntityReferenceSelection\DefaultSelection

Default plugin implementation of the Entity Reference Selection plugin.

Also serves as a base class for specific types of Entity Reference Selection plugins.

Hierarchy

Expanded class hierarchy of DefaultSelection

See also

\Drupal\Core\Entity\EntityReferenceSelection\SelectionPluginManager

\Drupal\Core\Entity\Annotation\EntityReferenceSelection

\Drupal\Core\Entity\EntityReferenceSelection\SelectionInterface

\Drupal\Core\Entity\Plugin\Derivative\DefaultSelectionDeriver

Plugin API

9 files declare their use of DefaultSelection
AllExceptHostEntity.php in core/modules/system/tests/modules/entity_reference_test/src/Plugin/EntityReferenceSelection/AllExceptHostEntity.php
CommentSelection.php in core/modules/comment/src/Plugin/EntityReferenceSelection/CommentSelection.php
FileSelection.php in core/modules/file/src/Plugin/EntityReferenceSelection/FileSelection.php
MediaSelection.php in core/modules/media/src/Plugin/EntityReferenceSelection/MediaSelection.php
NodeSelection.php in core/modules/node/src/Plugin/EntityReferenceSelection/NodeSelection.php

... See full list

File

core/lib/Drupal/Core/Entity/Plugin/EntityReferenceSelection/DefaultSelection.php, line 38

Namespace

Drupal\Core\Entity\Plugin\EntityReferenceSelection
View source
class DefaultSelection extends SelectionPluginBase implements ContainerFactoryPluginInterface, SelectionWithAutocreateInterface {
    
    /**
     * The entity type manager service.
     *
     * @var \Drupal\Core\Entity\EntityTypeManagerInterface
     */
    protected $entityTypeManager;
    
    /**
     * The entity field manager service.
     *
     * @var \Drupal\Core\Entity\EntityFieldManagerInterface
     */
    protected $entityFieldManager;
    
    /**
     * Entity type bundle info service.
     *
     * @var \Drupal\Core\Entity\EntityTypeBundleInfoInterface
     */
    public $entityTypeBundleInfo;
    
    /**
     * The entity repository.
     *
     * @var \Drupal\Core\Entity\EntityRepositoryInterface
     */
    protected $entityRepository;
    
    /**
     * The module handler service.
     *
     * @var \Drupal\Core\Extension\ModuleHandlerInterface
     */
    protected $moduleHandler;
    
    /**
     * The current user.
     *
     * @var \Drupal\Core\Session\AccountInterface
     */
    protected $currentUser;
    
    /**
     * Constructs a new DefaultSelection object.
     *
     * @param array $configuration
     *   A configuration array containing information about the plugin instance.
     * @param string $plugin_id
     *   The plugin ID for the plugin instance.
     * @param mixed $plugin_definition
     *   The plugin implementation definition.
     * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
     *   The entity type manager service.
     * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
     *   The module handler service.
     * @param \Drupal\Core\Session\AccountInterface $current_user
     *   The current user.
     * @param \Drupal\Core\Entity\EntityFieldManagerInterface $entity_field_manager
     *   The entity field manager.
     * @param \Drupal\Core\Entity\EntityTypeBundleInfoInterface $entity_type_bundle_info
     *   The entity type bundle info service.
     * @param \Drupal\Core\Entity\EntityRepositoryInterface $entity_repository
     *   The entity repository.
     */
    public function __construct(array $configuration, $plugin_id, $plugin_definition, EntityTypeManagerInterface $entity_type_manager, ModuleHandlerInterface $module_handler, AccountInterface $current_user, EntityFieldManagerInterface $entity_field_manager, EntityTypeBundleInfoInterface $entity_type_bundle_info, EntityRepositoryInterface $entity_repository) {
        parent::__construct($configuration, $plugin_id, $plugin_definition);
        $this->entityTypeManager = $entity_type_manager;
        $this->moduleHandler = $module_handler;
        $this->currentUser = $current_user;
        $this->entityFieldManager = $entity_field_manager;
        $this->entityTypeBundleInfo = $entity_type_bundle_info;
        $this->entityRepository = $entity_repository;
    }
    
    /**
     * {@inheritdoc}
     */
    public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
        return new static($configuration, $plugin_id, $plugin_definition, $container->get('entity_type.manager'), $container->get('module_handler'), $container->get('current_user'), $container->get('entity_field.manager'), $container->get('entity_type.bundle.info'), $container->get('entity.repository'));
    }
    
    /**
     * {@inheritdoc}
     */
    public function defaultConfiguration() {
        return [
            // For the 'target_bundles' setting, a NULL value is equivalent to "allow
            // entities from any bundle to be referenced" and an empty array value is
            // equivalent to "no entities from any bundle can be referenced". The
            // reason for NULL and an empty array having a different meaning is to
            // correctly handle config updates.
            // For example, in the scenario where a single target bundle is allowed,
            // and that bundle is then deleted, the automatic removal of that bundle
            // from the entity reference field's settings leaves an empty array.
            // This empty array must therefore indicate that no bundles are allowed,
            // as otherwise the field would suddenly allow all bundles.
'target_bundles' => NULL,
            'sort' => [
                'field' => '_none',
                'direction' => 'ASC',
            ],
            'auto_create' => FALSE,
            'auto_create_bundle' => NULL,
        ] + parent::defaultConfiguration();
    }
    
    /**
     * {@inheritdoc}
     */
    public function buildConfigurationForm(array $form, FormStateInterface $form_state) {
        $form = parent::buildConfigurationForm($form, $form_state);
        $configuration = $this->getConfiguration();
        $entity_type_id = $configuration['target_type'];
        $entity_type = $this->entityTypeManager
            ->getDefinition($entity_type_id);
        $bundles = $this->entityTypeBundleInfo
            ->getBundleInfo($entity_type_id);
        $selected_bundles = [];
        if ($entity_type->hasKey('bundle')) {
            $bundle_options = [];
            foreach ($bundles as $bundle_name => $bundle_info) {
                $bundle_options[$bundle_name] = $bundle_info['label'];
            }
            natsort($bundle_options);
            $selected_bundles = array_intersect_key($bundle_options, array_filter((array) $configuration['target_bundles']));
            $form['target_bundles'] = [
                '#type' => 'checkboxes',
                '#title' => $entity_type->getBundleLabel(),
                '#options' => $bundle_options,
                '#default_value' => (array) $configuration['target_bundles'],
                '#required' => TRUE,
                '#size' => 6,
                '#multiple' => TRUE,
                '#element_validate' => [
                    [
                        static::class,
                        'elementValidateFilter',
                    ],
                ],
                // Use a form process callback to build #ajax property properly and also
                // to avoid code duplication.
                // @see \Drupal\Core\Field\Plugin\Field\FieldType\EntityReferenceItem::fieldSettingsAjaxProcess()
'#ajax' => TRUE,
                '#limit_validation_errors' => [],
            ];
            $form['target_bundles_update'] = [
                '#type' => 'submit',
                '#value' => $this->t('Update form'),
                '#limit_validation_errors' => [],
                '#attributes' => [
                    'class' => [
                        'js-hide',
                    ],
                ],
                '#submit' => [
                    [
                        EntityReferenceItem::class,
                        'settingsAjaxSubmit',
                    ],
                ],
                '#element_validate' => [
                    [
                        static::class,
                        'validateTargetBundlesUpdate',
                    ],
                ],
            ];
        }
        else {
            $form['target_bundles'] = [
                '#type' => 'value',
                '#value' => [],
            ];
        }
        $form['target_bundles']['#element_validate'][] = [
            static::class,
            'validateTargetBundles',
        ];
        if ($entity_type->entityClassImplements(FieldableEntityInterface::class)) {
            $options = $entity_type->hasKey('bundle') ? $selected_bundles : $bundles;
            $fields = [];
            foreach (array_keys($options) as $bundle) {
                $bundle_fields = array_filter($this->entityFieldManager
                    ->getFieldDefinitions($entity_type_id, $bundle), function ($field_definition) {
                    return !$field_definition->isComputed();
                });
                foreach ($bundle_fields as $field_name => $field_definition) {
                    
                    /** @var \Drupal\Core\Field\FieldDefinitionInterface $field_definition */
                    $columns = $field_definition->getFieldStorageDefinition()
                        ->getColumns();
                    // If there is more than one column, display them all, otherwise just
                    // display the field label.
                    // @todo Use property labels instead of the column name.
                    if (count($columns) > 1) {
                        foreach ($columns as $column_name => $column_info) {
                            $fields[$field_name . '.' . $column_name] = $this->t('@label (@column)', [
                                '@label' => $field_definition->getLabel(),
                                '@column' => $column_name,
                            ]);
                        }
                    }
                    else {
                        $fields[$field_name] = $this->t('@label', [
                            '@label' => $field_definition->getLabel(),
                        ]);
                    }
                }
            }
            $form['sort']['field'] = [
                '#type' => 'select',
                '#title' => $this->t('Sort by'),
                '#options' => $fields,
                // Use a form process callback to build #ajax property properly and also
                // to avoid code duplication.
                // @see \Drupal\Core\Field\Plugin\Field\FieldType\EntityReferenceItem::fieldSettingsAjaxProcess()
'#ajax' => TRUE,
                '#empty_value' => '_none',
                '#sort_options' => TRUE,
                '#limit_validation_errors' => [],
                '#default_value' => $configuration['sort']['field'],
            ];
            if ($entity_type->hasKey('bundle')) {
                $form['sort']['field']['#states'] = [
                    'visible' => [
                        ':input[name^="settings[handler_settings][target_bundles]["]' => [
                            'checked' => TRUE,
                        ],
                    ],
                ];
            }
            $form['sort']['settings'] = [
                '#type' => 'container',
                '#attributes' => [
                    'class' => [
                        'entity_reference-settings',
                    ],
                ],
                '#process' => [
                    [
                        EntityReferenceItem::class,
                        'formProcessMergeParent',
                    ],
                ],
            ];
            $form['sort']['settings']['direction'] = [
                '#type' => 'select',
                '#title' => $this->t('Sort direction'),
                '#required' => TRUE,
                '#options' => [
                    'ASC' => $this->t('Ascending'),
                    'DESC' => $this->t('Descending'),
                ],
                '#default_value' => $configuration['sort']['direction'],
                '#states' => [
                    'visible' => [
                        ':input[name="settings[handler_settings][sort][field]"]' => [
                            '!value' => '_none',
                        ],
                    ],
                ],
            ];
            if ($entity_type->hasKey('bundle')) {
                $form['sort']['settings']['direction']['#states']['visible'][] = [
                    ':input[name^="settings[handler_settings][target_bundles]["]' => [
                        'checked' => TRUE,
                    ],
                ];
            }
        }
        $form['auto_create'] = [
            '#type' => 'checkbox',
            '#title' => $this->t("Create referenced entities if they don't already exist"),
            '#default_value' => $configuration['auto_create'],
            '#weight' => -2,
        ];
        if ($entity_type->hasKey('bundle')) {
            $form['auto_create_bundle'] = [
                '#type' => 'select',
                '#title' => $this->t('Store new items in'),
                '#options' => $selected_bundles,
                '#default_value' => $configuration['auto_create_bundle'],
                '#access' => count($selected_bundles) > 1,
                '#states' => [
                    'visible' => [
                        ':input[name="settings[handler_settings][auto_create]"]' => [
                            'checked' => TRUE,
                        ],
                    ],
                ],
                '#weight' => -1,
            ];
        }
        return $form;
    }
    
    /**
     * Validates a target_bundles element.
     */
    public static function validateTargetBundles($element, FormStateInterface $form_state, $form) {
        // If no checkboxes were checked for 'target_bundles', store NULL ("all
        // bundles are referenceable") rather than empty array ("no bundle is
        // referenceable" - typically happens when all referenceable bundles have
        // been deleted).
        if ($form_state->getValue($element['#parents']) === []) {
            $form_state->setValueForElement($element, NULL);
        }
    }
    
    /**
     * Validates a target_bundles_update element.
     */
    public static function validateTargetBundlesUpdate($element, FormStateInterface $form_state, $form) {
        // Don't store the 'target_bundles_update' button value into the field
        // config settings.
        $form_state->unsetValue($element['#parents']);
    }
    
    /**
     * 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}
     */
    public function getReferenceableEntities($match = NULL, $match_operator = 'CONTAINS', $limit = 0) {
        $target_type = $this->getConfiguration()['target_type'];
        $query = $this->buildEntityQuery($match, $match_operator);
        if ($limit > 0) {
            $query->range(0, $limit);
        }
        $result = $query->execute();
        if (empty($result)) {
            return [];
        }
        $options = [];
        $entities = $this->entityTypeManager
            ->getStorage($target_type)
            ->loadMultiple($result);
        foreach ($entities as $entity_id => $entity) {
            $bundle = $entity->bundle();
            $options[$bundle][$entity_id] = Html::escape($this->entityRepository
                ->getTranslationFromContext($entity)
                ->label() ?? '');
        }
        return $options;
    }
    
    /**
     * {@inheritdoc}
     */
    public function countReferenceableEntities($match = NULL, $match_operator = 'CONTAINS') {
        $query = $this->buildEntityQuery($match, $match_operator);
        return $query->count()
            ->execute();
    }
    
    /**
     * {@inheritdoc}
     */
    public function validateReferenceableEntities(array $ids) {
        $result = [];
        if ($ids) {
            $target_type = $this->configuration['target_type'];
            $entity_type = $this->entityTypeManager
                ->getDefinition($target_type);
            $query = $this->buildEntityQuery();
            $result = $query->condition($entity_type->getKey('id'), $ids, 'IN')
                ->execute();
        }
        return $result;
    }
    
    /**
     * {@inheritdoc}
     */
    public function createNewEntity($entity_type_id, $bundle, $label, $uid) {
        $entity_type = $this->entityTypeManager
            ->getDefinition($entity_type_id);
        $values = [
            $entity_type->getKey('label') => $label,
        ];
        if ($bundle_key = $entity_type->getKey('bundle')) {
            $values[$bundle_key] = $bundle;
        }
        $entity = $this->entityTypeManager
            ->getStorage($entity_type_id)
            ->create($values);
        if ($entity instanceof EntityOwnerInterface) {
            $entity->setOwnerId($uid);
        }
        return $entity;
    }
    
    /**
     * {@inheritdoc}
     */
    public function validateReferenceableNewEntities(array $entities) {
        return array_filter($entities, function ($entity) {
            $target_bundles = $this->getConfiguration()['target_bundles'];
            if (isset($target_bundles)) {
                return in_array($entity->bundle(), $target_bundles);
            }
            return TRUE;
        });
    }
    
    /**
     * Builds an EntityQuery to get referenceable entities.
     *
     * @param string|null $match
     *   (Optional) Text to match the label against. Defaults to NULL.
     * @param string $match_operator
     *   (Optional) The operation the matching should be done with. Defaults
     *   to "CONTAINS".
     *
     * @return \Drupal\Core\Entity\Query\QueryInterface
     *   The EntityQuery object with the basic conditions and sorting applied to
     *   it.
     */
    protected function buildEntityQuery($match = NULL, $match_operator = 'CONTAINS') {
        $configuration = $this->getConfiguration();
        $target_type = $configuration['target_type'];
        $entity_type = $this->entityTypeManager
            ->getDefinition($target_type);
        $query = $this->entityTypeManager
            ->getStorage($target_type)
            ->getQuery();
        $query->accessCheck(TRUE);
        // If 'target_bundles' is NULL, all bundles are referenceable, no further
        // conditions are needed.
        if (is_array($configuration['target_bundles'])) {
            // If 'target_bundles' is an empty array, no bundle is referenceable,
            // force the query to never return anything and bail out early.
            if ($configuration['target_bundles'] === []) {
                $query->condition($entity_type->getKey('id'), NULL, '=');
                return $query;
            }
            elseif ($entity_type->hasKey('bundle')) {
                $query->condition($entity_type->getKey('bundle'), $configuration['target_bundles'], 'IN');
            }
            else {
                // If 'target_bundle' is set and entity type doesn't support bundles
                // something is wrong.
                $message = \sprintf("Trying to use non-empty 'target_bundle' configuration on entity type '%s' without bundle support.", $entity_type->id());
                throw new UnsupportedEntityTypeDefinitionException($message);
            }
        }
        if (isset($match) && ($label_key = $entity_type->getKey('label'))) {
            $query->condition($label_key, $match, $match_operator);
        }
        // Add entity-access tag.
        $query->addTag($target_type . '_access');
        // Add the Selection handler for system_query_entity_reference_alter().
        $query->addTag('entity_reference');
        $query->addMetaData('entity_reference_selection_handler', $this);
        // Add the sort option.
        if ($configuration['sort']['field'] !== '_none') {
            $query->sort($configuration['sort']['field'], $configuration['sort']['direction']);
        }
        return $query;
    }
    
    /**
     * Helper method: Passes a query to the alteration system again.
     *
     * This allows Entity Reference to add a tag to an existing query so it can
     * ask access control mechanisms to alter it again.
     */
    protected function reAlterQuery(AlterableInterface $query, $tag, $base_table) {
        // Save the old tags and metadata.
        // For some reason, those are public.
        $old_tags = $query->alterTags;
        $old_metadata = $query->alterMetaData;
        $query->alterTags = [
            $tag => TRUE,
        ];
        $query->alterMetaData['base_table'] = $base_table;
        $this->moduleHandler
            ->alter([
            'query',
            'query_' . $tag,
        ], $query);
        // Restore the tags and metadata.
        $query->alterTags = $old_tags;
        $query->alterMetaData = $old_metadata;
    }

}

Members

Title Sort descending Modifiers Object type Summary Overriden Title Overrides
DefaultSelection::$currentUser protected property The current user.
DefaultSelection::$entityFieldManager protected property The entity field manager service.
DefaultSelection::$entityRepository protected property The entity repository.
DefaultSelection::$entityTypeBundleInfo public property Entity type bundle info service.
DefaultSelection::$entityTypeManager protected property The entity type manager service.
DefaultSelection::$moduleHandler protected property The module handler service.
DefaultSelection::buildConfigurationForm public function Form constructor. Overrides SelectionPluginBase::buildConfigurationForm 3
DefaultSelection::buildEntityQuery protected function Builds an EntityQuery to get referenceable entities. 8
DefaultSelection::countReferenceableEntities public function Counts entities that are referenceable. Overrides SelectionInterface::countReferenceableEntities 3
DefaultSelection::create public static function Creates an instance of the plugin. Overrides ContainerFactoryPluginInterface::create 2
DefaultSelection::createNewEntity public function Creates a new entity object that can be used as a valid reference. Overrides SelectionWithAutocreateInterface::createNewEntity 6
DefaultSelection::defaultConfiguration public function Gets default configuration for this plugin. Overrides SelectionPluginBase::defaultConfiguration 3
DefaultSelection::elementValidateFilter public static function Form element validation handler; Filters the #value property of an element.
DefaultSelection::getReferenceableEntities public function Gets the list of referenceable entities. Overrides SelectionInterface::getReferenceableEntities 4
DefaultSelection::reAlterQuery protected function Helper method: Passes a query to the alteration system again.
DefaultSelection::validateReferenceableEntities public function Validates which existing entities can be referenced. Overrides SelectionInterface::validateReferenceableEntities 1
DefaultSelection::validateReferenceableNewEntities public function Validates which newly created entities can be referenced. Overrides SelectionWithAutocreateInterface::validateReferenceableNewEntities 6
DefaultSelection::validateTargetBundles public static function Validates a target_bundles element.
DefaultSelection::validateTargetBundlesUpdate public static function Validates a target_bundles_update element.
DefaultSelection::__construct public function Constructs a new DefaultSelection object. Overrides SelectionPluginBase::__construct 1
SelectionPluginBase::calculateDependencies public function Calculates dependencies for the configured plugin. Overrides DependentPluginInterface::calculateDependencies
SelectionPluginBase::entityQueryAlter public function Allows the selection to alter the SelectQuery generated by EntityFieldQuery. Overrides SelectionInterface::entityQueryAlter 2
SelectionPluginBase::getConfiguration public function Gets this plugin's configuration. Overrides ConfigurableInterface::getConfiguration
SelectionPluginBase::setConfiguration public function Sets the configuration for this plugin instance. Overrides ConfigurableInterface::setConfiguration
SelectionPluginBase::submitConfigurationForm public function Form submission handler. Overrides PluginFormInterface::submitConfigurationForm
SelectionPluginBase::validateConfigurationForm public function Form validation handler. Overrides PluginFormInterface::validateConfigurationForm

Buggy or inaccurate documentation? Please file an issue. Need support? Need help programming? Connect with the Drupal community.