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