1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
/** |
4
|
|
|
* Contains \Drupal\entity_browser\Plugin\EntityBrowser\Display\Modal. |
5
|
|
|
*/ |
6
|
|
|
|
7
|
|
|
namespace Drupal\entity_browser\Plugin\EntityBrowser\Display; |
8
|
|
|
|
9
|
|
|
use Drupal\Component\Utility\NestedArray; |
10
|
|
|
use Drupal\Component\Uuid\UuidInterface; |
11
|
|
|
use Drupal\Core\Ajax\OpenModalDialogCommand; |
12
|
|
|
use Drupal\Core\Entity\EntityInterface; |
13
|
|
|
use Drupal\Core\Routing\RouteMatchInterface; |
14
|
|
|
use Drupal\Core\Url; |
15
|
|
|
use Drupal\entity_browser\DisplayAjaxInterface; |
16
|
|
|
use Drupal\entity_browser\DisplayBase; |
17
|
|
|
use Drupal\entity_browser\DisplayRouterInterface; |
18
|
|
|
use Drupal\entity_browser\Events\Events; |
19
|
|
|
use Drupal\entity_browser\Events\RegisterJSCallbacks; |
20
|
|
|
use Symfony\Component\DependencyInjection\ContainerInterface; |
21
|
|
|
use Symfony\Component\EventDispatcher\EventDispatcherInterface; |
22
|
|
|
use Drupal\Core\Path\CurrentPathStack; |
23
|
|
|
use Drupal\Core\Ajax\AjaxResponse; |
24
|
|
|
use Drupal\Core\Ajax\CloseDialogCommand; |
25
|
|
|
use Drupal\entity_browser\Ajax\SelectEntitiesCommand; |
26
|
|
|
use Symfony\Component\HttpKernel\Event\FilterResponseEvent; |
27
|
|
|
use Symfony\Component\HttpKernel\KernelEvents; |
28
|
|
|
use Drupal\Core\Form\FormStateInterface; |
29
|
|
|
use Symfony\Component\HttpFoundation\Request; |
30
|
|
|
use Symfony\Component\HttpFoundation\Response; |
31
|
|
|
|
32
|
|
|
/** |
33
|
|
|
* Presents entity browser in an Modal. |
34
|
|
|
* |
35
|
|
|
* @EntityBrowserDisplay( |
36
|
|
|
* id = "modal", |
37
|
|
|
* label = @Translation("Modal"), |
38
|
|
|
* description = @Translation("Displays entity browser in a Modal."), |
39
|
|
|
* uses_route = TRUE |
40
|
|
|
* ) |
41
|
|
|
*/ |
42
|
|
|
class Modal extends DisplayBase implements DisplayRouterInterface { |
43
|
|
|
|
44
|
|
|
/** |
45
|
|
|
* Current route match service. |
46
|
|
|
* |
47
|
|
|
* @var \Drupal\Core\Routing\RouteMatchInterface |
48
|
|
|
*/ |
49
|
|
|
protected $currentRouteMatch; |
50
|
|
|
|
51
|
|
|
/** |
52
|
|
|
* UUID generator interface. |
53
|
|
|
* |
54
|
|
|
* @var \Drupal\Component\Uuid\UuidInterface |
55
|
|
|
*/ |
56
|
|
|
protected $uuidGenerator; |
57
|
|
|
|
58
|
|
|
/** |
59
|
|
|
* Current path. |
60
|
|
|
* |
61
|
|
|
* @var \Drupal\Core\Path\CurrentPathStack |
62
|
|
|
*/ |
63
|
|
|
protected $currentPath; |
64
|
|
|
|
65
|
|
|
/** |
66
|
|
|
* UIID string. |
67
|
|
|
* |
68
|
|
|
* @var string |
69
|
|
|
*/ |
70
|
|
|
protected $uuid = NULL; |
71
|
|
|
|
72
|
|
|
/** |
73
|
|
|
* Current request. |
74
|
|
|
* |
75
|
|
|
* @var \Symfony\Component\HttpFoundation\Request |
76
|
|
|
*/ |
77
|
|
|
protected $request; |
78
|
|
|
|
79
|
|
|
/** |
80
|
|
|
* Constructs display plugin. |
81
|
|
|
* |
82
|
|
|
* @param array $configuration |
83
|
|
|
* A configuration array containing information about the plugin instance. |
84
|
|
|
* @param string $plugin_id |
85
|
|
|
* The plugin_id for the plugin instance. |
86
|
|
|
* @param mixed $plugin_definition |
87
|
|
|
* The plugin implementation definition. |
88
|
|
|
* @param \Symfony\Component\EventDispatcher\EventDispatcherInterface $event_dispatcher |
89
|
|
|
* Event dispatcher service. |
90
|
|
|
* @param \Drupal\Core\Routing\RouteMatchInterface |
91
|
|
|
* The currently active route match object. |
92
|
|
|
* @param \Drupal\Component\Uuid\UuidInterface |
93
|
|
|
* UUID generator interface. |
94
|
|
|
*/ |
95
|
|
View Code Duplication |
public function __construct(array $configuration, $plugin_id, $plugin_definition, EventDispatcherInterface $event_dispatcher, RouteMatchInterface $current_route_match, UuidInterface $uuid, CurrentPathStack $current_path, Request $request) { |
|
|
|
|
96
|
|
|
parent::__construct($configuration, $plugin_id, $plugin_definition, $event_dispatcher); |
97
|
|
|
$this->currentRouteMatch = $current_route_match; |
98
|
|
|
$this->uuidGenerator = $uuid; |
99
|
|
|
$this->currentPath = $current_path; |
100
|
|
|
$this->request = $request; |
101
|
|
|
} |
102
|
|
|
|
103
|
|
|
/** |
104
|
|
|
* {@inheritdoc} |
105
|
|
|
*/ |
106
|
|
View Code Duplication |
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) { |
|
|
|
|
107
|
|
|
return new static( |
108
|
|
|
$configuration, |
109
|
|
|
$plugin_id, |
110
|
|
|
$plugin_definition, |
111
|
|
|
$container->get('event_dispatcher'), |
112
|
|
|
$container->get('current_route_match'), |
113
|
|
|
$container->get('uuid'), |
114
|
|
|
$container->get('path.current'), |
115
|
|
|
$container->get('request_stack')->getCurrentRequest() |
116
|
|
|
); |
117
|
|
|
} |
118
|
|
|
|
119
|
|
|
/** |
120
|
|
|
* {@inheritdoc} |
121
|
|
|
*/ |
122
|
|
View Code Duplication |
public function defaultConfiguration() { |
|
|
|
|
123
|
|
|
return array( |
124
|
|
|
'width' => '650', |
125
|
|
|
'height' => '500', |
126
|
|
|
'link_text' => t('Select entities'), |
127
|
|
|
) + parent::defaultConfiguration(); |
128
|
|
|
} |
129
|
|
|
|
130
|
|
|
/** |
131
|
|
|
* {@inheritdoc} |
132
|
|
|
*/ |
133
|
|
|
public function displayEntityBrowser() { |
134
|
|
|
$uuid = $this->getUuid(); |
135
|
|
|
/** @var \Drupal\entity_browser\Events\RegisterJSCallbacks $event */ |
136
|
|
|
// TODO - $uuid is unused in this event but we need to pass it as |
137
|
|
|
// constructor expects it. See https://www.drupal.org/node/2600706 for more |
138
|
|
|
// info. |
139
|
|
|
$event_object = new RegisterJSCallbacks($this->configuration['entity_browser_id'], $uuid); |
140
|
|
|
$event_object->registerCallback('Drupal.entityBrowser.selectionCompleted'); |
141
|
|
|
$event = $this->eventDispatcher->dispatch(Events::REGISTER_JS_CALLBACKS, $event_object ); |
142
|
|
|
$original_path = $this->currentPath->getPath(); |
143
|
|
|
return [ |
144
|
|
|
'#theme_wrappers' => ['container'], |
145
|
|
|
'path' => [ |
146
|
|
|
'#type' => 'hidden', |
147
|
|
|
'#value' => Url::fromRoute('entity_browser.' . $this->configuration['entity_browser_id'], [], [ |
148
|
|
|
'query' => [ |
149
|
|
|
'uuid' => $uuid, |
150
|
|
|
'original_path' => $original_path, |
151
|
|
|
], |
152
|
|
|
])->toString(), |
153
|
|
|
], |
154
|
|
|
'open_modal' => [ |
155
|
|
|
'#type' => 'submit', |
156
|
|
|
'#value' => $this->configuration['link_text'], |
157
|
|
|
'#limit_validation_errors' => [], |
158
|
|
|
'#submit' => [], |
159
|
|
|
'#name' => 'op_' . $this->configuration['entity_browser_id'], |
160
|
|
|
'#ajax' => [ |
161
|
|
|
'callback' => [$this, 'openModal'], |
162
|
|
|
'event' => 'click', |
163
|
|
|
], |
164
|
|
|
'#attributes' => [ |
165
|
|
|
'data-uuid' => $uuid, |
166
|
|
|
], |
167
|
|
|
'#attached' => [ |
168
|
|
|
'library' => ['core/drupal.dialog.ajax', 'entity_browser/modal'], |
169
|
|
|
'drupalSettings' => [ |
170
|
|
|
'entity_browser' => [ |
171
|
|
|
'modal' => [ |
172
|
|
|
$uuid => [ |
173
|
|
|
'uuid' => $uuid, |
174
|
|
|
'js_callbacks' => $event->getCallbacks(), |
175
|
|
|
'original_path' => $original_path, |
176
|
|
|
], |
177
|
|
|
], |
178
|
|
|
], |
179
|
|
|
], |
180
|
|
|
], |
181
|
|
|
], |
182
|
|
|
]; |
183
|
|
|
} |
184
|
|
|
|
185
|
|
|
/** |
186
|
|
|
* Generates the content and opens the modal. |
187
|
|
|
* |
188
|
|
|
* @param array $form |
189
|
|
|
* The form array. |
190
|
|
|
* @param \Drupal\Core\Form\FormStateInterface $form_state |
191
|
|
|
* The form state object. |
192
|
|
|
* |
193
|
|
|
* @return \Drupal\Core\Ajax\AjaxResponse |
194
|
|
|
* An ajax response. |
195
|
|
|
*/ |
196
|
|
|
public function openModal(array &$form, FormStateInterface $form_state) { |
197
|
|
|
$triggering_element = $form_state->getTriggeringElement(); |
198
|
|
|
$parents = $triggering_element['#parents']; |
199
|
|
|
array_pop($parents); |
200
|
|
|
$parents = array_merge($parents, ['path']); |
201
|
|
|
$input = $form_state->getUserInput(); |
202
|
|
|
$src = NestedArray::getValue($input, $parents); |
203
|
|
|
|
204
|
|
|
$content = [ |
205
|
|
|
'#type' => 'html_tag', |
206
|
|
|
'#tag' => 'iframe', |
207
|
|
|
'#attributes' => [ |
208
|
|
|
'src' => $src, |
209
|
|
|
'width' => '100%', |
210
|
|
|
'height' => $this->configuration['height'] - 90, |
211
|
|
|
'frameborder' => 0, |
212
|
|
|
'style' => 'padding:' |
213
|
|
|
], |
214
|
|
|
]; |
215
|
|
|
$html = drupal_render($content); |
216
|
|
|
|
217
|
|
|
$response = new AjaxResponse(); |
218
|
|
|
$response->addCommand(new OpenModalDialogCommand($this->configuration['link_text'], $html, [ |
219
|
|
|
'width' => $this->configuration['width'], |
220
|
|
|
'height' => $this->configuration['height'], |
221
|
|
|
])); |
222
|
|
|
return $response; |
223
|
|
|
} |
224
|
|
|
|
225
|
|
|
/** |
226
|
|
|
* {@inheritdoc} |
227
|
|
|
*/ |
228
|
|
|
public function selectionCompleted(array $entities) { |
229
|
|
|
$this->entities = $entities; |
230
|
|
|
$this->eventDispatcher->addListener(KernelEvents::RESPONSE, [$this, 'propagateSelection']); |
231
|
|
|
} |
232
|
|
|
|
233
|
|
|
/** |
234
|
|
|
* {@inheritdoc} |
235
|
|
|
*/ |
236
|
|
|
public function addAjax(array &$form) { |
237
|
|
|
// Set a wrapper container to replace the form on ajax callback. |
238
|
|
|
$form['#prefix'] = '<div id="entity-browser-form">'; |
239
|
|
|
$form['#suffix'] = '</div>'; |
240
|
|
|
|
241
|
|
|
// Add the browser id to use in the FormAjaxController. |
242
|
|
|
$form['browser_id'] = array( |
243
|
|
|
'#type' => 'hidden', |
244
|
|
|
'#value' => $this->configuration['entity_browser_id'], |
245
|
|
|
); |
246
|
|
|
|
247
|
|
|
$form['actions']['submit']['#ajax'] = array( |
248
|
|
|
'callback' => array($this, 'widgetAjaxCallback'), |
249
|
|
|
'wrapper' => 'entity-browser-form', |
250
|
|
|
); |
251
|
|
|
} |
252
|
|
|
|
253
|
|
|
/** |
254
|
|
|
* Ajax callback for entity browser form. |
255
|
|
|
* |
256
|
|
|
* Allows the entity browser form to submit the form via ajax. |
257
|
|
|
* |
258
|
|
|
* @param array $form |
259
|
|
|
* The form array. |
260
|
|
|
* @param FormStateInterface $form_state |
261
|
|
|
* The form state object. |
262
|
|
|
* |
263
|
|
|
* @return \Drupal\Core\Ajax\AjaxResponse |
264
|
|
|
* Response. |
265
|
|
|
*/ |
266
|
|
|
public function widgetAjaxCallback(array &$form, FormStateInterface $form_state) { |
267
|
|
|
// If we've got any validation error, print out the form again. |
268
|
|
|
if ($form_state->hasAnyErrors()) { |
269
|
|
|
return $form; |
|
|
|
|
270
|
|
|
} |
271
|
|
|
|
272
|
|
|
$commands = $this->getAjaxCommands($form_state); |
273
|
|
|
$response = new AjaxResponse(); |
274
|
|
|
foreach ($commands as $command) { |
275
|
|
|
$response->addCommand($command); |
276
|
|
|
} |
277
|
|
|
|
278
|
|
|
return $response; |
279
|
|
|
} |
280
|
|
|
|
281
|
|
|
/** |
282
|
|
|
* Helper function to return commands to return in AjaxResponse. |
283
|
|
|
* |
284
|
|
|
* @return array |
285
|
|
|
* An array of ajax commands. |
286
|
|
|
*/ |
287
|
|
|
public function getAjaxCommands(FormStateInterface $form_state) { |
288
|
|
|
$entities = array_map(function(EntityInterface $item) {return [$item->id(), $item->uuid(), $item->getEntityTypeId()];}, $form_state->get(['entity_browser', 'selected_entities'])); |
289
|
|
|
|
290
|
|
|
$commands = array(); |
291
|
|
|
$commands[] = new SelectEntitiesCommand($this->uuid, $entities); |
292
|
|
|
|
293
|
|
|
return $commands; |
294
|
|
|
} |
295
|
|
|
|
296
|
|
|
/** |
297
|
|
|
* KernelEvents::RESPONSE listener. |
298
|
|
|
* |
299
|
|
|
* Intercepts default response and injects |
300
|
|
|
* response that will trigger JS to propagate selected entities upstream. |
301
|
|
|
* |
302
|
|
|
* @param FilterResponseEvent $event |
303
|
|
|
* Response event. |
304
|
|
|
*/ |
305
|
|
View Code Duplication |
public function propagateSelection(FilterResponseEvent $event) { |
|
|
|
|
306
|
|
|
$render = [ |
307
|
|
|
'labels' => [ |
308
|
|
|
'#markup' => 'Labels: ' . implode(', ', array_map(function (EntityInterface $item) {return $item->label();}, $this->entities)), |
309
|
|
|
'#attached' => [ |
310
|
|
|
'library' => ['entity_browser/modal_selection'], |
311
|
|
|
'drupalSettings' => [ |
312
|
|
|
'entity_browser' => [ |
313
|
|
|
'modal' => [ |
314
|
|
|
'entities' => array_map(function (EntityInterface $item) {return [$item->id(), $item->uuid(), $item->getEntityTypeId()];}, $this->entities), |
315
|
|
|
'uuid' => $this->request->query->get('uuid'), |
316
|
|
|
], |
317
|
|
|
], |
318
|
|
|
], |
319
|
|
|
], |
320
|
|
|
], |
321
|
|
|
]; |
322
|
|
|
|
323
|
|
|
$event->setResponse(new Response(\Drupal::service('bare_html_page_renderer')->renderBarePage($render, 'Entity browser', 'entity_browser_propagation'))); |
324
|
|
|
} |
325
|
|
|
|
326
|
|
|
/** |
327
|
|
|
* {@inheritdoc} |
328
|
|
|
*/ |
329
|
|
|
public function path() { |
330
|
|
|
return '/entity-browser/modal/' . $this->configuration['entity_browser_id']; |
331
|
|
|
} |
332
|
|
|
|
333
|
|
|
/** |
334
|
|
|
* {@inheritdoc} |
335
|
|
|
*/ |
336
|
|
|
public function getUuid() { |
337
|
|
|
if (empty($this->uuid)) { |
338
|
|
|
$this->uuid = $this->uuidGenerator->generate(); |
339
|
|
|
} |
340
|
|
|
return $this->uuid; |
341
|
|
|
} |
342
|
|
|
|
343
|
|
|
public function __sleep() { |
344
|
|
|
return ['configuration']; |
345
|
|
|
} |
346
|
|
|
|
347
|
|
|
/** |
348
|
|
|
* {@inheritdoc} |
349
|
|
|
*/ |
350
|
|
|
public function buildConfigurationForm(array $form, FormStateInterface $form_state) { |
351
|
|
|
$configuration = $this->getConfiguration(); |
352
|
|
|
$form['width'] = [ |
353
|
|
|
'#type' => 'number', |
354
|
|
|
'#title' => $this->t('Width of the modal'), |
355
|
|
|
'#min' => 1, |
356
|
|
|
'#default_value' => $configuration['width'], |
357
|
|
|
]; |
358
|
|
|
|
359
|
|
|
$form['height'] = [ |
360
|
|
|
'#type' => 'number', |
361
|
|
|
'#title' => $this->t('Height of the modal'), |
362
|
|
|
'#min' => 1, |
363
|
|
|
'#default_value' => $configuration['height'], |
364
|
|
|
]; |
365
|
|
|
|
366
|
|
|
$form['link_text'] = [ |
367
|
|
|
'#type' => 'textfield', |
368
|
|
|
'#title' => $this->t('Link text'), |
369
|
|
|
'#default_value' => $configuration['link_text'], |
370
|
|
|
]; |
371
|
|
|
|
372
|
|
|
return $form; |
373
|
|
|
} |
374
|
|
|
|
375
|
|
|
} |
376
|
|
|
|
Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.
You can also find more detailed suggestions in the “Code” section of your repository.