218131c2cdf176fb2a93fc6b658dcde75cfb1b8b
[yaffs-website] / web / core / lib / Drupal / Core / Entity / Plugin / EntityReferenceSelection / DefaultSelection.php
1 <?php
2
3 namespace Drupal\Core\Entity\Plugin\EntityReferenceSelection;
4
5 use Drupal\Component\Utility\Html;
6 use Drupal\Core\Database\Query\AlterableInterface;
7 use Drupal\Core\Entity\EntityManagerInterface;
8 use Drupal\Core\Entity\EntityReferenceSelection\SelectionPluginBase;
9 use Drupal\Core\Entity\EntityReferenceSelection\SelectionTrait;
10 use Drupal\Core\Entity\EntityReferenceSelection\SelectionWithAutocreateInterface;
11 use Drupal\Core\Entity\FieldableEntityInterface;
12 use Drupal\Core\Extension\ModuleHandlerInterface;
13 use Drupal\Core\Field\Plugin\Field\FieldType\EntityReferenceItem;
14 use Drupal\Core\Form\FormStateInterface;
15 use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
16 use Drupal\Core\Session\AccountInterface;
17 use Drupal\user\EntityOwnerInterface;
18
19 /**
20  * Default plugin implementation of the Entity Reference Selection plugin.
21  *
22  * Also serves as a base class for specific types of Entity Reference
23  * Selection plugins.
24  *
25  * @see \Drupal\Core\Entity\EntityReferenceSelection\SelectionPluginManager
26  * @see \Drupal\Core\Entity\Annotation\EntityReferenceSelection
27  * @see \Drupal\Core\Entity\EntityReferenceSelection\SelectionInterface
28  * @see \Drupal\Core\Entity\Plugin\Derivative\DefaultSelectionDeriver
29  * @see plugin_api
30  *
31  * @EntityReferenceSelection(
32  *   id = "default",
33  *   label = @Translation("Default"),
34  *   group = "default",
35  *   weight = 0,
36  *   deriver = "Drupal\Core\Entity\Plugin\Derivative\DefaultSelectionDeriver"
37  * )
38  */
39 class DefaultSelection extends SelectionPluginBase implements ContainerFactoryPluginInterface, SelectionWithAutocreateInterface {
40
41   use SelectionTrait {
42     // PHP 5.5.9 gets confused between SelectionPluginBase::__construct() and
43     // SelectionTrait::__construct() that's why we are renaming the
44     // SelectionTrait::__construct() to avoid the confusion.
45     // @todo Remove this in https://www.drupal.org/node/2670966.
46     SelectionTrait::__construct as private initialize;
47   }
48
49   /**
50    * {@inheritdoc}
51    */
52   public function __construct(array $configuration, $plugin_id, $plugin_definition, EntityManagerInterface $entity_manager, ModuleHandlerInterface $module_handler, AccountInterface $current_user) {
53     $this->initialize($configuration, $plugin_id, $plugin_definition, $entity_manager, $module_handler, $current_user);
54   }
55
56   /**
57    * {@inheritdoc}
58    */
59   public function defaultConfiguration() {
60     return [
61       // For the 'target_bundles' setting, a NULL value is equivalent to "allow
62       // entities from any bundle to be referenced" and an empty array value is
63       // equivalent to "no entities from any bundle can be referenced".
64       'target_bundles' => NULL,
65       'sort' => [
66         'field' => '_none',
67         'direction' => 'ASC',
68       ],
69       'auto_create' => FALSE,
70       'auto_create_bundle' => NULL,
71     ] + parent::defaultConfiguration();
72   }
73
74   /**
75    * {@inheritdoc}
76    */
77   public function buildConfigurationForm(array $form, FormStateInterface $form_state) {
78     $form = parent::buildConfigurationForm($form, $form_state);
79
80     $configuration = $this->getConfiguration();
81     $entity_type_id = $configuration['target_type'];
82     $entity_type = $this->entityManager->getDefinition($entity_type_id);
83     $bundles = $this->entityManager->getBundleInfo($entity_type_id);
84
85     if ($entity_type->hasKey('bundle')) {
86       $bundle_options = [];
87       foreach ($bundles as $bundle_name => $bundle_info) {
88         $bundle_options[$bundle_name] = $bundle_info['label'];
89       }
90       natsort($bundle_options);
91
92       $form['target_bundles'] = [
93         '#type' => 'checkboxes',
94         '#title' => $this->t('Bundles'),
95         '#options' => $bundle_options,
96         '#default_value' => (array) $configuration['target_bundles'],
97         '#required' => TRUE,
98         '#size' => 6,
99         '#multiple' => TRUE,
100         '#element_validate' => [[get_class($this), 'elementValidateFilter']],
101         '#ajax' => TRUE,
102         '#limit_validation_errors' => [],
103       ];
104
105       $form['target_bundles_update'] = [
106         '#type' => 'submit',
107         '#value' => $this->t('Update form'),
108         '#limit_validation_errors' => [],
109         '#attributes' => [
110           'class' => ['js-hide'],
111         ],
112         '#submit' => [[EntityReferenceItem::class, 'settingsAjaxSubmit']],
113       ];
114     }
115     else {
116       $form['target_bundles'] = [
117         '#type' => 'value',
118         '#value' => [],
119       ];
120     }
121
122     if ($entity_type->entityClassImplements(FieldableEntityInterface::class)) {
123       $fields = [];
124       foreach (array_keys($bundles) as $bundle) {
125         $bundle_fields = array_filter($this->entityManager->getFieldDefinitions($entity_type_id, $bundle), function ($field_definition) {
126           return !$field_definition->isComputed();
127         });
128         foreach ($bundle_fields as $field_name => $field_definition) {
129           /* @var \Drupal\Core\Field\FieldDefinitionInterface $field_definition */
130           $columns = $field_definition->getFieldStorageDefinition()->getColumns();
131           // If there is more than one column, display them all, otherwise just
132           // display the field label.
133           // @todo: Use property labels instead of the column name.
134           if (count($columns) > 1) {
135             foreach ($columns as $column_name => $column_info) {
136               $fields[$field_name . '.' . $column_name] = $this->t('@label (@column)', ['@label' => $field_definition->getLabel(), '@column' => $column_name]);
137             }
138           }
139           else {
140             $fields[$field_name] = $this->t('@label', ['@label' => $field_definition->getLabel()]);
141           }
142         }
143       }
144
145       $form['sort']['field'] = [
146         '#type' => 'select',
147         '#title' => $this->t('Sort by'),
148         '#options' => [
149           '_none' => $this->t('- None -'),
150         ] + $fields,
151         '#ajax' => TRUE,
152         '#limit_validation_errors' => [],
153         '#default_value' => $configuration['sort']['field'],
154       ];
155
156       $form['sort']['settings'] = [
157         '#type' => 'container',
158         '#attributes' => ['class' => ['entity_reference-settings']],
159         '#process' => [[EntityReferenceItem::class, 'formProcessMergeParent']],
160       ];
161
162       if ($configuration['sort']['field'] != '_none') {
163         $form['sort']['settings']['direction'] = [
164           '#type' => 'select',
165           '#title' => $this->t('Sort direction'),
166           '#required' => TRUE,
167           '#options' => [
168             'ASC' => $this->t('Ascending'),
169             'DESC' => $this->t('Descending'),
170           ],
171           '#default_value' => $configuration['sort']['direction'],
172         ];
173       }
174     }
175
176     $form['auto_create'] = [
177       '#type' => 'checkbox',
178       '#title' => $this->t("Create referenced entities if they don't already exist"),
179       '#default_value' => $configuration['auto_create'],
180       '#weight' => -2,
181     ];
182
183     if ($entity_type->hasKey('bundle')) {
184       $bundles = array_intersect_key($bundle_options, array_filter((array) $configuration['target_bundles']));
185       $form['auto_create_bundle'] = [
186         '#type' => 'select',
187         '#title' => $this->t('Store new items in'),
188         '#options' => $bundles,
189         '#default_value' => $configuration['auto_create_bundle'],
190         '#access' => count($bundles) > 1,
191         '#states' => [
192           'visible' => [
193             ':input[name="settings[handler_settings][auto_create]"]' => ['checked' => TRUE],
194           ],
195         ],
196         '#weight' => -1,
197       ];
198     }
199
200     return $form;
201   }
202
203   /**
204    * {@inheritdoc}
205    */
206   public function validateConfigurationForm(array &$form, FormStateInterface $form_state) {
207     parent::validateConfigurationForm($form, $form_state);
208
209     // If no checkboxes were checked for 'target_bundles', store NULL ("all
210     // bundles are referenceable") rather than empty array ("no bundle is
211     // referenceable" - typically happens when all referenceable bundles have
212     // been deleted).
213     if ($form_state->getValue(['settings', 'handler_settings', 'target_bundles']) === []) {
214       $form_state->setValue(['settings', 'handler_settings', 'target_bundles'], NULL);
215     }
216
217     // Don't store the 'target_bundles_update' button value into the field
218     // config settings.
219     $form_state->unsetValue(['settings', 'handler_settings', 'target_bundles_update']);
220   }
221
222   /**
223    * Form element validation handler; Filters the #value property of an element.
224    */
225   public static function elementValidateFilter(&$element, FormStateInterface $form_state) {
226     $element['#value'] = array_filter($element['#value']);
227     $form_state->setValueForElement($element, $element['#value']);
228   }
229
230   /**
231    * {@inheritdoc}
232    */
233   public function getReferenceableEntities($match = NULL, $match_operator = 'CONTAINS', $limit = 0) {
234     $target_type = $this->getConfiguration()['target_type'];
235
236     $query = $this->buildEntityQuery($match, $match_operator);
237     if ($limit > 0) {
238       $query->range(0, $limit);
239     }
240
241     $result = $query->execute();
242
243     if (empty($result)) {
244       return [];
245     }
246
247     $options = [];
248     $entities = $this->entityManager->getStorage($target_type)->loadMultiple($result);
249     foreach ($entities as $entity_id => $entity) {
250       $bundle = $entity->bundle();
251       $options[$bundle][$entity_id] = Html::escape($this->entityManager->getTranslationFromContext($entity)->label());
252     }
253
254     return $options;
255   }
256
257   /**
258    * {@inheritdoc}
259    */
260   public function countReferenceableEntities($match = NULL, $match_operator = 'CONTAINS') {
261     $query = $this->buildEntityQuery($match, $match_operator);
262     return $query
263       ->count()
264       ->execute();
265   }
266
267   /**
268    * {@inheritdoc}
269    */
270   public function validateReferenceableEntities(array $ids) {
271     $result = [];
272     if ($ids) {
273       $target_type = $this->configuration['target_type'];
274       $entity_type = $this->entityManager->getDefinition($target_type);
275       $query = $this->buildEntityQuery();
276       $result = $query
277         ->condition($entity_type->getKey('id'), $ids, 'IN')
278         ->execute();
279     }
280
281     return $result;
282   }
283
284   /**
285    * {@inheritdoc}
286    */
287   public function createNewEntity($entity_type_id, $bundle, $label, $uid) {
288     $entity_type = $this->entityManager->getDefinition($entity_type_id);
289     $bundle_key = $entity_type->getKey('bundle');
290     $label_key = $entity_type->getKey('label');
291
292     $entity = $this->entityManager->getStorage($entity_type_id)->create([
293       $bundle_key => $bundle,
294       $label_key => $label,
295     ]);
296
297     if ($entity instanceof EntityOwnerInterface) {
298       $entity->setOwnerId($uid);
299     }
300
301     return $entity;
302   }
303
304   /**
305    * {@inheritdoc}
306    */
307   public function validateReferenceableNewEntities(array $entities) {
308     return array_filter($entities, function ($entity) {
309       $target_bundles = $this->getConfiguration()['target_bundles'];
310       if (isset($target_bundles)) {
311         return in_array($entity->bundle(), $target_bundles);
312       }
313       return TRUE;
314     });
315   }
316
317   /**
318    * Builds an EntityQuery to get referenceable entities.
319    *
320    * @param string|null $match
321    *   (Optional) Text to match the label against. Defaults to NULL.
322    * @param string $match_operator
323    *   (Optional) The operation the matching should be done with. Defaults
324    *   to "CONTAINS".
325    *
326    * @return \Drupal\Core\Entity\Query\QueryInterface
327    *   The EntityQuery object with the basic conditions and sorting applied to
328    *   it.
329    */
330   protected function buildEntityQuery($match = NULL, $match_operator = 'CONTAINS') {
331     $configuration = $this->getConfiguration();
332     $target_type = $configuration['target_type'];
333     $entity_type = $this->entityManager->getDefinition($target_type);
334
335     $query = $this->entityManager->getStorage($target_type)->getQuery();
336
337     // If 'target_bundles' is NULL, all bundles are referenceable, no further
338     // conditions are needed.
339     if (is_array($configuration['target_bundles'])) {
340       // If 'target_bundles' is an empty array, no bundle is referenceable,
341       // force the query to never return anything and bail out early.
342       if ($configuration['target_bundles'] === []) {
343         $query->condition($entity_type->getKey('id'), NULL, '=');
344         return $query;
345       }
346       else {
347         $query->condition($entity_type->getKey('bundle'), $configuration['target_bundles'], 'IN');
348       }
349     }
350
351     if (isset($match) && $label_key = $entity_type->getKey('label')) {
352       $query->condition($label_key, $match, $match_operator);
353     }
354
355     // Add entity-access tag.
356     $query->addTag($target_type . '_access');
357
358     // Add the Selection handler for system_query_entity_reference_alter().
359     $query->addTag('entity_reference');
360     $query->addMetaData('entity_reference_selection_handler', $this);
361
362     // Add the sort option.
363     if ($configuration['sort']['field'] !== '_none') {
364       $query->sort($configuration['sort']['field'], $configuration['sort']['direction']);
365     }
366
367     return $query;
368   }
369
370   /**
371    * Helper method: Passes a query to the alteration system again.
372    *
373    * This allows Entity Reference to add a tag to an existing query so it can
374    * ask access control mechanisms to alter it again.
375    */
376   protected function reAlterQuery(AlterableInterface $query, $tag, $base_table) {
377     // Save the old tags and metadata.
378     // For some reason, those are public.
379     $old_tags = $query->alterTags;
380     $old_metadata = $query->alterMetaData;
381
382     $query->alterTags = [$tag => TRUE];
383     $query->alterMetaData['base_table'] = $base_table;
384     $this->moduleHandler->alter(['query', 'query_' . $tag], $query);
385
386     // Restore the tags and metadata.
387     $query->alterTags = $old_tags;
388     $query->alterMetaData = $old_metadata;
389   }
390
391 }