3 namespace Drupal\Tests\ckeditor\Functional;
5 use Drupal\editor\Entity\Editor;
6 use Drupal\filter\Entity\FilterFormat;
7 use Drupal\Tests\BrowserTestBase;
10 * Tests loading of CKEditor.
14 class CKEditorLoadingTest extends BrowserTestBase {
21 public static $modules = ['filter', 'editor', 'ckeditor', 'node'];
24 * An untrusted user with access to only the 'plain_text' format.
26 * @var \Drupal\user\UserInterface
28 protected $untrustedUser;
31 * A normal user with access to the 'plain_text' and 'filtered_html' formats.
33 * @var \Drupal\user\UserInterface
35 protected $normalUser;
37 protected function setUp() {
40 // Create text format, associate CKEditor.
41 $filtered_html_format = FilterFormat::create([
42 'format' => 'filtered_html',
43 'name' => 'Filtered HTML',
47 $filtered_html_format->save();
48 $editor = Editor::create([
49 'format' => 'filtered_html',
50 'editor' => 'ckeditor',
54 // Create a second format without an associated editor so a drop down select
55 // list is created when selecting formats.
56 $full_html_format = FilterFormat::create([
57 'format' => 'full_html',
58 'name' => 'Full HTML',
62 $full_html_format->save();
65 $this->drupalCreateContentType([
70 $this->untrustedUser = $this->drupalCreateUser(['create article content', 'edit any article content']);
71 $this->normalUser = $this->drupalCreateUser(['create article content', 'edit any article content', 'use text format filtered_html', 'use text format full_html']);
75 * Tests loading of CKEditor CSS, JS and JS settings.
77 public function testLoading() {
78 // The untrusted user:
79 // - has access to 1 text format (plain_text);
80 // - doesn't have access to the filtered_html text format, so: no text editor.
81 $this->drupalLogin($this->untrustedUser);
82 $this->drupalGet('node/add/article');
83 list($settings, $editor_settings_present, $editor_js_present, $body, $format_selector) = $this->getThingsToCheck();
84 $this->assertFalse($editor_settings_present, 'No Text Editor module settings.');
85 $this->assertFalse($editor_js_present, 'No Text Editor JavaScript.');
86 $this->assertTrue(count($body) === 1, 'A body field exists.');
87 $this->assertTrue(count($format_selector) === 0, 'No text format selector exists on the page.');
88 $hidden_input = $this->xpath('//input[@type="hidden" and contains(@class, "editor")]');
89 $this->assertTrue(count($hidden_input) === 0, 'A single text format hidden input does not exist on the page.');
90 $this->assertNoRaw(drupal_get_path('module', 'ckeditor') . '/js/ckeditor.js', 'CKEditor glue JS is absent.');
92 // On pages where there would never be a text editor, CKEditor JS is absent.
93 $this->drupalGet('user');
94 $this->assertNoRaw(drupal_get_path('module', 'ckeditor') . '/js/ckeditor.js', 'CKEditor glue JS is absent.');
97 // - has access to 2 text formats;
98 // - does have access to the filtered_html text format, so: CKEditor.
99 $this->drupalLogin($this->normalUser);
100 $this->drupalGet('node/add/article');
101 list($settings, $editor_settings_present, $editor_js_present, $body, $format_selector) = $this->getThingsToCheck();
102 $ckeditor_plugin = $this->container->get('plugin.manager.editor')->createInstance('ckeditor');
103 $editor = Editor::load('filtered_html');
107 'format' => 'filtered_html',
108 'editor' => 'ckeditor',
109 'editorSettings' => $this->castSafeStrings($ckeditor_plugin->getJSSettings($editor)),
110 'editorSupportsContentFiltering' => TRUE,
111 'isXssSafe' => FALSE,
115 $this->assertTrue($editor_settings_present, "Text Editor module's JavaScript settings are on the page.");
116 $this->assertIdentical($expected, $this->castSafeStrings($settings['editor']), "Text Editor module's JavaScript settings on the page are correct.");
117 $this->assertTrue($editor_js_present, 'Text Editor JavaScript is present.');
118 $this->assertTrue(count($body) === 1, 'A body field exists.');
119 $this->assertTrue(count($format_selector) === 1, 'A single text format selector exists on the page.');
120 $specific_format_selector = $this->xpath('//select[contains(@class, "filter-list") and @data-editor-for="edit-body-0-value"]');
121 $this->assertTrue(count($specific_format_selector) === 1, 'A single text format selector exists on the page and has a "data-editor-for" attribute with the correct value.');
122 $this->assertTrue(in_array('ckeditor/drupal.ckeditor', explode(',', $settings['ajaxPageState']['libraries'])), 'CKEditor glue library is present.');
124 // Enable the ckeditor_test module, customize configuration. In this case,
125 // there is additional CSS and JS to be loaded.
126 // NOTE: the tests in CKEditorTest already ensure that changing the
127 // configuration also results in modified CKEditor configuration, so we
128 // don't test that here.
129 \Drupal::service('module_installer')->install(['ckeditor_test']);
130 $this->container->get('plugin.manager.ckeditor.plugin')->clearCachedDefinitions();
131 $editor_settings = $editor->getSettings();
132 $editor_settings['toolbar']['rows'][0][0]['items'][] = 'Llama';
133 $editor->setSettings($editor_settings);
135 $this->drupalGet('node/add/article');
136 list($settings, $editor_settings_present, $editor_js_present, $body, $format_selector) = $this->getThingsToCheck();
140 'format' => 'filtered_html',
141 'editor' => 'ckeditor',
142 'editorSettings' => $this->castSafeStrings($ckeditor_plugin->getJSSettings($editor)),
143 'editorSupportsContentFiltering' => TRUE,
144 'isXssSafe' => FALSE,
148 $this->assertTrue($editor_settings_present, "Text Editor module's JavaScript settings are on the page.");
149 $this->assertIdentical($expected, $this->castSafeStrings($settings['editor']), "Text Editor module's JavaScript settings on the page are correct.");
150 $this->assertTrue($editor_js_present, 'Text Editor JavaScript is present.');
151 $this->assertTrue(in_array('ckeditor/drupal.ckeditor', explode(',', $settings['ajaxPageState']['libraries'])), 'CKEditor glue library is present.');
153 // Assert that CKEditor uses Drupal's cache-busting query string by
154 // comparing the setting sent with the page with the current query string.
155 $settings = $this->getDrupalSettings();
156 $expected = $settings['ckeditor']['timestamp'];
157 $this->assertIdentical($expected, \Drupal::state()->get('system.css_js_query_string'), "CKEditor scripts cache-busting string is correct before flushing all caches.");
158 // Flush all caches then make sure that $settings['ckeditor']['timestamp']
160 drupal_flush_all_caches();
161 $this->assertIdentical($expected, \Drupal::state()->get('system.css_js_query_string'), "CKEditor scripts cache-busting string is correct after flushing all caches.");
165 * Tests presence of essential configuration even without Internal's buttons.
167 public function testLoadingWithoutInternalButtons() {
168 // Change the CKEditor text editor configuration to only have link buttons.
170 // - 0 buttons are from \Drupal\ckeditor\Plugin\CKEditorPlugin\Internal
171 // - 2 buttons are from \Drupal\ckeditor\Plugin\CKEditorPlugin\DrupalLink
172 $filtered_html_editor = Editor::load('filtered_html');
173 $settings = $filtered_html_editor->getSettings();
174 $settings['toolbar']['rows'] = [
185 $filtered_html_editor->setSettings($settings)->save();
187 // Even when no buttons of \Drupal\ckeditor\Plugin\CKEditorPlugin\Internal
188 // are in use, its configuration (Internal::getConfig()) is still essential:
189 // this is configuration that is associated with the (custom, optimized)
190 // build of CKEditor that Drupal core ships with. For example, it configures
191 // CKEditor to not perform its default action of loading a config.js file,
192 // to not convert special characters into HTML entities, and the allowedContent
193 // setting to configure CKEditor's Advanced Content Filter.
194 $this->drupalLogin($this->normalUser);
195 $this->drupalGet('node/add/article');
196 $editor_settings = $this->getDrupalSettings()['editor']['formats']['filtered_html']['editorSettings'];
197 $this->assertTrue(isset($editor_settings['customConfig']));
198 $this->assertTrue(isset($editor_settings['entities']));
199 $this->assertTrue(isset($editor_settings['allowedContent']));
200 $this->assertTrue(isset($editor_settings['disallowedContent']));
204 * Tests loading of theme's CKEditor stylesheets defined in the .info file.
206 public function testExternalStylesheets() {
207 $theme_handler = \Drupal::service('theme_handler');
208 // Case 1: Install theme which has an absolute external CSS URL.
209 $theme_handler->install(['test_ckeditor_stylesheets_external']);
210 $this->config('system.theme')->set('default', 'test_ckeditor_stylesheets_external')->save();
212 'https://fonts.googleapis.com/css?family=Open+Sans',
214 $this->assertIdentical($expected, _ckeditor_theme_css('test_ckeditor_stylesheets_external'));
216 // Case 2: Install theme which has an external protocol-relative CSS URL.
217 $theme_handler->install(['test_ckeditor_stylesheets_protocol_relative']);
218 $this->config('system.theme')->set('default', 'test_ckeditor_stylesheets_protocol_relative')->save();
220 '//fonts.googleapis.com/css?family=Open+Sans',
222 $this->assertIdentical($expected, _ckeditor_theme_css('test_ckeditor_stylesheets_protocol_relative'));
224 // Case 3: Install theme which has a relative CSS URL.
225 $theme_handler->install(['test_ckeditor_stylesheets_relative']);
226 $this->config('system.theme')->set('default', 'test_ckeditor_stylesheets_relative')->save();
228 'core/modules/system/tests/themes/test_ckeditor_stylesheets_relative/css/yokotsoko.css',
230 $this->assertIdentical($expected, _ckeditor_theme_css('test_ckeditor_stylesheets_relative'));
233 protected function getThingsToCheck() {
234 $settings = $this->getDrupalSettings();
236 // JavaScript settings.
238 // Editor.module's JS settings present.
239 isset($settings['editor']),
240 // Editor.module's JS present. Note: ckeditor/drupal.ckeditor depends on
241 // editor/drupal.editor, hence presence of the former implies presence of
243 isset($settings['ajaxPageState']['libraries']) && in_array('ckeditor/drupal.ckeditor', explode(',', $settings['ajaxPageState']['libraries'])),
245 $this->xpath('//textarea[@id="edit-body-0-value"]'),
247 $this->xpath('//select[contains(@class, "filter-list")]'),