diff --git a/core/config/schema/core.data_types.schema.yml b/core/config/schema/core.data_types.schema.yml index 160b526ebd52..92782406ca04 100644 --- a/core/config/schema/core.data_types.schema.yml +++ b/core/config/schema/core.data_types.schema.yml @@ -780,24 +780,11 @@ text_format: # The text format should not be translated as part of the string # translation system, so this is not marked as translatable. -# Base schema for all entity reference selection handler schemas. +# Schema for the configuration of the Entity reference selection plugins. + entity_reference_selection: type: mapping - label: 'Entity reference selection handler settings' - mapping: - target_type: - type: string - label: 'Type of item to reference' - -# Schema for all entity reference selection handlers that are not providing a -# specific schema. -entity_reference_selection.*: - type: entity_reference_selection - -# Schema for the entity reference 'default' selection handler settings. -entity_reference_selection.default: - type: entity_reference_selection - label: 'Default selection handler settings' + label: 'Entity reference selection plugin configuration' mapping: target_bundles: type: sequence @@ -805,7 +792,7 @@ entity_reference_selection.default: nullable: true sequence: type: string - label: 'Bundle' + label: 'Type' sort: type: mapping label: 'Sort settings' @@ -823,7 +810,5 @@ entity_reference_selection.default: type: string label: 'Bundle assigned to the auto-created entities.' -# Schema for all entity reference 'default:*' selection handlers that are not -# providing a specific schema. -entity_reference_selection.default:*: - type: entity_reference_selection.default +entity_reference_selection.*: + type: entity_reference_selection diff --git a/core/lib/Drupal/Core/Entity/Element/EntityAutocomplete.php b/core/lib/Drupal/Core/Entity/Element/EntityAutocomplete.php index 466032d96a01..89f99a049f54 100644 --- a/core/lib/Drupal/Core/Entity/Element/EntityAutocomplete.php +++ b/core/lib/Drupal/Core/Entity/Element/EntityAutocomplete.php @@ -152,9 +152,10 @@ class EntityAutocomplete extends Textfield { $value = NULL; if (!empty($element['#value'])) { - $options = $element['#selection_settings'] + [ + $options = [ 'target_type' => $element['#target_type'], 'handler' => $element['#selection_handler'], + 'handler_settings' => $element['#selection_settings'], ]; /** @var /Drupal\Core\Entity\EntityReferenceSelection\SelectionInterface $handler */ $handler = \Drupal::service('plugin.manager.entity_reference_selection')->getInstance($options); diff --git a/core/lib/Drupal/Core/Entity/EntityAutocompleteMatcher.php b/core/lib/Drupal/Core/Entity/EntityAutocompleteMatcher.php index 2cede4dffaa5..55372f9e7185 100644 --- a/core/lib/Drupal/Core/Entity/EntityAutocompleteMatcher.php +++ b/core/lib/Drupal/Core/Entity/EntityAutocompleteMatcher.php @@ -52,9 +52,10 @@ class EntityAutocompleteMatcher { public function getMatches($target_type, $selection_handler, $selection_settings, $string = '') { $matches = []; - $options = $selection_settings + [ + $options = [ 'target_type' => $target_type, 'handler' => $selection_handler, + 'handler_settings' => $selection_settings, ]; $handler = $this->selectionManager->getInstance($options); diff --git a/core/lib/Drupal/Core/Entity/EntityReferenceSelection/SelectionPluginBase.php b/core/lib/Drupal/Core/Entity/EntityReferenceSelection/SelectionPluginBase.php deleted file mode 100644 index b5795c1bcd3f..000000000000 --- a/core/lib/Drupal/Core/Entity/EntityReferenceSelection/SelectionPluginBase.php +++ /dev/null @@ -1,160 +0,0 @@ -setConfiguration($configuration); - } - - /** - * {@inheritdoc} - */ - public function defaultConfiguration() { - return [ - 'target_type' => NULL, - // @todo Remove this key in Drupal 9.0.x. - 'handler' => $this->getPluginId(), - 'entity' => NULL, - ]; - } - - /** - * {@inheritdoc} - */ - public function getConfiguration() { - return $this->configuration; - } - - /** - * {@inheritdoc} - */ - public function setConfiguration(array $configuration) { - // Resolve backward compatibility level configurations, if any. - $this->resolveBackwardCompatibilityConfiguration($configuration); - - // Merge in defaults. - $this->configuration = NestedArray::mergeDeep( - $this->defaultConfiguration(), - $configuration - ); - - // Ensure a backward compatibility level configuration. - $this->ensureBackwardCompatibilityConfiguration(); - } - - /** - * {@inheritdoc} - */ - public function calculateDependencies() { - return []; - } - - /** - * {@inheritdoc} - */ - public function buildConfigurationForm(array $form, FormStateInterface $form_state) { - return $form; - } - - /** - * {@inheritdoc} - */ - public function validateConfigurationForm(array &$form, FormStateInterface $form_state) { } - - /** - * {@inheritdoc} - */ - public function submitConfigurationForm(array &$form, FormStateInterface $form_state) { } - - /** - * {@inheritdoc} - */ - public function entityQueryAlter(SelectInterface $query) { } - - /** - * Moves the backward compatibility level configurations in the right place. - * - * In order to keep backward compatibility, we copy all settings, except - * 'target_type', 'handler' and 'entity' under 'handler_settings', following - * the structure from the field config. If the plugin was instantiated using - * the 'handler_settings' level, those values will be used. In case of - * conflict, the root level settings will take precedence. The backward - * compatibility aware configuration will have the next structure: - * - target_type - * - handler (will be removed in Drupal 9.0.x, it's the plugin id) - * - entity - * - setting_1 - * - setting_2 - * ... - * - setting_N - * - handler_settings: (will be removed in Drupal 9.0.x) - * - setting_1 - * - setting_2 - * ... - * - setting_N - * - * @param array $configuration - * The configuration array to be altered. - * - * @deprecated Scheduled for removal in Drupal 9.0.x. - * - * @see https://www.drupal.org/node/2870971 - */ - protected function resolveBackwardCompatibilityConfiguration(array &$configuration) { - if (isset($this->defaultConfiguration()['handler_settings'])) { - throw new \InvalidArgumentException("{$this->getPluginDefinition()['class']}::defaultConfiguration() should not contain a 'handler_settings' key. All settings should be placed in the root level."); - } - - // Extract the BC level from the passed configuration, if any. - if (array_key_exists('handler_settings', $configuration)) { - if (!is_array($configuration['handler_settings'])) { - throw new \InvalidArgumentException("The setting 'handler_settings' is reserved and cannot be used."); - } - @trigger_error("Providing settings under 'handler_settings' is deprecated and will be removed before 9.0.0. Move the settings in the root of the configuration array. See https://www.drupal.org/node/2870971.", E_USER_DEPRECATED); - - // Settings passed in the root level take precedence over BC settings. - $configuration += $configuration['handler_settings']; - unset($configuration['handler_settings']); - } - } - - /** - * Ensures a backward compatibility level configuration. - * - * @deprecated Scheduled for removal in Drupal 9.0.x. - * - * @see https://www.drupal.org/node/2870971 - */ - protected function ensureBackwardCompatibilityConfiguration() { - // Synchronize back 'handler_settings'. - foreach ($this->configuration as $key => $value) { - // Filter out keys that belong strictly to the root level. - if (!in_array($key, ['handler', 'target_type', 'entity', 'handler_settings'])) { - $this->configuration['handler_settings'][$key] = $value; - } - } - } - -} diff --git a/core/lib/Drupal/Core/Entity/EntityReferenceSelection/SelectionPluginManager.php b/core/lib/Drupal/Core/Entity/EntityReferenceSelection/SelectionPluginManager.php index cf9bb7e49de8..53b43fdc6892 100644 --- a/core/lib/Drupal/Core/Entity/EntityReferenceSelection/SelectionPluginManager.php +++ b/core/lib/Drupal/Core/Entity/EntityReferenceSelection/SelectionPluginManager.php @@ -39,6 +39,7 @@ class SelectionPluginManager extends DefaultPluginManager implements SelectionPl // Initialize default options. $options += [ 'handler' => $this->getPluginId($options['target_type'], 'default'), + 'handler_settings' => [], ]; // A specific selection plugin ID was already specified. @@ -49,7 +50,6 @@ class SelectionPluginManager extends DefaultPluginManager implements SelectionPl else { $plugin_id = $this->getPluginId($options['target_type'], $options['handler']); } - unset($options['handler']); return $this->createInstance($plugin_id, $options); } @@ -92,10 +92,10 @@ class SelectionPluginManager extends DefaultPluginManager implements SelectionPl * {@inheritdoc} */ public function getSelectionHandler(FieldDefinitionInterface $field_definition, EntityInterface $entity = NULL) { - $options = $field_definition->getSetting('handler_settings') ?: []; - $options += [ + $options = [ 'target_type' => $field_definition->getFieldStorageDefinition()->getSetting('target_type'), 'handler' => $field_definition->getSetting('handler'), + 'handler_settings' => $field_definition->getSetting('handler_settings') ?: [], 'entity' => $entity, ]; return $this->getInstance($options); diff --git a/core/lib/Drupal/Core/Entity/EntityReferenceSelection/SelectionTrait.php b/core/lib/Drupal/Core/Entity/EntityReferenceSelection/SelectionTrait.php deleted file mode 100644 index 1d7947df21d2..000000000000 --- a/core/lib/Drupal/Core/Entity/EntityReferenceSelection/SelectionTrait.php +++ /dev/null @@ -1,74 +0,0 @@ -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') - ); - } - -} diff --git a/core/lib/Drupal/Core/Entity/Plugin/EntityReferenceSelection/Broken.php b/core/lib/Drupal/Core/Entity/Plugin/EntityReferenceSelection/Broken.php index e8251e631f74..f53eeb618902 100644 --- a/core/lib/Drupal/Core/Entity/Plugin/EntityReferenceSelection/Broken.php +++ b/core/lib/Drupal/Core/Entity/Plugin/EntityReferenceSelection/Broken.php @@ -2,7 +2,8 @@ namespace Drupal\Core\Entity\Plugin\EntityReferenceSelection; -use Drupal\Core\Entity\EntityReferenceSelection\SelectionPluginBase; +use Drupal\Core\Database\Query\SelectInterface; +use Drupal\Core\Entity\EntityReferenceSelection\SelectionInterface; use Drupal\Core\Form\FormStateInterface; /** @@ -13,19 +14,28 @@ use Drupal\Core\Form\FormStateInterface; * label = @Translation("Broken/Missing") * ) */ -class Broken extends SelectionPluginBase { +class Broken implements SelectionInterface { /** * {@inheritdoc} */ public function buildConfigurationForm(array $form, FormStateInterface $form_state) { - $form = parent::buildConfigurationForm($form, $form_state); $form['selection_handler'] = [ '#markup' => t('The selected selection handler is broken.'), ]; return $form; } + /** + * {@inheritdoc} + */ + public function validateConfigurationForm(array &$form, FormStateInterface $form_state) { } + + /** + * {@inheritdoc} + */ + public function submitConfigurationForm(array &$form, FormStateInterface $form_state) { } + /** * {@inheritdoc} */ @@ -47,4 +57,9 @@ class Broken extends SelectionPluginBase { return []; } + /** + * {@inheritdoc} + */ + public function entityQueryAlter(SelectInterface $query) { } + } diff --git a/core/lib/Drupal/Core/Entity/Plugin/EntityReferenceSelection/DefaultSelection.php b/core/lib/Drupal/Core/Entity/Plugin/EntityReferenceSelection/DefaultSelection.php index e88602773c60..e3b41b1bbe36 100644 --- a/core/lib/Drupal/Core/Entity/Plugin/EntityReferenceSelection/DefaultSelection.php +++ b/core/lib/Drupal/Core/Entity/Plugin/EntityReferenceSelection/DefaultSelection.php @@ -4,14 +4,19 @@ namespace Drupal\Core\Entity\Plugin\EntityReferenceSelection; use Drupal\Component\Utility\Html; use Drupal\Core\Database\Query\AlterableInterface; -use Drupal\Core\Entity\EntityReferenceSelection\SelectionPluginBase; -use Drupal\Core\Entity\EntityReferenceSelection\SelectionTrait; +use Drupal\Core\Database\Query\SelectInterface; +use Drupal\Core\Entity\EntityManagerInterface; use Drupal\Core\Entity\EntityReferenceSelection\SelectionWithAutocreateInterface; 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\Entity\EntityReferenceSelection\SelectionInterface; use Drupal\Core\Plugin\ContainerFactoryPluginInterface; +use Drupal\Core\Plugin\PluginBase; +use Drupal\Core\Session\AccountInterface; use Drupal\user\EntityOwnerInterface; +use Symfony\Component\DependencyInjection\ContainerInterface; /** * Default plugin implementation of the Entity Reference Selection plugin. @@ -33,39 +38,89 @@ use Drupal\user\EntityOwnerInterface; * deriver = "Drupal\Core\Entity\Plugin\Derivative\DefaultSelectionDeriver" * ) */ -class DefaultSelection extends SelectionPluginBase implements ContainerFactoryPluginInterface, SelectionWithAutocreateInterface { +class DefaultSelection extends PluginBase implements SelectionInterface, SelectionWithAutocreateInterface, ContainerFactoryPluginInterface { - use SelectionTrait; + /** + * 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 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(); + 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) { - $form = parent::buildConfigurationForm($form, $form_state); - - $configuration = $this->getConfiguration(); - $entity_type_id = $configuration['target_type']; + $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 += [ + // 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', + ], + 'auto_create' => FALSE, + 'auto_create_bundle' => NULL, + ]; + if ($entity_type->hasKey('bundle')) { $bundle_options = []; foreach ($bundles as $bundle_name => $bundle_info) { @@ -77,7 +132,7 @@ class DefaultSelection extends SelectionPluginBase implements ContainerFactoryPl '#type' => 'checkboxes', '#title' => $this->t('Bundles'), '#options' => $bundle_options, - '#default_value' => (array) $configuration['target_bundles'], + '#default_value' => (array) $selection_handler_settings['target_bundles'], '#required' => TRUE, '#size' => 6, '#multiple' => TRUE, @@ -134,7 +189,7 @@ class DefaultSelection extends SelectionPluginBase implements ContainerFactoryPl ] + $fields, '#ajax' => TRUE, '#limit_validation_errors' => [], - '#default_value' => $configuration['sort']['field'], + '#default_value' => $selection_handler_settings['sort']['field'], ]; $form['sort']['settings'] = [ @@ -143,7 +198,12 @@ class DefaultSelection extends SelectionPluginBase implements ContainerFactoryPl '#process' => [[EntityReferenceItem::class, 'formProcessMergeParent']], ]; - if ($configuration['sort']['field'] != '_none') { + if ($selection_handler_settings['sort']['field'] != '_none') { + // Merge-in default values. + $selection_handler_settings['sort'] += [ + 'direction' => 'ASC', + ]; + $form['sort']['settings']['direction'] = [ '#type' => 'select', '#title' => $this->t('Sort direction'), @@ -152,7 +212,7 @@ class DefaultSelection extends SelectionPluginBase implements ContainerFactoryPl 'ASC' => $this->t('Ascending'), 'DESC' => $this->t('Descending'), ], - '#default_value' => $configuration['sort']['direction'], + '#default_value' => $selection_handler_settings['sort']['direction'], ]; } } @@ -160,17 +220,17 @@ class DefaultSelection extends SelectionPluginBase implements ContainerFactoryPl $form['auto_create'] = [ '#type' => 'checkbox', '#title' => $this->t("Create referenced entities if they don't already exist"), - '#default_value' => $configuration['auto_create'], + '#default_value' => $selection_handler_settings['auto_create'], '#weight' => -2, ]; if ($entity_type->hasKey('bundle')) { - $bundles = array_intersect_key($bundle_options, array_filter((array) $configuration['target_bundles'])); + $bundles = array_intersect_key($bundle_options, array_filter((array) $selection_handler_settings['target_bundles'])); $form['auto_create_bundle'] = [ '#type' => 'select', '#title' => $this->t('Store new items in'), '#options' => $bundles, - '#default_value' => $configuration['auto_create_bundle'], + '#default_value' => $selection_handler_settings['auto_create_bundle'], '#access' => count($bundles) > 1, '#states' => [ 'visible' => [ @@ -188,8 +248,6 @@ class DefaultSelection extends SelectionPluginBase implements ContainerFactoryPl * {@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 @@ -203,6 +261,11 @@ class DefaultSelection extends SelectionPluginBase implements ContainerFactoryPl $form_state->unsetValue(['settings', 'handler_settings', 'target_bundles_update']); } + /** + * {@inheritdoc} + */ + public function submitConfigurationForm(array &$form, FormStateInterface $form_state) { } + /** * Form element validation handler; Filters the #value property of an element. */ @@ -215,7 +278,7 @@ class DefaultSelection extends SelectionPluginBase implements ContainerFactoryPl * {@inheritdoc} */ public function getReferenceableEntities($match = NULL, $match_operator = 'CONTAINS', $limit = 0) { - $target_type = $this->getConfiguration()['target_type']; + $target_type = $this->configuration['target_type']; $query = $this->buildEntityQuery($match, $match_operator); if ($limit > 0) { @@ -290,9 +353,8 @@ class DefaultSelection extends SelectionPluginBase implements ContainerFactoryPl */ 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); + if (isset($this->configuration['handler_settings']['target_bundles'])) { + return in_array($entity->bundle(), $this->configuration['handler_settings']['target_bundles']); } return TRUE; }); @@ -312,23 +374,23 @@ class DefaultSelection extends SelectionPluginBase implements ContainerFactoryPl * it. */ protected function buildEntityQuery($match = NULL, $match_operator = 'CONTAINS') { - $configuration = $this->getConfiguration(); - $target_type = $configuration['target_type']; + $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 (is_array($configuration['target_bundles'])) { + 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 ($configuration['target_bundles'] === []) { + if ($handler_settings['target_bundles'] === []) { $query->condition($entity_type->getKey('id'), NULL, '='); return $query; } else { - $query->condition($entity_type->getKey('bundle'), $configuration['target_bundles'], 'IN'); + $query->condition($entity_type->getKey('bundle'), $handler_settings['target_bundles'], 'IN'); } } @@ -344,13 +406,21 @@ class DefaultSelection extends SelectionPluginBase implements ContainerFactoryPl $query->addMetaData('entity_reference_selection_handler', $this); // Add the sort option. - if ($configuration['sort']['field'] !== '_none') { - $query->sort($configuration['sort']['field'], $configuration['sort']['direction']); + 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. * diff --git a/core/modules/comment/src/Plugin/EntityReferenceSelection/CommentSelection.php b/core/modules/comment/src/Plugin/EntityReferenceSelection/CommentSelection.php index 414c5b23111a..2492fc4336ba 100644 --- a/core/modules/comment/src/Plugin/EntityReferenceSelection/CommentSelection.php +++ b/core/modules/comment/src/Plugin/EntityReferenceSelection/CommentSelection.php @@ -66,8 +66,6 @@ class CommentSelection extends DefaultSelection { * {@inheritdoc} */ public function entityQueryAlter(SelectInterface $query) { - parent::entityQueryAlter($query); - $tables = $query->getTables(); $data_table = 'comment_field_data'; if (!isset($tables['comment_field_data']['alias'])) { diff --git a/core/modules/file/config/schema/file.schema.yml b/core/modules/file/config/schema/file.schema.yml index 9526758ea96f..b9f8918f6331 100644 --- a/core/modules/file/config/schema/file.schema.yml +++ b/core/modules/file/config/schema/file.schema.yml @@ -48,7 +48,7 @@ base_file_field_field_settings: label: 'Reference method' handler_settings: type: entity_reference_selection.[%parent.handler] - label: 'File selection handler settings' + label: 'Entity reference selection settings' file_directory: type: string label: 'File directory' diff --git a/core/modules/system/tests/src/Functional/Entity/EntityReferenceSelection/EntityReferenceSelectionAccessTest.php b/core/modules/system/tests/src/Functional/Entity/EntityReferenceSelection/EntityReferenceSelectionAccessTest.php index 3772f3a97b4f..df33102811fc 100644 --- a/core/modules/system/tests/src/Functional/Entity/EntityReferenceSelection/EntityReferenceSelectionAccessTest.php +++ b/core/modules/system/tests/src/Functional/Entity/EntityReferenceSelection/EntityReferenceSelectionAccessTest.php @@ -74,7 +74,9 @@ class EntityReferenceSelectionAccessTest extends BrowserTestBase { $selection_options = [ 'target_type' => 'node', 'handler' => 'default', - 'target_bundles' => NULL, + 'handler_settings' => [ + 'target_bundles' => NULL, + ], ]; // Build a set of test data. @@ -198,8 +200,10 @@ class EntityReferenceSelectionAccessTest extends BrowserTestBase { $selection_options = [ 'target_type' => 'user', 'handler' => 'default', - 'target_bundles' => NULL, - 'include_anonymous' => TRUE, + 'handler_settings' => [ + 'target_bundles' => NULL, + 'include_anonymous' => TRUE, + ], ]; // Build a set of test data. @@ -318,7 +322,7 @@ class EntityReferenceSelectionAccessTest extends BrowserTestBase { $this->assertReferenceable($selection_options, $referenceable_tests, 'User handler (admin)'); // Test the 'include_anonymous' option. - $selection_options['include_anonymous'] = FALSE; + $selection_options['handler_settings']['include_anonymous'] = FALSE; $referenceable_tests = [ [ 'arguments' => [ @@ -357,7 +361,9 @@ class EntityReferenceSelectionAccessTest extends BrowserTestBase { $selection_options = [ 'target_type' => 'comment', 'handler' => 'default', - 'target_bundles' => NULL, + 'handler_settings' => [ + 'target_bundles' => NULL, + ], ]; // Build a set of test data. diff --git a/core/modules/taxonomy/src/Plugin/EntityReferenceSelection/TermSelection.php b/core/modules/taxonomy/src/Plugin/EntityReferenceSelection/TermSelection.php index a66eaf51c609..b8f4d14fbe1d 100644 --- a/core/modules/taxonomy/src/Plugin/EntityReferenceSelection/TermSelection.php +++ b/core/modules/taxonomy/src/Plugin/EntityReferenceSelection/TermSelection.php @@ -3,6 +3,7 @@ namespace Drupal\taxonomy\Plugin\EntityReferenceSelection; use Drupal\Component\Utility\Html; +use Drupal\Core\Database\Query\SelectInterface; use Drupal\Core\Entity\Plugin\EntityReferenceSelection\DefaultSelection; use Drupal\Core\Form\FormStateInterface; use Drupal\taxonomy\Entity\Vocabulary; @@ -23,13 +24,8 @@ class TermSelection extends DefaultSelection { /** * {@inheritdoc} */ - public function defaultConfiguration() { - return [ - 'sort' => [ - 'field' => 'name', - 'direction' => 'asc', - ] - ] + parent::defaultConfiguration(); + public function entityQueryAlter(SelectInterface $query) { + // @todo: How to set access, as vocabulary is now config? } /** @@ -53,13 +49,15 @@ class TermSelection extends DefaultSelection { */ public function getReferenceableEntities($match = NULL, $match_operator = 'CONTAINS', $limit = 0) { if ($match || $limit) { + $this->configuration['handler_settings']['sort'] = ['field' => 'name', 'direction' => 'asc']; return parent::getReferenceableEntities($match, $match_operator, $limit); } $options = []; $bundles = $this->entityManager->getBundleInfo('taxonomy_term'); - $bundle_names = $this->getConfiguration()['target_bundles'] ?: array_keys($bundles); + $handler_settings = $this->configuration['handler_settings']; + $bundle_names = !empty($handler_settings['target_bundles']) ? $handler_settings['target_bundles'] : array_keys($bundles); foreach ($bundle_names as $bundle) { if ($vocabulary = Vocabulary::load($bundle)) { diff --git a/core/modules/user/config/schema/user.schema.yml b/core/modules/user/config/schema/user.schema.yml index 2f9bda44f28d..627d8a696080 100644 --- a/core/modules/user/config/schema/user.schema.yml +++ b/core/modules/user/config/schema/user.schema.yml @@ -166,10 +166,8 @@ condition.plugin.user_role: sequence: type: string -# Schema for the entity reference 'default:user' selection handler settings. entity_reference_selection.default:user: - type: entity_reference_selection.default - label: 'User selection handler settings' + type: entity_reference_selection mapping: filter: type: mapping diff --git a/core/modules/user/src/Plugin/EntityReferenceSelection/UserSelection.php b/core/modules/user/src/Plugin/EntityReferenceSelection/UserSelection.php index d06a81c41e2d..feec81b46828 100644 --- a/core/modules/user/src/Plugin/EntityReferenceSelection/UserSelection.php +++ b/core/modules/user/src/Plugin/EntityReferenceSelection/UserSelection.php @@ -83,26 +83,21 @@ class UserSelection extends DefaultSelection { /** * {@inheritdoc} */ - public function defaultConfiguration() { - return [ + public function buildConfigurationForm(array $form, FormStateInterface $form_state) { + $selection_handler_settings = $this->configuration['handler_settings']; + + // Merge in default values. + $selection_handler_settings += [ 'filter' => [ 'type' => '_none', - 'role' => NULL, ], 'include_anonymous' => TRUE, - ] + parent::defaultConfiguration(); - } - - /** - * {@inheritdoc} - */ - public function buildConfigurationForm(array $form, FormStateInterface $form_state) { - $configuration = $this->getConfiguration(); + ]; $form['include_anonymous'] = [ '#type' => 'checkbox', '#title' => $this->t('Include the anonymous user.'), - '#default_value' => $configuration['include_anonymous'], + '#default_value' => $selection_handler_settings['include_anonymous'], ]; // Add user specific filter options. @@ -115,7 +110,7 @@ class UserSelection extends DefaultSelection { ], '#ajax' => TRUE, '#limit_validation_errors' => [], - '#default_value' => $configuration['filter']['type'], + '#default_value' => $selection_handler_settings['filter']['type'], ]; $form['filter']['settings'] = [ @@ -124,13 +119,18 @@ class UserSelection extends DefaultSelection { '#process' => [['\Drupal\Core\Field\Plugin\Field\FieldType\EntityReferenceItem', 'formProcessMergeParent']], ]; - if ($configuration['filter']['type'] == 'role') { + if ($selection_handler_settings['filter']['type'] == 'role') { + // Merge in default values. + $selection_handler_settings['filter'] += [ + 'role' => NULL, + ]; + $form['filter']['settings']['role'] = [ '#type' => 'checkboxes', '#title' => $this->t('Restrict to the selected roles'), '#required' => TRUE, '#options' => array_diff_key(user_role_names(TRUE), [RoleInterface::AUTHENTICATED_ID => RoleInterface::AUTHENTICATED_ID]), - '#default_value' => $configuration['filter']['role'], + '#default_value' => $selection_handler_settings['filter']['role'], ]; } @@ -144,12 +144,11 @@ class UserSelection extends DefaultSelection { */ protected function buildEntityQuery($match = NULL, $match_operator = 'CONTAINS') { $query = parent::buildEntityQuery($match, $match_operator); - - $configuration = $this->getConfiguration(); + $handler_settings = $this->configuration['handler_settings']; // Filter out the Anonymous user if the selection handler is configured to // exclude it. - if (!$configuration['include_anonymous']) { + if (isset($handler_settings['include_anonymous']) && !$handler_settings['include_anonymous']) { $query->condition('uid', 0, '<>'); } @@ -159,8 +158,8 @@ class UserSelection extends DefaultSelection { } // Filter by role. - if (!empty($configuration['filter']['role'])) { - $query->condition('roles', $configuration['filter']['role'], 'IN'); + if (!empty($handler_settings['filter']['role'])) { + $query->condition('roles', $handler_settings['filter']['role'], 'IN'); } // Adding the permission check is sadly insufficient for users: core @@ -192,10 +191,10 @@ class UserSelection extends DefaultSelection { public function validateReferenceableNewEntities(array $entities) { $entities = parent::validateReferenceableNewEntities($entities); // Mirror the conditions checked in buildEntityQuery(). - if ($role = $this->getConfiguration()['filter']['role']) { - $entities = array_filter($entities, function ($user) use ($role) { + if (!empty($this->configuration['handler_settings']['filter']['role'])) { + $entities = array_filter($entities, function ($user) { /** @var \Drupal\user\UserInterface $user */ - return !empty(array_intersect($user->getRoles(), $role)); + return !empty(array_intersect($user->getRoles(), $this->configuration['handler_settings']['filter']['role'])); }); } if (!$this->currentUser->hasPermission('administer users')) { @@ -211,10 +210,9 @@ class UserSelection extends DefaultSelection { * {@inheritdoc} */ public function entityQueryAlter(SelectInterface $query) { - parent::entityQueryAlter($query); - // Bail out early if we do not need to match the Anonymous user. - if (!$this->getConfiguration()['include_anonymous']) { + $handler_settings = $this->configuration['handler_settings']; + if (isset($handler_settings['include_anonymous']) && !$handler_settings['include_anonymous']) { return; } diff --git a/core/modules/views/config/schema/views.entity_reference.schema.yml b/core/modules/views/config/schema/views.entity_reference.schema.yml index f13645ab4320..027c62fa46c1 100644 --- a/core/modules/views/config/schema/views.entity_reference.schema.yml +++ b/core/modules/views/config/schema/views.entity_reference.schema.yml @@ -1,8 +1,8 @@ -# Schema for the entity reference 'views' selection handler settings. +# Schema for the views entity reference selection plugins. entity_reference_selection.views: - type: entity_reference_selection - label: 'Views selection handler settings' + type: mapping + label: 'View handler settings' mapping: view: type: mapping diff --git a/core/modules/views/src/Plugin/EntityReferenceSelection/ViewsSelection.php b/core/modules/views/src/Plugin/EntityReferenceSelection/ViewsSelection.php index 9911f61a0c87..05ac9b99f14e 100644 --- a/core/modules/views/src/Plugin/EntityReferenceSelection/ViewsSelection.php +++ b/core/modules/views/src/Plugin/EntityReferenceSelection/ViewsSelection.php @@ -2,12 +2,17 @@ namespace Drupal\views\Plugin\EntityReferenceSelection; -use Drupal\Core\Entity\EntityReferenceSelection\SelectionPluginBase; -use Drupal\Core\Entity\EntityReferenceSelection\SelectionTrait; +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. @@ -19,9 +24,66 @@ use Drupal\views\Views; * weight = 0 * ) */ -class ViewsSelection extends SelectionPluginBase implements ContainerFactoryPluginInterface { +class ViewsSelection extends PluginBase implements SelectionInterface, ContainerFactoryPluginInterface { - use SelectionTrait; + /** + * 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. @@ -30,26 +92,12 @@ class ViewsSelection extends SelectionPluginBase implements ContainerFactoryPlug */ protected $view; - /** - * {@inheritdoc} - */ - public function defaultConfiguration() { - return [ - 'view' => [ - 'view_name' => NULL, - 'display_name' => NULL, - 'arguments' => [], - ], - ] + parent::defaultConfiguration(); - } - /** * {@inheritdoc} */ public function buildConfigurationForm(array $form, FormStateInterface $form_state) { - $form = parent::buildConfigurationForm($form, $form_state); - - $view_settings = $this->getConfiguration()['view']; + $selection_handler_settings = $this->configuration['handler_settings']; + $view_settings = !empty($selection_handler_settings['view']) ? $selection_handler_settings['view'] : []; $displays = Views::getApplicableViews('entity_reference_display'); // Filter views that list the entity type we want, and group the separate // displays by view. @@ -108,6 +156,16 @@ class ViewsSelection extends SelectionPluginBase implements ContainerFactoryPlug return $form; } + /** + * {@inheritdoc} + */ + public function validateConfigurationForm(array &$form, FormStateInterface $form_state) { } + + /** + * {@inheritdoc} + */ + public function submitConfigurationForm(array &$form, FormStateInterface $form_state) { } + /** * Initializes a view. * @@ -126,8 +184,9 @@ class ViewsSelection extends SelectionPluginBase implements ContainerFactoryPlug * Return TRUE if the view was initialized, FALSE otherwise. */ protected function initializeView($match = NULL, $match_operator = 'CONTAINS', $limit = 0, $ids = NULL) { - $view_name = $this->getConfiguration()['view']['view_name']; - $display_name = $this->getConfiguration()['view']['display_name']; + $handler_settings = $this->configuration['handler_settings']; + $view_name = $handler_settings['view']['view_name']; + $display_name = $handler_settings['view']['display_name']; // Check that the view is valid and the display still exists. $this->view = Views::getView($view_name); @@ -152,8 +211,9 @@ class ViewsSelection extends SelectionPluginBase implements ContainerFactoryPlug * {@inheritdoc} */ public function getReferenceableEntities($match = NULL, $match_operator = 'CONTAINS', $limit = 0) { - $display_name = $this->getConfiguration()['view']['display_name']; - $arguments = $this->getConfiguration()['view']['arguments']; + $handler_settings = $this->configuration['handler_settings']; + $display_name = $handler_settings['view']['display_name']; + $arguments = $handler_settings['view']['arguments']; $result = []; if ($this->initializeView($match, $match_operator, $limit)) { // Get the results. @@ -182,8 +242,9 @@ class ViewsSelection extends SelectionPluginBase implements ContainerFactoryPlug * {@inheritdoc} */ public function validateReferenceableEntities(array $ids) { - $display_name = $this->getConfiguration()['view']['display_name']; - $arguments = $this->getConfiguration()['view']['arguments']; + $handler_settings = $this->configuration['handler_settings']; + $display_name = $handler_settings['view']['display_name']; + $arguments = $handler_settings['view']['arguments']; $result = []; if ($this->initializeView(NULL, 'CONTAINS', 0, $ids)) { // Get the results. @@ -222,4 +283,9 @@ class ViewsSelection extends SelectionPluginBase implements ContainerFactoryPlug $form_state->setValueForElement($element, $value); } + /** + * {@inheritdoc} + */ + public function entityQueryAlter(SelectInterface $query) { } + } diff --git a/core/tests/Drupal/KernelTests/Core/Entity/EntityReferenceSelection/EntityReferenceSelectionSortTest.php b/core/tests/Drupal/KernelTests/Core/Entity/EntityReferenceSelection/EntityReferenceSelectionSortTest.php index b2c72b99d92c..eb13d0e49126 100644 --- a/core/tests/Drupal/KernelTests/Core/Entity/EntityReferenceSelection/EntityReferenceSelectionSortTest.php +++ b/core/tests/Drupal/KernelTests/Core/Entity/EntityReferenceSelection/EntityReferenceSelectionSortTest.php @@ -96,11 +96,13 @@ class EntityReferenceSelectionSortTest extends EntityKernelTestBase { $selection_options = [ 'target_type' => 'node', 'handler' => 'default', - 'target_bundles' => NULL, - // Add sorting. - 'sort' => [ - 'field' => 'field_text.value', - 'direction' => 'DESC', + 'handler_settings' => [ + 'target_bundles' => NULL, + // Add sorting. + 'sort' => [ + 'field' => 'field_text.value', + 'direction' => 'DESC', + ], ], ]; $handler = $this->container->get('plugin.manager.entity_reference_selection')->getInstance($selection_options); @@ -115,7 +117,7 @@ class EntityReferenceSelectionSortTest extends EntityKernelTestBase { $this->assertIdentical($result['article'], $expected_result, 'Query sorted by field returned expected values.'); // Assert sort by base field. - $selection_options['sort'] = [ + $selection_options['handler_settings']['sort'] = [ 'field' => 'nid', 'direction' => 'ASC', ]; diff --git a/core/tests/Drupal/Tests/Core/EntityReferenceSelection/EntityReferenceSelectionUnitTest.php b/core/tests/Drupal/Tests/Core/EntityReferenceSelection/EntityReferenceSelectionUnitTest.php deleted file mode 100644 index eec5b0f0dce2..000000000000 --- a/core/tests/Drupal/Tests/Core/EntityReferenceSelection/EntityReferenceSelectionUnitTest.php +++ /dev/null @@ -1,235 +0,0 @@ -setExpectedException(\InvalidArgumentException::class, "TestSelectionWithInvalidDefaultConfiguration::defaultConfiguration() should not contain a 'handler_settings' key. All settings should be placed in the root level."); - new TestSelectionWithInvalidDefaultConfiguration( - [], - 'test_selector', - ['class' => 'TestSelectionWithInvalidDefaultConfiguration'] - ); - } - - /** - * Tests the selection handler with malformed 'handler_settings' value. - * - * @covers ::setConfiguration - * @covers ::resolveBackwardCompatibilityConfiguration - */ - public function testMalformedHandlerSettingsValue() { - $this->setExpectedException(\InvalidArgumentException::class, "The setting 'handler_settings' is reserved and cannot be used."); - new TestSelection( - // The deprecated 'handler_setting' should be an array. - ['handler_settings' => FALSE], - 'test_selector', - ['class' => 'TestSelectionWithInvalidDefaultConfiguration'] - ); - } - - /** - * Provides test data for ::testSetConfiguration() - * - * @return array - * - * @see \Drupal\Tests\Core\EntityReferenceSelection\testSetConfiguration - */ - public function providerTestSetConfiguration() { - return [ - [ - [ - 'setting1' => 'foo', - 'setting2' => [ - 'bar' => 'bar value', - 'baz' => 'baz value', - ], - ], - ], - [ - [ - 'handler_settings' => [ - 'setting1' => 'foo', - 'setting2' => [ - 'bar' => 'bar value', - 'baz' => 'baz value', - ], - ], - ], - ], - [ - [ - 'setting1' => 'foo', - 'handler_settings' => [ - 'setting2' => [ - 'bar' => 'bar value', - 'baz' => 'baz value', - ], - ] - ], - ], - [ - [ - 'setting1' => 'foo', - 'setting2' => [ - 'bar' => 'bar value', - 'baz' => 'baz value', - ], - 'handler_settings' => [ - // Same setting from root level takes precedence. - 'setting2' => 'this will be overwritten', - ] - ], - ], - ]; - } - - /** - * Tests selection handler plugin configuration set. - * - * @dataProvider providerTestSetConfiguration - * @covers ::setConfiguration - * @covers ::resolveBackwardCompatibilityConfiguration - * @covers ::ensureBackwardCompatibilityConfiguration - * - * @param array $options - * The configuration passed to the plugin. - */ - public function testSetConfiguration($options) { - $selection = new TestSelection($options, 'test_selector', []); - - $expected = [ - 'target_type' => NULL, - 'handler' => 'test_selector', - 'entity' => NULL, - 'setting1' => 'foo', - 'setting2' => [ - 'qux' => 'qux value', - 'bar' => 'bar value', - 'baz' => 'baz value', - ], - 'setting3' => 'foobar', - 'handler_settings' => [ - 'setting1' => 'foo', - 'setting2' => [ - 'qux' => 'qux value', - 'bar' => 'bar value', - 'baz' => 'baz value', - ], - 'setting3' => 'foobar', - ], - ]; - - $this->assertArrayEquals($expected, $selection->getConfiguration()); - } - - /** - * Tests the selection handler plugin BC structure. - * - * @covers ::setConfiguration - * @covers ::resolveBackwardCompatibilityConfiguration - * @covers ::ensureBackwardCompatibilityConfiguration - */ - public function testSetConfigurationBcLevel() { - $config = [ - 'target_type' => 'some_entity_type_id', - 'handler' => 'test_selector', - 'setting1' => 'foo', - ]; - $selection = new TestSelection($config, 'test_selector', []); - - $expected = [ - 'target_type' => 'some_entity_type_id', - 'handler' => 'test_selector', - 'entity' => NULL, - 'setting1' => 'foo', - 'setting2' => ['qux' => 'qux value'], - 'setting3' => 'foobar', - 'handler_settings' => [ - 'setting1' => 'foo', - 'setting2' => ['qux' => 'qux value'], - 'setting3' => 'foobar', - ], - ]; - - $this->assertArrayEquals($expected, $selection->getConfiguration()); - - // Read the stored values and override a setting. - $config = $selection->getConfiguration(); - $config['setting1'] = 'bar'; - $selection->setConfiguration($config); - $expected['setting1'] = 'bar'; - $expected['handler_settings']['setting1'] = 'bar'; - - $this->assertArrayEquals($expected, $selection->getConfiguration()); - } - - /** - * Tests deprecation error triggering. - * - * @covers ::setConfiguration - * @covers ::resolveBackwardCompatibilityConfiguration - * @expectedDeprecation Providing settings under 'handler_settings' is deprecated and will be removed before 9.0.0. Move the settings in the root of the configuration array. See https://www.drupal.org/node/2870971. - */ - public function testDeprecationErrorTriggering() { - // Configuration with BC level. - $config = ['handler_settings' => ['setting1' => TRUE]]; - new TestSelection($config, 'test_selector', []); - // Ensure at least one assertion. - $this->assertTrue(TRUE); - } - -} - -/** - * Provides a testing plugin. - */ -class TestSelection extends SelectionPluginBase { - - public function defaultConfiguration() { - return [ - 'setting2' => [ - 'qux' => 'qux value', - ], - 'setting3' => 'foobar', - ] + parent::defaultConfiguration(); - } - - public function getReferenceableEntities($match = NULL, $match_operator = 'CONTAINS', $limit = 0) { } - - public function validateReferenceableEntities(array $ids) { } - - public function countReferenceableEntities($match = NULL, $match_operator = 'CONTAINS') { } - -} - -/** - * Provides a testing plugin with invalid default configuration. - */ -class TestSelectionWithInvalidDefaultConfiguration extends TestSelection { - - public function defaultConfiguration() { - return [ - 'handler_settings' => ['foo' => 'bar'], - ]; - } - -}