layout_builder_ipe-1.0.x-dev/src/Controller/PageVariantController.php
src/Controller/PageVariantController.php
<?php
namespace Drupal\layout_builder_ipe\Controller;
use Drupal\Core\Form\FormState;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Routing\RouteMatch;
use Drupal\gin_lb\GinLayoutBuilderUtility;
use Drupal\layout_builder\SectionStorageInterface;
use Drupal\page_manager\PageVariantInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Defines an edit controller controller for page manager variant entities.
*
* @internal
* Controller classes are internal.
*/
class PageVariantController extends BaseController {
/**
* The route provider service.
*
* @var \Drupal\Core\Routing\RouteProviderInterface
*/
protected $routeProvider;
/**
* The ctools wizard factory.
*
* @var \Drupal\ctools\Wizard\WizardFactoryInterface
*/
protected $wizardFactory;
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container) {
$instance = parent::create($container);
$instance->routeProvider = $container->get('router.route_provider');
$instance->wizardFactory = $container->get('ctools.wizard.factory');
return $instance;
}
/**
* Check access to the Layout Builder IPE frontend.
*
* @param \Drupal\layout_builder\SectionStorageInterface $section_storage
* The section storage.
*
* @return \Drupal\Core\Access\AccessResult
* An access result object.
*/
public function access(SectionStorageInterface $section_storage) {
return $this->layoutBuilderIpe->access($section_storage);
}
/**
* Get the edit part of a layout builder page.
*
* @param \Drupal\layout_builder\SectionStorageInterface $section_storage
* The section storage.
*
* @return \Drupal\Core\Ajax\AjaxResponse
* The response containing the layout builder form.
*/
public function edit(SectionStorageInterface $section_storage) {
return $this->buildAjaxResponse($section_storage);
}
/**
* Build an ajax response.
*
* @param \Drupal\layout_builder\SectionStorageInterface $section_storage
* The section storage.
*
* @return \Drupal\Core\Ajax\AjaxResponse
* The response containing the layout builder form.
*/
protected function buildAjaxResponse(SectionStorageInterface $section_storage) {
$layout_builder_layout = NULL;
$layout_builder_form = NULL;
$layout_builder_message = NULL;
/** @var \Drupal\page_manager\PageVariantInterface $entity */
$entity = $this->layoutBuilderIpe->getEntityFromSectionStorage($section_storage);
$editing = FALSE;
// Get the layout builder form.
$form_class = $entity->getEntityType()->getFormClass('layout_builder');
if ($entity && $form_class) {
$editing = TRUE;
/** @var \Drupal\page_manager\Entity\Page $page */
$page = $entity->getPage();
$form_object = $this->classResolver->getInstanceFromDefinition($form_class);
$form_state = new FormState();
$form_state->setTemporaryValue('wizard', [
'page_variant' => $entity,
'page' => $page,
]);
$layout_builder_form = $this->formBuilder()->buildForm($form_object, $form_state);
$layout_builder_layout = $layout_builder_form['layout_builder'];
$layout_builder_message = $layout_builder_form['layout_builder_message'] ?? NULL;
unset($layout_builder_form['layout_builder']);
unset($layout_builder_form['layout_builder_message']);
$this->processPageManagerForm($layout_builder_form, $form_state, $entity, $section_storage);
if ($form_state->isSubmitted()) {
// If the form has been submitted, render the page variant entity to
// replace the current form.
$layout_builder_layout = $this->renderPageVariantEntity($entity);
$layout_builder_form = NULL;
// Also include any messages to notify the user about the status of
// the operation.
$layout_builder_message = [
'#type' => 'container',
'#attributes' => [
'class' => [
'layout-builder__message',
'layout-builder__message--defaults',
],
],
'message' => [
'#theme' => 'status_messages',
'#message_list' => $this->messenger()->all(),
'#status_headings' => [
'status' => $this->t('Status message'),
],
],
'#weight' => -900,
];
$this->messenger()->deleteAll();
$editing = FALSE;
}
}
return $this->ajaxResponse([
$layout_builder_layout,
$layout_builder_form,
$layout_builder_message,
], $editing);
}
/**
* Process the layout builder form as part of a page manager wizard form.
*
* This is complicated, but basically necessary in order to get the form
* actions in and properly setup.
*
* @param array $layout_builder_form
* The already build form.
* @param \Drupal\Core\Form\FormStateInterface $form_state
* The form state object.
* @param \Drupal\page_manager\PageVariantInterface $entity
* The page variant entity.
* @param \Drupal\layout_builder\SectionStorageInterface $section_storage
* The section storage.
*/
private function processPageManagerForm(array &$layout_builder_form, FormStateInterface $form_state, PageVariantInterface $entity, SectionStorageInterface $section_storage) {
/** @var \Drupal\page_manager\Entity\Page $page */
$page = $entity->getPage();
// Setup some things to be able to use the wizard factory.
$form_arg = 'Drupal\page_manager_ui\Wizard\PageEditWizard';
$step = 'page_variant__' . $entity->id() . '__layout_builder';
$tempstore_id = $section_storage->getPluginId() . '.' . $page->getEntityTypeId();
$machine_name = $page->id();
$route_name = 'entity.page.edit_form';
$route = $this->routeProvider->getRouteByName($route_name);
$route_match = new RouteMatch($route_name, $route, [
'tempstore_id' => $tempstore_id,
'page' => $page,
'machine_name' => $machine_name,
'step' => $step,
], [
'machine_name' => $machine_name,
'step' => $step,
]);
$parameters = $route_match->getParameters()->all();
$parameters += $form_arg::getParameters();
$parameters['route_match'] = $route_match;
/** @var \Drupal\page_manager_ui\Wizard\PageEditWizard $wizard */
$wizard = $this->wizardFactory->createWizard($form_arg, $parameters);
$wizard->initValues();
$wizard_form = $wizard->buildForm($layout_builder_form, $form_state);
unset($wizard_form['actions']['submit']);
unset($wizard_form['actions']['update_and_save']);
// Extract the actions and make some adjustments. Keep a reference to the
// original form, which is processed later by the form builder class.
$actions = &$wizard_form['actions'];
$actions['#type'] = 'actions';
$actions['#attributes']['role'] = 'region';
$actions['#attributes']['aria-label'] = $this->t('Layout Builder tools');
$actions['#attributes']['class'][] = 'form-actions';
$actions['preview_toggle'] = $layout_builder_form['preview_toggle'];
$actions['preview_toggle']['#weight'] = 10;
unset($layout_builder_form['preview_toggle']);
// Again, keep a reference so that form processing later just works.
$layout_builder_form['actions'] = &$actions;
if ($this->moduleHandler()->moduleExists('gin_lb')) {
// To support Gin LB, we'll call their form alter and attach functions
// directly.
gin_lb_form_alter($layout_builder_form, $form_state, $form_state->getFormObject()->getFormId());
GinLayoutBuilderUtility::attachGinLbForm($layout_builder_form);
// And also fix a missing property. It's probably missing because we are
// doing this after the form has been build and processed already.
$layout_builder_form['actions']['preview_regions']['#name'] = 'preview_regions';
// Add class to the submit button to make sure that the save button still
// works.
$layout_builder_form['actions']['finish']['#attributes']['class'][] = 'js-glb-button--primary';
}
// Make some more adjustments, mainly to have similar wording as in the
// core layout manager.
$layout_builder_form['actions']['finish']['#button_type'] = 'primary';
$layout_builder_form['actions']['finish']['#value'] = $this->t('Save Layout');
$layout_builder_form['actions']['cancel']['#value'] = $this->t('Discard changes');
// And make the form ajax submittable.
$layout_builder_form['actions']['finish']['#attributes']['class'][] = 'use-ajax-submit';
$layout_builder_form['actions']['cancel']['#attributes']['class'][] = 'use-ajax-submit';
// Add dependencies: https://www.drupal.org/project/drupal/issues/2897120
$layout_builder_form['actions']['#attached']['library'][] = 'core/jquery.form';
$layout_builder_form['actions']['#attached']['library'][] = 'core/drupal.ajax';
// We process the form, so that handling of submission data can happen.
$this->formBuilder()->processForm($wizard->getFormId(), $wizard_form, $form_state);
// We also handle form submission here in order to make the entity actually
// save. Not sure why this doesn't work out of the box like for the content
// entity based controllers. Those ctools wizards are complicated.
$action = $form_state->getTriggeringElement() ? end($form_state->getTriggeringElement()['#parents']) : NULL;
if (array_key_exists($action, $actions)) {
if ($action == 'finish') {
$cached_values = $form_state->getTemporaryValue('wizard');
$cached_values['id'] = $page->id();
$cached_values['label'] = $page->label();
$form_state->setTemporaryValue('wizard', $cached_values);
$wizard->finish($wizard_form, $form_state);
$entity->save();
}
if ($action == 'cancel' || $action == 'finish') {
// Remove all temporary data and mark the form as submitted.
$wizard->clearTempstore($wizard_form, $form_state);
$this->layoutTempstoreRepository->delete($section_storage);
$this->tempstore->get('page_manager.layout_builder')->delete($entity->id());
$this->tempstore->get('page_manager.page_variant')->delete($entity->id());
$form_state->setSubmitted();
}
}
}
/**
* Render a page variant entity.
*
* In order to render properly, we need to simulate the original request for
* the edited page, so that access checks and conditions can apply correctly.
*
* @param \Drupal\page_manager\PageVariantInterface $entity
* The page variant to render.
*/
private function renderPageVariantEntity(PageVariantInterface $entity) {
$current_path = $this->layoutBuilderIpe->getCurrentEditPath();
if (!$current_path) {
return NULL;
}
// Reload the entity so that rendering takes in the latest updates.
$_entity = $this->entityTypeManager()->getStorage($entity->getEntityTypeId())->load($entity->id());
$view_builder = $this->entityTypeManager()->getViewBuilder($_entity->getEntityTypeId());
$build = $view_builder->view($_entity);
return $build;
}
}
