3 namespace Drupal\user\Plugin\EntityReferenceSelection;
5 use Drupal\Core\Database\Connection;
6 use Drupal\Core\Database\Query\SelectInterface;
7 use Drupal\Core\Entity\EntityManagerInterface;
8 use Drupal\Core\Entity\Plugin\EntityReferenceSelection\DefaultSelection;
9 use Drupal\Core\Extension\ModuleHandlerInterface;
10 use Drupal\Core\Form\FormStateInterface;
11 use Drupal\Core\Session\AccountInterface;
12 use Drupal\user\RoleInterface;
13 use Symfony\Component\DependencyInjection\ContainerInterface;
16 * Provides specific access control for the user entity type.
18 * @EntityReferenceSelection(
19 * id = "default:user",
20 * label = @Translation("User selection"),
21 * entity_types = {"user"},
26 class UserSelection extends DefaultSelection {
29 * The database connection.
31 * @var \Drupal\Core\Database\Connection
33 protected $connection;
38 * @var \Drupal\user\UserStorageInterface
40 protected $userStorage;
43 * Constructs a new UserSelection object.
45 * @param array $configuration
46 * A configuration array containing information about the plugin instance.
47 * @param string $plugin_id
48 * The plugin_id for the plugin instance.
49 * @param mixed $plugin_definition
50 * The plugin implementation definition.
51 * @param \Drupal\Core\Entity\EntityManagerInterface $entity_manager
52 * The entity manager service.
53 * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
54 * The module handler service.
55 * @param \Drupal\Core\Session\AccountInterface $current_user
57 * @param \Drupal\Core\Database\Connection $connection
58 * The database connection.
60 public function __construct(array $configuration, $plugin_id, $plugin_definition, EntityManagerInterface $entity_manager, ModuleHandlerInterface $module_handler, AccountInterface $current_user, Connection $connection) {
61 parent::__construct($configuration, $plugin_id, $plugin_definition, $entity_manager, $module_handler, $current_user);
63 $this->connection = $connection;
64 $this->userStorage = $entity_manager->getStorage('user');
70 public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
75 $container->get('entity.manager'),
76 $container->get('module_handler'),
77 $container->get('current_user'),
78 $container->get('database')
85 public function buildConfigurationForm(array $form, FormStateInterface $form_state) {
86 $selection_handler_settings = $this->configuration['handler_settings'];
88 // Merge in default values.
89 $selection_handler_settings += [
93 'include_anonymous' => TRUE,
96 $form['include_anonymous'] = [
97 '#type' => 'checkbox',
98 '#title' => $this->t('Include the anonymous user.'),
99 '#default_value' => $selection_handler_settings['include_anonymous'],
102 // Add user specific filter options.
103 $form['filter']['type'] = [
105 '#title' => $this->t('Filter by'),
107 '_none' => $this->t('- None -'),
108 'role' => $this->t('User role'),
111 '#limit_validation_errors' => [],
112 '#default_value' => $selection_handler_settings['filter']['type'],
115 $form['filter']['settings'] = [
116 '#type' => 'container',
117 '#attributes' => ['class' => ['entity_reference-settings']],
118 '#process' => [['\Drupal\Core\Field\Plugin\Field\FieldType\EntityReferenceItem', 'formProcessMergeParent']],
121 if ($selection_handler_settings['filter']['type'] == 'role') {
122 // Merge in default values.
123 $selection_handler_settings['filter'] += [
127 $form['filter']['settings']['role'] = [
128 '#type' => 'checkboxes',
129 '#title' => $this->t('Restrict to the selected roles'),
131 '#options' => array_diff_key(user_role_names(TRUE), [RoleInterface::AUTHENTICATED_ID => RoleInterface::AUTHENTICATED_ID]),
132 '#default_value' => $selection_handler_settings['filter']['role'],
136 $form += parent::buildConfigurationForm($form, $form_state);
144 protected function buildEntityQuery($match = NULL, $match_operator = 'CONTAINS') {
145 $query = parent::buildEntityQuery($match, $match_operator);
146 $handler_settings = $this->configuration['handler_settings'];
148 // Filter out the Anonymous user if the selection handler is configured to
150 if (isset($handler_settings['include_anonymous']) && !$handler_settings['include_anonymous']) {
151 $query->condition('uid', 0, '<>');
154 // The user entity doesn't have a label column.
156 $query->condition('name', $match, $match_operator);
160 if (!empty($handler_settings['filter']['role'])) {
161 $query->condition('roles', $handler_settings['filter']['role'], 'IN');
164 // Adding the permission check is sadly insufficient for users: core
165 // requires us to also know about the concept of 'blocked' and 'active'.
166 if (!$this->currentUser->hasPermission('administer users')) {
167 $query->condition('status', 1);
175 public function createNewEntity($entity_type_id, $bundle, $label, $uid) {
176 $user = parent::createNewEntity($entity_type_id, $bundle, $label, $uid);
178 // In order to create a referenceable user, it needs to be active.
179 if (!$this->currentUser->hasPermission('administer users')) {
180 /** @var \Drupal\user\UserInterface $user */
190 public function validateReferenceableNewEntities(array $entities) {
191 $entities = parent::validateReferenceableNewEntities($entities);
192 // Mirror the conditions checked in buildEntityQuery().
193 if (!empty($this->configuration['handler_settings']['filter']['role'])) {
194 $entities = array_filter($entities, function ($user) {
195 /** @var \Drupal\user\UserInterface $user */
196 return !empty(array_intersect($user->getRoles(), $this->configuration['handler_settings']['filter']['role']));
199 if (!$this->currentUser->hasPermission('administer users')) {
200 $entities = array_filter($entities, function ($user) {
201 /** @var \Drupal\user\UserInterface $user */
202 return $user->isActive();
211 public function entityQueryAlter(SelectInterface $query) {
212 // Bail out early if we do not need to match the Anonymous user.
213 $handler_settings = $this->configuration['handler_settings'];
214 if (isset($handler_settings['include_anonymous']) && !$handler_settings['include_anonymous']) {
218 if ($this->currentUser->hasPermission('administer users')) {
219 // In addition, if the user is administrator, we need to make sure to
220 // match the anonymous user, that doesn't actually have a name in the
222 $conditions = &$query->conditions();
223 foreach ($conditions as $key => $condition) {
224 if ($key !== '#conjunction' && is_string($condition['field']) && $condition['field'] === 'users_field_data.name') {
225 // Remove the condition.
226 unset($conditions[$key]);
228 // Re-add the condition and a condition on uid = 0 so that we end up
229 // with a query in the form:
230 // WHERE (name LIKE :name) OR (:anonymous_name LIKE :name AND uid = 0)
232 $or->condition($condition['field'], $condition['value'], $condition['operator']);
233 // Sadly, the Database layer doesn't allow us to build a condition
234 // in the form ':placeholder = :placeholder2', because the 'field'
235 // part of a condition is always escaped.
236 // As a (cheap) workaround, we separately build a condition with no
237 // field, and concatenate the field and the condition separately.
238 $value_part = db_and();
239 $value_part->condition('anonymous_name', $condition['value'], $condition['operator']);
240 $value_part->compile($this->connection, $query);
241 $or->condition(db_and()
242 ->where(str_replace('anonymous_name', ':anonymous_name', (string) $value_part), $value_part->arguments() + [':anonymous_name' => \Drupal::config('user.settings')->get('anonymous')])
243 ->condition('base_table.uid', 0)
245 $query->condition($or);