b2887afb05f9936f24aa43e051b61b99686b344c
[yaffs-website] / web / core / modules / user / src / Plugin / EntityReferenceSelection / UserSelection.php
1 <?php
2
3 namespace Drupal\user\Plugin\EntityReferenceSelection;
4
5 use Drupal\Core\Database\Connection;
6 use Drupal\Core\Database\Query\Condition;
7 use Drupal\Core\Database\Query\SelectInterface;
8 use Drupal\Core\Entity\EntityManagerInterface;
9 use Drupal\Core\Entity\Plugin\EntityReferenceSelection\DefaultSelection;
10 use Drupal\Core\Extension\ModuleHandlerInterface;
11 use Drupal\Core\Form\FormStateInterface;
12 use Drupal\Core\Session\AccountInterface;
13 use Drupal\user\RoleInterface;
14 use Symfony\Component\DependencyInjection\ContainerInterface;
15
16 /**
17  * Provides specific access control for the user entity type.
18  *
19  * @EntityReferenceSelection(
20  *   id = "default:user",
21  *   label = @Translation("User selection"),
22  *   entity_types = {"user"},
23  *   group = "default",
24  *   weight = 1
25  * )
26  */
27 class UserSelection extends DefaultSelection {
28
29   /**
30    * The database connection.
31    *
32    * @var \Drupal\Core\Database\Connection
33    */
34   protected $connection;
35
36   /**
37    * The user storage.
38    *
39    * @var \Drupal\user\UserStorageInterface
40    */
41   protected $userStorage;
42
43   /**
44    * Constructs a new UserSelection object.
45    *
46    * @param array $configuration
47    *   A configuration array containing information about the plugin instance.
48    * @param string $plugin_id
49    *   The plugin_id for the plugin instance.
50    * @param mixed $plugin_definition
51    *   The plugin implementation definition.
52    * @param \Drupal\Core\Entity\EntityManagerInterface $entity_manager
53    *   The entity manager service.
54    * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
55    *   The module handler service.
56    * @param \Drupal\Core\Session\AccountInterface $current_user
57    *   The current user.
58    * @param \Drupal\Core\Database\Connection $connection
59    *   The database connection.
60    */
61   public function __construct(array $configuration, $plugin_id, $plugin_definition, EntityManagerInterface $entity_manager, ModuleHandlerInterface $module_handler, AccountInterface $current_user, Connection $connection) {
62     parent::__construct($configuration, $plugin_id, $plugin_definition, $entity_manager, $module_handler, $current_user);
63
64     $this->connection = $connection;
65     $this->userStorage = $entity_manager->getStorage('user');
66   }
67
68   /**
69    * {@inheritdoc}
70    */
71   public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
72     return new static(
73       $configuration,
74       $plugin_id,
75       $plugin_definition,
76       $container->get('entity.manager'),
77       $container->get('module_handler'),
78       $container->get('current_user'),
79       $container->get('database')
80     );
81   }
82
83   /**
84    * {@inheritdoc}
85    */
86   public function defaultConfiguration() {
87     return [
88       'filter' => [
89         'type' => '_none',
90         'role' => NULL,
91       ],
92       'include_anonymous' => TRUE,
93     ] + parent::defaultConfiguration();
94   }
95
96   /**
97    * {@inheritdoc}
98    */
99   public function buildConfigurationForm(array $form, FormStateInterface $form_state) {
100     $configuration = $this->getConfiguration();
101
102     $form['include_anonymous'] = [
103       '#type' => 'checkbox',
104       '#title' => $this->t('Include the anonymous user.'),
105       '#default_value' => $configuration['include_anonymous'],
106     ];
107
108     // Add user specific filter options.
109     $form['filter']['type'] = [
110       '#type' => 'select',
111       '#title' => $this->t('Filter by'),
112       '#options' => [
113         '_none' => $this->t('- None -'),
114         'role' => $this->t('User role'),
115       ],
116       '#ajax' => TRUE,
117       '#limit_validation_errors' => [],
118       '#default_value' => $configuration['filter']['type'],
119     ];
120
121     $form['filter']['settings'] = [
122       '#type' => 'container',
123       '#attributes' => ['class' => ['entity_reference-settings']],
124       '#process' => [['\Drupal\Core\Field\Plugin\Field\FieldType\EntityReferenceItem', 'formProcessMergeParent']],
125     ];
126
127     if ($configuration['filter']['type'] == 'role') {
128       $form['filter']['settings']['role'] = [
129         '#type' => 'checkboxes',
130         '#title' => $this->t('Restrict to the selected roles'),
131         '#required' => TRUE,
132         '#options' => array_diff_key(user_role_names(TRUE), [RoleInterface::AUTHENTICATED_ID => RoleInterface::AUTHENTICATED_ID]),
133         '#default_value' => $configuration['filter']['role'],
134       ];
135     }
136
137     $form += parent::buildConfigurationForm($form, $form_state);
138
139     return $form;
140   }
141
142   /**
143    * {@inheritdoc}
144    */
145   protected function buildEntityQuery($match = NULL, $match_operator = 'CONTAINS') {
146     $query = parent::buildEntityQuery($match, $match_operator);
147
148     $configuration = $this->getConfiguration();
149
150     // Filter out the Anonymous user if the selection handler is configured to
151     // exclude it.
152     if (!$configuration['include_anonymous']) {
153       $query->condition('uid', 0, '<>');
154     }
155
156     // The user entity doesn't have a label column.
157     if (isset($match)) {
158       $query->condition('name', $match, $match_operator);
159     }
160
161     // Filter by role.
162     if (!empty($configuration['filter']['role'])) {
163       $query->condition('roles', $configuration['filter']['role'], 'IN');
164     }
165
166     // Adding the permission check is sadly insufficient for users: core
167     // requires us to also know about the concept of 'blocked' and 'active'.
168     if (!$this->currentUser->hasPermission('administer users')) {
169       $query->condition('status', 1);
170     }
171     return $query;
172   }
173
174   /**
175    * {@inheritdoc}
176    */
177   public function createNewEntity($entity_type_id, $bundle, $label, $uid) {
178     $user = parent::createNewEntity($entity_type_id, $bundle, $label, $uid);
179
180     // In order to create a referenceable user, it needs to be active.
181     if (!$this->currentUser->hasPermission('administer users')) {
182       /** @var \Drupal\user\UserInterface $user */
183       $user->activate();
184     }
185
186     return $user;
187   }
188
189   /**
190    * {@inheritdoc}
191    */
192   public function validateReferenceableNewEntities(array $entities) {
193     $entities = parent::validateReferenceableNewEntities($entities);
194     // Mirror the conditions checked in buildEntityQuery().
195     if ($role = $this->getConfiguration()['filter']['role']) {
196       $entities = array_filter($entities, function ($user) use ($role) {
197         /** @var \Drupal\user\UserInterface $user */
198         return !empty(array_intersect($user->getRoles(), $role));
199       });
200     }
201     if (!$this->currentUser->hasPermission('administer users')) {
202       $entities = array_filter($entities, function ($user) {
203         /** @var \Drupal\user\UserInterface $user */
204         return $user->isActive();
205       });
206     }
207     return $entities;
208   }
209
210   /**
211    * {@inheritdoc}
212    */
213   public function entityQueryAlter(SelectInterface $query) {
214     parent::entityQueryAlter($query);
215
216     // Bail out early if we do not need to match the Anonymous user.
217     if (!$this->getConfiguration()['include_anonymous']) {
218       return;
219     }
220
221     if ($this->currentUser->hasPermission('administer users')) {
222       // In addition, if the user is administrator, we need to make sure to
223       // match the anonymous user, that doesn't actually have a name in the
224       // database.
225       $conditions = &$query->conditions();
226       foreach ($conditions as $key => $condition) {
227         if ($key !== '#conjunction' && is_string($condition['field']) && $condition['field'] === 'users_field_data.name') {
228           // Remove the condition.
229           unset($conditions[$key]);
230
231           // Re-add the condition and a condition on uid = 0 so that we end up
232           // with a query in the form:
233           // WHERE (name LIKE :name) OR (:anonymous_name LIKE :name AND uid = 0)
234           $or = new Condition('OR');
235           $or->condition($condition['field'], $condition['value'], $condition['operator']);
236           // Sadly, the Database layer doesn't allow us to build a condition
237           // in the form ':placeholder = :placeholder2', because the 'field'
238           // part of a condition is always escaped.
239           // As a (cheap) workaround, we separately build a condition with no
240           // field, and concatenate the field and the condition separately.
241           $value_part = new Condition('AND');
242           $value_part->condition('anonymous_name', $condition['value'], $condition['operator']);
243           $value_part->compile($this->connection, $query);
244           $or->condition((new Condition('AND'))
245             ->where(str_replace($query->escapeField('anonymous_name'), ':anonymous_name', (string) $value_part), $value_part->arguments() + [':anonymous_name' => \Drupal::config('user.settings')->get('anonymous')])
246             ->condition('base_table.uid', 0)
247           );
248           $query->condition($or);
249         }
250       }
251     }
252   }
253
254 }