You are here

class Style in Drupal 10

CKEditor 5 Style plugin configuration.

@internal Plugin classes are internal.

Hierarchy

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

Expanded class hierarchy of Style

2 files declare their use of Style
Core.php in core/modules/ckeditor5/src/Plugin/CKEditor4To5Upgrade/Core.php
StylePluginTest.php in core/modules/ckeditor5/tests/src/Unit/StylePluginTest.php
6 string references to 'Style'
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
ckeditor5.schema.yml in core/modules/ckeditor5/config/schema/ckeditor5.schema.yml
core/modules/ckeditor5/config/schema/ckeditor5.schema.yml
drupal6.php in core/modules/migrate_drupal/tests/fixtures/drupal6.php
A database agnostic dump for testing purposes.
StyleTest::testStyleSettingsForm in core/modules/ckeditor5/tests/src/FunctionalJavascript/StyleTest.php
@covers \Drupal\ckeditor5\Plugin\CKEditor5Plugin\Style::buildConfigurationForm

... See full list

File

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

Namespace

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

  /**
   * {@inheritdoc}
   */
  public function buildConfigurationForm(array $form, FormStateInterface $form_state) {
    $form['styles'] = [
      '#title' => $this
        ->t('Styles'),
      '#type' => 'textarea',
      '#description' => $this
        ->t('A list of classes that will be provided in the "Style" dropdown. Enter one or more classes on each line in the format: element.classA.classB|Label. Example: h1.title|Title. Advanced example: h1.fancy.title|Fancy title.<br />These styles should be available in your theme\'s CSS file.'),
    ];
    if (!empty($this->configuration['styles'])) {
      $as_selectors = '';
      foreach ($this->configuration['styles'] as $style) {
        [
          $tag,
          $classes,
        ] = self::getTagAndClasses(HTMLRestrictions::fromString($style['element']));
        $as_selectors .= sprintf("%s.%s|%s\n", $tag, implode('.', $classes), $style['label']);
      }
      $form['styles']['#default_value'] = $as_selectors;
    }
    return $form;
  }

  /**
   * Gets the tag and classes for a parsed style element.
   *
   * @param \Drupal\ckeditor5\HTMLRestrictions $style_element
   *   A parsed style element.
   *
   * @return array
   *   An array containing two values:
   *   - a HTML tag name
   *   - a list of classes
   *
   * @internal
   */
  public static function getTagAndClasses(HTMLRestrictions $style_element) : array {
    $tag = array_keys($style_element
      ->getAllowedElements())[0];
    $classes = array_keys($style_element
      ->getAllowedElements()[$tag]['class']);
    return [
      $tag,
      $classes,
    ];
  }

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

    // Match the config schema structure at ckeditor5.plugin.ckeditor5_style.
    $form_value = $form_state
      ->getValue('styles');
    [
      $styles,
      $unparseable_lines,
    ] = self::parseStylesFormValue($form_value);
    if (!empty($unparseable_lines)) {
      $line_numbers = array_keys($unparseable_lines);
      $form_state
        ->setError($form['styles'], $this
        ->formatPlural(count($unparseable_lines), 'Line @line-number does not contain a valid value. Enter a valid CSS selector containing one or more classes, followed by a pipe symbol and a label.', 'Lines @line-numbers do not contain a valid value. Enter a valid CSS selector containing one or more classes, followed by a pipe symbol and a label.', [
        '@line-number' => reset($line_numbers),
        '@line-numbers' => implode(', ', $line_numbers),
      ]));
    }
    $form_state
      ->setValue('styles', $styles);
  }

  /**
   * Parses the line-based (for form) style configuration.
   *
   * @param string $form_value
   *   A string containing >=1 lines with on each line a CSS selector targeting
   *   1 tag with >=1 classes, a pipe symbol and a label. An example of a single
   *   line: `p.foo.bar|Foo bar paragraph`.
   *
   * @return array
   *   The parsed equivalent: a list of arrays with each containing:
   *   - label: the label after the pipe symbol, with whitespace trimmed
   *   - element: the CKEditor 5 element equivalent of the tag + classes
   *
   * @internal
   *   This method is public only to allow the CKEditor 4 to 5 upgrade path to
   *   reuse this logic. Mark this private in https://www.drupal.org/i/3239012.
   *
   * @see \Drupal\ckeditor5\Plugin\CKEditor4To5Upgrade\Core::mapCKEditor4SettingsToCKEditor5Configuration()
   */
  public static function parseStylesFormValue(string $form_value) : array {
    $unparseable_lines = [];
    $lines = explode("\n", $form_value);
    $styles = [];
    foreach ($lines as $index => $line) {
      if (empty(trim($line))) {
        continue;
      }

      // Parse the line.
      [
        $selector,
        $label,
      ] = array_map('trim', explode('|', $line));

      // Validate the selector.
      $selector_matches = [];

      // @see https://www.w3.org/TR/CSS2/syndata.html#:~:text=In%20CSS%2C%20identifiers%20(including%20element,hyphen%20followed%20by%20a%20digit
      if (!preg_match('/^([a-z][0-9a-zA-Z\\-]*)((\\.[a-zA-Z0-9\\x{00A0}-\\x{FFFF}\\-_]+)+)$/u', $selector, $selector_matches)) {
        $unparseable_lines[$index + 1] = $line;
        continue;
      }

      // Parse selector into tag + classes and normalize.
      $tag = $selector_matches[1];
      $classes = array_filter(explode('.', $selector_matches[2]));
      $normalized = HTMLRestrictions::fromString(sprintf('<%s class="%s">', $tag, implode(' ', $classes)));
      $styles[] = [
        'label' => $label,
        'element' => $normalized
          ->toCKEditor5ElementsArray()[0],
      ];
    }
    return [
      $styles,
      $unparseable_lines,
    ];
  }

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

  /**
   * {@inheritdoc}
   */
  public function defaultConfiguration() {
    return [
      'styles' => [],
    ];
  }

  /**
   * {@inheritdoc}
   */
  public function getElementsSubset() : array {
    return array_column($this->configuration['styles'], 'element');
  }

  /**
   * {@inheritdoc}
   */
  public function getDynamicPluginConfig(array $static_plugin_config, EditorInterface $editor) : array {
    $definitions = [];
    foreach ($this->configuration['styles'] as $style) {
      [
        $tag,
        $classes,
      ] = self::getTagAndClasses(HTMLRestrictions::fromString($style['element']));

      // Transform configured styles to the configuration structure expected by
      // the CKEditor 5 Style plugin.
      $definitions[] = [
        'name' => $style['label'],
        'element' => $tag,
        'classes' => $classes,
      ];
    }
    return [
      'style' => [
        'definitions' => $definitions,
      ],
    ];
  }

}

Members