layout_builder_ipe-1.0.x-dev/src/LayoutBuilder/LayoutBuilderConfirmForm.php
src/LayoutBuilder/LayoutBuilderConfirmForm.php
<?php
namespace Drupal\layout_builder_ipe\LayoutBuilder;
use Drupal\Core\Ajax\AjaxResponse;
use Drupal\Core\Ajax\CloseDialogCommand;
use Drupal\Core\Ajax\OpenDialogCommand;
use Drupal\Core\Ajax\RedirectCommand;
use Drupal\Core\Form\FormBuilderInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\StringTranslation\StringTranslationTrait;
use Drupal\Core\Url;
use Drupal\layout_builder\Form\DiscardLayoutChangesForm;
use Drupal\layout_builder\Form\RevertOverridesForm;
use Drupal\layout_builder\Plugin\SectionStorage\OverridesSectionStorage;
use Drupal\layout_builder\SectionStorageInterface;
use Drupal\layout_builder_ipe\LayoutBuilderIpeService;
use Drupal\layout_builder_ipe\Traits\ConfirmDialogTrait;
use Drupal\layout_builder_ipe\Traits\RedirectUriTrait;
use Drupal\layout_builder_ipe\Traits\SectionStorageTrait;
/**
* Service class for helping with altering the behavior of confirm forms.
*
* In the context of IPE, the default behavior of the confirmation forms for
* "Discard changes" and "Revert to defaults" doesn't make much sense. Instead
* of redirecting to the confirmation pages for those forms, where the cancel
* action then redirects to the canonical URL with a deactivated IPE, we want
* them to be displayed in a modal, that can simply be closed for canceling the
* action, so that the users stays in context.
*/
class LayoutBuilderConfirmForm {
use StringTranslationTrait;
use SectionStorageTrait;
use RedirectUriTrait;
use ConfirmDialogTrait;
/**
* The entity decorator service.
*
* @var \Drupal\layout_builder_ipe\LayoutBuilderIpeService
*/
protected $layoutBuilderIpe;
/**
* Creates an LayoutBuilderConfirmForm.
*
* @param \Drupal\layout_builder_ipe\LayoutBuilderIpeService $layout_builder_ipe
* The layout builder IPE service.
*/
public function __construct(LayoutBuilderIpeService $layout_builder_ipe) {
$this->layoutBuilderIpe = $layout_builder_ipe;
}
/**
* Get the confirm form class that is responsible to build the actual form.
*
* @param string $action
* The action. This is basically the buttons element key in the form array.
*
* @return \Drupal\Core\Form\ConfirmFormInterface|null
* An instance of the confirm class.
*/
private static function getConfirmFormClass($action) {
switch ($action) {
case 'discard_changes':
return DiscardLayoutChangesForm::create(\Drupal::getContainer());
case 'revert':
return RevertOverridesForm::create(\Drupal::getContainer());
}
}
/**
* Add our own handler for the given button in the LB interface.
*
* @param array $form
* An associative array containing the structure of the form.
* @param \Drupal\Core\Form\FormStateInterface $form_state
* The current state of the form.
* @param string $button_key
* The key of the button in the layout builder form structure.
*/
public function setConfirmButtonHandler(array &$form, FormStateInterface $form_state, $button_key) {
if (!array_key_exists($button_key, $form['actions'])) {
return;
}
$submit_handlers = $form['actions'][$button_key]['#submit'];
if (count($submit_handlers) != 1 || $submit_handlers[0] != '::redirectOnSubmit') {
return;
}
$section_storage = self::getSectionStorageFromFormState($form_state);
if (!$section_storage instanceof OverridesSectionStorage && $section_storage->getPluginId() != 'page_manager') {
return;
}
$entity = $this->layoutBuilderIpe->getEntityFromSectionStorage($section_storage);
// Ok, this is the default submit handler that simply redirects to the
// confirm form, so we are save to proceed.
$form['actions'][$button_key]['#ajax'] = [
'event' => 'click',
'callback' => [static::class, 'handleConfirmableFormSubmit'],
// Setting the route here is important, otherwise the form in the modal
// inherits the main URL outside the modal, which in our case is the
// layout_builder_ipe edit route. Confirming that will obviously fail, so
// we set the URL manually here and also mark this request as coming from
// AJAX.
'url' => Url::fromRoute('layout_builder.' . $section_storage->getStorageType() . '.' . $entity->getEntityTypeId() . '.' . $button_key, [
$entity->getEntityTypeId() => $entity->id(),
]),
'options' => [
'query' => array_filter([
FormBuilderInterface::AJAX_FORM_REQUEST => TRUE,
'destination' => $this->getRedirectUri($form_state),
]),
],
];
$form['#attached']['library'][] = 'core/drupal.dialog.ajax';
}
/**
* Handle a form submit that will lead to a confirmable form.
*
* @param array $form
* An associative array containing the structure of the form.
* @param \Drupal\Core\Form\FormStateInterface $form_state
* The current state of the form.
*
* @return \Drupal\Core\Ajax\AjaxResponse
* An AJAX response object.
*/
public static function handleConfirmableFormSubmit(array &$form, FormStateInterface $form_state) {
$action = end($form_state->getTriggeringElement()['#parents']);
$confirm_form_class = self::getConfirmFormClass($action);
if (!$confirm_form_class) {
return NULL;
}
$section_storage = self::getSectionStorageFromFormState($form_state);
$confirm_form = \Drupal::formBuilder()->getForm($confirm_form_class, $section_storage);
$ajax_response = new AjaxResponse();
$ajax_response->addCommand(new OpenDialogCommand('#layout-builder-modal', $confirm_form_class->getQuestion(), $confirm_form, self::getDialogOptions()));
return $ajax_response;
}
/**
* Alter the confirmation form for the "Revert to defaults" button.
*
* @param array $form
* An associative array containing the structure of the form.
* @param \Drupal\Core\Form\FormStateInterface $form_state
* The current state of the form.
* @param string $form_id
* The id of the form.
*/
public function alterConfirmationForm(array &$form, FormStateInterface $form_state, $form_id) {
$section_storage = self::getSectionStorageFromFormState($form_state);
if (!$section_storage instanceof OverridesSectionStorage && !$section_storage->getPluginId() == 'page_manager') {
return;
}
$entity = $this->layoutBuilderIpe->getEntityFromSectionStorage($section_storage);
if (!$this->layoutBuilderIpe->ipeEnabled($entity)) {
return;
}
// Check this is one of the confirmation forms that we want to handle.
$confirm_forms = [
'layout_builder_discard_changes' => [
'callback' => 'discardChangesConfirm',
'route_fragment' => 'discard_changes',
],
'layout_builder_revert_overrides' => [
'callback' => 'revertConfirm',
'route_fragment' => 'revert',
],
];
$callback = $confirm_forms[$form_id]['callback'] ?? NULL;
$route_fragment = $confirm_forms[$form_id]['route_fragment'] ?? NULL;
if (!$callback || !$route_fragment) {
// Something is fishy, better bail out.
return;
}
$entity = $section_storage->getContextValue('entity');
$section_storage_identifier = $section_storage->getPluginId() . ($section_storage->getPluginId() != 'page_manager' ? '.' . $entity->getEntityTypeId() : '');
$redirect_url = $this->getRedirectUri($form_state);
if (!$redirect_url && $section_storage->getPluginId() == 'page_manager') {
$redirect_url = $this->layoutBuilderIpe->getCurrentEditPath();
}
$form['#submit'] = [[static::class, $callback]];
$form['actions']['submit']['#ajax'] = [
'event' => 'click',
'callback' => [static::class, $callback],
// Setting the route here is important, otherwise the form in the modal
// inherits the main URL outside the modal, which in our case is the
// layout_builder_ipe edit route. Confirming that will obviously fail, so
// we set the URL manually here and also mark this request as coming from
// AJAX.
'url' => Url::fromRoute('layout_builder.' . $section_storage_identifier . '.' . $route_fragment, [
$entity->getEntityTypeId() => $entity->id(),
]),
'options' => [
'query' => array_filter([
FormBuilderInterface::AJAX_FORM_REQUEST => TRUE,
'destination' => $redirect_url,
]),
],
];
$form['actions']['cancel']['#attributes']['class'][] = 'dialog-cancel';
}
/**
* Create a redirect AJAX response for the given section storage.
*
* @param \Drupal\layout_builder\SectionStorageInterface $section_storage
* The section storage to get the redirect URL.
* @param string $redirect_uri
* An optional redirect uri.
*
* @return \Drupal\Core\Ajax\AjaxResponse
* An AJAX response object.
*/
private static function redirectAjaxResponse(SectionStorageInterface $section_storage, $redirect_uri = NULL) {
$ajax_response = new AjaxResponse();
$ajax_response->addCommand(new CloseDialogCommand('#layout-builder-modal'));
$ajax_response->addCommand(new RedirectCommand($redirect_uri ?? $section_storage->getRedirectUrl()->toString()));
return $ajax_response;
}
/**
* Handle the confirm action of the discard changes confirmation form.
*
* @param array $form
* An associative array containing the structure of the form.
* @param \Drupal\Core\Form\FormStateInterface $form_state
* The current state of the form.
*
* @return \Drupal\Core\Ajax\AjaxResponse
* An AJAX response object.
*/
public static function discardChangesConfirm(array $form, FormStateInterface $form_state) {
$section_storage = self::getSectionStorageFromFormState($form_state);
/** @var \Drupal\layout_builder\LayoutTempstoreRepositoryInterface $layout_tempstore_repository */
$layout_tempstore_repository = \Drupal::service('layout_builder.tempstore_repository');
$layout_tempstore_repository->delete($section_storage);
\Drupal::messenger()->addMessage(t('The changes to the layout have been discarded.'));
return self::redirectAjaxResponse($section_storage, self::getRedirectUri($form_state));
}
/**
* Handle the confirm action of the revert confirmation form.
*
* @param array $form
* An associative array containing the structure of the form.
* @param \Drupal\Core\Form\FormStateInterface $form_state
* The current state of the form.
*
* @return \Drupal\Core\Ajax\AjaxResponse
* An AJAX response object.
*/
public static function revertConfirm(array $form, FormStateInterface $form_state) {
$section_storage = self::getSectionStorageFromFormState($form_state);
$section_storage->removeAllSections()->save();
/** @var \Drupal\layout_builder\LayoutTempstoreRepositoryInterface $layout_tempstore_repository */
$layout_tempstore_repository = \Drupal::service('layout_builder.tempstore_repository');
$layout_tempstore_repository->delete($section_storage);
\Drupal::messenger()->addMessage(t('The layout has been reverted back to defaults.'));
return self::redirectAjaxResponse($section_storage);
}
}
