diff --git a/core/lib/Drupal/Core/Entity/Annotation/EntityReferenceSelection.php b/core/lib/Drupal/Core/Entity/Annotation/EntityReferenceSelection.php index 96e2283736488b0169f922d60c496fb163c1dbf4..0b701423bea44f6889c9faca1c1dea3f0e387b8f 100644 --- a/core/lib/Drupal/Core/Entity/Annotation/EntityReferenceSelection.php +++ b/core/lib/Drupal/Core/Entity/Annotation/EntityReferenceSelection.php @@ -19,8 +19,6 @@ * * @see \Drupal\Core\Entity\EntityReferenceSelection\SelectionPluginManager * @see \Drupal\Core\Entity\EntityReferenceSelection\SelectionInterface - * @see \Drupal\Core\Entity\Plugin\EntityReferenceSelection\SelectionBase - * @see \Drupal\Core\Entity\Plugin\Derivative\SelectionBase * @see plugin_api * * @Annotation diff --git a/core/lib/Drupal/Core/Entity/Element/EntityAutocomplete.php b/core/lib/Drupal/Core/Entity/Element/EntityAutocomplete.php index eed8739ddc891026b3a5df2c380347f33a0ecae6..2a52b79b33aa7ab2a18f3c9a6c5662a4a4252ac1 100644 --- a/core/lib/Drupal/Core/Entity/Element/EntityAutocomplete.php +++ b/core/lib/Drupal/Core/Entity/Element/EntityAutocomplete.php @@ -155,7 +155,7 @@ public static function validateEntityAutocomplete(array &$element, FormStateInte if ($match === NULL) { // Try to get a match from the input string when the user didn't use // the autocomplete but filled in a value manually. - $match = $handler->validateAutocompleteInput($input, $element, $form_state, $complete_form, !$autocreate); + $match = static::matchEntityByTitle($handler, $input, $element, $form_state, !$autocreate); } if ($match !== NULL) { @@ -202,6 +202,60 @@ public static function validateEntityAutocomplete(array &$element, FormStateInte $form_state->setValueForElement($element, $value); } + /** + * Finds an entity from an autocomplete input without an explicit ID. + * + * The method will return an entity ID if one single entity unambuguously + * matches the incoming input, and sill assign form errors otherwise. + * + * @param string $input + * Single string from autocomplete element. + * @param array $element + * The form element to set a form error. + * @param \Drupal\Core\Form\FormStateInterface $form_state + * The current form state. + * @param bool $strict + * Whether to trigger a form error if an element from $input (eg. an entity) + * is not found. + * + * @return integer|null + * Value of a matching entity ID, or NULL if none. + */ + protected static function matchEntityByTitle($handler, $input, &$element, FormStateInterface $form_state, $strict) { + $entities_by_bundle = $handler->getReferenceableEntities($input, '=', 6); + $entities = array_reduce($entities_by_bundle, function ($flattened, $bundle_entities) { + return $flattened + $bundle_entities; + }, []); + $params = array( + '%value' => $input, + '@value' => $input, + ); + if (empty($entities)) { + if ($strict) { + // Error if there are no entities available for a required field. + $form_state->setError($element, t('There are no entities matching "%value".', $params)); + } + } + elseif (count($entities) > 5) { + $params['@id'] = key($entities); + // Error if there are more than 5 matching entities. + $form_state->setError($element, t('Many entities are called %value. Specify the one you want by appending the id in parentheses, like "@value (@id)".', $params)); + } + elseif (count($entities) > 1) { + // More helpful error if there are only a few matching entities. + $multiples = array(); + foreach ($entities as $id => $name) { + $multiples[] = $name . ' (' . $id . ')'; + } + $params['@id'] = $id; + $form_state->setError($element, t('Multiple entities match this reference; "%multiple". Specify the one you want by appending the id in parentheses, like "@value (@id)".', array('%multiple' => implode('", "', $multiples)))); + } + else { + // Take the one and only matching entity. + return key($entities); + } + } + /** * Converts an array of entity objects into a string of entity labels. * diff --git a/core/lib/Drupal/Core/Entity/EntityReferenceSelection/SelectionInterface.php b/core/lib/Drupal/Core/Entity/EntityReferenceSelection/SelectionInterface.php index 864e717d857acb2cc8a7c19249e0711948dc46ad..83120d1950d6cd9df84c74ac87f4f9bc2b4baee0 100644 --- a/core/lib/Drupal/Core/Entity/EntityReferenceSelection/SelectionInterface.php +++ b/core/lib/Drupal/Core/Entity/EntityReferenceSelection/SelectionInterface.php @@ -8,16 +8,13 @@ namespace Drupal\Core\Entity\EntityReferenceSelection; use Drupal\Core\Database\Query\SelectInterface; -use Drupal\Core\Form\FormStateInterface; use Drupal\Core\Plugin\PluginFormInterface; /** * Interface definition for Entity Reference Selection plugins. * - * @see \Drupal\Core\Entity\Plugin\EntityReferenceSelection\SelectionBase * @see \Drupal\Core\Entity\EntityReferenceSelection\SelectionPluginManager * @see \Drupal\Core\Entity\Annotation\EntityReferenceSelection - * @see \Drupal\Core\Entity\Plugin\Derivative\SelectionBase * @see plugin_api */ interface SelectionInterface extends PluginFormInterface { @@ -48,28 +45,6 @@ public function countReferenceableEntities($match = NULL, $match_operator = 'CON */ public function validateReferenceableEntities(array $ids); - /** - * Validates input from an autocomplete widget that has no ID. - * - * @param string $input - * Single string from autocomplete widget. - * @param array $element - * The form element to set a form error. - * @param \Drupal\Core\Form\FormStateInterface $form_state - * The current form state. - * @param array $form - * The form. - * @param bool $strict - * Whether to trigger a form error if an element from $input (eg. an entity) - * is not found. Defaults to TRUE. - * - * @return integer|null - * Value of a matching entity ID, or NULL if none. - * - * @see \Drupal\entity_reference\Plugin\Field\FieldWidget::elementValidate() - */ - public function validateAutocompleteInput($input, &$element, FormStateInterface $form_state, $form, $strict = TRUE); - /** * Allows the selection to alter the SelectQuery generated by EntityFieldQuery. * diff --git a/core/lib/Drupal/Core/Entity/EntityReferenceSelection/SelectionPluginManager.php b/core/lib/Drupal/Core/Entity/EntityReferenceSelection/SelectionPluginManager.php index 199a8cfbb70e5445203367b2487ab44f57c24454..af1c2694da6557c69e123c43cc76c60fd877de16 100644 --- a/core/lib/Drupal/Core/Entity/EntityReferenceSelection/SelectionPluginManager.php +++ b/core/lib/Drupal/Core/Entity/EntityReferenceSelection/SelectionPluginManager.php @@ -19,8 +19,6 @@ * * @see \Drupal\Core\Entity\Annotation\EntityReferenceSelection * @see \Drupal\Core\Entity\EntityReferenceSelection\SelectionInterface - * @see \Drupal\Core\Entity\Plugin\EntityReferenceSelection\SelectionBase - * @see \Drupal\Core\Entity\Plugin\Derivative\SelectionBase * @see plugin_api */ class SelectionPluginManager extends DefaultPluginManager implements SelectionPluginManagerInterface, FallbackPluginManagerInterface { diff --git a/core/lib/Drupal/Core/Entity/Plugin/Derivative/SelectionBase.php b/core/lib/Drupal/Core/Entity/Plugin/Derivative/DefaultSelectionDeriver.php similarity index 89% rename from core/lib/Drupal/Core/Entity/Plugin/Derivative/SelectionBase.php rename to core/lib/Drupal/Core/Entity/Plugin/Derivative/DefaultSelectionDeriver.php index 01ebbaadbe4b55a6c8d0f90accdd638c19ddf297..d0aba027efaea19b5f0ab2df513efcd8e2631292 100644 --- a/core/lib/Drupal/Core/Entity/Plugin/Derivative/SelectionBase.php +++ b/core/lib/Drupal/Core/Entity/Plugin/Derivative/DefaultSelectionDeriver.php @@ -2,7 +2,7 @@ /** * @file - * Contains \Drupal\Core\Entity\Plugin\Derivative\SelectionBase. + * Contains \Drupal\Core\Entity\Plugin\Derivative\DefaultSelectionDeriver. */ namespace Drupal\Core\Entity\Plugin\Derivative; @@ -13,15 +13,15 @@ use Symfony\Component\DependencyInjection\ContainerInterface; /** - * Provides derivative plugins for Entity Reference Selection plugins. + * Provides derivative plugins for the DefaultSelection plugin. * - * @see \Drupal\Core\Entity\Plugin\EntityReferenceSelection\SelectionBase + * @see \Drupal\Core\Entity\Plugin\EntityReferenceSelection\DefaultSelection * @see \Drupal\Core\Entity\EntityReferenceSelection\SelectionPluginManager * @see \Drupal\Core\Entity\Annotation\EntityReferenceSelection * @see \Drupal\Core\Entity\EntityReferenceSelection\SelectionInterface * @see plugin_api */ -class SelectionBase extends DeriverBase implements ContainerDeriverInterface { +class DefaultSelectionDeriver extends DeriverBase implements ContainerDeriverInterface { /** * The entity manager diff --git a/core/lib/Drupal/Core/Entity/Plugin/EntityReferenceSelection/Broken.php b/core/lib/Drupal/Core/Entity/Plugin/EntityReferenceSelection/Broken.php index f5fbf1291062c7c7a66235f762c174dda428dadc..da4da2f71073fa5a06343b91db7278de91bfd061 100644 --- a/core/lib/Drupal/Core/Entity/Plugin/EntityReferenceSelection/Broken.php +++ b/core/lib/Drupal/Core/Entity/Plugin/EntityReferenceSelection/Broken.php @@ -9,7 +9,6 @@ use Drupal\Core\Database\Query\SelectInterface; use Drupal\Core\Entity\EntityReferenceSelection\SelectionInterface; -use Drupal\Core\Field\FieldDefinitionInterface; use Drupal\Core\Form\FormStateInterface; /** @@ -63,11 +62,6 @@ public function validateReferenceableEntities(array $ids) { return array(); } - /** - * {@inheritdoc} - */ - public function validateAutocompleteInput($input, &$element, FormStateInterface $form_state, $form, $strict = TRUE) { } - /** * {@inheritdoc} */ diff --git a/core/lib/Drupal/Core/Entity/Plugin/EntityReferenceSelection/DefaultSelection.php b/core/lib/Drupal/Core/Entity/Plugin/EntityReferenceSelection/DefaultSelection.php new file mode 100644 index 0000000000000000000000000000000000000000..eb321ecdba913c8ba7b2bf915f1bb565b5a68c4c --- /dev/null +++ b/core/lib/Drupal/Core/Entity/Plugin/EntityReferenceSelection/DefaultSelection.php @@ -0,0 +1,365 @@ +<?php + +/** + * @file + * Contains \Drupal\Core\Entity\Plugin\EntityReferenceSelection\DefaultSelection. + */ + +namespace Drupal\Core\Entity\Plugin\EntityReferenceSelection; + +use Drupal\Component\Utility\Html; +use Drupal\Core\Database\Query\AlterableInterface; +use Drupal\Core\Database\Query\SelectInterface; +use Drupal\Core\Entity\EntityManagerInterface; +use Drupal\Core\Extension\ModuleHandlerInterface; +use Drupal\Core\Form\FormStateInterface; +use Drupal\Core\Entity\EntityReferenceSelection\SelectionInterface; +use Drupal\Core\Plugin\ContainerFactoryPluginInterface; +use Drupal\Core\Plugin\PluginBase; +use Drupal\Core\Session\AccountInterface; +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 PluginBase implements SelectionInterface, ContainerFactoryPluginInterface { + + /** + * The entity manager. + * + * @var \Drupal\Core\Entity\EntityManagerInterface + */ + protected $entityManager; + + /** + * The module handler service. + * + * @var \Drupal\Core\Extension\ModuleHandlerInterface + */ + protected $moduleHandler; + + /** + * The current user. + * + * @var \Drupal\Core\Session\AccountInterface + */ + protected $currentUser; + + /** + * Constructs a new SelectionBase 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\EntityManagerInterface $entity_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. + */ + public function __construct(array $configuration, $plugin_id, $plugin_definition, EntityManagerInterface $entity_manager, ModuleHandlerInterface $module_handler, AccountInterface $current_user) { + parent::__construct($configuration, $plugin_id, $plugin_definition); + + $this->entityManager = $entity_manager; + $this->moduleHandler = $module_handler; + $this->currentUser = $current_user; + } + + /** + * {@inheritdoc} + */ + public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) { + return new static( + $configuration, + $plugin_id, + $plugin_definition, + $container->get('entity.manager'), + $container->get('module_handler'), + $container->get('current_user') + ); + } + + /** + * {@inheritdoc} + */ + public function buildConfigurationForm(array $form, FormStateInterface $form_state) { + $entity_type_id = $this->configuration['target_type']; + $selection_handler_settings = $this->configuration['handler_settings']; + $entity_type = $this->entityManager->getDefinition($entity_type_id); + $bundles = $this->entityManager->getBundleInfo($entity_type_id); + + // Merge-in default values. + $selection_handler_settings += array( + // 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' => array( + 'field' => '_none', + ), + 'auto_create' => FALSE, + ); + + if ($entity_type->hasKey('bundle')) { + $bundle_options = array(); + foreach ($bundles as $bundle_name => $bundle_info) { + $bundle_options[$bundle_name] = $bundle_info['label']; + } + + $form['target_bundles'] = array( + '#type' => 'checkboxes', + '#title' => $this->t('Bundles'), + '#options' => $bundle_options, + '#default_value' => (array) $selection_handler_settings['target_bundles'], + '#required' => TRUE, + '#size' => 6, + '#multiple' => TRUE, + '#element_validate' => array('_entity_reference_element_validate_filter'), + ); + } + else { + $form['target_bundles'] = array( + '#type' => 'value', + '#value' => array(), + ); + } + + if ($entity_type->isSubclassOf('\Drupal\Core\Entity\FieldableEntityInterface')) { + $fields = array(); + foreach (array_keys($bundles) as $bundle) { + $bundle_fields = array_filter($this->entityManager->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)', array('@label' => $field_definition->getLabel(), '@column' => $column_name)); + } + } + else { + $fields[$field_name] = $this->t('@label', array('@label' => $field_definition->getLabel())); + } + } + } + + $form['sort']['field'] = array( + '#type' => 'select', + '#title' => $this->t('Sort by'), + '#options' => array( + '_none' => $this->t('- None -'), + ) + $fields, + '#ajax' => TRUE, + '#limit_validation_errors' => array(), + '#default_value' => $selection_handler_settings['sort']['field'], + ); + + $form['sort']['settings'] = array( + '#type' => 'container', + '#attributes' => array('class' => array('entity_reference-settings')), + '#process' => array('_entity_reference_form_process_merge_parent'), + ); + + if ($selection_handler_settings['sort']['field'] != '_none') { + // Merge-in default values. + $selection_handler_settings['sort'] += array( + 'direction' => 'ASC', + ); + + $form['sort']['settings']['direction'] = array( + '#type' => 'select', + '#title' => $this->t('Sort direction'), + '#required' => TRUE, + '#options' => array( + 'ASC' => $this->t('Ascending'), + 'DESC' => $this->t('Descending'), + ), + '#default_value' => $selection_handler_settings['sort']['direction'], + ); + } + } + + return $form; + } + + /** + * {@inheritdoc} + */ + public function validateConfigurationForm(array &$form, FormStateInterface $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); + } + } + + /** + * {@inheritdoc} + */ + public function submitConfigurationForm(array &$form, FormStateInterface $form_state) { } + + /** + * {@inheritdoc} + */ + public function getReferenceableEntities($match = NULL, $match_operator = 'CONTAINS', $limit = 0) { + $target_type = $this->configuration['target_type']; + + $query = $this->buildEntityQuery($match, $match_operator); + if ($limit > 0) { + $query->range(0, $limit); + } + + $result = $query->execute(); + + if (empty($result)) { + return array(); + } + + $options = array(); + $entities = entity_load_multiple($target_type, $result); + foreach ($entities as $entity_id => $entity) { + $bundle = $entity->bundle(); + $options[$bundle][$entity_id] = Html::escape($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 = array(); + if ($ids) { + $target_type = $this->configuration['target_type']; + $entity_type = $this->entityManager->getDefinition($target_type); + $query = $this->buildEntityQuery(); + $result = $query + ->condition($entity_type->getKey('id'), $ids, 'IN') + ->execute(); + } + + return $result; + } + + /** + * 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') { + $target_type = $this->configuration['target_type']; + $handler_settings = $this->configuration['handler_settings']; + $entity_type = $this->entityManager->getDefinition($target_type); + + $query = $this->entityManager->getStorage($target_type)->getQuery(); + + // If 'target_bundles' is NULL, all bundles are referenceable, no further + // conditions are needed. + if (isset($handler_settings['target_bundles']) && is_array($handler_settings['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 ($handler_settings['target_bundles'] === []) { + $query->condition($entity_type->getKey('id'), NULL, '='); + return $query; + } + else { + $query->condition($entity_type->getKey('bundle'), $handler_settings['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 + // entity_reference_query_entity_reference_alter(). + $query->addTag('entity_reference'); + $query->addMetaData('entity_reference_selection_handler', $this); + + // Add the sort option. + if (!empty($handler_settings['sort'])) { + $sort_settings = $handler_settings['sort']; + if ($sort_settings['field'] != '_none') { + $query->sort($sort_settings['field'], $sort_settings['direction']); + } + } + + return $query; + } + + /** + * {@inheritdoc} + */ + public function entityQueryAlter(SelectInterface $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 = array($tag => TRUE); + $query->alterMetaData['base_table'] = $base_table; + $this->moduleHandler->alter(array('query', 'query_' . $tag), $query); + + // Restore the tags and metadata. + $query->alterTags = $old_tags; + $query->alterMetaData = $old_metadata; + } + +} diff --git a/core/lib/Drupal/Core/Entity/Plugin/EntityReferenceSelection/PhpSelection.php b/core/lib/Drupal/Core/Entity/Plugin/EntityReferenceSelection/PhpSelection.php index 25c99c82a41cfbfa0d05b3f6cfb0820ee56c7789..7b6b4962bbcd7ce1077424f102baa4361fdc6398 100644 --- a/core/lib/Drupal/Core/Entity/Plugin/EntityReferenceSelection/PhpSelection.php +++ b/core/lib/Drupal/Core/Entity/Plugin/EntityReferenceSelection/PhpSelection.php @@ -17,10 +17,9 @@ * cannot filter properly, for example when the target entity type has no * 'label' key provided in the entity type plugin definition. * - * @see \Drupal\Core\Entity\Plugin\EntityReferenceSelection\SelectionBase - * @see \Drupal\Core\Entity\Plugin\Derivative\SelectionBase + * @see \Drupal\Core\Entity\Plugin\Derivative\DefaultSelectionDeriver */ -class PhpSelection extends SelectionBase { +class PhpSelection extends DefaultSelection { /** * {@inheritdoc} diff --git a/core/lib/Drupal/Core/Entity/Plugin/EntityReferenceSelection/SelectionBase.php b/core/lib/Drupal/Core/Entity/Plugin/EntityReferenceSelection/SelectionBase.php index 0cf95026f6de6c28b41c8eb0bf647e64ccd33283..4ee09d843983e6fbf33458fac6b567cba41835cb 100644 --- a/core/lib/Drupal/Core/Entity/Plugin/EntityReferenceSelection/SelectionBase.php +++ b/core/lib/Drupal/Core/Entity/Plugin/EntityReferenceSelection/SelectionBase.php @@ -7,399 +7,8 @@ namespace Drupal\Core\Entity\Plugin\EntityReferenceSelection; -use Drupal\Component\Utility\Html; -use Drupal\Core\Database\Query\AlterableInterface; -use Drupal\Core\Database\Query\SelectInterface; -use Drupal\Core\Entity\EntityManagerInterface; -use Drupal\Core\Extension\ModuleHandlerInterface; -use Drupal\Core\Form\FormStateInterface; -use Drupal\Core\Entity\EntityReferenceSelection\SelectionInterface; -use Drupal\Core\Plugin\ContainerFactoryPluginInterface; -use Drupal\Core\Plugin\PluginBase; -use Drupal\Core\Session\AccountInterface; -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\SelectionBase - * @see plugin_api - * - * @EntityReferenceSelection( - * id = "default", - * label = @Translation("Default"), - * group = "default", - * weight = 0, - * deriver = "Drupal\Core\Entity\Plugin\Derivative\SelectionBase" - * ) + * @deprecated in Drupal 8.0.0, will be removed before Drupal 9.0.0. + * Use \Drupal\Core\Entity\Plugin\EntityReferenceSelection\DefaultSelection */ -class SelectionBase extends PluginBase implements SelectionInterface, ContainerFactoryPluginInterface { - - /** - * The entity manager. - * - * @var \Drupal\Core\Entity\EntityManagerInterface - */ - protected $entityManager; - - /** - * The module handler service. - * - * @var \Drupal\Core\Extension\ModuleHandlerInterface - */ - protected $moduleHandler; - - /** - * The current user. - * - * @var \Drupal\Core\Session\AccountInterface - */ - protected $currentUser; - - /** - * Constructs a new SelectionBase 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\EntityManagerInterface $entity_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. - */ - public function __construct(array $configuration, $plugin_id, $plugin_definition, EntityManagerInterface $entity_manager, ModuleHandlerInterface $module_handler, AccountInterface $current_user) { - parent::__construct($configuration, $plugin_id, $plugin_definition); - - $this->entityManager = $entity_manager; - $this->moduleHandler = $module_handler; - $this->currentUser = $current_user; - } - - /** - * {@inheritdoc} - */ - public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) { - return new static( - $configuration, - $plugin_id, - $plugin_definition, - $container->get('entity.manager'), - $container->get('module_handler'), - $container->get('current_user') - ); - } - - /** - * {@inheritdoc} - */ - public function buildConfigurationForm(array $form, FormStateInterface $form_state) { - $entity_type_id = $this->configuration['target_type']; - $selection_handler_settings = $this->configuration['handler_settings']; - $entity_type = $this->entityManager->getDefinition($entity_type_id); - $bundles = $this->entityManager->getBundleInfo($entity_type_id); - - // Merge-in default values. - $selection_handler_settings += array( - // 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' => array( - 'field' => '_none', - ), - 'auto_create' => FALSE, - ); - - if ($entity_type->hasKey('bundle')) { - $bundle_options = array(); - foreach ($bundles as $bundle_name => $bundle_info) { - $bundle_options[$bundle_name] = $bundle_info['label']; - } - - $form['target_bundles'] = array( - '#type' => 'checkboxes', - '#title' => $this->t('Bundles'), - '#options' => $bundle_options, - '#default_value' => (array) $selection_handler_settings['target_bundles'], - '#required' => TRUE, - '#size' => 6, - '#multiple' => TRUE, - '#element_validate' => array('_entity_reference_element_validate_filter'), - ); - } - else { - $form['target_bundles'] = array( - '#type' => 'value', - '#value' => array(), - ); - } - - if ($entity_type->isSubclassOf('\Drupal\Core\Entity\FieldableEntityInterface')) { - $fields = array(); - foreach (array_keys($bundles) as $bundle) { - $bundle_fields = array_filter($this->entityManager->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)', array('@label' => $field_definition->getLabel(), '@column' => $column_name)); - } - } - else { - $fields[$field_name] = $this->t('@label', array('@label' => $field_definition->getLabel())); - } - } - } - - $form['sort']['field'] = array( - '#type' => 'select', - '#title' => $this->t('Sort by'), - '#options' => array( - '_none' => $this->t('- None -'), - ) + $fields, - '#ajax' => TRUE, - '#limit_validation_errors' => array(), - '#default_value' => $selection_handler_settings['sort']['field'], - ); - - $form['sort']['settings'] = array( - '#type' => 'container', - '#attributes' => array('class' => array('entity_reference-settings')), - '#process' => array('_entity_reference_form_process_merge_parent'), - ); - - if ($selection_handler_settings['sort']['field'] != '_none') { - // Merge-in default values. - $selection_handler_settings['sort'] += array( - 'direction' => 'ASC', - ); - - $form['sort']['settings']['direction'] = array( - '#type' => 'select', - '#title' => $this->t('Sort direction'), - '#required' => TRUE, - '#options' => array( - 'ASC' => $this->t('Ascending'), - 'DESC' => $this->t('Descending'), - ), - '#default_value' => $selection_handler_settings['sort']['direction'], - ); - } - } - - return $form; - } - - /** - * {@inheritdoc} - */ - public function validateConfigurationForm(array &$form, FormStateInterface $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); - } - } - - /** - * {@inheritdoc} - */ - public function submitConfigurationForm(array &$form, FormStateInterface $form_state) { } - - /** - * {@inheritdoc} - */ - public function getReferenceableEntities($match = NULL, $match_operator = 'CONTAINS', $limit = 0) { - $target_type = $this->configuration['target_type']; - - $query = $this->buildEntityQuery($match, $match_operator); - if ($limit > 0) { - $query->range(0, $limit); - } - - $result = $query->execute(); - - if (empty($result)) { - return array(); - } - - $options = array(); - $entities = entity_load_multiple($target_type, $result); - foreach ($entities as $entity_id => $entity) { - $bundle = $entity->bundle(); - $options[$bundle][$entity_id] = Html::escape($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 = array(); - if ($ids) { - $target_type = $this->configuration['target_type']; - $entity_type = $this->entityManager->getDefinition($target_type); - $query = $this->buildEntityQuery(); - $result = $query - ->condition($entity_type->getKey('id'), $ids, 'IN') - ->execute(); - } - - return $result; - } - - /** - * {@inheritdoc} - */ - public function validateAutocompleteInput($input, &$element, FormStateInterface $form_state, $form, $strict = TRUE) { - $bundled_entities = $this->getReferenceableEntities($input, '=', 6); - $entities = array(); - foreach ($bundled_entities as $entities_list) { - $entities += $entities_list; - } - $params = array( - '%value' => $input, - '@value' => $input, - ); - if (empty($entities)) { - if ($strict) { - // Error if there are no entities available for a required field. - $form_state->setError($element, $this->t('There are no entities matching "%value".', $params)); - } - } - elseif (count($entities) > 5) { - $params['@id'] = key($entities); - // Error if there are more than 5 matching entities. - $form_state->setError($element, $this->t('Many entities are called %value. Specify the one you want by appending the id in parentheses, like "@value (@id)".', $params)); - } - elseif (count($entities) > 1) { - // More helpful error if there are only a few matching entities. - $multiples = array(); - foreach ($entities as $id => $name) { - $multiples[] = $name . ' (' . $id . ')'; - } - $params['@id'] = $id; - $form_state->setError($element, $this->t('Multiple entities match this reference; "%multiple". Specify the one you want by appending the id in parentheses, like "@value (@id)".', array('%multiple' => implode('", "', $multiples)))); - } - else { - // Take the one and only matching entity. - return key($entities); - } - } - - /** - * 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') { - $target_type = $this->configuration['target_type']; - $handler_settings = $this->configuration['handler_settings']; - $entity_type = $this->entityManager->getDefinition($target_type); - - $query = $this->entityManager->getStorage($target_type)->getQuery(); - - // If 'target_bundles' is NULL, all bundles are referenceable, no further - // conditions are needed. - if (isset($handler_settings['target_bundles']) && is_array($handler_settings['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 ($handler_settings['target_bundles'] === []) { - $query->condition($entity_type->getKey('id'), NULL, '='); - - return $query; - } - else { - $query->condition($entity_type->getKey('bundle'), $handler_settings['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 - // entity_reference_query_entity_reference_alter(). - $query->addTag('entity_reference'); - $query->addMetaData('entity_reference_selection_handler', $this); - - // Add the sort option. - if (!empty($handler_settings['sort'])) { - $sort_settings = $handler_settings['sort']; - if ($sort_settings['field'] != '_none') { - $query->sort($sort_settings['field'], $sort_settings['direction']); - } - } - - return $query; - } - - /** - * {@inheritdoc} - */ - public function entityQueryAlter(SelectInterface $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 = array($tag => TRUE); - $query->alterMetaData['base_table'] = $base_table; - $this->moduleHandler->alter(array('query', 'query_' . $tag), $query); - - // Restore the tags and metadata. - $query->alterTags = $old_tags; - $query->alterMetaData = $old_metadata; - } - -} +class SelectionBase extends DefaultSelection { } diff --git a/core/modules/comment/src/Plugin/EntityReferenceSelection/CommentSelection.php b/core/modules/comment/src/Plugin/EntityReferenceSelection/CommentSelection.php index 0127e6cee46fc35747bcd1e361f05d48549afcbf..c8bc4a7013cbf9201363982f148f6d8b938bdad4 100644 --- a/core/modules/comment/src/Plugin/EntityReferenceSelection/CommentSelection.php +++ b/core/modules/comment/src/Plugin/EntityReferenceSelection/CommentSelection.php @@ -8,7 +8,7 @@ namespace Drupal\comment\Plugin\EntityReferenceSelection; use Drupal\Core\Database\Query\SelectInterface; -use Drupal\Core\Entity\Plugin\EntityReferenceSelection\SelectionBase; +use Drupal\Core\Entity\Plugin\EntityReferenceSelection\DefaultSelection; use Drupal\comment\CommentInterface; /** @@ -22,7 +22,7 @@ * weight = 1 * ) */ -class CommentSelection extends SelectionBase { +class CommentSelection extends DefaultSelection { /** * {@inheritdoc} diff --git a/core/modules/file/src/Plugin/EntityReferenceSelection/FileSelection.php b/core/modules/file/src/Plugin/EntityReferenceSelection/FileSelection.php index 1c157d0ab605b0cbc950be945aea2bd4f2c4f615..e789154d22e25fb4e9e2009e22948fe18f4f3519 100644 --- a/core/modules/file/src/Plugin/EntityReferenceSelection/FileSelection.php +++ b/core/modules/file/src/Plugin/EntityReferenceSelection/FileSelection.php @@ -7,7 +7,7 @@ namespace Drupal\file\Plugin\EntityReferenceSelection; -use Drupal\Core\Entity\Plugin\EntityReferenceSelection\SelectionBase; +use Drupal\Core\Entity\Plugin\EntityReferenceSelection\DefaultSelection; /** * Provides specific access control for the file entity type. @@ -20,7 +20,7 @@ * weight = 1 * ) */ -class FileSelection extends SelectionBase { +class FileSelection extends DefaultSelection { /** * {@inheritdoc} diff --git a/core/modules/node/src/Plugin/EntityReferenceSelection/NodeSelection.php b/core/modules/node/src/Plugin/EntityReferenceSelection/NodeSelection.php index c2b6f3a09460ece796989167a5928a91575e023e..276c795b6d242a0e60d242bba46c0dac4ed5350c 100644 --- a/core/modules/node/src/Plugin/EntityReferenceSelection/NodeSelection.php +++ b/core/modules/node/src/Plugin/EntityReferenceSelection/NodeSelection.php @@ -7,7 +7,7 @@ namespace Drupal\node\Plugin\EntityReferenceSelection; -use Drupal\Core\Entity\Plugin\EntityReferenceSelection\SelectionBase; +use Drupal\Core\Entity\Plugin\EntityReferenceSelection\DefaultSelection; use Drupal\Core\Form\FormStateInterface; /** @@ -21,7 +21,7 @@ * weight = 1 * ) */ -class NodeSelection extends SelectionBase { +class NodeSelection extends DefaultSelection { /** * {@inheritdoc} diff --git a/core/modules/taxonomy/src/Plugin/EntityReferenceSelection/TermSelection.php b/core/modules/taxonomy/src/Plugin/EntityReferenceSelection/TermSelection.php index 1ddde502bbe8246ed6bd4810f5abbb418e0043ee..9420d955d76d8c878b96ad3c1472492d8e443bbe 100644 --- a/core/modules/taxonomy/src/Plugin/EntityReferenceSelection/TermSelection.php +++ b/core/modules/taxonomy/src/Plugin/EntityReferenceSelection/TermSelection.php @@ -9,7 +9,7 @@ use Drupal\Component\Utility\Html; use Drupal\Core\Database\Query\SelectInterface; -use Drupal\Core\Entity\Plugin\EntityReferenceSelection\SelectionBase; +use Drupal\Core\Entity\Plugin\EntityReferenceSelection\DefaultSelection; use Drupal\Core\Form\FormStateInterface; use Drupal\taxonomy\Entity\Vocabulary; @@ -24,7 +24,7 @@ * weight = 1 * ) */ -class TermSelection extends SelectionBase { +class TermSelection extends DefaultSelection { /** * {@inheritdoc} diff --git a/core/modules/user/src/Plugin/EntityReferenceSelection/UserSelection.php b/core/modules/user/src/Plugin/EntityReferenceSelection/UserSelection.php index a1679cbc30da5deed80e1b48b096acbb88378101..59f4d05d8c04f5c902f69b39e1dc947b973b458f 100644 --- a/core/modules/user/src/Plugin/EntityReferenceSelection/UserSelection.php +++ b/core/modules/user/src/Plugin/EntityReferenceSelection/UserSelection.php @@ -10,7 +10,7 @@ use Drupal\Core\Database\Connection; use Drupal\Core\Database\Query\SelectInterface; use Drupal\Core\Entity\EntityManagerInterface; -use Drupal\Core\Entity\Plugin\EntityReferenceSelection\SelectionBase; +use Drupal\Core\Entity\Plugin\EntityReferenceSelection\DefaultSelection; use Drupal\Core\Extension\ModuleHandlerInterface; use Drupal\Core\Form\FormStateInterface; use Drupal\Core\Session\AccountInterface; @@ -28,7 +28,7 @@ * weight = 1 * ) */ -class UserSelection extends SelectionBase { +class UserSelection extends DefaultSelection { /** * The database connection. diff --git a/core/modules/views/src/Plugin/EntityReferenceSelection/ViewsSelection.php b/core/modules/views/src/Plugin/EntityReferenceSelection/ViewsSelection.php index f9651494c9bd65fd71014d4a9921f4a9ad148c77..1fefc002889091942ee0ec1c8556806da595422e 100644 --- a/core/modules/views/src/Plugin/EntityReferenceSelection/ViewsSelection.php +++ b/core/modules/views/src/Plugin/EntityReferenceSelection/ViewsSelection.php @@ -7,10 +7,17 @@ namespace Drupal\views\Plugin\EntityReferenceSelection; -use Drupal\Core\Entity\Plugin\EntityReferenceSelection\SelectionBase; +use Drupal\Core\Database\Query\SelectInterface; +use Drupal\Core\Entity\EntityManagerInterface; +use Drupal\Core\Entity\EntityReferenceSelection\SelectionInterface; +use Drupal\Core\Extension\ModuleHandlerInterface; use Drupal\Core\Form\FormStateInterface; +use Drupal\Core\Plugin\ContainerFactoryPluginInterface; +use Drupal\Core\Plugin\PluginBase; +use Drupal\Core\Session\AccountInterface; use Drupal\Core\Url; use Drupal\views\Views; +use Symfony\Component\DependencyInjection\ContainerInterface; /** * Plugin implementation of the 'selection' entity_reference. @@ -22,7 +29,66 @@ * weight = 0 * ) */ -class ViewsSelection extends SelectionBase { +class ViewsSelection extends PluginBase implements SelectionInterface, ContainerFactoryPluginInterface { + + /** + * The entity manager. + * + * @var \Drupal\Core\Entity\EntityManagerInterface + */ + protected $entityManager; + + /** + * The module handler service. + * + * @var \Drupal\Core\Extension\ModuleHandlerInterface + */ + protected $moduleHandler; + + /** + * The current user. + * + * @var \Drupal\Core\Session\AccountInterface + */ + protected $currentUser; + + /** + * Constructs a new SelectionBase 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\EntityManagerInterface $entity_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. + */ + public function __construct(array $configuration, $plugin_id, $plugin_definition, EntityManagerInterface $entity_manager, ModuleHandlerInterface $module_handler, AccountInterface $current_user) { + parent::__construct($configuration, $plugin_id, $plugin_definition); + + $this->entityManager = $entity_manager; + $this->moduleHandler = $module_handler; + $this->currentUser = $current_user; + } + + /** + * {@inheritdoc} + */ + public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) { + return new static( + $configuration, + $plugin_id, + $plugin_definition, + $container->get('entity.manager'), + $container->get('module_handler'), + $container->get('current_user') + ); + } /** * The loaded View object. @@ -98,10 +164,12 @@ public function buildConfigurationForm(array $form, FormStateInterface $form_sta /** * {@inheritdoc} */ - public function validateConfigurationForm(array &$form, FormStateInterface $form_state) { - // Don't call the parent validation handler because we don't have any - // 'target_bundles' setting. - } + public function validateConfigurationForm(array &$form, FormStateInterface $form_state) { } + + /** + * {@inheritdoc} + */ + public function submitConfigurationForm(array &$form, FormStateInterface $form_state) { } /** * Initializes a view. @@ -223,8 +291,6 @@ public static function settingsFormValidate($element, FormStateInterface $form_s /** * {@inheritdoc} */ - protected function buildEntityQuery($match = NULL, $match_operator = 'CONTAINS') { - throw new \BadMethodCallException('The Views selection plugin does not use the Entity Query system for entity selection.'); - } + public function entityQueryAlter(SelectInterface $query) { } }