You are here

class Heading in Drupal 10

CKEditor 5 Heading plugin.

@internal Plugin classes are internal.

Hierarchy

  • class \Drupal\ckeditor5\Plugin\CKEditor5Plugin\Heading extends \Drupal\ckeditor5\Plugin\CKEditor5PluginDefault implements \Drupal\ckeditor5\Plugin\CKEditor5PluginConfigurableInterface, \Drupal\ckeditor5\Plugin\CKEditor5PluginElementsSubsetInterface uses \Drupal\ckeditor5\Plugin\CKEditor5PluginConfigurableTrait

Expanded class hierarchy of Heading

5 files declare their use of Heading
CKEditor5.php in core/modules/ckeditor5/src/Plugin/Editor/CKEditor5.php
CKEditor5PluginManagerTest.php in core/modules/ckeditor5/tests/src/Kernel/CKEditor5PluginManagerTest.php
EditorResourceTestBase.php in core/modules/editor/tests/src/Functional/Rest/EditorResourceTestBase.php
EditorTest.php in core/modules/jsonapi/tests/src/Functional/EditorTest.php
HeadingPluginTest.php in core/modules/ckeditor5/tests/src/Unit/HeadingPluginTest.php
3 string references to 'Heading'
ckeditor5.ckeditor5.yml in core/modules/ckeditor5/ckeditor5.ckeditor5.yml
core/modules/ckeditor5/ckeditor5.ckeditor5.yml
ckeditor5.ckeditor5.yml in core/modules/ckeditor5/ckeditor5.ckeditor5.yml
core/modules/ckeditor5/ckeditor5.ckeditor5.yml
FilterHtml::tips in core/modules/filter/src/Plugin/Filter/FilterHtml.php

File

core/modules/ckeditor5/src/Plugin/CKEditor5Plugin/Heading.php, line 21

Namespace

Drupal\ckeditor5\Plugin\CKEditor5Plugin
View source
class Heading extends CKEditor5PluginDefault implements CKEditor5PluginConfigurableInterface, CKEditor5PluginElementsSubsetInterface {
  use CKEditor5PluginConfigurableTrait;

  /**
   * The headings that cannot be disabled.
   *
   * @var string[]
   */
  const ALWAYS_ENABLED_HEADINGS = [
    'paragraph',
  ];

  /**
   * The default configuration for this plugin.
   *
   * @var string[][]
   */
  const DEFAULT_CONFIGURATION = [
    'enabled_headings' => [
      'heading2',
      'heading3',
      'heading4',
      'heading5',
      'heading6',
    ],
  ];

  /**
   * {@inheritdoc}
   */
  public function defaultConfiguration() {
    return static::DEFAULT_CONFIGURATION;
  }

  /**
   * Computes all valid choices for the "enabled_headings" setting.
   *
   * @see ckeditor5.schema.yml
   *
   * @return string[]
   *   All valid choices.
   */
  public static function validChoices() : array {
    $cke5_plugin_manager = \Drupal::service('plugin.manager.ckeditor5.plugin');
    assert($cke5_plugin_manager instanceof CKEditor5PluginManagerInterface);
    $plugin_definition = $cke5_plugin_manager
      ->getDefinition('ckeditor5_heading');
    assert($plugin_definition
      ->getClass() === static::class);
    return array_diff(array_column($plugin_definition
      ->getCKEditor5Config()['heading']['options'], 'model'), static::ALWAYS_ENABLED_HEADINGS);
  }

  /**
   * Gets all enabled headings.
   *
   * @return string[]
   *   The values in the plugins.ckeditor5_heading.enabled_headings configuration
   *   plus the headings that are always enabled.
   */
  private function getEnabledHeadings() : array {
    return array_merge(self::ALWAYS_ENABLED_HEADINGS, $this->configuration['enabled_headings']);
  }

  /**
   * {@inheritdoc}
   *
   * Form for choosing which heading tags are available.
   */
  public function buildConfigurationForm(array $form, FormStateInterface $form_state) {
    $form['enabled_headings'] = [
      '#type' => 'fieldset',
      '#title' => $this
        ->t('Enabled Headings'),
      '#description' => $this
        ->t('These are the headings that will appear in the headings dropdown. If a heading is not chosen here, it does not necessarily mean the corresponding tag is disallowed in the text format.'),
    ];
    foreach ($this
      ->getPluginDefinition()
      ->getCKEditor5Config()['heading']['options'] as $heading_option) {
      $model = $heading_option['model'];
      if (in_array($model, self::ALWAYS_ENABLED_HEADINGS, TRUE)) {
        continue;
      }

      // It's safe to use $model as a key: listing the same model twice with
      // different properties triggers a schema error in CKEditor 5.
      // @see https://ckeditor.com/docs/ckeditor5/latest/framework/guides/support/error-codes.html#error-schema-cannot-register-item-twice
      // @see https://ckeditor.com/docs/ckeditor5/latest/features/headings.html#configuring-custom-heading-elements
      $form['enabled_headings'][$model] = self::generateCheckboxForHeadingOption($heading_option);
      $form['enabled_headings'][$model]['#default_value'] = in_array($model, $this->configuration['enabled_headings'], TRUE) ? $model : NULL;
    }
    return $form;
  }

  /**
   * {@inheritdoc}
   */
  public function validateConfigurationForm(array &$form, FormStateInterface $form_state) {

    // Match the config schema structure at ckeditor5.plugin.ckeditor5_heading.
    $form_value = $form_state
      ->getValue('enabled_headings');
    $config_value = array_values(array_filter($form_value));
    $form_state
      ->setValue('enabled_headings', $config_value);
  }

  /**
   * {@inheritdoc}
   */
  public function submitConfigurationForm(array &$form, FormStateInterface $form_state) {
    $this->configuration['enabled_headings'] = $form_state
      ->getValue('enabled_headings');
  }

  /**
   * Generates checkbox for a CKEditor 5 heading plugin config option.
   *
   * @param array $heading_option
   *   A heading option configuration as the CKEditor 5 Heading plugin expects
   *   in its configuration.
   *
   * @return array
   *   The checkbox render array.
   *
   * @see https://ckeditor.com/docs/ckeditor5/latest/api/module_heading_heading-HeadingConfig.html#member-options
   */
  private static function generateCheckboxForHeadingOption(array $heading_option) : array {

    // This requires the `title` and `model` properties. The `class` property is
    // optional. The `view` property is not used.
    assert(array_key_exists('title', $heading_option));
    assert(array_key_exists('model', $heading_option));
    $checkbox = [
      '#type' => 'checkbox',
      '#title' => $heading_option['title'],
      '#return_value' => $heading_option['model'],
    ];
    if (isset($heading_option['class'])) {
      $checkbox['#label_attributes']['class'][] = $heading_option['class'];
      $checkbox['#label_attributes']['class'][] = 'ck';
    }
    return $checkbox;
  }

  /**
   * {@inheritdoc}
   *
   * Filters the header options to those chosen in editor config.
   */
  public function getDynamicPluginConfig(array $static_plugin_config, EditorInterface $editor) : array {
    $enabled_headings = $this
      ->getEnabledHeadings();
    $all_heading_options = $static_plugin_config['heading']['options'];
    $configured_heading_options = array_filter($all_heading_options, function ($option) use ($enabled_headings) {
      return in_array($option['model'], $enabled_headings, TRUE);
    });
    return [
      'heading' => [
        'options' => array_values($configured_heading_options),
      ],
    ];
  }

  /**
   * {@inheritdoc}
   */
  public function getElementsSubset() : array {
    return $this
      ->enabledHeadingsToTags($this->configuration['enabled_headings']);
  }

  /**
   * Returns an array of enabled tags based on the enabled headings.
   *
   * @param string[] $enabled_headings
   *   Array of the enabled headings.
   *
   * @return string[]
   *   List of tags provided by the enabled headings.
   */
  private function enabledHeadingsToTags(array $enabled_headings) : array {
    $plugin_definition = $this
      ->getPluginDefinition();
    $elements = $plugin_definition
      ->getElements();
    $heading_keyed_by_model = [];
    foreach ($plugin_definition
      ->getCKEditor5Config()['heading']['options'] as $configured_heading) {
      if (isset($configured_heading['model'])) {
        $heading_keyed_by_model[$configured_heading['model']] = $configured_heading;
      }
    }
    $tags_to_return = [];
    foreach ($enabled_headings as $model) {
      if (isset($heading_keyed_by_model[$model]) && isset($heading_keyed_by_model[$model]['view'])) {
        $element_as_tag = "<{$heading_keyed_by_model[$model]['view']}>";
        if (in_array($element_as_tag, $elements, TRUE)) {
          $tags_to_return[] = "<{$heading_keyed_by_model[$model]['view']}>";
        }
      }
    }
    return $tags_to_return;
  }

}

Members