3 namespace Drupal\Core\Entity\Plugin\EntityReferenceSelection;
5 use Drupal\Component\Utility\Html;
6 use Drupal\Core\Database\Query\AlterableInterface;
7 use Drupal\Core\Entity\EntityManagerInterface;
8 use Drupal\Core\Entity\EntityReferenceSelection\SelectionPluginBase;
9 use Drupal\Core\Entity\EntityReferenceSelection\SelectionTrait;
10 use Drupal\Core\Entity\EntityReferenceSelection\SelectionWithAutocreateInterface;
11 use Drupal\Core\Entity\FieldableEntityInterface;
12 use Drupal\Core\Extension\ModuleHandlerInterface;
13 use Drupal\Core\Field\Plugin\Field\FieldType\EntityReferenceItem;
14 use Drupal\Core\Form\FormStateInterface;
15 use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
16 use Drupal\Core\Session\AccountInterface;
17 use Drupal\user\EntityOwnerInterface;
20 * Default plugin implementation of the Entity Reference Selection plugin.
22 * Also serves as a base class for specific types of Entity Reference
25 * @see \Drupal\Core\Entity\EntityReferenceSelection\SelectionPluginManager
26 * @see \Drupal\Core\Entity\Annotation\EntityReferenceSelection
27 * @see \Drupal\Core\Entity\EntityReferenceSelection\SelectionInterface
28 * @see \Drupal\Core\Entity\Plugin\Derivative\DefaultSelectionDeriver
31 * @EntityReferenceSelection(
33 * label = @Translation("Default"),
36 * deriver = "Drupal\Core\Entity\Plugin\Derivative\DefaultSelectionDeriver"
39 class DefaultSelection extends SelectionPluginBase implements ContainerFactoryPluginInterface, SelectionWithAutocreateInterface {
42 // PHP 5.5.9 gets confused between SelectionPluginBase::__construct() and
43 // SelectionTrait::__construct() that's why we are renaming the
44 // SelectionTrait::__construct() to avoid the confusion.
45 // @todo Remove this in https://www.drupal.org/node/2670966.
46 SelectionTrait::__construct as private initialize;
52 public function __construct(array $configuration, $plugin_id, $plugin_definition, EntityManagerInterface $entity_manager, ModuleHandlerInterface $module_handler, AccountInterface $current_user) {
53 $this->initialize($configuration, $plugin_id, $plugin_definition, $entity_manager, $module_handler, $current_user);
59 public function defaultConfiguration() {
61 // For the 'target_bundles' setting, a NULL value is equivalent to "allow
62 // entities from any bundle to be referenced" and an empty array value is
63 // equivalent to "no entities from any bundle can be referenced".
64 'target_bundles' => NULL,
69 'auto_create' => FALSE,
70 'auto_create_bundle' => NULL,
71 ] + parent::defaultConfiguration();
77 public function buildConfigurationForm(array $form, FormStateInterface $form_state) {
78 $form = parent::buildConfigurationForm($form, $form_state);
80 $configuration = $this->getConfiguration();
81 $entity_type_id = $configuration['target_type'];
82 $entity_type = $this->entityManager->getDefinition($entity_type_id);
83 $bundles = $this->entityManager->getBundleInfo($entity_type_id);
85 if ($entity_type->hasKey('bundle')) {
87 foreach ($bundles as $bundle_name => $bundle_info) {
88 $bundle_options[$bundle_name] = $bundle_info['label'];
90 natsort($bundle_options);
92 $form['target_bundles'] = [
93 '#type' => 'checkboxes',
94 '#title' => $entity_type->getBundleLabel(),
95 '#options' => $bundle_options,
96 '#default_value' => (array) $configuration['target_bundles'],
100 '#element_validate' => [[get_class($this), 'elementValidateFilter']],
102 '#limit_validation_errors' => [],
105 $form['target_bundles_update'] = [
107 '#value' => $this->t('Update form'),
108 '#limit_validation_errors' => [],
110 'class' => ['js-hide'],
112 '#submit' => [[EntityReferenceItem::class, 'settingsAjaxSubmit']],
116 $form['target_bundles'] = [
122 if ($entity_type->entityClassImplements(FieldableEntityInterface::class)) {
124 foreach (array_keys($bundles) as $bundle) {
125 $bundle_fields = array_filter($this->entityManager->getFieldDefinitions($entity_type_id, $bundle), function ($field_definition) {
126 return !$field_definition->isComputed();
128 foreach ($bundle_fields as $field_name => $field_definition) {
129 /* @var \Drupal\Core\Field\FieldDefinitionInterface $field_definition */
130 $columns = $field_definition->getFieldStorageDefinition()->getColumns();
131 // If there is more than one column, display them all, otherwise just
132 // display the field label.
133 // @todo: Use property labels instead of the column name.
134 if (count($columns) > 1) {
135 foreach ($columns as $column_name => $column_info) {
136 $fields[$field_name . '.' . $column_name] = $this->t('@label (@column)', ['@label' => $field_definition->getLabel(), '@column' => $column_name]);
140 $fields[$field_name] = $this->t('@label', ['@label' => $field_definition->getLabel()]);
145 $form['sort']['field'] = [
147 '#title' => $this->t('Sort by'),
149 '_none' => $this->t('- None -'),
152 '#limit_validation_errors' => [],
153 '#default_value' => $configuration['sort']['field'],
156 $form['sort']['settings'] = [
157 '#type' => 'container',
158 '#attributes' => ['class' => ['entity_reference-settings']],
159 '#process' => [[EntityReferenceItem::class, 'formProcessMergeParent']],
162 if ($configuration['sort']['field'] != '_none') {
163 $form['sort']['settings']['direction'] = [
165 '#title' => $this->t('Sort direction'),
168 'ASC' => $this->t('Ascending'),
169 'DESC' => $this->t('Descending'),
171 '#default_value' => $configuration['sort']['direction'],
176 $form['auto_create'] = [
177 '#type' => 'checkbox',
178 '#title' => $this->t("Create referenced entities if they don't already exist"),
179 '#default_value' => $configuration['auto_create'],
183 if ($entity_type->hasKey('bundle')) {
184 $bundles = array_intersect_key($bundle_options, array_filter((array) $configuration['target_bundles']));
185 $form['auto_create_bundle'] = [
187 '#title' => $this->t('Store new items in'),
188 '#options' => $bundles,
189 '#default_value' => $configuration['auto_create_bundle'],
190 '#access' => count($bundles) > 1,
193 ':input[name="settings[handler_settings][auto_create]"]' => ['checked' => TRUE],
206 public function validateConfigurationForm(array &$form, FormStateInterface $form_state) {
207 parent::validateConfigurationForm($form, $form_state);
209 // If no checkboxes were checked for 'target_bundles', store NULL ("all
210 // bundles are referenceable") rather than empty array ("no bundle is
211 // referenceable" - typically happens when all referenceable bundles have
213 if ($form_state->getValue(['settings', 'handler_settings', 'target_bundles']) === []) {
214 $form_state->setValue(['settings', 'handler_settings', 'target_bundles'], NULL);
217 // Don't store the 'target_bundles_update' button value into the field
219 $form_state->unsetValue(['settings', 'handler_settings', 'target_bundles_update']);
223 * Form element validation handler; Filters the #value property of an element.
225 public static function elementValidateFilter(&$element, FormStateInterface $form_state) {
226 $element['#value'] = array_filter($element['#value']);
227 $form_state->setValueForElement($element, $element['#value']);
233 public function getReferenceableEntities($match = NULL, $match_operator = 'CONTAINS', $limit = 0) {
234 $target_type = $this->getConfiguration()['target_type'];
236 $query = $this->buildEntityQuery($match, $match_operator);
238 $query->range(0, $limit);
241 $result = $query->execute();
243 if (empty($result)) {
248 $entities = $this->entityManager->getStorage($target_type)->loadMultiple($result);
249 foreach ($entities as $entity_id => $entity) {
250 $bundle = $entity->bundle();
251 $options[$bundle][$entity_id] = Html::escape($this->entityManager->getTranslationFromContext($entity)->label());
260 public function countReferenceableEntities($match = NULL, $match_operator = 'CONTAINS') {
261 $query = $this->buildEntityQuery($match, $match_operator);
270 public function validateReferenceableEntities(array $ids) {
273 $target_type = $this->configuration['target_type'];
274 $entity_type = $this->entityManager->getDefinition($target_type);
275 $query = $this->buildEntityQuery();
277 ->condition($entity_type->getKey('id'), $ids, 'IN')
287 public function createNewEntity($entity_type_id, $bundle, $label, $uid) {
288 $entity_type = $this->entityManager->getDefinition($entity_type_id);
289 $bundle_key = $entity_type->getKey('bundle');
290 $label_key = $entity_type->getKey('label');
292 $entity = $this->entityManager->getStorage($entity_type_id)->create([
293 $bundle_key => $bundle,
294 $label_key => $label,
297 if ($entity instanceof EntityOwnerInterface) {
298 $entity->setOwnerId($uid);
307 public function validateReferenceableNewEntities(array $entities) {
308 return array_filter($entities, function ($entity) {
309 $target_bundles = $this->getConfiguration()['target_bundles'];
310 if (isset($target_bundles)) {
311 return in_array($entity->bundle(), $target_bundles);
318 * Builds an EntityQuery to get referenceable entities.
320 * @param string|null $match
321 * (Optional) Text to match the label against. Defaults to NULL.
322 * @param string $match_operator
323 * (Optional) The operation the matching should be done with. Defaults
326 * @return \Drupal\Core\Entity\Query\QueryInterface
327 * The EntityQuery object with the basic conditions and sorting applied to
330 protected function buildEntityQuery($match = NULL, $match_operator = 'CONTAINS') {
331 $configuration = $this->getConfiguration();
332 $target_type = $configuration['target_type'];
333 $entity_type = $this->entityManager->getDefinition($target_type);
335 $query = $this->entityManager->getStorage($target_type)->getQuery();
337 // If 'target_bundles' is NULL, all bundles are referenceable, no further
338 // conditions are needed.
339 if (is_array($configuration['target_bundles'])) {
340 // If 'target_bundles' is an empty array, no bundle is referenceable,
341 // force the query to never return anything and bail out early.
342 if ($configuration['target_bundles'] === []) {
343 $query->condition($entity_type->getKey('id'), NULL, '=');
347 $query->condition($entity_type->getKey('bundle'), $configuration['target_bundles'], 'IN');
351 if (isset($match) && $label_key = $entity_type->getKey('label')) {
352 $query->condition($label_key, $match, $match_operator);
355 // Add entity-access tag.
356 $query->addTag($target_type . '_access');
358 // Add the Selection handler for system_query_entity_reference_alter().
359 $query->addTag('entity_reference');
360 $query->addMetaData('entity_reference_selection_handler', $this);
362 // Add the sort option.
363 if ($configuration['sort']['field'] !== '_none') {
364 $query->sort($configuration['sort']['field'], $configuration['sort']['direction']);
371 * Helper method: Passes a query to the alteration system again.
373 * This allows Entity Reference to add a tag to an existing query so it can
374 * ask access control mechanisms to alter it again.
376 protected function reAlterQuery(AlterableInterface $query, $tag, $base_table) {
377 // Save the old tags and metadata.
378 // For some reason, those are public.
379 $old_tags = $query->alterTags;
380 $old_metadata = $query->alterMetaData;
382 $query->alterTags = [$tag => TRUE];
383 $query->alterMetaData['base_table'] = $base_table;
384 $this->moduleHandler->alter(['query', 'query_' . $tag], $query);
386 // Restore the tags and metadata.
387 $query->alterTags = $old_tags;
388 $query->alterMetaData = $old_metadata;