a12s-1.0.0-beta7/modules/page_context/src/Form/PageContextForm.php
modules/page_context/src/Form/PageContextForm.php
<?php
namespace Drupal\a12s_page_context\Form;
use Drupal\a12s_page_context\Entity\PageContextForm as PageContextFormEntity;
use Drupal\a12s_page_context\Entity\PageContextFormInterface;
use Drupal\a12s_page_context\Plugin\FormDisplayPluginInterface;
use Drupal\a12s_page_context\Plugin\FormDisplayPluginManager;
use Drupal\Component\Plugin\Exception\PluginException;
use Drupal\Component\Uuid\UuidInterface;
use Drupal\Core\DependencyInjection\ContainerInjectionInterface;
use Drupal\Core\Entity\EntityForm;
use Drupal\Core\Extension\Extension;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Form\SubformState;
use Drupal\Core\Serialization\Yaml;
use Drupal\Core\Utility\Error;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Page context form.
*
* @property \Drupal\a12s_page_context\Entity\PageContextFormInterface $entity
*/
class PageContextForm extends EntityForm implements ContainerInjectionInterface {
/**
* Constructs a page context form.
*
* @param \Drupal\a12s_page_context\Plugin\FormDisplayPluginManager $formDisplayPluginManager
* The manager for "page context form" plugins.
* @param \Drupal\Component\Uuid\UuidInterface $uuidGenerator
* The UUID service.
*/
public function __construct(protected FormDisplayPluginManager $formDisplayPluginManager, protected UuidInterface $uuidGenerator) {}
/**
* {@inheritdoc}
* @noinspection PhpParamsInspection
*/
public static function create(ContainerInterface $container): static {
return new static(
$container->get('plugin.manager.a12s_page_context_form_display'),
$container->get('uuid')
);
}
/**
* {@inheritdoc}
*/
public function form(array $form, FormStateInterface $form_state): array {
$form = parent::form($form, $form_state);
$form['#entity_builders'][] = '::buildEntityThemeSettings';
$form['#entity_builders'][] = '::buildEntityDisplayPlugins';
$form['label'] = [
'#type' => 'textfield',
'#title' => $this->t('Label'),
'#maxlength' => 255,
'#default_value' => $this->entity->label(),
'#description' => $this->t('Label for the form.'),
'#required' => TRUE,
];
$form['id'] = [
'#type' => 'machine_name',
'#default_value' => $this->entity->id(),
'#machine_name' => [
'exists' => [static::class, 'pathIdExists'],
],
'#disabled' => !$this->entity->isNew(),
];
$form['status'] = [
'#type' => 'checkbox',
'#title' => $this->t('Enabled'),
'#default_value' => $this->entity->status(),
];
$form['description'] = [
'#type' => 'textarea',
'#default_value' => $this->entity->get('description'),
'#title' => $this->t('Description'),
];
$form['definition'] = [
'#type' => 'textarea',
'#title' => $this->t('Form content (YAML)'),
'#default_value' => $this->entity->get('definition') ?? '',
'#description' => $this->t('The definition of the form, using form elements from the form API.'),
'#cols' => 60,
'#rows' => 5,
'#resizable' => 'vertical',
'#attributes' => ['style' => 'min-height: 300px'],
];
$form['all_themes'] = [
'#type' => 'checkbox',
'#title' => $this->t('Enable this page context form for all themes'),
'#default_value' => $this->entity->get('all_themes'),
];
$themes = static::getThemesList();
$defaultThemes = $this->entity->get('themes') ?? [];
$defaultThemes = array_intersect($defaultThemes, array_keys($themes));
$form['themes'] = [
'#type' => 'checkboxes',
'#title' => $this->t('Themes'),
'#description' => $this->t('The page context form is only available for the selected themes.'),
'#options' => $themes,
'#default_value' => $defaultThemes,
'#states' => [
'visible' => [
':input[name="all_themes"]' => ['checked' => FALSE],
],
],
];
$form['display'] = [
'#title' => $this->t('Display settings'),
'#type' => 'fieldset',
'#tree' => TRUE,
'#prefix' => '<div id="a12s-page-context-form-display-settings-ajax-wrapper">',
'#suffix' => '</div>',
];
$form['display']['plugins'] = [
'#type' => 'table',
'#header' => [
'plugin' => $this->t('Plugin'),
'configuration' => $this->t('Configuration'),
'operations' => $this->t('Operations'),
'weight' => $this->t('Weight'),
],
'#empty' => $this->t('You need to add some plugins if you want to use this form.'),
'#tabledrag' => [
[
'action' => 'order',
'relationship' => 'sibling',
'group' => 'weight',
],
],
];
$inUsePlugins = [];
foreach ($this->entity->getDisplaySettings() as $uuid => $settings) {
try {
$configuration = $settings['configuration'] ?? [];
/** @var \Drupal\a12s_page_context\Plugin\FormDisplayPluginInterface $plugin */
$plugin = $this->formDisplayPluginManager->createInstance($settings['plugin_id'], $configuration);
}
catch (PluginException $e) {
Error::logException(\Drupal::logger('a12s_page_context'), $e);
continue;
}
$inUsePlugins[] = $plugin->getPluginId();
$subForm = ['#parents' => ['display', 'plugins', $uuid, 'configuration']];
$subformState = SubformState::createForSubform($subForm, $form, $form_state);
$subForm = $plugin->buildConfigurationForm($subForm, $subformState);
$form['display']['plugins'][$uuid] = $this->buildPluginRow($uuid, $settings['weight'], $plugin, $subForm);
}
$pluginOptions = array_map(
fn(array $definition) => $definition['description'] ?? $definition['label'],
$this->formDisplayPluginManager->getDefinitions()
);
$pluginOptions = array_diff_key($pluginOptions, array_flip($inUsePlugins));
$form['display']['add'] = [
'#type' => 'fieldset',
'#title' => $this->t('Add a new display plugin'),
'#attributes' => [
'class' => ['container-inline'],
],
'#access' => !empty($pluginOptions),
];
$form['display']['add']['plugin_id'] = [
'#type' => 'select',
'#title' => $this->t('Select plugin'),
'#title_display' => 'invisible',
'#options' => $pluginOptions,
];
$form['display']['add']['submit'] = [
'#type' => 'button',
'#value' => $this->t('Add'),
'#ajax' => $this->ajaxPluginSettings(),
];
return $form;
}
/**
* Default AJAX settings when updating the display plugins list.
*
* @param string|null $message
* The loading message.
*
* @return array
* The AJAX settings.
*/
protected function ajaxPluginSettings(?string $message = NULL): array {
if (!$message) {
$message = $this->t('Loading plugin');
}
return [
'callback' => '::updateDisplayPlugins',
'wrapper' => 'a12s-page-context-form-display-settings-ajax-wrapper',
'#limit_validation_errors' => [],
'progress' => [
'type' => 'throbber',
'message' => $message,
],
];
}
/**
* Build a single row, related to the given plugin.
*/
protected function buildPluginRow(string $uuid, int $weight, FormDisplayPluginInterface $plugin, array $configuration_form): array {
return [
'#weight' => $weight,
'#attributes' => ['class' => ['draggable']],
'plugin' => [
'label' => ['#plain_text' => $plugin->label()],
'plugin_id' => [
'#type' => 'hidden',
'#value' => $plugin->getPluginId(),
'#parents' => ['display', 'plugins', $uuid, 'plugin_id'],
],
],
'configuration' => $configuration_form,
'operations' => [
'delete' => [
'#type' => 'button',
'#value' => $this->t('Delete'),
'#ajax' => $this->ajaxPluginSettings($this->t('Deleting plugin')),
'#op' => 'delete',
'#name' => 'delete-' . $uuid,
'#uuid' => $uuid,
],
],
'weight' => [
'#type' => 'weight',
'#title' => $this->t('Weight'),
'#title_display' => 'invisible',
'#default_value' => $weight,
'#attributes' => ['class' => ['weight']],
],
];
}
/**
* AJAX callback to update the display plugins.
*
* @param array $form
* The form array.
* @param \Drupal\Core\Form\FormStateInterface $form_state
* The form state.
*/
public function updateDisplayPlugins(array $form, FormStateInterface $form_state): array {
return $form['display'];
}
/**
* {@inheritdoc}
*/
public function validateForm(array &$form, FormStateInterface $form_state): void {
parent::validateForm($form, $form_state);
$definition = $form_state->getValue('definition');
try {
$data = Yaml::decode($definition);
if (!is_array($data) && $definition) {
throw new \Exception($this->t('YAML must contain an associative array of elements.'));
}
$form_state->setValueForElement($form['definition'], Yaml::encode($data));
}
catch (\Exception $exception) {
$form_state->setErrorByName('definition', $exception->getMessage());
}
// Validate display plugin configuration. We use the "entity" property and
// not the $form_state values, as the entity has been built from those last.
// So the entity contains a valid configuration for the plugin.
foreach ($this->entity->getDisplaySettings() as $uuid => $settings) {
try {
/** @var \Drupal\a12s_page_context\Plugin\FormDisplayPluginInterface $plugin */
$plugin = $this->formDisplayPluginManager->createInstance($settings['plugin_id'], $settings['configuration'] ?? []);
$subForm = &$form['display']['plugins'][$uuid]['configuration'];
if (isset($subForm)) {
$subformState = SubformState::createForSubform($subForm, $form, $form_state);
$plugin->validateConfigurationForm($subForm, $subformState);
}
}
catch (PluginException $e) {
continue;
}
}
}
/**
* Custom entity builder for handling theme settings.
*
* @param string $entity_type
* The entity type.
* @param \Drupal\a12s_page_context\Entity\PageContextFormInterface $entity
* The Page Context Form entity.
* @param array $form
* The form array.
* @param \Drupal\Core\Form\FormStateInterface $form_state
* The current state of the form.
*/
public function buildEntityThemeSettings(string $entity_type, PageContextFormInterface $entity, array &$form, FormStateInterface $form_state): void {
$themes = $form_state->getValue('themes');
if ($form_state->getValue('all_themes')) {
$themes = [];
}
$entity->setThemes(array_filter(array_values($themes)));
}
/**
* Custom entity builder for handling display plugins.
*
* @param string $entity_type
* The entity type.
* @param \Drupal\a12s_page_context\Entity\PageContextFormInterface $entity
* The "Page Context Form" entity.
* @param array $form
* The form array.
* @param \Drupal\Core\Form\FormStateInterface $form_state
* The current state of the form.
*/
public function buildEntityDisplayPlugins(string $entity_type, PageContextFormInterface $entity, array &$form, FormStateInterface $form_state): void {
unset($entity->display);
$displaySettings = [];
$maxWeight = -10;
$validKeys = array_flip(['plugin_id', 'weight', 'configuration']);
foreach ($form_state->getValue(['display', 'plugins']) ?: [] as $uuid => $values) {
try {
/** @var \Drupal\a12s_page_context\Plugin\FormDisplayPluginInterface $plugin */
$plugin = $this->formDisplayPluginManager->createInstance($values['plugin_id']);
if (!empty($values['configuration'])) {
$plugin->setConfigurationFromFormInput($values['configuration']);
$values['configuration'] = $plugin->getConfiguration();
}
}
catch (PluginException $e) {
// Remove invalid plugin from $form_state, before validating or
// submitting the form.
$form_state->unsetValue(['display', 'plugins', $uuid]);
continue;
}
$displaySettings[$uuid] = array_intersect_key($values, $validKeys);
if ($values['weight'] > $maxWeight) {
$maxWeight = $values['weight'];
}
}
$triggering_element = $form_state->getTriggeringElement();
$pluginId = $form_state->getValue(['display', 'add', 'plugin_id']);
if ($pluginId && $triggering_element['#parents'] === ['display', 'add', 'submit']) {
try {
/** @var \Drupal\a12s_page_context\Plugin\FormDisplayPluginInterface $plugin */
$plugin = $this->formDisplayPluginManager->createInstance($pluginId);
$uuid = $this->uuidGenerator->generate();
$displaySettings[$uuid] = [
'plugin_id' => $plugin->getPluginId(),
'weight' => $maxWeight,
];
}
catch (PluginException $e) {
// $this->messenger()->addWarning('Impossible to add the requested plugin.');
}
}
if (isset($triggering_element['#op']) && $triggering_element['#op'] === 'delete' && !empty($triggering_element['#uuid'])) {
unset($displaySettings[$triggering_element['#uuid']]);
$form_state->unsetValue(['display', 'plugins', $triggering_element['#uuid']]);
}
$entity->setDisplaySettings($displaySettings);
}
/**
* {@inheritdoc}
*/
public function submitForm(array &$form, FormStateInterface $form_state): void {
foreach ($this->entity->getDisplaySettings() as $uuid => $settings) {
$subForm = &$form['display']['plugins'][$uuid]['configuration'];
if (isset($subForm)) {
/** @var \Drupal\a12s_page_context\Plugin\FormDisplayPluginInterface $plugin */
$plugin = $this->formDisplayPluginManager->createInstance($settings['plugin_id'], $settings['configuration'] ?? []);
$subformState = SubformState::createForSubform($subForm, $form, $form_state);
$plugin->submitConfigurationForm($subForm, $subformState);
}
}
parent::submitForm($form, $form_state);
}
/**
* {@inheritdoc}
*/
public function save(array $form, FormStateInterface $form_state): int {
$result = parent::save($form, $form_state);
$message_args = ['%label' => $this->entity->label()];
$message = $result == SAVED_NEW
? $this->t('Created new page context form %label.', $message_args)
: $this->t('Updated page context form %label.', $message_args);
$this->messenger()->addStatus($message);
$form_state->setRedirectUrl($this->entity->toUrl('collection'));
return $result;
}
/**
* Callback for machine_name.
*
* @param string $id
* The ID to look for.
*
* @return bool
* TRUE if the ID is found.
*/
public static function pathIdExists(string $id): bool {
if ($id === 'paths') {
return TRUE;
}
return (bool) PageContextFormEntity::load($id);
}
/**
* Get the list of actives themes.
*
* @return array
* An array whose keys are machine names and values readable names.
*/
public static function getThemesList(): array {
$themes = \Drupal::service('theme_handler')->listInfo();
return array_map(fn(Extension $theme) => $theme->info['name'] ?? $theme->getName(), $themes);
}
}
