UserSelection.php

Same filename in other branches
  1. 9 core/modules/user/src/Plugin/EntityReferenceSelection/UserSelection.php
  2. 8.9.x core/modules/user/src/Plugin/EntityReferenceSelection/UserSelection.php
  3. 10 core/modules/user/src/Plugin/EntityReferenceSelection/UserSelection.php

Namespace

Drupal\user\Plugin\EntityReferenceSelection

File

core/modules/user/src/Plugin/EntityReferenceSelection/UserSelection.php

View source
<?php

namespace Drupal\user\Plugin\EntityReferenceSelection;

use Drupal\Core\Database\Connection;
use Drupal\Core\Database\Query\SelectInterface;
use Drupal\Core\Entity\Attribute\EntityReferenceSelection;
use Drupal\Core\Entity\EntityFieldManagerInterface;
use Drupal\Core\Entity\EntityRepositoryInterface;
use Drupal\Core\Entity\EntityTypeBundleInfoInterface;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Entity\Plugin\EntityReferenceSelection\DefaultSelection;
use Drupal\Core\Extension\ModuleHandlerInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Session\AccountInterface;
use Drupal\Core\StringTranslation\TranslatableMarkup;
use Drupal\user\Entity\Role;
use Drupal\user\RoleInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;

/**
 * Provides specific access control for the user entity type.
 */
class UserSelection extends DefaultSelection {
    
    /**
     * The database connection.
     *
     * @var \Drupal\Core\Database\Connection
     */
    protected $connection;
    
    /**
     * Constructs a new UserSelection 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\EntityTypeManagerInterface $entity_type_manager
     *   The entity type manager service.
     * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
     *   The module handler service.
     * @param \Drupal\Core\Session\AccountInterface $current_user
     *   The current user.
     * @param \Drupal\Core\Database\Connection $connection
     *   The database connection.
     * @param \Drupal\Core\Entity\EntityFieldManagerInterface $entity_field_manager
     *   The entity field manager.
     * @param \Drupal\Core\Entity\EntityTypeBundleInfoInterface $entity_type_bundle_info
     *   The entity type bundle info service.
     * @param \Drupal\Core\Entity\EntityRepositoryInterface $entity_repository
     *   The entity repository.
     */
    public function __construct(array $configuration, $plugin_id, $plugin_definition, EntityTypeManagerInterface $entity_type_manager, ModuleHandlerInterface $module_handler, AccountInterface $current_user, Connection $connection, ?EntityFieldManagerInterface $entity_field_manager = NULL, ?EntityTypeBundleInfoInterface $entity_type_bundle_info = NULL, ?EntityRepositoryInterface $entity_repository = NULL) {
        parent::__construct($configuration, $plugin_id, $plugin_definition, $entity_type_manager, $module_handler, $current_user, $entity_field_manager, $entity_type_bundle_info, $entity_repository);
        $this->connection = $connection;
    }
    
    /**
     * {@inheritdoc}
     */
    public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
        return new static($configuration, $plugin_id, $plugin_definition, $container->get('entity_type.manager'), $container->get('module_handler'), $container->get('current_user'), $container->get('database'), $container->get('entity_field.manager'), $container->get('entity_type.bundle.info'), $container->get('entity.repository'));
    }
    
    /**
     * {@inheritdoc}
     */
    public function defaultConfiguration() {
        return [
            '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'],
        ];
        // Add user specific filter options.
        $form['filter']['type'] = [
            '#type' => 'select',
            '#title' => $this->t('Filter by'),
            '#options' => [
                '_none' => $this->t('- None -'),
                'role' => $this->t('User role'),
            ],
            // Use a form process callback to build #ajax property properly and also
            // to avoid code duplication.
            // @see \Drupal\Core\Field\Plugin\Field\FieldType\EntityReferenceItem::fieldSettingsAjaxProcess()
'#ajax' => TRUE,
            '#limit_validation_errors' => [],
            '#default_value' => $configuration['filter']['type'],
        ];
        $form['filter']['settings'] = [
            '#type' => 'container',
            '#attributes' => [
                'class' => [
                    'entity_reference-settings',
                ],
            ],
            '#process' => [
                [
                    '\\Drupal\\Core\\Field\\Plugin\\Field\\FieldType\\EntityReferenceItem',
                    'formProcessMergeParent',
                ],
            ],
        ];
        if ($configuration['filter']['type'] == 'role') {
            $roles = Role::loadMultiple();
            unset($roles[RoleInterface::ANONYMOUS_ID]);
            unset($roles[RoleInterface::AUTHENTICATED_ID]);
            $roles = array_map(fn(RoleInterface $role) => $role->label(), $roles);
            $form['filter']['settings']['role'] = [
                '#type' => 'checkboxes',
                '#title' => $this->t('Restrict to the selected roles'),
                '#required' => TRUE,
                '#options' => $roles,
                '#default_value' => $configuration['filter']['role'],
            ];
        }
        $form += parent::buildConfigurationForm($form, $form_state);
        return $form;
    }
    
    /**
     * {@inheritdoc}
     */
    protected function buildEntityQuery($match = NULL, $match_operator = 'CONTAINS') {
        $query = parent::buildEntityQuery($match, $match_operator);
        $configuration = $this->getConfiguration();
        // Filter out the Anonymous user if the selection handler is configured to
        // exclude it.
        if (!$configuration['include_anonymous']) {
            $query->condition('uid', 0, '<>');
        }
        // The user entity doesn't have a label column.
        if (isset($match)) {
            $query->condition('name', $match, $match_operator);
        }
        // Filter by role.
        if (!empty($configuration['filter']['role'])) {
            $query->condition('roles', $configuration['filter']['role'], 'IN');
        }
        // Adding the permission check is sadly insufficient for users: core
        // requires us to also know about the concept of 'blocked' and 'active'.
        if (!$this->currentUser
            ->hasPermission('administer users')) {
            $query->condition('status', 1);
        }
        return $query;
    }
    
    /**
     * {@inheritdoc}
     */
    public function createNewEntity($entity_type_id, $bundle, $label, $uid) {
        $user = parent::createNewEntity($entity_type_id, $bundle, $label, $uid);
        // In order to create a referenceable user, it needs to be active.
        if (!$this->currentUser
            ->hasPermission('administer users')) {
            
            /** @var \Drupal\user\UserInterface $user */
            $user->activate();
        }
        return $user;
    }
    
    /**
     * {@inheritdoc}
     */
    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) {
                
                /** @var \Drupal\user\UserInterface $user */
                return !empty(array_intersect($user->getRoles(), $role));
            });
        }
        if (!$this->currentUser
            ->hasPermission('administer users')) {
            $entities = array_filter($entities, function ($user) {
                
                /** @var \Drupal\user\UserInterface $user */
                return $user->isActive();
            });
        }
        return $entities;
    }
    
    /**
     * {@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']) {
            return;
        }
        if ($this->currentUser
            ->hasPermission('administer users')) {
            // In addition, if the user is administrator, we need to make sure to
            // match the anonymous user, that doesn't actually have a name in the
            // database.
            $conditions =& $query->conditions();
            foreach ($conditions as $key => $condition) {
                if ($key !== '#conjunction' && is_string($condition['field']) && $condition['field'] === 'users_field_data.name') {
                    // Remove the condition.
                    unset($conditions[$key]);
                    // Re-add the condition and a condition on uid = 0 so that we end up
                    // with a query in the form:
                    // WHERE (name LIKE :name) OR (:anonymous_name LIKE :name AND uid = 0)
                    $or = $this->connection
                        ->condition('OR');
                    $or->condition($condition['field'], $condition['value'], $condition['operator']);
                    // Sadly, the Database layer doesn't allow us to build a condition
                    // in the form ':placeholder = :placeholder2', because the 'field'
                    // part of a condition is always escaped.
                    // As a (cheap) workaround, we separately build a condition with no
                    // field, and concatenate the field and the condition separately.
                    $value_part = $this->connection
                        ->condition('AND');
                    $value_part->condition('anonymous_name', $condition['value'], $condition['operator']);
                    $value_part->compile($this->connection, $query);
                    $or->condition($this->connection
                        ->condition('AND')
                        ->where(str_replace($query->escapeField('anonymous_name'), ':anonymous_name', (string) $value_part), $value_part->arguments() + [
                        ':anonymous_name' => \Drupal::config('user.settings')->get('anonymous'),
                    ])
                        ->condition('base_table.uid', 0));
                    $query->condition($or);
                }
            }
        }
    }

}

Classes

Title Deprecated Summary
UserSelection Provides specific access control for the user entity type.

Buggy or inaccurate documentation? Please file an issue. Need support? Need help programming? Connect with the Drupal community.