123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503 |
- <?php
- namespace Drupal\Core\Entity\Plugin\EntityReferenceSelection;
- use Drupal\Component\Utility\Html;
- use Drupal\Core\Database\Query\AlterableInterface;
- use Drupal\Core\DependencyInjection\DeprecatedServicePropertyTrait;
- use Drupal\Core\Entity\EntityFieldManagerInterface;
- use Drupal\Core\Entity\EntityReferenceSelection\SelectionPluginBase;
- use Drupal\Core\Entity\EntityReferenceSelection\SelectionWithAutocreateInterface;
- use Drupal\Core\Entity\EntityRepositoryInterface;
- use Drupal\Core\Entity\EntityTypeBundleInfoInterface;
- use Drupal\Core\Entity\EntityTypeManagerInterface;
- use Drupal\Core\Entity\FieldableEntityInterface;
- use Drupal\Core\Extension\ModuleHandlerInterface;
- use Drupal\Core\Field\Plugin\Field\FieldType\EntityReferenceItem;
- use Drupal\Core\Form\FormStateInterface;
- use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
- use Drupal\Core\Session\AccountInterface;
- use Drupal\user\EntityOwnerInterface;
- use Symfony\Component\DependencyInjection\ContainerInterface;
- /**
- * Default plugin implementation of the Entity Reference Selection plugin.
- *
- * Also serves as a base class for specific types of Entity Reference
- * Selection plugins.
- *
- * @see \Drupal\Core\Entity\EntityReferenceSelection\SelectionPluginManager
- * @see \Drupal\Core\Entity\Annotation\EntityReferenceSelection
- * @see \Drupal\Core\Entity\EntityReferenceSelection\SelectionInterface
- * @see \Drupal\Core\Entity\Plugin\Derivative\DefaultSelectionDeriver
- * @see plugin_api
- *
- * @EntityReferenceSelection(
- * id = "default",
- * label = @Translation("Default"),
- * group = "default",
- * weight = 0,
- * deriver = "Drupal\Core\Entity\Plugin\Derivative\DefaultSelectionDeriver"
- * )
- */
- class DefaultSelection extends SelectionPluginBase implements ContainerFactoryPluginInterface, SelectionWithAutocreateInterface {
- use DeprecatedServicePropertyTrait;
- /**
- * {@inheritdoc}
- */
- protected $deprecatedProperties = ['entityManager' => 'entity.manager'];
- /**
- * 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 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 = NULL, EntityTypeBundleInfoInterface $entity_type_bundle_info = NULL, EntityRepositoryInterface $entity_repository = NULL) {
- parent::__construct($configuration, $plugin_id, $plugin_definition);
- $this->entityTypeManager = $entity_type_manager;
- $this->moduleHandler = $module_handler;
- $this->currentUser = $current_user;
- if (!$entity_field_manager) {
- @trigger_error('Calling DefaultSelection::__construct() with the $entity_field_manager argument is supported in drupal:8.7.0 and will be required before drupal:9.0.0. See https://www.drupal.org/node/2549139.', E_USER_DEPRECATED);
- $entity_field_manager = \Drupal::service('entity_field.manager');
- }
- $this->entityFieldManager = $entity_field_manager;
- if (!$entity_type_bundle_info) {
- @trigger_error('Calling DefaultSelection::__construct() with the $entity_type_bundle_info argument is supported in drupal:8.7.0 and will be required before drupal:9.0.0. See https://www.drupal.org/node/2549139.', E_USER_DEPRECATED);
- $entity_type_bundle_info = \Drupal::service('entity_type.bundle.info');
- }
- $this->entityTypeBundleInfo = $entity_type_bundle_info;
- if (!$entity_repository) {
- @trigger_error('Calling DefaultSelection::__construct() with the $entity_repository argument is supported in drupal:8.7.0 and will be required before drupal:9.0.0. See https://www.drupal.org/node/2549139.', E_USER_DEPRECATED);
- $entity_repository = \Drupal::service('entity.repository');
- }
- $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".
- '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);
- if ($entity_type->hasKey('bundle')) {
- $bundle_options = [];
- foreach ($bundles as $bundle_name => $bundle_info) {
- $bundle_options[$bundle_name] = $bundle_info['label'];
- }
- natsort($bundle_options);
- $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' => [[get_class($this), 'elementValidateFilter']],
- '#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']],
- ];
- }
- else {
- $form['target_bundles'] = [
- '#type' => 'value',
- '#value' => [],
- ];
- }
- if ($entity_type->entityClassImplements(FieldableEntityInterface::class)) {
- $fields = [];
- foreach (array_keys($bundles) 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,
- '#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']],
- ];
- if ($configuration['sort']['field'] != '_none') {
- $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'],
- ];
- }
- }
- $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')) {
- $bundles = array_intersect_key($bundle_options, array_filter((array) $configuration['target_bundles']));
- $form['auto_create_bundle'] = [
- '#type' => 'select',
- '#title' => $this->t('Store new items in'),
- '#options' => $bundles,
- '#default_value' => $configuration['auto_create_bundle'],
- '#access' => count($bundles) > 1,
- '#states' => [
- 'visible' => [
- ':input[name="settings[handler_settings][auto_create]"]' => ['checked' => TRUE],
- ],
- ],
- '#weight' => -1,
- ];
- }
- return $form;
- }
- /**
- * {@inheritdoc}
- */
- public function validateConfigurationForm(array &$form, FormStateInterface $form_state) {
- parent::validateConfigurationForm($form, $form_state);
- // 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(['settings', 'handler_settings', 'target_bundles']) === []) {
- $form_state->setValue(['settings', 'handler_settings', 'target_bundles'], NULL);
- }
- // Don't store the 'target_bundles_update' button value into the field
- // config settings.
- $form_state->unsetValue(['settings', 'handler_settings', 'target_bundles_update']);
- }
- /**
- * 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();
- // 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;
- }
- else {
- $query->condition($entity_type->getKey('bundle'), $configuration['target_bundles'], 'IN');
- }
- }
- 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;
- }
- }
|