3 namespace Drupal\entity_browser\Element;
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;
12 * Provides an Entity Browser form element.
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
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.
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.
34 * @FormElement("entity_browser")
36 class EntityBrowserElement extends FormElement {
39 * Indicating an entity browser can return an unlimited number of values.
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.
45 const CARDINALITY_UNLIMITED = -1;
48 * Selection from entity browser will be appended to existing list.
50 * When this selection mode is used, then entity browser will not be
51 * populated with existing selection. Preselected list will be empty.
53 * Note: This option is also used by "js/entity_browser.common.js".
55 const SELECTION_MODE_APPEND = 'selection_append';
58 * Selection from entity browser will be prepended to existing list.
60 * When this selection mode is used, then entity browser will not be
61 * populated with existing selection. Preselected list will be empty.
63 * Note: This option is also used by "js/entity_browser.common.js".
65 const SELECTION_MODE_PREPEND = 'selection_prepend';
68 * Selection from entity browser will replace existing.
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.
75 * Note: This option is also used by "js/entity_browser.common.js".
77 const SELECTION_MODE_EDIT = 'selection_edit';
82 public function getInfo() {
83 $class = get_class($this);
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']],
98 * Get selection mode options.
101 * Selection mode options.
103 public static function getSelectionModeOptions() {
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'),
112 * Check whether entity browser should be available for selection of entities.
114 * @param string $selection_mode
115 * Used selection mode.
116 * @param int $cardinality
118 * @param int $preselection_size
119 * Preseletion size, if it's available.
122 * Returns positive if entity browser can be used.
124 public static function isEntityBrowserAvailable($selection_mode, $cardinality, $preselection_size) {
125 if ($selection_mode == static::SELECTION_MODE_EDIT) {
129 $cardinality_exceeded =
130 $cardinality != static::CARDINALITY_UNLIMITED
131 && $preselection_size >= $cardinality;
133 return !$cardinality_exceeded;
137 * Render API callback: Processes the entity browser element.
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']);
145 $entity_browser = $element['#entity_browser'];
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();
153 $entity_browser_preselected_entities = $element['#default_value'];
156 $default_value = implode(' ', array_map(
157 function (EntityInterface $item) {
158 return $item->getEntityTypeId() . ':' . $item->id();
160 $entity_browser_preselected_entities
162 $validators = array_merge(
163 $element['#entity_browser_validators'],
164 ['cardinality' => ['cardinality' => $element['#cardinality']]]
167 $display = $entity_browser->getDisplay();
168 $display->setUuid(sha1(implode('-', array_merge([$complete_form['#build_id']], $element['#parents']))));
169 $element['entity_browser'] = [
170 '#eb_parents' => array_merge($element['#parents'], ['entity_browser']),
172 $element['entity_browser'] = $display->displayEntityBrowser(
173 $element['entity_browser'],
177 'validators' => $validators,
178 'selected_entities' => $entity_browser_preselected_entities,
179 'widget_context' => $element['#widget_context'],
183 $hidden_id = Html::getUniqueId($element['#id'] . '-target');
184 $element['entity_ids'] = [
187 // We need to repeat ID here as it is otherwise skipped when rendering.
188 '#attributes' => ['id' => $hidden_id, 'class' => ['eb-target']],
189 '#default_value' => $default_value,
192 $element['#attached']['drupalSettings']['entity_browser'] = [
193 $entity_browser->getDisplay()->getUuid() => [
194 'cardinality' => $element['#cardinality'],
195 'selection_mode' => $element['#selection_mode'],
196 'selector' => '#' . $hidden_id,
206 public static function valueCallback(&$element, $input, FormStateInterface $form_state) {
207 if ($input === FALSE) {
208 return $element['#default_value'] ?: [];
212 if ($input['entity_ids']) {
213 $entities = static::processEntityIds($input['entity_ids']);
216 return ['entities' => $entities];
220 * Processes entity IDs and gets array of loaded entities.
222 * @param array|string $ids
223 * Processes entity IDs as they are returned from the entity browser. They
224 * are in [entity_type_id]:[entity_id] form. Array of IDs or a
225 * space-delimited string is supported.
227 * @return \Drupal\Core\Entity\EntityInterface[]
228 * Array of entity objects.
230 public static function processEntityIds($ids) {
231 if (!is_array($ids)) {
232 $ids = array_filter(explode(' ', $ids));
237 list($entity_type, $entity_id) = explode(':', $item);
238 return \Drupal::entityTypeManager()->getStorage($entity_type)->load($entity_id);
245 * Processes entity IDs and gets array of loaded entities.
248 * Processes entity ID as it is returned from the entity browser. ID should
249 * be in [entity_type_id]:[entity_id] form.
251 * @return \Drupal\Core\Entity\EntityInterface
254 public static function processEntityId($id) {
255 $return = static::processEntityIds([$id]);
256 return current($return);