fieldry-1.0.x-dev/src/FieldryWrapperFormBuilder.php
src/FieldryWrapperFormBuilder.php
<?php
namespace Drupal\fieldry;
use Drupal\Component\Plugin\Exception\PluginNotFoundException;
use Drupal\Component\Utility\Html;
use Drupal\Component\Utility\NestedArray;
use Drupal\Core\Ajax\AjaxResponse;
use Drupal\Core\DependencyInjection\DependencySerializationTrait;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Form\SubformState;
use Drupal\Core\Field\FormatterInterface;
use Drupal\Core\Field\FieldDefinitionInterface;
use Drupal\Core\Session\AccountInterface;
use Drupal\Core\StringTranslation\StringTranslationTrait;
use Drupal\fieldry\Plugin\FieldryWrapperManagerInterface;
use Drupal\fieldry\Plugin\ConfigurableWrapperInterface;
/**
* Form builder for field wrapper plugin configurations.
*
* Builds form elements for configuring field wrapper plugins and containts all
* the required callbacks to rebuild, and update form when wrapper plugins are
* changed.
*/
class FieldryWrapperFormBuilder {
use StringTranslationTrait;
use DependencySerializationTrait;
/**
* Creates a new instance of the FieldWrapperFormBuilder object.
*
* @param \Drupal\fieldry\Plugin\FieldryWrapperManagerInterface $fieldWrapperManager
* Plugin manager for discovery and instantiation of field wrapper plugins.
*/
public function __construct(protected FieldryWrapperManagerInterface $fieldWrapperManager) {
}
/**
* Generate the form elements for configuring field wrappers for a field.
*
* @param array $form
* The entire form.
* @param \Drupal\Core\Form\FormStateInterface $form_state
* Form build info, state and values.
* @param \Drupal\Core\Field\FormatterInterface $formatter
* The field formatter to configure a field wrapper for.
* @param \Drupal\Core\Field\FieldDefinitionInterface $field_definition
* The field definition to configure a field wrapper for.
* @param \Drupal\Core\Session\AccountInterface $account
* The account to build this form element for. Field wrapper options are
* only available to users with the permissions to control them.
*
* @return array
* Renderable array for use as form elements in the overall field wrapper
* configuration form.
*/
public function buildForm(array &$form, FormStateInterface $form_state, FormatterInterface $formatter, FieldDefinitionInterface $field_definition, AccountInterface $account): array {
// Get the current field wrapper settings for this formatter.
$settings = $formatter->getThirdPartySettings('fieldry') + [
'wrapper' => 'default',
'config' => [],
];
// Check if user has permission to alter the wrapper configurations.
// Otherwise set the current values for this third party settings and
// do not allow the user to alter any of the existing settings.
if ($account->hasPermission('administer fieldry field wrappers')) {
$options = $this->fieldWrapperManager->getFieldWrappers($formatter, $field_definition);
// Only present these form elements if there are valid field wrappers
// available, otherwise the settings are locked in, and should only
// be the default wrapper.
if (!empty($options)) {
return [
'#type' => 'details',
'#title' => $this->t('Field wrapper'),
'#process' => [[$this, 'processWrapperConfig']],
'#field_wrapper_settings' => $settings['config'],
'wrapper' => [
'#type' => 'select',
'#title' => $this->t('Field wrapper type'),
'#options' => [
'default' => $this->t('Default field wrapper'),
] + $options,
'#default_value' => $settings['wrapper'],
'#description' => $this->t('This option only overrrides the default field theme, creating more specific field templates will still take precedence.'),
],
];
}
}
// Ensure the current settings remain intact if user did not have access.
return [
'#type' => 'value',
'#value' => $settings,
];
}
/**
* Form element callback to apply AJAX and update form values.
*
* @param array $elements
* Form elements for the entire field wrapper configuration form. This is
* the form element to get process, and updated.
* @param \Drupal\Core\Form\FormStateInterface $form_state
* The current form state, build information and values.
* @param array $complete_form
* The complete form elements array, for the form being built.
*
* @return array
* Form elements processed to include the AJAX for the field wrapper
* configuration, and update the configuration form on wrapper type change.
*/
public function processWrapperConfig(array $elements, FormStateInterface $form_state, array &$complete_form): array {
$values = $form_state->getValue($elements['#parents']);
$wrapper = $values ? $values['wrapper'] : $elements['wrapper']['#default_value'];
if ($wrapper !== 'default') {
try {
// Initialize the wrapper plugin, with the current form configurations.
$config = $values['config'] ?? $elements['#field_wrapper_settings'];
$plugin = $this->fieldWrapperManager->createInstance($wrapper, $config);
if ($plugin instanceof ConfigurableWrapperInterface) {
$subform = &$elements['config'];
$subform['#parents'] = array_merge($elements['#parents'], ['config']);
$subform['#array_parents'] = array_merge($elements['#array_parents'], ['config']);
$subformState = SubformState::createForSubform($subform, $complete_form, $form_state);
$elements['config'] = $plugin->buildConfigurationForm($subform, $subformState);
}
}
catch (PluginNotFoundException $e) {
$elements['config']['#markup'] = $this->t('Field wrapper @label is not available.', [
'@label' => $wrapper,
]);
}
}
// Ensure a consistent wrapper for the AJAX content replace to target.
$htmlId = Html::cleanCssIdentifier(implode('-', $elements['#array_parents'])) . '-field-wrapper-config';
$elements['config']['#prefix'] = '<div id="' . $htmlId . '">';
$elements['config']['#suffix'] = '</div>';
$elements['config']['#tree'] = TRUE;
// Update the plugin configuration when the field wrapper is changed.
$elements['wrapper']['#ajax'] = [
'callback' => static::class . '::updateWrapperAjax',
'wrapper' => $htmlId,
];
return $elements;
}
/**
* AJAX callback to update the field wrapper configuration options.
*
* @param array $form
* Reference to the complete form elements for the configuration form.
* @param \Drupal\Core\Form\FormStateInterface $form_state
* The form state, build information and values.
*
* @return \Drupal\Core\Ajax\AjaxResponse|array
* Either a AJAX response or renderable elements to update the field wrapper
* configuration form.
*/
public static function updateWrapperAjax(array &$form, FormStateInterface $form_state): array|AjaxResponse {
$trigger = $form_state->getTriggeringElement();
$parents = array_slice($trigger['#array_parents'], 0, -1);
$parents[] = 'config';
return NestedArray::getValue($form, $parents) ?? [];
}
}
