Pull merge.
[yaffs-website] / web / modules / contrib / entity_browser / src / Element / EntityBrowserElement.php
1 <?php
2
3 namespace Drupal\entity_browser\Element;
4
5 use Drupal\Component\Utility\Html;
6 use Drupal\Core\Form\FormStateInterface;
7 use Drupal\Core\Render\Element\FormElement;
8 use Drupal\entity_browser\Entity\EntityBrowser;
9 use Drupal\Core\Entity\EntityInterface;
10
11 /**
12  * Provides an Entity Browser form element.
13  *
14  * Properties:
15  * - #entity_browser: Entity browser or ID of the Entity browser to be used.
16  * - #cardinality: (optional) Maximum number of items that are expected from
17  *     the entity browser. Unlimited by default.
18  * - #default_value: (optional) Array of entities that Entity browser should be
19  *     initialized with. It's only applicable when edit selection mode is used.
20  * - #entity_browser_validators: (optional) Array of validators that are to be
21  *     passed to the entity browser. Array keys are plugin IDs and array values
22  *     are plugin configuration values. Cardinality validator will be set
23  *     automatically.
24  * - #selection_mode: (optional) Determines how selection in entity browser will
25  *     be handled. Will selection be appended/prepended or it will be replaced
26  *     in case of editing. Defaults to append.
27  * - #widget_context: (optional) Widget configuration overrides which enable
28  *     use cases where the instance of a widget needs awareness of contextual
29  *     configuration like field settings.
30  *
31  * Return value will be an array of selected entities, which will appear under
32  * 'entities' key on the root level of the element's values in the form state.
33  *
34  * @FormElement("entity_browser")
35  */
36 class EntityBrowserElement extends FormElement {
37
38   /**
39    * Indicating an entity browser can return an unlimited number of values.
40    *
41    * Note: When entity browser is used in Fields, cardinality is directly
42    * propagated from Field settings, that's why this constant should be equal to
43    * FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED.
44    */
45   const CARDINALITY_UNLIMITED = -1;
46
47   /**
48    * Selection from entity browser will be appended to existing list.
49    *
50    * When this selection mode is used, then entity browser will not be
51    * populated with existing selection. Preselected list will be empty.
52    *
53    * Note: This option is also used by "js/entity_browser.common.js".
54    */
55   const SELECTION_MODE_APPEND = 'selection_append';
56
57   /**
58    * Selection from entity browser will be prepended to existing list.
59    *
60    * When this selection mode is used, then entity browser will not be
61    * populated with existing selection. Preselected list will be empty.
62    *
63    * Note: This option is also used by "js/entity_browser.common.js".
64    */
65   const SELECTION_MODE_PREPEND = 'selection_prepend';
66
67   /**
68    * Selection from entity browser will replace existing.
69    *
70    * When this selection mode is used, then entity browser will be populated
71    * with existing selection and returned selected list will replace existing
72    * selection. This option requires entity browser selection display with
73    * preselection support.
74    *
75    * Note: This option is also used by "js/entity_browser.common.js".
76    */
77   const SELECTION_MODE_EDIT = 'selection_edit';
78
79   /**
80    * {@inheritdoc}
81    */
82   public function getInfo() {
83     $class = get_class($this);
84     return [
85       '#input' => TRUE,
86       '#tree' => TRUE,
87       '#cardinality' => static::CARDINALITY_UNLIMITED,
88       '#selection_mode' => static::SELECTION_MODE_APPEND,
89       '#process' => [[$class, 'processEntityBrowser']],
90       '#default_value' => [],
91       '#entity_browser_validators' => [],
92       '#widget_context' => [],
93       '#attached' => ['library' => ['entity_browser/common']],
94     ];
95   }
96
97   /**
98    * Get selection mode options.
99    *
100    * @return array
101    *   Selection mode options.
102    */
103   public static function getSelectionModeOptions() {
104     return [
105       static::SELECTION_MODE_APPEND => t('Append to selection'),
106       static::SELECTION_MODE_PREPEND => t('Prepend selection'),
107       static::SELECTION_MODE_EDIT => t('Edit selection'),
108     ];
109   }
110
111   /**
112    * Check whether entity browser should be available for selection of entities.
113    *
114    * @param string $selection_mode
115    *   Used selection mode.
116    * @param int $cardinality
117    *   Used cardinality.
118    * @param int $preselection_size
119    *   Preseletion size, if it's available.
120    *
121    * @return bool
122    *   Returns positive if entity browser can be used.
123    */
124   public static function isEntityBrowserAvailable($selection_mode, $cardinality, $preselection_size) {
125     if ($selection_mode == static::SELECTION_MODE_EDIT) {
126       return TRUE;
127     }
128
129     $cardinality_exceeded =
130       $cardinality != static::CARDINALITY_UNLIMITED
131       && $preselection_size >= $cardinality;
132
133     return !$cardinality_exceeded;
134   }
135
136   /**
137    * Render API callback: Processes the entity browser element.
138    */
139   public static function processEntityBrowser(&$element, FormStateInterface $form_state, &$complete_form) {
140     /** @var \Drupal\entity_browser\EntityBrowserInterface $entity_browser */
141     if (is_string($element['#entity_browser'])) {
142       $entity_browser = EntityBrowser::load($element['#entity_browser']);
143     }
144     else {
145       $entity_browser = $element['#entity_browser'];
146     }
147
148     // Propagate selection if edit selection mode is used.
149     $entity_browser_preselected_entities = [];
150     if ($element['#selection_mode'] === static::SELECTION_MODE_EDIT) {
151       $entity_browser->getSelectionDisplay()->checkPreselectionSupport();
152
153       $entity_browser_preselected_entities = $element['#default_value'];
154     }
155
156     $default_value = implode(' ', array_map(
157       function (EntityInterface $item) {
158         return $item->getEntityTypeId() . ':' . $item->id();
159       },
160       $entity_browser_preselected_entities
161     ));
162     $validators = array_merge(
163       $element['#entity_browser_validators'],
164       ['cardinality' => ['cardinality' => $element['#cardinality']]]
165     );
166
167     // Display error message if the entity browser was not found.
168     if (!$entity_browser) {
169       $element['entity_browser'] = [
170         '#type' => 'markup',
171         '#markup' => is_string($element['#entity_browser']) ? t('Entity browser @browser not found.', ['@browser' => $element['#entity_browser']]) : t('Entity browser not found.'),
172       ];
173     }
174     // Display entity_browser
175     else {
176       $display = $entity_browser->getDisplay();
177       $display->setUuid(sha1(implode('-', array_merge([$complete_form['#build_id']], $element['#parents']))));
178       $element['entity_browser'] = [
179         '#eb_parents' => array_merge($element['#parents'], ['entity_browser']),
180       ];
181       $element['entity_browser'] = $display->displayEntityBrowser(
182         $element['entity_browser'],
183         $form_state,
184         $complete_form,
185         [
186           'validators' => $validators,
187           'selected_entities' => $entity_browser_preselected_entities,
188           'widget_context' => $element['#widget_context'],
189         ]
190       );
191
192       $hidden_id = Html::getUniqueId($element['#id'] . '-target');
193       $element['entity_ids'] = [
194         '#type' => 'hidden',
195         '#id' => $hidden_id,
196         // We need to repeat ID here as it is otherwise skipped when rendering.
197         '#attributes' => ['id' => $hidden_id, 'class' => ['eb-target']],
198         '#default_value' => $default_value,
199       ];
200
201       $element['#attached']['drupalSettings']['entity_browser'] = [
202         $entity_browser->getDisplay()->getUuid() => [
203           'cardinality' => $element['#cardinality'],
204           'selection_mode' => $element['#selection_mode'],
205           'selector' => '#' . $hidden_id,
206         ],
207       ];
208     }
209
210     return $element;
211   }
212
213   /**
214    * {@inheritdoc}
215    */
216   public static function valueCallback(&$element, $input, FormStateInterface $form_state) {
217     if ($input === FALSE) {
218       return $element['#default_value'] ?: [];
219     }
220
221     $entities = [];
222     if ($input['entity_ids']) {
223       $entities = static::processEntityIds($input['entity_ids']);
224     }
225
226     return ['entities' => $entities];
227   }
228
229   /**
230    * Processes entity IDs and gets array of loaded entities.
231    *
232    * @param array|string $ids
233    *   Processes entity IDs as they are returned from the entity browser. They
234    *   are in [entity_type_id]:[entity_id] form. Array of IDs or a
235    *   space-delimited string is supported.
236    *
237    * @return \Drupal\Core\Entity\EntityInterface[]
238    *   Array of entity objects.
239    */
240   public static function processEntityIds($ids) {
241     if (!is_array($ids)) {
242       $ids = array_filter(explode(' ', $ids));
243     }
244
245     return array_map(
246       function ($item) {
247         list($entity_type, $entity_id) = explode(':', $item);
248         return \Drupal::entityTypeManager()->getStorage($entity_type)->load($entity_id);
249       },
250       $ids
251     );
252   }
253
254   /**
255    * Processes entity IDs and gets array of loaded entities.
256    *
257    * @param string $id
258    *   Processes entity ID as it is returned from the entity browser. ID should
259    *   be in [entity_type_id]:[entity_id] form.
260    *
261    * @return \Drupal\Core\Entity\EntityInterface
262    *   Entity object.
263    */
264   public static function processEntityId($id) {
265     $return = static::processEntityIds([$id]);
266     return current($return);
267   }
268
269 }