Chris@0: tempStoreFactory = $temp_store_factory; Chris@0: $this->metadataGenerator = $metadata_generator; Chris@0: $this->editorSelector = $editor_selector; Chris@0: $this->renderer = $renderer; Chris@18: if (!$entity_display_repository) { Chris@18: @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); Chris@18: $entity_display_repository = \Drupal::service('entity_display.repository'); Chris@18: } Chris@18: $this->entityDisplayRepository = $entity_display_repository; Chris@18: if (!$entity_repository) { Chris@18: @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); Chris@18: $entity_repository = \Drupal::service('entity.repository'); Chris@18: } Chris@18: $this->entityRepository = $entity_repository; Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public static function create(ContainerInterface $container) { Chris@0: return new static( Chris@14: $container->get('tempstore.private'), Chris@0: $container->get('quickedit.metadata.generator'), Chris@0: $container->get('quickedit.editor.selector'), Chris@18: $container->get('renderer'), Chris@18: $container->get('entity_display.repository'), Chris@18: $container->get('entity.repository') Chris@0: ); Chris@0: } Chris@0: Chris@0: /** Chris@0: * Returns the metadata for a set of fields. Chris@0: * Chris@0: * Given a list of field quick edit IDs as POST parameters, run access checks Chris@0: * on the entity and field level to determine whether the current user may Chris@0: * edit them. Also retrieves other metadata. Chris@0: * Chris@0: * @return \Symfony\Component\HttpFoundation\JsonResponse Chris@0: * The JSON response. Chris@0: */ Chris@0: public function metadata(Request $request) { Chris@0: $fields = $request->request->get('fields'); Chris@0: if (!isset($fields)) { Chris@0: throw new NotFoundHttpException(); Chris@0: } Chris@0: $entities = $request->request->get('entities'); Chris@0: Chris@0: $metadata = []; Chris@0: foreach ($fields as $field) { Chris@0: list($entity_type, $entity_id, $field_name, $langcode, $view_mode) = explode('/', $field); Chris@0: Chris@0: // Load the entity. Chris@18: if (!$entity_type || !$this->entityTypeManager()->getDefinition($entity_type)) { Chris@0: throw new NotFoundHttpException(); Chris@0: } Chris@18: $entity = $this->entityTypeManager()->getStorage($entity_type)->load($entity_id); Chris@0: if (!$entity) { Chris@0: throw new NotFoundHttpException(); Chris@0: } Chris@0: Chris@0: // Validate the field name and language. Chris@0: if (!$field_name || !$entity->hasField($field_name)) { Chris@0: throw new NotFoundHttpException(); Chris@0: } Chris@0: if (!$langcode || !$entity->hasTranslation($langcode)) { Chris@0: throw new NotFoundHttpException(); Chris@0: } Chris@0: Chris@0: $entity = $entity->getTranslation($langcode); Chris@0: Chris@0: // If the entity information for this field is requested, include it. Chris@0: $entity_id = $entity->getEntityTypeId() . '/' . $entity_id; Chris@0: if (is_array($entities) && in_array($entity_id, $entities) && !isset($metadata[$entity_id])) { Chris@0: $metadata[$entity_id] = $this->metadataGenerator->generateEntityMetadata($entity); Chris@0: } Chris@0: Chris@0: $metadata[$field] = $this->metadataGenerator->generateFieldMetadata($entity->get($field_name), $view_mode); Chris@0: } Chris@0: Chris@0: return new JsonResponse($metadata); Chris@0: } Chris@0: Chris@0: /** Chris@0: * Returns AJAX commands to load in-place editors' attachments. Chris@0: * Chris@0: * Given a list of in-place editor IDs as POST parameters, render AJAX Chris@0: * commands to load those in-place editors. Chris@0: * Chris@0: * @return \Drupal\Core\Ajax\AjaxResponse Chris@0: * The Ajax response. Chris@0: */ Chris@0: public function attachments(Request $request) { Chris@0: $response = new AjaxResponse(); Chris@0: $editors = $request->request->get('editors'); Chris@0: if (!isset($editors)) { Chris@0: throw new NotFoundHttpException(); Chris@0: } Chris@0: Chris@0: $response->setAttachments($this->editorSelector->getEditorAttachments($editors)); Chris@0: Chris@0: return $response; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Returns a single field edit form as an Ajax response. Chris@0: * Chris@0: * @param \Drupal\Core\Entity\EntityInterface $entity Chris@0: * The entity being edited. Chris@0: * @param string $field_name Chris@0: * The name of the field that is being edited. Chris@0: * @param string $langcode Chris@0: * The name of the language for which the field is being edited. Chris@0: * @param string $view_mode_id Chris@0: * The view mode the field should be rerendered in. Chris@0: * @param \Symfony\Component\HttpFoundation\Request $request Chris@0: * The current request object containing the search string. Chris@0: * Chris@0: * @return \Drupal\Core\Ajax\AjaxResponse Chris@0: * The Ajax response. Chris@0: */ Chris@0: public function fieldForm(EntityInterface $entity, $field_name, $langcode, $view_mode_id, Request $request) { Chris@0: $response = new AjaxResponse(); Chris@0: Chris@0: // Replace entity with PrivateTempStore copy if available and not resetting, Chris@0: // init PrivateTempStore copy otherwise. Chris@0: $tempstore_entity = $this->tempStoreFactory->get('quickedit')->get($entity->uuid()); Chris@0: if ($tempstore_entity && $request->request->get('reset') !== 'true') { Chris@0: $entity = $tempstore_entity; Chris@0: } Chris@0: else { Chris@0: $this->tempStoreFactory->get('quickedit')->set($entity->uuid(), $entity); Chris@0: } Chris@0: Chris@0: $form_state = (new FormState()) Chris@0: ->set('langcode', $langcode) Chris@0: ->disableRedirect() Chris@0: ->addBuildInfo('args', [$entity, $field_name]); Chris@0: $form = $this->formBuilder()->buildForm('Drupal\quickedit\Form\QuickEditFieldForm', $form_state); Chris@0: Chris@0: if ($form_state->isExecuted()) { Chris@0: // The form submission saved the entity in PrivateTempStore. Return the Chris@0: // updated view of the field from the PrivateTempStore copy. Chris@0: $entity = $this->tempStoreFactory->get('quickedit')->get($entity->uuid()); Chris@0: Chris@0: // Closure to render the field given a view mode. Chris@0: $render_field_in_view_mode = function ($view_mode_id) use ($entity, $field_name, $langcode) { Chris@0: return $this->renderField($entity, $field_name, $langcode, $view_mode_id); Chris@0: }; Chris@0: Chris@0: // Re-render the updated field. Chris@0: $output = $render_field_in_view_mode($view_mode_id); Chris@0: Chris@0: // Re-render the updated field for other view modes (i.e. for other Chris@0: // instances of the same logical field on the user's page). Chris@0: $other_view_mode_ids = $request->request->get('other_view_modes') ?: []; Chris@0: $other_view_modes = array_map($render_field_in_view_mode, array_combine($other_view_mode_ids, $other_view_mode_ids)); Chris@0: Chris@0: $response->addCommand(new FieldFormSavedCommand($output, $other_view_modes)); Chris@0: } Chris@0: else { Chris@0: $output = $this->renderer->renderRoot($form); Chris@0: // When working with a hidden form, we don't want its CSS/JS to be loaded. Chris@0: if ($request->request->get('nocssjs') !== 'true') { Chris@0: $response->setAttachments($form['#attached']); Chris@0: } Chris@0: $response->addCommand(new FieldFormCommand($output)); Chris@0: Chris@0: $errors = $form_state->getErrors(); Chris@0: if (count($errors)) { Chris@0: $status_messages = [ Chris@17: '#type' => 'status_messages', Chris@0: ]; Chris@0: $response->addCommand(new FieldFormValidationErrorsCommand($this->renderer->renderRoot($status_messages))); Chris@0: } Chris@0: } Chris@0: Chris@0: return $response; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Renders a field. Chris@0: * Chris@0: * If the view mode ID is not an Entity Display view mode ID, then the field Chris@0: * was rendered using a custom render pipeline (not the Entity/Field API Chris@0: * render pipeline). Chris@0: * Chris@0: * An example could be Views' render pipeline. In that case, the view mode ID Chris@0: * would probably contain the View's ID, display and the row index. Chris@0: * Chris@0: * @param \Drupal\Core\Entity\EntityInterface $entity Chris@0: * The entity being edited. Chris@0: * @param string $field_name Chris@0: * The name of the field that is being edited. Chris@0: * @param string $langcode Chris@0: * The name of the language for which the field is being edited. Chris@0: * @param string $view_mode_id Chris@0: * The view mode the field should be rerendered in. Either an Entity Display Chris@0: * view mode ID, or a custom one. See hook_quickedit_render_field(). Chris@0: * Chris@0: * @return \Drupal\Component\Render\MarkupInterface Chris@0: * Rendered HTML. Chris@0: * Chris@0: * @see hook_quickedit_render_field() Chris@0: */ Chris@0: protected function renderField(EntityInterface $entity, $field_name, $langcode, $view_mode_id) { Chris@18: $entity_view_mode_ids = array_keys($this->entityDisplayRepository->getViewModes($entity->getEntityTypeId())); Chris@0: if (in_array($view_mode_id, $entity_view_mode_ids)) { Chris@18: $entity = $this->entityRepository->getTranslationFromContext($entity, $langcode); Chris@0: $output = $entity->get($field_name)->view($view_mode_id); Chris@0: } Chris@0: else { Chris@0: // Each part of a custom (non-Entity Display) view mode ID is separated Chris@0: // by a dash; the first part must be the module name. Chris@0: $mode_id_parts = explode('-', $view_mode_id, 2); Chris@0: $module = reset($mode_id_parts); Chris@0: $args = [$entity, $field_name, $view_mode_id, $langcode]; Chris@0: $output = $this->moduleHandler()->invoke($module, 'quickedit_render_field', $args); Chris@0: } Chris@0: Chris@0: return $this->renderer->renderRoot($output); Chris@0: } Chris@0: Chris@0: /** Chris@0: * Saves an entity into the database, from PrivateTempStore. Chris@0: * Chris@0: * @param \Drupal\Core\Entity\EntityInterface $entity Chris@0: * The entity being edited. Chris@0: * Chris@0: * @return \Drupal\Core\Ajax\AjaxResponse Chris@0: * The Ajax response. Chris@0: */ Chris@0: public function entitySave(EntityInterface $entity) { Chris@0: // Take the entity from PrivateTempStore and save in entity storage. Chris@0: // fieldForm() ensures that the PrivateTempStore copy exists ahead. Chris@0: $tempstore = $this->tempStoreFactory->get('quickedit'); Chris@0: $tempstore->get($entity->uuid())->save(); Chris@0: $tempstore->delete($entity->uuid()); Chris@0: Chris@0: // Return information about the entity that allows a front end application Chris@0: // to identify it. Chris@0: $output = [ Chris@0: 'entity_type' => $entity->getEntityTypeId(), Chris@17: 'entity_id' => $entity->id(), Chris@0: ]; Chris@0: Chris@0: // Respond to client that the entity was saved properly. Chris@0: $response = new AjaxResponse(); Chris@0: $response->addCommand(new EntitySavedCommand($output)); Chris@0: return $response; Chris@0: } Chris@0: Chris@0: }