QuickEditController.php
10.8 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
<?php
/**
* @file
* Contains \Drupal\quickedit\QuickEditController.
*/
namespace Drupal\quickedit;
use Drupal\Core\Controller\ControllerBase;
use Drupal\Core\Form\FormState;
use Drupal\Core\Render\RendererInterface;
use Drupal\user\PrivateTempStoreFactory;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
use Drupal\Core\Ajax\AjaxResponse;
use Drupal\Core\Entity\EntityInterface;
use Drupal\quickedit\Ajax\FieldFormCommand;
use Drupal\quickedit\Ajax\FieldFormSavedCommand;
use Drupal\quickedit\Ajax\FieldFormValidationErrorsCommand;
use Drupal\quickedit\Ajax\EntitySavedCommand;
/**
* Returns responses for Quick Edit module routes.
*/
class QuickEditController extends ControllerBase {
/**
* The PrivateTempStore factory.
*
* @var \Drupal\user\PrivateTempStoreFactory
*/
protected $tempStoreFactory;
/**
* The in-place editing metadata generator.
*
* @var \Drupal\quickedit\MetadataGeneratorInterface
*/
protected $metadataGenerator;
/**
* The in-place editor selector.
*
* @var \Drupal\quickedit\EditorSelectorInterface
*/
protected $editorSelector;
/**
* The renderer.
*
* @var \Drupal\Core\Render\RendererInterface
*/
protected $renderer;
/**
* Constructs a new QuickEditController.
*
* @param \Drupal\user\PrivateTempStoreFactory $temp_store_factory
* The PrivateTempStore factory.
* @param \Drupal\quickedit\MetadataGeneratorInterface $metadata_generator
* The in-place editing metadata generator.
* @param \Drupal\quickedit\EditorSelectorInterface $editor_selector
* The in-place editor selector.
* @param \Drupal\Core\Render\RendererInterface $renderer
* The renderer.
*/
public function __construct(PrivateTempStoreFactory $temp_store_factory, MetadataGeneratorInterface $metadata_generator, EditorSelectorInterface $editor_selector, RendererInterface $renderer) {
$this->tempStoreFactory = $temp_store_factory;
$this->metadataGenerator = $metadata_generator;
$this->editorSelector = $editor_selector;
$this->renderer = $renderer;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container) {
return new static(
$container->get('user.private_tempstore'),
$container->get('quickedit.metadata.generator'),
$container->get('quickedit.editor.selector'),
$container->get('renderer')
);
}
/**
* Returns the metadata for a set of fields.
*
* Given a list of field quick edit IDs as POST parameters, run access checks
* on the entity and field level to determine whether the current user may
* edit them. Also retrieves other metadata.
*
* @return \Symfony\Component\HttpFoundation\JsonResponse
* The JSON response.
*/
public function metadata(Request $request) {
$fields = $request->request->get('fields');
if (!isset($fields)) {
throw new NotFoundHttpException();
}
$entities = $request->request->get('entities');
$metadata = array();
foreach ($fields as $field) {
list($entity_type, $entity_id, $field_name, $langcode, $view_mode) = explode('/', $field);
// Load the entity.
if (!$entity_type || !$this->entityManager()->getDefinition($entity_type)) {
throw new NotFoundHttpException();
}
$entity = $this->entityManager()->getStorage($entity_type)->load($entity_id);
if (!$entity) {
throw new NotFoundHttpException();
}
// Validate the field name and language.
if (!$field_name || !$entity->hasField($field_name)) {
throw new NotFoundHttpException();
}
if (!$langcode || !$entity->hasTranslation($langcode)) {
throw new NotFoundHttpException();
}
$entity = $entity->getTranslation($langcode);
// If the entity information for this field is requested, include it.
$entity_id = $entity->getEntityTypeId() . '/' . $entity_id;
if (is_array($entities) && in_array($entity_id, $entities) && !isset($metadata[$entity_id])) {
$metadata[$entity_id] = $this->metadataGenerator->generateEntityMetadata($entity);
}
$metadata[$field] = $this->metadataGenerator->generateFieldMetadata($entity->get($field_name), $view_mode);
}
return new JsonResponse($metadata);
}
/**
* Returns AJAX commands to load in-place editors' attachments.
*
* Given a list of in-place editor IDs as POST parameters, render AJAX
* commands to load those in-place editors.
*
* @return \Drupal\Core\Ajax\AjaxResponse
* The Ajax response.
*/
public function attachments(Request $request) {
$response = new AjaxResponse();
$editors = $request->request->get('editors');
if (!isset($editors)) {
throw new NotFoundHttpException();
}
$response->setAttachments($this->editorSelector->getEditorAttachments($editors));
return $response;
}
/**
* Returns a single field edit form as an Ajax response.
*
* @param \Drupal\Core\Entity\EntityInterface $entity
* The entity being edited.
* @param string $field_name
* The name of the field that is being edited.
* @param string $langcode
* The name of the language for which the field is being edited.
* @param string $view_mode_id
* The view mode the field should be rerendered in.
* @param \Symfony\Component\HttpFoundation\Request $request
* The current request object containing the search string.
*
* @return \Drupal\Core\Ajax\AjaxResponse
* The Ajax response.
*/
public function fieldForm(EntityInterface $entity, $field_name, $langcode, $view_mode_id, Request $request) {
$response = new AjaxResponse();
// Replace entity with PrivateTempStore copy if available and not resetting,
// init PrivateTempStore copy otherwise.
$tempstore_entity = $this->tempStoreFactory->get('quickedit')->get($entity->uuid());
if ($tempstore_entity && $request->request->get('reset') !== 'true') {
$entity = $tempstore_entity;
}
else {
$this->tempStoreFactory->get('quickedit')->set($entity->uuid(), $entity);
}
$form_state = (new FormState())
->set('langcode', $langcode)
->disableRedirect()
->addBuildInfo('args', [$entity, $field_name]);
$form = $this->formBuilder()->buildForm('Drupal\quickedit\Form\QuickEditFieldForm', $form_state);
if ($form_state->isExecuted()) {
// The form submission saved the entity in PrivateTempStore. Return the
// updated view of the field from the PrivateTempStore copy.
$entity = $this->tempStoreFactory->get('quickedit')->get($entity->uuid());
// Closure to render the field given a view mode.
$render_field_in_view_mode = function ($view_mode_id) use ($entity, $field_name, $langcode) {
return $this->renderField($entity, $field_name, $langcode, $view_mode_id);
};
// Re-render the updated field.
$output = $render_field_in_view_mode($view_mode_id);
// Re-render the updated field for other view modes (i.e. for other
// instances of the same logical field on the user's page).
$other_view_mode_ids = $request->request->get('other_view_modes') ?: array();
$other_view_modes = array_map($render_field_in_view_mode, array_combine($other_view_mode_ids, $other_view_mode_ids));
$response->addCommand(new FieldFormSavedCommand($output, $other_view_modes));
}
else {
$output = $this->renderer->renderRoot($form);
// When working with a hidden form, we don't want its CSS/JS to be loaded.
if ($request->request->get('nocssjs') !== 'true') {
$response->setAttachments($form['#attached']);
}
$response->addCommand(new FieldFormCommand($output));
$errors = $form_state->getErrors();
if (count($errors)) {
$status_messages = array(
'#type' => 'status_messages'
);
$response->addCommand(new FieldFormValidationErrorsCommand($this->renderer->renderRoot($status_messages)));
}
}
return $response;
}
/**
* Renders a field.
*
* If the view mode ID is not an Entity Display view mode ID, then the field
* was rendered using a custom render pipeline (not the Entity/Field API
* render pipeline).
*
* An example could be Views' render pipeline. In that case, the view mode ID
* would probably contain the View's ID, display and the row index.
*
* @param \Drupal\Core\Entity\EntityInterface $entity
* The entity being edited.
* @param string $field_name
* The name of the field that is being edited.
* @param string $langcode
* The name of the language for which the field is being edited.
* @param string $view_mode_id
* The view mode the field should be rerendered in. Either an Entity Display
* view mode ID, or a custom one. See hook_quickedit_render_field().
*
* @return \Drupal\Component\Render\MarkupInterface
* Rendered HTML.
*
* @see hook_quickedit_render_field()
*/
protected function renderField(EntityInterface $entity, $field_name, $langcode, $view_mode_id) {
$entity_view_mode_ids = array_keys($this->entityManager()->getViewModes($entity->getEntityTypeId()));
if (in_array($view_mode_id, $entity_view_mode_ids)) {
$entity = \Drupal::entityManager()->getTranslationFromContext($entity, $langcode);
$output = $entity->get($field_name)->view($view_mode_id);
}
else {
// Each part of a custom (non-Entity Display) view mode ID is separated
// by a dash; the first part must be the module name.
$mode_id_parts = explode('-', $view_mode_id, 2);
$module = reset($mode_id_parts);
$args = array($entity, $field_name, $view_mode_id, $langcode);
$output = $this->moduleHandler()->invoke($module, 'quickedit_render_field', $args);
}
return $this->renderer->renderRoot($output);
}
/**
* Saves an entity into the database, from PrivateTempStore.
*
* @param \Drupal\Core\Entity\EntityInterface $entity
* The entity being edited.
*
* @return \Drupal\Core\Ajax\AjaxResponse
* The Ajax response.
*/
public function entitySave(EntityInterface $entity) {
// Take the entity from PrivateTempStore and save in entity storage.
// fieldForm() ensures that the PrivateTempStore copy exists ahead.
$tempstore = $this->tempStoreFactory->get('quickedit');
$tempstore->get($entity->uuid())->save();
$tempstore->delete($entity->uuid());
// Return information about the entity that allows a front end application
// to identify it.
$output = array(
'entity_type' => $entity->getEntityTypeId(),
'entity_id' => $entity->id()
);
// Respond to client that the entity was saved properly.
$response = new AjaxResponse();
$response->addCommand(new EntitySavedCommand($output));
return $response;
}
}