Skip to content
Snippets Groups Projects
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
No related branches found
No related tags found
38 merge requests!54479.5.x SF update,!5014Issue #3071143: Table Render Array Example Is Incorrect,!3878Removed unused condition head title for views,!38582585169-10.1.x,!3818Issue #2140179: $entity->original gets stale between updates,!3742Issue #3328429: Create item list field formatter for displaying ordered and unordered lists,!3731Claro: role=button on status report items,!3668Resolve #3347842 "Deprecate the trusted",!3651Issue #3347736: Create new SDC component for Olivero (header-search),!3546refactored dialog.pcss file,!3531Issue #3336994: StringFormatter always displays links to entity even if the user in context does not have access,!3502Issue #3335308: Confusing behavior with FormState::setFormState and FormState::setMethod,!3452Issue #3332701: Refactor Claro's tablesort-indicator stylesheet,!3451Issue #2410579: Allows setting the current language programmatically.,!3355Issue #3209129: Scrolling problems when adding a block via layout builder,!3226Issue #2987537: Custom menu link entity type should not declare "bundle" entity key,!3154Fixes #2987987 - CSRF token validation broken on routes with optional parameters.,!3147Issue #3328457: Replace most substr($a, $i) where $i is negative with str_ends_with(),!3146Issue #3328456: Replace substr($a, 0, $i) with str_starts_with(),!3133core/modules/system/css/components/hidden.module.css,!31312878513-10.1.x,!2812Issue #3312049: [Followup] Fix Drupal.Commenting.FunctionComment.MissingReturnType returns for NULL,!2614Issue #2981326: Replace non-test usages of \Drupal::logger() with IoC injection,!2378Issue #2875033: Optimize joins and table selection in SQL entity query implementation,!2334Issue #3228209: Add hasRole() method to AccountInterface,!2062Issue #3246454: Add weekly granularity to views date sort,!1591Issue #3199697: Add JSON:API Translation experimental module,!1255Issue #3238922: Refactor (if feasible) uses of the jQuery serialize function to use vanillaJS,!1105Issue #3025039: New non translatable field on translatable content throws error,!1073issue #3191727: Focus states on mobile second level navigation items fixed,!877Issue #2708101: Default value for link text is not saved,!844Resolve #3036010 "Updaters",!673Issue #3214208: FinishResponseSubscriber could create duplicate headers,!617Issue #3043725: Provide a Entity Handler for user cancelation,!579Issue #2230909: Simple decimals fail to pass validation,!560Move callback classRemove outside of the loop,!555Issue #3202493,!485Sets the autocomplete attribute for username/password input field on login form.
Pipeline #24610 failed
......@@ -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:
......
......@@ -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;
......
......@@ -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>');
......
......@@ -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);
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment