PageRenderTime 44ms CodeModel.GetById 17ms RepoModel.GetById 0ms app.codeStats 0ms

/core/modules/quickedit/src/QuickEditController.php

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