Allow to massage form settings values

Created on 16 May 2025, 21 days ago

Problem/Motivation

I try to add to the settings form of a field group a complex element, but i cannot change values.

I create a field group details icon which is based on details field group, but on which i can add and configure an icon to display with label.
I use icon_autocomplete widget, which return values composed by an IconDefinition object and settings as array.
I don't want to store all the IconDefinition in my schema, but only pack_id and icon_id.

Unfortunately, i cannot edit those values before they are casted and validated to be stored. This throw an exception.

Drupal\Core\Config\UnsupportedDataTypeConfigException: Invalid data type for config element core.entity_form_display.node.page.default:third_party_settings.field_group.group_test.format_settings.icon.icon in Drupal\Core\Config\StorableConfigBase->validateValue() (line 201 of core/lib/Drupal/Core/Config/StorableConfigBase.php).

Drupal\Core\Config\StorableConfigBase->validateValue('third_party_settings.field_group.group_test.format_settings.icon', Array) (Line: 197)
Drupal\Core\Config\StorableConfigBase->validateValue('third_party_settings.field_group.group_test.format_settings', Array) (Line: 230)
Drupal\Core\Config\StorableConfigBase->castValue('third_party_settings.field_group.group_test.format_settings', Array) (Line: 258)
Drupal\Core\Config\StorableConfigBase->castValue('third_party_settings.field_group.group_test', Array) (Line: 258)
Drupal\Core\Config\StorableConfigBase->castValue('third_party_settings.field_group', Array) (Line: 258)
Drupal\Core\Config\StorableConfigBase->castValue('third_party_settings', Array) (Line: 258)
Drupal\Core\Config\StorableConfigBase->castValue(NULL, Array) (Line: 211)
Drupal\Core\Config\Config->save() (Line: 260)
Drupal\Core\Config\Entity\ConfigEntityStorage->doSave('node.page.default', Object) (Line: 486)
Drupal\Core\Entity\EntityStorageBase->save(Object) (Line: 239)
Drupal\Core\Config\Entity\ConfigEntityStorage->save(Object) (Line: 354)
Drupal\Core\Entity\EntityBase->save() (Line: 617)
Drupal\Core\Config\Entity\ConfigEntityBase->save() (Line: 914)
field_group_group_save(Object, Object) (Line: 525)
field_group_field_overview_submit(Array, Object)
call_user_func_array('field_group_field_overview_submit', Array) (Line: 105)

Steps to reproduce

Add in the settings form of a new field group :

    <?php

namespace Drupal\field_group_icon\Plugin\field_group\FieldGroupFormatter;

use Drupal\Component\Utility\Html;
use Drupal\Component\Utility\Xss;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Render\Markup;
use Drupal\Core\Theme\Icon\IconDefinitionInterface;
use Drupal\field_group\FieldGroupFormatterBase;
use Drupal\field_group\Plugin\field_group\FieldGroupFormatter\Details;

/**
 * Plugin implementation of the 'details_icon' formatter.
 *
 * @FieldGroupFormatter(
 *   id = "details_icon",
 *   label = @Translation("Details with icon"),
 *   description = @Translation("Add a details element with icon"),
 *   supported_contexts = {
 *     "form",
 *     "view"
 *   }
 * )
 */
class DetailsIcon extends Details {

  /**
   * {@inheritdoc}
   */
  public function settingsForm() {
    $form = parent::settingsForm();

    $icon = NULL;
    if ($this->getSetting('icon') && isset($this->getSetting('icon')['icon'])) {
      $icon = $this->getSetting('icon')['icon'];
      if ($icon instanceof IconDefinitionInterface) {
        $icon = [
          'pack_id' => $icon->getPackId(),
          'icon_id' => $icon->getIconId(),
        ];
      }
    }
    $form['icon'] = [
      '#title' => $this->t('Icon'),
      '#type' => 'icon_autocomplete',
      '#default_value' => $icon ? "{$icon['pack_id']}:{$icon['icon_id']}" : NULL,
      '#default_settings' => $this->getSetting('icon') ? $this->getSetting('icon')['settings'] : NULL,
    ];

    $form['icon_position'] = [
      '#title' => $this->t('Icon position'),
      '#type' => 'select',
      '#options' => [
        'left' => $this->t('Left'),
        'right' => $this->t('Right'),
        'top' => $this->t('Top'),
        'bottom' => $this->t('Bottom'),
      ],
      '#required' => TRUE,
      '#default_value' => $this->getSetting('icon_position') ?? 'left',
    ];

    $form['icon_size'] = [
      '#title' => $this->t('Icon size'),
      '#type' => 'css_size',
      '#default_value' => $this->getSetting('icon_size') ?? ['number' => 1, 'unit' => 'em'],
    ];

    $form['#submit'][] = [[get_class($this), 'submitSettingsForm']];

    return $form;
  }

  public function massageFormValues(array &$values) {
    if (NULL === $values['icon']) {
      return;
    }

    /** @var \Drupal\Core\Theme\Icon\IconDefinitionInterface $iconDefinition */
    $iconDefinition = $values['icon']['icon'];
    $values['icon'] = [
      'icon' => [
        'pack_id' => $iconDefinition->getPackId(),
        'icon_id' => $iconDefinition->getIconId(),
      ],
      'settings' => $values['icon']['settings'],
    ];
  }

  /**
   * {@inheritdoc}
   */
  public function settingsSummary() {

    $summary = parent::settingsSummary();
    if ($this->getSetting('icon_position')) {
      $summary[] = $this->t('Icon position: @position', ['@position' => $this->getSetting('icon_position')]);
    }

    if ($this->getSetting('icon_size')) {
      $icon_size = $this->getSetting('icon_size');
      $summary[] = $this->t('Icon size: @number @unit', ['@number' => $icon_size['number'], '@unit' => $icon_size['unit']]);
    }

    return $summary;
  }

}

and here is my schema for this new field group

field_group_icon.field_group_formatter_plugin.details_icon:
  type: field_group.field_group_formatter_plugin.details
  label: 'Mapping for the details formatter settings'
  mapping:
    icon:
      mapping:
        icon:
          type: mapping
          label: 'Icon settings'
          mapping:
            pack_id:
              type: string
            icon_id:
              type: string
        settings:
          type: sequence
          label: 'Icon extractor settings'
          sequence:
            type: ui_icons.icon_pack_options.[%key]
    icon_position:
      type: string
      label: 'Position of the icon.'
    icon_size:
      type: mapping
      label: 'Size of the icon.'
      mapping:
        number:
          type: float
          label: 'The value number of the size.'
        unit:
          type: string
          label: 'The unit of the size.'

Proposed resolution

Like Form widget do, add a massageFormValues() methods so plugin can alter values to make them storable.

In field_ui.inc, update field_group_formatter_settings_update to allow massage form values.

function field_group_formatter_settings_update(&$group, array $settings) {

  // For format changes we load the defaults.
  if (empty($settings['settings_edit_form']['settings'])) {
    $group->format_settings = Drupal::service('plugin.manager.field_group.formatters')->getDefaultSettings($group->format_type, $group->context);
  }
  else {
    $group->format_type = $settings['format']['type'];
    $group->label = $settings['settings_edit_form']['settings']['label'];
    $group->format_settings = $settings['settings_edit_form']['settings'];
  }

  $manager = Drupal::service('plugin.manager.field_group.formatters');
  $plugin = $manager->getInstance([
    'format_type' => $group->format_type,
    'configuration' => [
      'label' => $group->label,
    ],
    'group' => $group,
  ]);
  $plugin->massageSettingsFormValues($group->format_settings);
}

So, in my case, i'm able to transform submitted settings by settings defined in my plugin schema :

  public function massageSettingsFormValues(array &$values) {
    if (NULL === $values['icon']) {
      return;
    }

    /** @var \Drupal\Core\Theme\Icon\IconDefinitionInterface $iconDefinition */
    $iconDefinition = $values['icon']['icon'];
    $values['icon'] = [
      'icon' => [
        'pack_id' => $iconDefinition->getPackId(),
        'icon_id' => $iconDefinition->getIconId(),
      ],
      'settings' => $values['icon']['settings'],
    ];
  }
Feature request
Status

Active

Version

4.0

Component

Code

Created by

🇫🇷France goz

Live updates comments and jobs are added and updated live.
Sign in to follow issues

Merge Requests

Comments & Activities

Production build 0.71.5 2024