Revert "Issue #2787873 by claudiu.cristea, amateescu, jibran, dawehner, catch: Add a base class for entity reference selection handlers and fix the structure of their configuration"

This reverts commit b0900c73b1.
8.4.x
Chris McCafferty 2017-05-27 20:49:12 -04:00
parent 05ffa70cf5
commit d5d37e0649
18 changed files with 288 additions and 619 deletions

View File

@ -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

View File

@ -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);

View File

@ -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);

View File

@ -1,160 +0,0 @@
<?php
namespace Drupal\Core\Entity\EntityReferenceSelection;
use Drupal\Component\Plugin\ConfigurablePluginInterface;
use Drupal\Component\Utility\NestedArray;
use Drupal\Core\Database\Query\SelectInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Plugin\PluginBase;
/**
* Provides a base class for configurable selection handlers.
*/
abstract class SelectionPluginBase extends PluginBase implements SelectionInterface, ConfigurablePluginInterface {
/**
* Constructs a new selection 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.
*/
public function __construct(array $configuration, $plugin_id, $plugin_definition) {
parent::__construct($configuration, $plugin_id, $plugin_definition);
$this->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;
}
}
}
}

View File

@ -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);

View File

@ -1,74 +0,0 @@
<?php
namespace Drupal\Core\Entity\EntityReferenceSelection;
use Drupal\Core\Entity\EntityManagerInterface;
use Drupal\Core\Extension\ModuleHandlerInterface;
use Drupal\Core\Session\AccountInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Provides common methods and injects services for core selection handlers.
*/
trait SelectionTrait {
/**
* The entity manager service.
*
* @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 selection 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')
);
}
}

View File

@ -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) { }
}

View File

@ -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.
*

View File

@ -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'])) {

View File

@ -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'

View File

@ -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.

View File

@ -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)) {

View File

@ -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

View File

@ -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;
}

View File

@ -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

View File

@ -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) { }
}

View File

@ -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',
];

View File

@ -1,235 +0,0 @@
<?php
namespace Drupal\Tests\Core\EntityReferenceSelection;
use Drupal\Core\Entity\EntityReferenceSelection\SelectionPluginBase;
use Drupal\Tests\UnitTestCase;
/**
* Provides unit testing for selection handlers.
*
* @coversDefaultClass \Drupal\Core\Entity\EntityReferenceSelection\SelectionPluginBase
*
* @group entity_reference
* @group legacy
*/
class EntityReferenceSelectionUnitTest extends UnitTestCase {
/**
* Tests invalid default configuration.
*
* @covers ::defaultConfiguration
* @covers ::resolveBackwardCompatibilityConfiguration
*/
public function testInvalidDefaultConfiguration() {
$this->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'],
];
}
}