|
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
|
|
|
} |
|
270
|
|
|
|