Verified Commit e1fc76a1 authored by Lauri Timmanee's avatar Lauri Timmanee
Browse files

Issue #3273986 by ifrik, xurizaemon, rpayanm, Wim Leers, Charles Belov,...

Issue #3273986 by ifrik, xurizaemon, rpayanm, Wim Leers, Charles Belov, jonathan_hunt: Third option for the CKEditor 5 "Language" button: site_configured (in addition to un and all)
parent 288f761c
Loading
Loading
Loading
Loading
Loading
+5 −1
Original line number Diff line number Diff line
@@ -41,10 +41,14 @@ ckeditor5.plugin.ckeditor5_language:
        # Configuring this does not make sense without the corresponding button.
        CKEditor5ToolbarItemDependencyConstraint:
          toolbarItem: textPartLanguage
        # Only two possible values are accepted.
        # Only the following values are accepted.
        Choice:
          # United Nations "official languages".
          - un
          # Drupal's predefined language list.
          - all
          # Languages configured at /admin/config/regional/language for the site.
          - site_configured

# Plugin \Drupal\ckeditor5\Plugin\CKEditor5Plugin\Heading
ckeditor5.plugin.ckeditor5_heading:
+84 −14
Original line number Diff line number Diff line
@@ -4,13 +4,19 @@

namespace Drupal\ckeditor5\Plugin\CKEditor5Plugin;

use Drupal\ckeditor5\Plugin\CKEditor5PluginConfigurableInterface;
use Drupal\ckeditor5\Plugin\CKEditor5PluginConfigurableTrait;
use Drupal\ckeditor5\Plugin\CKEditor5PluginDefault;
use Drupal\ckeditor5\Plugin\CKEditor5PluginConfigurableInterface;
use Drupal\ckeditor5\Plugin\CKEditor5PluginDefinition;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Language\LanguageManager;
use Drupal\Core\Language\LanguageInterface;
use Drupal\Core\Language\LanguageManager;
use Drupal\Core\Language\LanguageManagerInterface;
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
use Drupal\Core\Routing\RouteProviderInterface;
use Drupal\Core\Url;
use Drupal\editor\EditorInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;

/**
 * CKEditor 5 Language plugin.
@@ -18,23 +24,72 @@
 * @internal
 *   Plugin classes are internal.
 */
class Language extends CKEditor5PluginDefault implements CKEditor5PluginConfigurableInterface {
class Language extends CKEditor5PluginDefault implements CKEditor5PluginConfigurableInterface, ContainerFactoryPluginInterface {

  use CKEditor5PluginConfigurableTrait;

  /**
   * Language constructor.
   *
   * @param array $configuration
   *   A configuration array containing information about the plugin instance.
   * @param string $plugin_id
   *   The plugin_id for the plugin instance.
   * @param \Drupal\ckeditor5\Plugin\CKEditor5PluginDefinition $plugin_definition
   *   The plugin implementation definition.
   * @param \Drupal\Core\Language\LanguageManagerInterface $languageManager
   *   The language manager.
   * @param \Drupal\Core\Routing\RouteProviderInterface $routeProvider
   *   The route provider.
   */
  public function __construct(array $configuration, string $plugin_id, CKEditor5PluginDefinition $plugin_definition, protected LanguageManagerInterface $languageManager, protected RouteProviderInterface $routeProvider) {
    parent::__construct($configuration, $plugin_id, $plugin_definition);
  }

  /**
   * {@inheritdoc}
   */
  public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
    return new static(
      $configuration,
      $plugin_id,
      $plugin_definition,
      $container->get('language_manager'),
      $container->get('router.route_provider'),
    );
  }

  /**
   * {@inheritdoc}
   */
  public function getDynamicPluginConfig(array $static_plugin_config, EditorInterface $editor): array {
    $predefined_languages = $this->configuration['language_list'] === 'all' ?
      LanguageManager::getStandardLanguageList() :
      LanguageManager::getUnitedNationsLanguageList();
    $languages = NULL;
    switch ($this->configuration['language_list']) {
      case 'site_configured':
        $configured_languages = $this->languageManager->getLanguages();
        $languages = [];
        foreach ($configured_languages as $language) {
          $languages[$language->getId()] = [
            $language->getName(),
            '',
            $language->getDirection(),
          ];
        }
        break;

      case 'all':
        $languages = LanguageManager::getStandardLanguageList();
        break;

      case 'un':
        $languages = LanguageManager::getUnitedNationsLanguageList();
    }

    // Generate the language_list setting as expected by the CKEditor Language
    // plugin, but key the values by the full language name so that we can sort
    // them later on.
    $language_list = [];
    foreach ($predefined_languages as $langcode => $language) {
    foreach ($languages as $langcode => $language) {
      $english_name = $language[0];
      $direction = empty($language[2]) ? NULL : $language[2];
      $language_list[$english_name] = [
@@ -60,20 +115,35 @@ public function getDynamicPluginConfig(array $static_plugin_config, EditorInterf
   * @see editor_image_upload_settings_form()
   */
  public function buildConfigurationForm(array $form, FormStateInterface $form_state) {
    $predefined_languages = LanguageManager::getStandardLanguageList();
    $configured = count($this->languageManager->getLanguages());
    $predefined = count(LanguageManager::getStandardLanguageList());
    $united_nations = count(LanguageManager::getUnitedNationsLanguageList());

    $language_list_description_args = [
      ':united-nations-official' => 'https://www.un.org/en/sections/about-un/official-languages',
      '@count_predefined' => $predefined,
      '@count_united_nations' => $united_nations,
      '@count_configured' => $configured,
    ];
    // If Language is enabled, link to the configuration route.
    if ($this->routeProvider->getRoutesByNames(['entity.configurable_language.collection'])) {
      $language_list_description = $this->t('The list of languages in the CKEditor "Language" dropdown can present the <a href=":united-nations-official">@count_united_nations official languages of the UN</a>, all @count_predefined languages predefined in Drupal, or the <a href=":admin-configure-languages">@count_configured languages configured for this site</a>.', $language_list_description_args + [':admin-configure-languages' => Url::fromRoute('entity.configurable_language.collection')->toString()]);
    }
    else {
      $language_list_description = $this->t('The list of languages in the CKEditor "Language" dropdown can present the <a href=":united-nations-official">@count_united_nations official languages of the UN</a>, all @count_predefined languages predefined in Drupal, or the languages configured for this site.', $language_list_description_args);
    }

    $form['language_list'] = [
      '#title' => $this->t('Language list'),
      '#title_display' => 'invisible',
      '#type' => 'select',
      '#options' => [
        'un' => $this->t("United Nations' official languages"),
        'all' => $this->t('All @count languages', ['@count' => count($predefined_languages)]),
        'un' => $this->t("United Nations' official languages (@count)", ['@count' => $united_nations]),
        'all' => $this->t('Drupal predefined languages (@count)', ['@count' => $predefined]),
        'site_configured' => $this->t("Site-configured languages (@count)", ['@count' => $configured]),
      ],
      '#default_value' => $this->configuration['language_list'],
      '#description' => $this->t('The list of languages to show in the language dropdown. The basic list will only show the <a href=":url">six official languages of the UN</a>. The extended list will show all @count languages that are available in Drupal.', [
        ':url' => 'https://www.un.org/en/sections/about-un/official-languages',
        '@count' => count($predefined_languages),
      ]),
      '#description' => $language_list_description,
    ];

    return $form;
+59 −27
Original line number Diff line number Diff line
@@ -7,13 +7,14 @@
use Drupal\editor\Entity\Editor;
use Drupal\file\Entity\File;
use Drupal\filter\Entity\FilterFormat;
use Drupal\language\Entity\ConfigurableLanguage;
use Drupal\node\Entity\Node;
use Drupal\Tests\ckeditor5\Traits\CKEditor5TestTrait;
use Drupal\Tests\TestFileCreationTrait;
use Drupal\user\RoleInterface;
use Symfony\Component\Validator\ConstraintViolation;

// cspell:ignore esque splitbutton upcasted sourceediting
// cspell:ignore esque māori sourceediting splitbutton upcasted

/**
 * Tests for CKEditor 5.
@@ -31,6 +32,7 @@ class CKEditor5Test extends CKEditor5TestBase {
   */
  protected static $modules = [
    'media_library',
    'language',
  ];

  /**
@@ -41,7 +43,7 @@ public function testExistingContent() {
    $assert_session = $this->assertSession();

    // Add a node with text rendered via the Plain Text format.
    $this->drupalGet('node/add');
    $this->drupalGet('node/add/page');
    $page->fillField('title[0][value]', 'My test content');
    $page->fillField('body[0][value]', '<p>This is test content</p>');
    $page->pressButton('Save');
@@ -103,7 +105,7 @@ function (ConstraintViolation $v) {
      ))
    ));

    $this->drupalGet('node/add');
    $this->drupalGet('node/add/page');
    $this->waitForEditor();
    $page->fillField('title[0][value]', 'My test content');

@@ -147,7 +149,7 @@ public function testHeadingsPlugin() {
    $this->drupalGet('admin/config/content/formats/manage/ckeditor5');
    $this->assertHtmlEsqueFieldValueEquals('filters[filter_html][settings][allowed_html]', '<br> <p> <h2> <h3> <h4> <h5> <h6> <strong> <em>');

    $this->drupalGet('node/add');
    $this->drupalGet('node/add/page');
    $this->assertNotEmpty($assert_session->waitForElement('css', '.ck-heading-dropdown button'));

    $page->find('css', '.ck-heading-dropdown button')->click();
@@ -188,7 +190,7 @@ public function testHeadingsPlugin() {

    $page->pressButton('Save configuration');

    $this->drupalGet('node/add');
    $this->drupalGet('node/add/page');
    $this->assertNotEmpty($assert_session->waitForElement('css', '.ck-heading-dropdown button'));

    $page->find('css', '.ck-heading-dropdown button')->click();
@@ -212,12 +214,43 @@ public function testHeadingsPlugin() {
  }

  /**
   * Test for plugin Language of parts.
   * Test for Language of Parts plugin.
   */
  public function testLanguageOfPartsPlugin() {
    $page = $this->getSession()->getPage();
    $assert_session = $this->assertSession();

    $this->languageOfPartsPluginInitialConfigurationHelper($page, $assert_session);

    // Test for "United Nations' official languages" option.
    $languages = LanguageManager::getUnitedNationsLanguageList();
    $this->languageOfPartsPluginConfigureLanguageListHelper($page, $assert_session, 'un');
    $this->languageOfPartsPluginTestHelper($page, $assert_session, $languages);

    // Test for "Drupal predefined languages" option.
    $languages = LanguageManager::getStandardLanguageList();
    $this->languageOfPartsPluginConfigureLanguageListHelper($page, $assert_session, 'all');
    $this->languageOfPartsPluginTestHelper($page, $assert_session, $languages);

    // Test for "Site-configured languages" option.
    ConfigurableLanguage::createFromLangcode('ar')->save();
    ConfigurableLanguage::createFromLangcode('fr')->save();
    ConfigurableLanguage::createFromLangcode('mi')->setName('Māori')->save();
    $configured_languages = \Drupal::languageManager()->getLanguages();
    $languages = [];
    foreach ($configured_languages as $language) {
      $language_name = $language->getName();
      $language_code = $language->getId();
      $languages[$language_code] = [$language_name];
    }
    $this->languageOfPartsPluginConfigureLanguageListHelper($page, $assert_session, 'site_configured');
    $this->languageOfPartsPluginTestHelper($page, $assert_session, $languages);
  }

  /**
   * Helper to configure CKEditor5 with Language plugin.
   */
  public function languageOfPartsPluginInitialConfigurationHelper($page, $assert_session) {
    $this->createNewTextFormat($page, $assert_session);
    // Press arrow down key to add the button to the active toolbar.
    $this->assertNotEmpty($assert_session->waitForElement('css', '.ckeditor5-toolbar-item-textPartLanguage'));
@@ -248,21 +281,15 @@ public function testLanguageOfPartsPlugin() {

    // Confirm there are no longer any warnings.
    $assert_session->waitForElementRemoved('css', '[data-drupal-messages] [role="alert"]');

    // Test for "United Nations' official languages" option.
    $languages = LanguageManager::getUnitedNationsLanguageList();
    $this->languageOfPartsPluginTestHelper($page, $assert_session, $languages, "un");

    // Test for "All 95 languages" option.
    $this->drupalGet('admin/config/content/formats/manage/ckeditor5');
    $languages = LanguageManager::getStandardLanguageList();
    $this->languageOfPartsPluginTestHelper($page, $assert_session, $languages, "all");
    $page->pressButton('Save configuration');
    $assert_session->responseContains('Added text format <em class="placeholder">ckeditor5</em>.');
  }

  /**
   * Validate the available languages on the basis of selected language option.
   * Helper to set language list option for CKEditor.
   */
  public function languageOfPartsPluginTestHelper($page, $assert_session, $predefined_languages, $option) {
  public function languageOfPartsPluginConfigureLanguageListHelper($page, $assert_session, $option) {
    $this->drupalGet('admin/config/content/formats/manage/ckeditor5');
    $this->assertNotEmpty($assert_session->waitForElement('css', 'a[href^="#edit-editor-settings-plugins-ckeditor5-language"]'));

    // Set correct value.
@@ -271,9 +298,14 @@ public function languageOfPartsPluginTestHelper($page, $assert_session, $predefi
    $page->selectFieldOption('editor[settings][plugins][ckeditor5_language][language_list]', $option);
    $assert_session->assertWaitOnAjaxRequest();
    $page->pressButton('Save configuration');
    $assert_session->responseContains('The text format <em class="placeholder">ckeditor5</em> has been updated.');
  }

    // Validate plugin on node add page.
    $this->drupalGet('node/add');
  /**
   * Validate expected languages available in editor.
   */
  public function languageOfPartsPluginTestHelper($page, $assert_session, $configured_languages) {
    $this->drupalGet('node/add/page');
    $this->assertNotEmpty($assert_session->waitForText('Choose language'));

    // Click on the dropdown button.
@@ -290,13 +322,13 @@ public function languageOfPartsPluginTestHelper($page, $assert_session, $predefi
    foreach ($current_languages as $item) {
      $languages[] = $item->getText();
    }

    // Return the values from a single column.
    $predefined_languages = array_column($predefined_languages, 0);
    $configured_languages = array_column($configured_languages, 0);

    // Sort on full language name.
    asort($predefined_languages);

    $this->assertSame(array_values($predefined_languages), $languages);
    asort($configured_languages);
    $this->assertSame(array_values($configured_languages), $languages);
  }

  /**
@@ -518,7 +550,7 @@ public function testEditorFileReferenceIntegration() {
    $assert_session->assertWaitOnAjaxRequest();
    $this->saveNewTextFormat($page, $assert_session);

    $this->drupalGet('node/add');
    $this->drupalGet('node/add/page');
    $page->fillField('title[0][value]', 'My test content');

    // Ensure that CKEditor 5 is focused.
@@ -566,7 +598,7 @@ public function testEmphasis() {
    $assert_session = $this->assertSession();

    // Add a node with text rendered via the Plain Text format.
    $this->drupalGet('node/add');
    $this->drupalGet('node/add/page');
    $page->fillField('title[0][value]', 'My test content');
    $page->fillField('body[0][value]', '<p>This is a <em>test!</em></p>');
    $page->pressButton('Save');
@@ -624,7 +656,7 @@ function (ConstraintViolation $v) {
    $ordered_list_html = '<ol><li>apple</li><li>banana</li><li>cantaloupe</li></ol>';
    $page = $this->getSession()->getPage();
    $assert_session = $this->assertSession();
    $this->drupalGet('node/add');
    $this->drupalGet('node/add/page');
    $page->fillField('title[0][value]', 'My test content');
    $this->pressEditorButton('Source');
    $source_text_area = $assert_session->waitForElement('css', '.ck-source-editing-area textarea');
@@ -690,7 +722,7 @@ public function testFilterHtmlAllowedGlobalAttributes(): void {
    $assert_session = $this->assertSession();

    // Add a node with text rendered via the Plain Text format.
    $this->drupalGet('node/add');
    $this->drupalGet('node/add/page');
    $page->fillField('title[0][value]', 'Multilingual Hello World');
    // cSpell:disable-next-line
    $page->fillField('body[0][value]', '<p dir="ltr" lang="en">Hello World</p><p dir="rtl" lang="ar">مرحبا بالعالم</p>');
+39 −4
Original line number Diff line number Diff line
@@ -5,7 +5,11 @@
namespace Drupal\Tests\ckeditor5\Unit;

use Drupal\ckeditor5\Plugin\CKEditor5Plugin\Language;
use Drupal\ckeditor5\Plugin\CKEditor5PluginDefinition;
use Drupal\Core\Language\Language as LanguageLanguage;
use Drupal\Core\Language\LanguageManager;
use Drupal\Core\Language\LanguageManagerInterface;
use Drupal\Core\Routing\RouteProviderInterface;
use Drupal\editor\EditorInterface;
use Drupal\Tests\UnitTestCase;

@@ -20,7 +24,7 @@ class LanguagePluginTest extends UnitTestCase {
   * Provides a list of configs to test.
   */
  public static function providerGetDynamicPluginConfig(): array {
    $un_expected_output = [
    $united_nations_expected_output = [
      'language' => [
        'textPartLanguage' => [
          [
@@ -54,7 +58,25 @@ public static function providerGetDynamicPluginConfig(): array {
    return [
      'un' => [
        ['language_list' => 'un'],
        $un_expected_output,
        $united_nations_expected_output,
      ],
      'site_configured' => [
        ['language_list' => 'site_configured'],
        [
          'language' => [
            'textPartLanguage' => [
              [
                'title' => 'Arabic',
                'languageCode' => 'ar',
                'textDirection' => 'rtl',
              ],
              [
                'title' => 'German',
                'languageCode' => 'de',
              ],
            ],
          ],
        ],
      ],
      'all' => [
        ['language_list' => 'all'],
@@ -66,7 +88,7 @@ public static function providerGetDynamicPluginConfig(): array {
      ],
      'default configuration' => [
        [],
        $un_expected_output,
        $united_nations_expected_output,
      ],
    ];
  }
@@ -101,7 +123,20 @@ protected static function buildExpectedDynamicConfig(array $language_list) {
   * @dataProvider providerGetDynamicPluginConfig
   */
  public function testGetDynamicPluginConfig(array $configuration, array $expected_dynamic_config): void {
    $plugin = new Language($configuration, 'ckeditor5_language', NULL);
    $route_provider = $this->prophesize(RouteProviderInterface::class);
    $language_manager = $this->prophesize(LanguageManagerInterface::class);
    $language_manager->getLanguages()->willReturn([
      new LanguageLanguage([
        'id' => 'de',
        'name' => 'German',
      ]),
      new LanguageLanguage([
        'id' => 'ar',
        'name' => 'Arabic',
        'direction' => 'rtl',
      ]),
    ]);
    $plugin = new Language($configuration, 'ckeditor5_language', new CKEditor5PluginDefinition(['id' => 'IRRELEVANT-FOR-A-UNIT-TEST']), $language_manager->reveal(), $route_provider->reveal());
    $dynamic_config = $plugin->getDynamicPluginConfig([], $this->prophesize(EditorInterface::class)
      ->reveal());
    $this->assertSame($expected_dynamic_config, $dynamic_config);