Commit ca895b65 authored by catch's avatar catch
Browse files

Issue #3291744 by lauriii, xjm, Abhijith S, Wim Leers, bnjmnm, catch: Ensure...

Issue #3291744 by lauriii, xjm, Abhijith S, Wim Leers, bnjmnm, catch: Ensure Editor config entities using CKEditor 4 only store plugins settings for actually enabled plugins

(cherry picked from commit ccce8014)
parent 623dfc97
Loading
Loading
Loading
Loading
+87 −0
Original line number Diff line number Diff line
@@ -10,8 +10,11 @@
use Drupal\Component\Utility\UrlHelper;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Routing\RouteMatchInterface;
use Drupal\ckeditor\CKEditorPluginButtonsInterface;
use Drupal\ckeditor\CKEditorPluginContextualInterface;
use Drupal\Core\StringTranslation\TranslatableMarkup;
use Drupal\editor\Entity\Editor;
use Drupal\editor\EditorInterface;

/**
 * Implements hook_help().
@@ -122,6 +125,59 @@ function _ckeditor_theme_css($theme = NULL) {
  return $css;
}

/**
 * Gets all enabled CKEditor 4 plugins.
 *
 * @param \Drupal\editor\EditorInterface $editor
 *   A text editor config entity configured to use CKEditor 4.
 *
 * @return string[]
 *   The enabled CKEditor 4 plugin IDs.
 *
 * @internal
 */
function _ckeditor_get_enabled_plugins(EditorInterface $editor): array {
  assert($editor->getEditor() === 'ckeditor');

  $cke4_plugin_manager = \Drupal::service('plugin.manager.ckeditor.plugin');

  // This is largely copied from the CKEditor 4 plugin manager, because it
  // unfortunately does not provide the API this needs.
  // @see \Drupal\ckeditor\CKEditorPluginManager::getEnabledPluginFiles()
  $plugins = array_keys($cke4_plugin_manager->getDefinitions());
  $toolbar_buttons = $cke4_plugin_manager->getEnabledButtons($editor);
  $enabled_plugins = [];
  $additional_plugins = [];
  foreach ($plugins as $plugin_id) {
    $plugin = $cke4_plugin_manager->createInstance($plugin_id);

    $enabled = FALSE;
    // Plugin is enabled if it provides a button that has been enabled.
    if ($plugin instanceof CKEditorPluginButtonsInterface) {
      $plugin_buttons = array_keys($plugin->getButtons());
      $enabled = (count(array_intersect($toolbar_buttons, $plugin_buttons)) > 0);
    }
    // Otherwise plugin is enabled if it declares itself as enabled.
    if (!$enabled && $plugin instanceof CKEditorPluginContextualInterface) {
      $enabled = $plugin->isEnabled($editor);
    }

    if ($enabled) {
      $enabled_plugins[$plugin_id] = $plugin_id;
      // Check if this plugin has dependencies that should be considered
      // enabled.
      $additional_plugins = array_merge($additional_plugins, array_diff($plugin->getDependencies($editor), $additional_plugins));
    }
  }

  // Add the list of dependent plugins.
  foreach ($additional_plugins as $plugin_id) {
    $enabled_plugins[$plugin_id] = $plugin_id;
  }

  return $enabled_plugins;
}

/**
 * Implements hook_library_info_alter().
 */
@@ -203,3 +259,34 @@ function ckeditor_filter_format_edit_form_validate($form, FormStateInterface $fo
    }
  }
}

/**
 * Implements hook_ENTITY_TYPE_presave().
 */
function ckeditor_editor_presave(EditorInterface $editor) {
  // Only try to update editors using CKEditor 4.
  if ($editor->getEditor() !== 'ckeditor') {
    return FALSE;
  }

  $enabled_plugins = _ckeditor_get_enabled_plugins($editor);

  // Only update if the editor has plugin settings for disabled plugins.
  $needs_update = FALSE;
  $settings = $editor->getSettings();

  // Updates are not needed if plugin settings are not defined for the editor.
  if (!isset($settings['plugins'])) {
    return;
  }

  foreach (array_keys($settings['plugins']) as $plugin_id) {
    if (!in_array($plugin_id, $enabled_plugins, TRUE)) {
      unset($settings['plugins'][$plugin_id]);
      $needs_update = TRUE;
    }
  }
  if ($needs_update) {
    $editor->setSettings($settings);
  }
}
+41 −0
Original line number Diff line number Diff line
<?php

/**
 * @file
 * Post update functions for CKEditor.
 */

use Drupal\Core\Config\Entity\ConfigEntityUpdater;
use Drupal\editor\Entity\Editor;

/**
 * Updates Text Editors using CKEditor 4 to omit settings for disabled plugins.
 */
function ckeditor_post_update_omit_settings_for_disabled_plugins(&$sandbox = []) {
  $config_entity_updater = \Drupal::classResolver(ConfigEntityUpdater::class);
  $config_entity_updater->update($sandbox, 'editor', function (Editor $editor): bool {
    // Only try to update editors using CKEditor 4.
    if ($editor->getEditor() !== 'ckeditor') {
      return FALSE;
    }

    $enabled_plugins = _ckeditor_get_enabled_plugins($editor);

    // Only update if the editor has plugin settings for disabled plugins.
    $needs_update = FALSE;
    $settings = $editor->getSettings();

    // Updates are not needed if plugin settings are not defined for the editor.
    if (!isset($settings['plugins'])) {
      return FALSE;
    }

    foreach (array_keys($settings['plugins']) as $plugin_id) {
      if (!in_array($plugin_id, $enabled_plugins, TRUE)) {
        $needs_update = TRUE;
      }
    }

    return $needs_update;
  });
}
+19 −1
Original line number Diff line number Diff line
@@ -174,7 +174,7 @@ public function getDefaultSettings() {
          ],
        ],
      ],
      'plugins' => ['language' => ['language_list' => 'un']],
      'plugins' => [],
    ];
  }

@@ -295,6 +295,24 @@ public function submitConfigurationForm(array &$form, FormStateInterface $form_s
    if ($form_state->hasValue('plugins')) {
      $form_state->unsetValue('plugin_settings');
    }

    // Ensure plugin settings are only saved for plugins that are actually
    // enabled.
    $about_to_be_saved_editor = Editor::create([
      'editor' => 'ckeditor',
      'settings' => [
        'toolbar' => $form_state->getValue('toolbar'),
        'plugins' => $form_state->getValue('plugins'),
      ],
    ]);
    $enabled_plugins = _ckeditor_get_enabled_plugins($about_to_be_saved_editor);
    $plugin_settings = $form_state->getValue('plugins', []);
    foreach (array_keys($plugin_settings) as $plugin_id) {
      if (!in_array($plugin_id, $enabled_plugins, TRUE)) {
        unset($plugin_settings[$plugin_id]);
      }
    }
    $form_state->setValue('plugins', $plugin_settings);
  }

  /**
+16 −6
Original line number Diff line number Diff line
@@ -108,7 +108,7 @@ public function testExistingFormat() {
          ],
        ],
      ],
      'plugins' => ['language' => ['language_list' => 'un']],
      'plugins' => [],
    ];
    $this->assertEquals($expected_default_settings, $ckeditor->getDefaultSettings());

@@ -136,22 +136,27 @@ public function testExistingFormat() {
    $expected_buttons_value = json_encode($expected_default_settings['toolbar']['rows']);
    $this->assertSession()->fieldValueEquals('editor[settings][toolbar][button_groups]', $expected_buttons_value);

    // Ensure the styles textarea exists and is initialized empty.
    $this->assertSession()->fieldValueEquals('editor[settings][plugins][stylescombo][styles]', '');

    // Submit the form to save the selection of CKEditor as the chosen editor.
    $this->submitForm($edit, 'Save configuration');

    // Ensure an Editor object exists now, with the proper settings.
    $expected_settings = $expected_default_settings;
    $expected_settings['plugins']['stylescombo']['styles'] = '';
    $editor = Editor::load('filtered_html');
    $this->assertInstanceOf(Editor::class, $editor);
    $this->assertEquals($expected_settings, $editor->getSettings(), 'The Editor config entity has the correct settings.');

    // Configure the Styles plugin, and ensure the updated settings are saved.
    $this->drupalGet('admin/config/content/formats/manage/filtered_html');

    // Ensure the styles textarea exists and is initialized empty.
    $this->assertSession()->fieldValueEquals('editor[settings][plugins][stylescombo][styles]', '');

    $expected_settings['toolbar']['rows'][0][] = [
      'name' => 'Styles dropdown',
      'items' => ['Styles'],
    ];
    $edit = [
      'editor[settings][toolbar][button_groups]' => json_encode($expected_settings['toolbar']['rows']),
      'editor[settings][plugins][stylescombo][styles]' => "h1.title|Title\np.callout|Callout\n\n",
    ];
    $this->submitForm($edit, 'Save configuration');
@@ -164,6 +169,7 @@ public function testExistingFormat() {
    // done via drag and drop, but here we can only emulate the end result of
    // that interaction). Test multiple toolbar rows and a divider within a row.
    $this->drupalGet('admin/config/content/formats/manage/filtered_html');
    $expected_settings = $expected_default_settings;
    $expected_settings['toolbar']['rows'][0][] = [
      'name' => 'Action history',
      'items' => ['Undo', '|', 'Redo', 'JustifyCenter'],
@@ -205,7 +211,12 @@ public function testExistingFormat() {

    // Finally, check the "Ultra llama mode" checkbox.
    $this->drupalGet('admin/config/content/formats/manage/filtered_html');
    $expected_settings['toolbar']['rows'][0][] = [
      'name' => 'Ultra llama mode',
      'items' => ['Llama'],
    ];
    $edit = [
      'editor[settings][toolbar][button_groups]' => json_encode($expected_settings['toolbar']['rows']),
      'editor[settings][plugins][llama_contextual_and_button][ultra_llama_mode]' => '1',
    ];
    $this->submitForm($edit, 'Save configuration');
@@ -287,7 +298,6 @@ public function testNewFormat() {

    // Ensure an Editor object exists now, with the proper settings.
    $expected_settings = $default_settings;
    $expected_settings['plugins']['stylescombo']['styles'] = '';
    $editor = Editor::load('amazing_format');
    $this->assertInstanceOf(Editor::class, $editor);
    $this->assertEquals($expected_settings, $editor->getSettings(), 'The Editor config entity has the correct settings.');
+15 −4
Original line number Diff line number Diff line
@@ -39,6 +39,13 @@ class CKEditorStylesComboAdminTest extends BrowserTestBase {
   */
  protected $format;

  /**
   * The default editor settings.
   *
   * @var array
   */
  protected $defaultSettings;

  /**
   * {@inheritdoc}
   */
@@ -52,9 +59,16 @@ protected function setUp(): void {
      'filters' => [],
    ]);
    $filter_format->save();
    $ckeditor = $this->container->get('plugin.manager.editor')->createInstance('ckeditor');
    $this->defaultSettings = $ckeditor->getDefaultSettings();
    $this->defaultSettings['toolbar']['rows'][0][] = [
      'name' => 'Styles dropdown',
      'items' => ['Styles'],
    ];
    $editor = Editor::create([
      'format' => $this->format,
      'editor' => 'ckeditor',
      'settings' => $this->defaultSettings,
    ]);
    $editor->save();

@@ -65,14 +79,11 @@ protected function setUp(): void {
   * Tests StylesCombo settings for an existing text format.
   */
  public function testExistingFormat() {
    $ckeditor = $this->container->get('plugin.manager.editor')->createInstance('ckeditor');
    $default_settings = $ckeditor->getDefaultSettings();

    $this->drupalLogin($this->adminUser);
    $this->drupalGet('admin/config/content/formats/manage/' . $this->format);

    // Ensure an Editor config entity exists, with the proper settings.
    $expected_settings = $default_settings;
    $expected_settings = $this->defaultSettings;
    $editor = Editor::load($this->format);
    $this->assertEquals($expected_settings, $editor->getSettings(), 'The Editor config entity has the correct settings.');

Loading