You are here

public function CKEditor5::validateConfigurationForm in Drupal 10

File

core/modules/ckeditor5/src/Plugin/Editor/CKEditor5.php, line 605

Class

CKEditor5
Defines a CKEditor 5-based text editor for Drupal.

Namespace

Drupal\ckeditor5\Plugin\Editor

Code

public function validateConfigurationForm(array &$form, FormStateInterface $form_state) {
  $json = $form_state
    ->getValue([
    'toolbar',
    'items',
  ]);
  $toolbar_items = Json::decode($json);

  // This basic validation must live in the form logic because it can only
  // occur in a form context.
  if (!$toolbar_items) {
    $form_state
      ->setErrorByName('toolbar][items', $this
      ->t('Invalid toolbar value.'));
    return;
  }

  // Construct a Text Editor config entity with the submitted values for
  // validation. Do this on a clone: do not manipulate form state.
  $submitted_editor = clone $form_state
    ->get('editor');
  $settings = $submitted_editor
    ->getSettings();

  // Update settings first to match the submitted toolbar items. This is
  // necessary for ::shouldHaveVisiblePluginSettingsForm() to work.
  $settings['toolbar']['items'] = $toolbar_items;
  $submitted_editor
    ->setSettings($settings);
  $eventual_editor_and_format_for_plugin_settings_visibility = $this
    ->getEventualEditorWithPrimedFilterFormat($form_state, $submitted_editor);
  $settings['plugins'] = [];
  $default_configurations = [];
  foreach ($this->ckeditor5PluginManager
    ->getDefinitions() as $plugin_id => $definition) {
    if (!$definition
      ->isConfigurable()) {
      continue;
    }

    // Create a fresh instance of this CKEditor 5 plugin, not tied to a text
    // editor configuration entity.
    $plugin = $this->ckeditor5PluginManager
      ->getPlugin($plugin_id, NULL);

    // If this plugin is configurable but it has empty default configuration,
    // that means the configuration must be stored out of band.
    // @see \Drupal\ckeditor5\Plugin\CKEditor5Plugin\ImageUpload
    // @see editor_image_upload_settings_form()
    $default_configuration = $plugin
      ->defaultConfiguration();
    $configuration_stored_out_of_band = empty($default_configuration);

    // If this plugin is configurable but has not yet had user interaction,
    // the default configuration will still be active and may trigger
    // validation errors. Do not trigger those validation errors until the
    // form is actually saved, to allow the user to first configure other
    // CKEditor 5 functionality.
    $default_configurations[$plugin_id] = $default_configuration;
    if ($form_state
      ->hasValue([
      'plugins',
      $plugin_id,
    ])) {
      $subform = $form['plugins'][$plugin_id];
      $subform_state = SubformState::createForSubform($subform, $form, $form_state);
      $plugin
        ->validateConfigurationForm($subform, $subform_state);
      $plugin
        ->submitConfigurationForm($subform, $subform_state);

      // If the configuration is stored out of band, ::submitConfigurationForm
      // will already have stored it. If it is not stored out of band,
      // populate $settings, to populate $submitted_editor.
      if (!$configuration_stored_out_of_band) {
        $settings['plugins'][$plugin_id] = $plugin
          ->getConfiguration();
      }
    }
    elseif ($this
      ->shouldHaveVisiblePluginSettingsForm($definition, $eventual_editor_and_format_for_plugin_settings_visibility)) {
      if (!$configuration_stored_out_of_band) {
        $settings['plugins'][$plugin_id] = $default_configuration;
      }
    }
  }

  // All plugin settings have been collected, including defaults that depend
  // on visibility. Store the collected settings, throw away the interim state
  // that allowed determining which defaults to add.
  unset($eventual_editor_and_format_for_plugin_settings_visibility);
  $submitted_editor
    ->setSettings($settings);

  // Validate the text editor + text format pair.
  // Note that the eventual pair is computed and validated, not the received
  // pair: if the filter_html filter is in use, the CKEditor 5 configuration
  // dictates the filter_html's filter plugin's "allowed_html" setting.
  // @see ckeditor5_form_filter_format_form_alter()
  // @see ::getGeneratedAllowedHtmlValue()
  $eventual_editor_and_format = $this
    ->getEventualEditorWithPrimedFilterFormat($form_state, $submitted_editor);
  $violations = CKEditor5::validatePair($eventual_editor_and_format, $eventual_editor_and_format
    ->getFilterFormat());
  foreach ($violations as $violation) {
    $property_path_parts = explode('.', $violation
      ->getPropertyPath());

    // Special case: AJAX updates that do not submit the form (that cannot
    // result in configuration being saved).
    if ($form_state
      ->getSubmitHandlers() === [
      'editor_form_filter_admin_format_editor_configure',
    ]) {

      // Ensure that plugins' validation constraints do not immediately
      // trigger a validation error: the user may choose to configure other
      // CKEditor 5 aspects first.
      if ($property_path_parts[0] === 'settings' && $property_path_parts[1] === 'plugins') {
        $plugin_id = $property_path_parts[2];

        // This CKEditor 5 plugin settings form was just added: the user has
        // not yet had a chance to configure it.
        if (!$form_state
          ->hasValue([
          'plugins',
          $plugin_id,
        ])) {
          continue;
        }

        // This CKEditor 5 plugin settings form was added recently, the user
        // is triggering AJAX rebuilds of the configuration UI because they're
        // configuring other functionality first. Only require these to be
        // valid at form submission time.
        if ($form_state
          ->getValue([
          'plugins',
          $plugin_id,
        ]) === $default_configurations[$plugin_id]) {
          continue;
        }
      }
    }
    $form_item_name = static::mapPairViolationPropertyPathsToFormNames($violation
      ->getPropertyPath(), $form);

    // When adding a toolbar item, it is possible that not all conditions for
    // using it have been met yet. FormBuilder refuses to rebuild forms when a
    // validation error is present. But to meet the condition for the toolbar
    // item, configuration must be set in a vertical tab that must still
    // appear. Work-around: reduce the validation error to a warning message.
    // @see \Drupal\ckeditor5\Plugin\Validation\Constraint\ToolbarItemConditionsMetConstraintValidator
    if ($form_state
      ->isRedirectDisabled() && $form_item_name === 'editor][settings][toolbar][items') {
      $this
        ->messenger()
        ->addWarning($violation
        ->getMessage());
      continue;
    }
    $form_state
      ->getCompleteFormState()
      ->setErrorByName($form_item_name, $violation
      ->getMessage());
  }

  // Pass it on to ::submitConfigurationForm().
  $form_state
    ->get('editor')
    ->setSettings($settings);

  // Provide the validated eventual pair in form state to
  // ::getGeneratedAllowedHtmlValue(), to update filter_html's
  // "allowed_html".
  $form_state
    ->set('ckeditor5_validated_pair', $eventual_editor_and_format);
  assert(TRUE === $this
    ->checkConfigSchema(\Drupal::getContainer()
    ->get('config.typed'), 'editor.editor.id_does_not_matter', $submitted_editor
    ->toArray()), 'Schema errors: ' . print_r($this
    ->checkConfigSchema(\Drupal::getContainer()
    ->get('config.typed'), 'editor.editor.id_does_not_matter', $submitted_editor
    ->toArray()), TRUE));
}