Commit e3ae54a1 authored by catch's avatar catch
Browse files

Issue #3268174 by Wim Leers, nod_, catch, lauriii: Bug in CKE 4 → 5 upgrade...

Issue #3268174 by Wim Leers, nod_, catch, lauriii: Bug in CKE 4 → 5 upgrade path "format" does not always map to "heading", it could map to "codeBlock" too, or both, or neither

(cherry picked from commit 196b68e9)
parent 6dd76002
Loading
Loading
Loading
Loading
+37 −0
Original line number Diff line number Diff line
@@ -53,6 +53,17 @@ final class HTMLRestrictions {
   */
  private $elements;

  /**
   * Whether unrestricted, in other words: arbitrary HTML allowed.
   *
   * Used for when FilterFormatInterface::getHTMLRestrictions() returns `FALSE`,
   * e.g. in case of the default "Full HTML" text format.
   *
   * @var bool
   * @see \Drupal\filter\Plugin\FilterInterface::getHTMLRestrictions()
   */
  private $unrestricted = FALSE;

  /**
   * Wildcard types, and the methods that return tags the wildcard represents.
   *
@@ -214,6 +225,15 @@ public static function emptySet(): HTMLRestrictions {
    return new self([]);
  }

  /**
   * Whether this set of HTML restrictions is unrestricted.
   *
   * @return bool
   */
  public function isUnrestricted(): bool {
    return $this->unrestricted;
  }

  /**
   * Whether this is the empty set of HTML restrictions.
   *
@@ -249,6 +269,18 @@ public static function fromTextFormat(FilterFormatInterface $text_format): HTMLR
    return self::fromObjectWithHtmlRestrictions($text_format);
  }

  /**
   * Constructs an unrestricted set of HTML restrictions.
   *
   * @return \Drupal\ckeditor5\HTMLRestrictions
   */
  private static function unrestricted(): self {
    // @todo Refine in https://www.drupal.org/project/drupal/issues/3231336, including adding support for all operations.
    $restrictions = HTMLRestrictions::emptySet();
    $restrictions->unrestricted = TRUE;
    return $restrictions;
  }

  /**
   * Constructs a set of HTML restrictions matching the given object.
   *
@@ -272,6 +304,11 @@ private static function fromObjectWithHtmlRestrictions(object $object): HTMLRest
      throw new \InvalidArgumentException();
    }

    if ($object->getHtmlRestrictions() === FALSE) {
      // @todo Refine in https://www.drupal.org/project/drupal/issues/3231336
      return self::unrestricted();
    }

    $restrictions = $object->getHTMLRestrictions();
    if (!isset($restrictions['allowed'])) {
      // @todo Handle HTML restrictor filters that only set forbidden_tags
+33 −18
Original line number Diff line number Diff line
@@ -4,6 +4,7 @@

namespace Drupal\ckeditor5\Plugin\CKEditor4To5Upgrade;

use Drupal\ckeditor5\HTMLRestrictions;
use Drupal\ckeditor5\Plugin\CKEditor4To5UpgradePluginInterface;
use Drupal\Core\Plugin\PluginBase;
use Drupal\filter\FilterFormatInterface;
@@ -69,15 +70,15 @@ class Core extends PluginBase implements CKEditor4To5UpgradePluginInterface {
  /**
   * {@inheritdoc}
   */
  public function mapCKEditor4ToolbarButtonToCKEditor5ToolbarItem(string $cke4_button): ?string {
  public function mapCKEditor4ToolbarButtonToCKEditor5ToolbarItem(string $cke4_button, HTMLRestrictions $text_format_html_restrictions): ?array {
    switch ($cke4_button) {
      // @see \Drupal\ckeditor\Plugin\CKEditorPlugin\DrupalImage
      case 'DrupalImage':
        return 'uploadImage';
        return ['uploadImage'];

      // @see \Drupal\ckeditor\Plugin\CKEditorPlugin\DrupalLink
      case 'DrupalLink':
        return 'link';
        return ['link'];

      case 'DrupalUnlink':
        return NULL;
@@ -94,37 +95,51 @@ public function mapCKEditor4ToolbarButtonToCKEditor5ToolbarItem(string $cke4_but
      case 'Indent':
      case 'Undo':
      case 'Redo':
        return lcfirst($cke4_button);
        return [lcfirst($cke4_button)];

      case 'Blockquote':
        return 'blockQuote';
        return ['blockQuote'];

      case 'JustifyLeft':
        return "alignment:left";
        return ["alignment:left"];

      case 'JustifyCenter':
        return "alignment:center";
        return ["alignment:center"];

      case 'JustifyRight':
        return "alignment:right";
        return ["alignment:right"];

      case 'JustifyBlock':
        return "alignment:justify";
        return ["alignment:justify"];

      case 'HorizontalRule':
        return 'horizontalLine';
        return ['horizontalLine'];

      case 'Format':
        return 'heading';
        if ($text_format_html_restrictions->isUnrestricted()) {
          // When no restrictions exist, all tags possibly supported by "Format"
          // in CKEditor 4 must be supported.
          return ['heading'];
        }

        $allowed_elements = $text_format_html_restrictions->getAllowedElements();

        // Check if <h*> is supported.
        // Merely checking the existence of the array key is sufficient; this
        // plugin does not set or need any additional attributes.
        // @see \Drupal\filter\Plugin\FilterInterface::getHTMLRestrictions()
        $intersect = array_intersect(['h2', 'h3', 'h4', 'h5', 'h6'], array_keys($allowed_elements));

        return count($intersect) > 0 ? ['heading'] : NULL;

      case 'Table':
        return 'insertTable';
        return ['insertTable'];

      case 'Source':
        return 'sourceEditing';
        return ['sourceEditing'];

      case 'Strike':
        return 'strikethrough';
        return ['strikethrough'];

      case 'Cut':
      case 'Copy':
@@ -139,7 +154,7 @@ public function mapCKEditor4ToolbarButtonToCKEditor5ToolbarItem(string $cke4_but

      // @see \Drupal\ckeditor5\Plugin\CKEditor5Plugin\RemoveFormat
      case 'RemoveFormat':
        return 'removeFormat';
        return ['removeFormat'];

      // @see \Drupal\ckeditor\Plugin\CKEditorPlugin\StylesCombo
      case 'Styles':
@@ -148,15 +163,15 @@ public function mapCKEditor4ToolbarButtonToCKEditor5ToolbarItem(string $cke4_but

      // @see \Drupal\ckeditor5\Plugin\CKEditor5Plugin\specialCharacters
      case 'SpecialChar':
        return 'specialCharacters';
        return ['specialCharacters'];

      // @see \Drupal\ckeditor\Plugin\CKEditorPlugin\Language
      case 'Language':
        return 'textPartLanguage';
        return ['textPartLanguage'];

      // @see \Drupal\media_library\Plugin\CKEditorPlugin\DrupalMediaLibrary
      case 'DrupalMediaLibrary':
        return 'drupalMedia';
        return ['drupalMedia'];

      default:
        throw new \OutOfBoundsException();
+8 −4
Original line number Diff line number Diff line
@@ -4,6 +4,7 @@

namespace Drupal\ckeditor5\Plugin;

use Drupal\ckeditor5\HTMLRestrictions;
use Drupal\Component\Plugin\PluginInspectionInterface;
use Drupal\filter\FilterFormatInterface;

@@ -24,10 +25,13 @@ interface CKEditor4To5UpgradePluginInterface extends PluginInspectionInterface {
   *
   * @param string $cke4_button
   *   A valid CKEditor 4 button name.
   * @param \Drupal\ckeditor5\HTMLRestrictions $text_format_html_restrictions
   *   The restrictions of the text format, if this upgrade plugin needs to
   *   inspect the text format's HTML restrictions to make a decision.
   *
   * @return string|null
   *   The equivalent CKEditor 5 toolbar item, or NULL if no equivalent exists.
   *   In either case, the button name must be added to the annotation.
   * @return string[]|null
   *   The equivalent CKEditor 5 toolbar items, or NULL if no equivalent exists.
   *   In either case, the button names must be added to the annotation.
   *
   * @throws \OutOfBoundsException
   *   Thrown when this plugin does not know whether an equivalent exists.
@@ -35,7 +39,7 @@ interface CKEditor4To5UpgradePluginInterface extends PluginInspectionInterface {
   * @see \Drupal\ckeditor\CKEditorPluginButtonsInterface
   * @see \Drupal\ckeditor5\Annotation\CKEditor4To5Upgrade
   */
  public function mapCKEditor4ToolbarButtonToCKEditor5ToolbarItem(string $cke4_button): ?string;
  public function mapCKEditor4ToolbarButtonToCKEditor5ToolbarItem(string $cke4_button, HTMLRestrictions $text_format_html_restrictions): ?array;

  /**
   * Maps CKEditor 4 settings to the CKEditor 5 equivalent, if needed.
+8 −4
Original line number Diff line number Diff line
@@ -5,6 +5,7 @@
namespace Drupal\ckeditor5\Plugin;

use Drupal\ckeditor5\Annotation\CKEditor4To5Upgrade;
use Drupal\ckeditor5\HTMLRestrictions;
use Drupal\Component\Assertion\Inspector;
use Drupal\Core\Cache\CacheBackendInterface;
use Drupal\Core\Extension\ModuleHandlerInterface;
@@ -116,9 +117,12 @@ protected function validateAndBuildMaps(): void {
   *
   * @param string $cke4_button
   *   A valid CKEditor 4 button name.
   * @param \Drupal\ckeditor5\HTMLRestrictions $text_format_html_restrictions
   *   The restrictions of the text format, to allow an upgrade plugin to
   *   inspect the text format's HTML restrictions to make a decision.
   *
   * @return string|null
   *   The equivalent CKEditor 5 toolbar item, or NULL if no equivalent exists.
   * @return string[]|null
   *   The equivalent CKEditor 5 toolbar items, or NULL if no equivalent exists.
   *
   * @throws \OutOfBoundsException
   *   Thrown when no upgrade path exists.
@@ -127,7 +131,7 @@ protected function validateAndBuildMaps(): void {
   *
   * @see \Drupal\ckeditor\CKEditorPluginButtonsInterface
   */
  public function mapCKEditor4ToolbarButtonToCKEditor5ToolbarItem(string $cke4_button): ?string {
  public function mapCKEditor4ToolbarButtonToCKEditor5ToolbarItem(string $cke4_button, HTMLRestrictions $text_format_html_restrictions): ?array {
    $this->validateAndBuildMaps();

    if (!isset($this->cke4ButtonsMap[$cke4_button])) {
@@ -136,7 +140,7 @@ public function mapCKEditor4ToolbarButtonToCKEditor5ToolbarItem(string $cke4_but

    $plugin_id = $this->cke4ButtonsMap[$cke4_button];
    try {
      return $this->createInstance($plugin_id)->mapCKEditor4ToolbarButtonToCKEditor5ToolbarItem($cke4_button);
      return $this->createInstance($plugin_id)->mapCKEditor4ToolbarButtonToCKEditor5ToolbarItem($cke4_button, $text_format_html_restrictions);
    }
    catch (\OutOfBoundsException $e) {
      throw new \LogicException(sprintf('The "%s" CKEditor4To5Upgrade plugin claims to provide an upgrade path for the "%s" CKEditor 4 button but does not.', $plugin_id, $cke4_button));
+7 −4
Original line number Diff line number Diff line
@@ -124,7 +124,7 @@ public function computeSmartDefaultSettings(?EditorInterface $text_editor, Filte
    $old_editor = $editor->id() ? Editor::load($editor->id()) : NULL;
    if ($old_editor && $old_editor->getEditor() === 'ckeditor') {
      $enabled_cke4_plugins = $this->getEnabledCkeditor4Plugins($old_editor);
      [$upgraded_settings, $messages] = $this->createSettingsFromCKEditor4($old_editor->getSettings(), $enabled_cke4_plugins);
      [$upgraded_settings, $messages] = $this->createSettingsFromCKEditor4($old_editor->getSettings(), $enabled_cke4_plugins, HTMLRestrictions::fromTextFormat($old_editor->getFilterFormat()));
      $editor->setSettings($upgraded_settings);
      $editor->setImageUploadSettings($old_editor->getImageUploadSettings());
    }
@@ -200,6 +200,9 @@ private function addTagsToSourceEditing(EditorInterface $editor, HTMLRestriction
   * @param string[] $enabled_ckeditor4_plugins
   *   The list of enabled CKEditor 4 plugins: their settings will be mapped to
   *   the CKEditor 5 equivalents, if they have any.
   * @param \Drupal\ckeditor5\HTMLRestrictions $text_format_html_restrictions
   *   The restrictions of the text format, to allow an upgrade plugin to
   *   inspect the text format's HTML restrictions to make a decision.
   *
   * @return array
   *   An array with two values:
@@ -210,7 +213,7 @@ private function addTagsToSourceEditing(EditorInterface $editor, HTMLRestriction
   *   Thrown when an upgrade plugin is attempting to generate plugin settings
   *   for a CKEditor 4 plugin upgrade path that have already been generated.
   */
  private function createSettingsFromCKEditor4(array $ckeditor4_settings, array $enabled_ckeditor4_plugins): array {
  private function createSettingsFromCKEditor4(array $ckeditor4_settings, array $enabled_ckeditor4_plugins, HTMLRestrictions $text_format_html_restrictions): array {
    $settings = [
      'toolbar' => [
        'items' => [],
@@ -226,7 +229,7 @@ private function createSettingsFromCKEditor4(array $ckeditor4_settings, array $e
        $some_added = FALSE;
        foreach ($group['items'] as $cke4_button) {
          try {
            $equivalent = $this->upgradePluginManager->mapCKEditor4ToolbarButtonToCKEditor5ToolbarItem($cke4_button);
            $equivalent = $this->upgradePluginManager->mapCKEditor4ToolbarButtonToCKEditor5ToolbarItem($cke4_button, $text_format_html_restrictions);
          }
          catch (\OutOfBoundsException $e) {
            $messages[] = $this->t('The CKEditor 4 button %button does not have a known upgrade path. If it allowed editing markup, then you can do so now through the Source Editing functionality.', [
@@ -235,7 +238,7 @@ private function createSettingsFromCKEditor4(array $ckeditor4_settings, array $e
            continue;
          }
          if ($equivalent) {
            $settings['toolbar']['items'][] = $equivalent;
            $settings['toolbar']['items'] = array_merge($settings['toolbar']['items'], $equivalent);
            $some_added = TRUE;
          }
        }
Loading