core/modules/ckeditor/ckeditor.admin.inc | 146 ++++++++++
core/modules/ckeditor/ckeditor.api.php | 60 ++++
core/modules/ckeditor/ckeditor.info | 6 +
core/modules/ckeditor/ckeditor.module | 125 +++++++++
core/modules/ckeditor/css/ckeditor-iframe.css | 18 ++
core/modules/ckeditor/css/ckeditor-rtl.css | 18 ++
core/modules/ckeditor/css/ckeditor.admin.css | 173 ++++++++++++
core/modules/ckeditor/css/ckeditor.css | 56 ++++
core/modules/ckeditor/js/ckeditor.admin.js | 111 ++++++++
core/modules/ckeditor/js/ckeditor.js | 32 +++
.../ckeditor/js/ckeditor.stylescombo.admin.js | 52 ++++
.../lib/Drupal/ckeditor/CKEditorPluginBase.php | 42 +++
.../ckeditor/CKEditorPluginButtonsInterface.php | 56 ++++
.../CKEditorPluginConfigurableInterface.php | 45 +++
.../ckeditor/CKEditorPluginContextualInterface.php | 39 +++
.../Drupal/ckeditor/CKEditorPluginInterface.php | 69 +++++
.../lib/Drupal/ckeditor/CKEditorPluginManager.php | 158 +++++++++++
.../lib/Drupal/ckeditor/CkeditorBundle.php | 26 ++
.../ckeditor/Plugin/ckeditor/plugin/Internal.php | 291 ++++++++++++++++++++
.../Plugin/ckeditor/plugin/StylesCombo.php | 159 +++++++++++
.../ckeditor/Plugin/editor/editor/CKEditor.php | 200 ++++++++++++++
.../Drupal/ckeditor/Tests/CKEditorAdminTest.php | 142 ++++++++++
.../Drupal/ckeditor/Tests/CKEditorLoadingTest.php | 149 ++++++++++
.../ckeditor/Tests/CKEditorPluginManagerTest.php | 123 +++++++++
.../lib/Drupal/ckeditor/Tests/CKEditorTest.php | 261 ++++++++++++++++++
.../ckeditor/tests/modules/ckeditor_test.info | 6 +
.../ckeditor/tests/modules/ckeditor_test.module | 15 +
.../ckeditor_test/Plugin/ckeditor/plugin/Llama.php | 57 ++++
.../Plugin/ckeditor/plugin/LlamaButton.php | 44 +++
.../Plugin/ckeditor/plugin/LlamaContextual.php | 47 ++++
.../ckeditor/plugin/LlamaContextualAndButton.php | 80 ++++++
core/modules/editor/editor.module | 41 +--
.../lib/Drupal/editor/Plugin/EditorInterface.php | 4 +-
.../lib/Drupal/editor/Tests/EditorAdminTest.php | 26 +-
34 files changed, 2844 insertions(+), 33 deletions(-)
diff --git a/core/modules/ckeditor/ckeditor.admin.inc b/core/modules/ckeditor/ckeditor.admin.inc
new file mode 100644
index 0000000..c5b459b
--- /dev/null
+++ b/core/modules/ckeditor/ckeditor.admin.inc
@@ -0,0 +1,146 @@
+direction ? 'rtl' : 'ltr';
+
+ // Create lists of active and disabled buttons.
+ $editor = $variables['editor'];
+ $plugins = $variables['plugins'];
+ $buttons = array();
+ $variables['multiple_buttons'] = array();
+ foreach ($plugins as $plugin => $plugin_buttons) {
+ foreach ($plugin_buttons as $button_name => $button) {
+ $button['name'] = $button_name;
+ if (!empty($button['multiple'])) {
+ $variables['multiple_buttons'][$button_name] = $button;
+ }
+ $buttons[$button_name] = $button;
+ }
+ }
+ $variables['active_buttons'] = array();
+ foreach ($editor->settings['toolbar']['buttons'] as $row_number => $row) {
+ foreach ($row as $button_name) {
+ if (isset($buttons[$button_name])) {
+ $variables['active_buttons'][$row_number][] = $buttons[$button_name];
+ if (empty($buttons[$button_name]['multiple'])) {
+ unset($buttons[$button_name]);
+ }
+ }
+ }
+ }
+ $variables['disabled_buttons'] = array_diff_key($buttons, $variables['multiple_buttons']);
+}
+
+/**
+ * Displays the toolbar configuration for CKEditor.
+ */
+function theme_ckeditor_settings_toolbar($variables) {
+ $editor = $variables['editor'];
+ $plugins = $variables['plugins'];
+ $rtl = $variables['language_direction'] === 'rtl' ? '_rtl' : '';
+
+ $build_button_item = function($button, $rtl) {
+ // Value of the button item.
+ if (isset($button['image_alternative'])) {
+ $value = $button['image_alternative' . $rtl];
+ }
+ elseif (isset($button['image'])) {
+ $value = theme('image', array('uri' => $button['image' . $rtl], 'title' => $button['label']));
+ }
+ else {
+ $value = '?';
+ }
+
+ // Set additional attribute on the button if it can occur multiple times.
+ if (!empty($button['multiple'])) {
+ $button['attributes']['class'][] = 'ckeditor-multiple-button';
+ }
+
+ // Build the button item.
+ $button_item = array(
+ 'value' => $value,
+ 'data-button-name' => $button['name'],
+ );
+ if (!empty($button['attributes'])) {
+ $button_item = array_merge($button_item, $button['attributes']);
+ }
+
+ return $button_item;
+ };
+
+ // Assemble items to be added to active button rows.
+ $active_buttons = array();
+ foreach ($variables['active_buttons'] as $row_number => $row_buttons) {
+ foreach ($row_buttons as $button) {
+ $active_buttons[$row_number][] = $build_button_item($button, $rtl);
+ }
+ }
+ // Assemble list of disabled buttons (which are always a single row).
+ $disabled_buttons = array();
+ foreach ($variables['disabled_buttons'] as $button) {
+ $disabled_buttons[] = $build_button_item($button, $rtl);
+ }
+ // Assemble list of multiple buttons that may be added multiple times.
+ $multiple_buttons = array();
+ foreach ($variables['multiple_buttons'] as $button_name => $button) {
+ $multiple_buttons[] = $build_button_item($button, $rtl);
+ }
+
+ $print_buttons = function($buttons) {
+ $output = '';
+ foreach ($buttons as $button) {
+ $value = $button['value'];
+ unset($button['value']);
+ $attributes = (string) new Attribute($button);
+ $output .= '
' . $value . ' ';
+ }
+ return $output;
+ };
+
+ // We don't use theme_item_list() below in case there are no buttons in the
+ // active or disabled list, as theme_item_list() will not print an empty UL.
+ $output = '';
+ $output .= '' . t('Active toolbar') . ' ';
+ $output .= '';
+
+ $output .= '' . t('Available buttons') . ' ';
+ $output .= '';
+
+ return $output;
+}
diff --git a/core/modules/ckeditor/ckeditor.api.php b/core/modules/ckeditor/ckeditor.api.php
new file mode 100644
index 0000000..0b4f343
--- /dev/null
+++ b/core/modules/ckeditor/ckeditor.api.php
@@ -0,0 +1,60 @@
+ array(
+ 'modulePath' => drupal_get_path('module', 'ckeditor'),
+ ),
+ );
+ $libraries['drupal.ckeditor'] = array(
+ 'title' => 'Drupal behavior to enable CKEditor on textareas.',
+ 'version' => VERSION,
+ 'js' => array(
+ $module_path . '/js/ckeditor.js' => array(),
+ array('data' => $settings, 'type' => 'setting'),
+ ),
+ 'dependencies' => array(
+ array('editor', 'drupal.editor'),
+ array('ckeditor', 'ckeditor'),
+ ),
+ );
+ $libraries['drupal.ckeditor.css'] = array(
+ 'title' => 'Formatting CSS for common classes used in CKEditor.',
+ 'version' => VERSION,
+ 'css' => array(
+ $module_path . '/css/ckeditor.css' => array(),
+ ),
+ );
+ $libraries['drupal.ckeditor.admin'] = array(
+ 'title' => 'Drupal behavior for drag-and-drop CKEditor toolbar builder UI.',
+ 'version' => VERSION,
+ 'js' => array(
+ $module_path . '/js/ckeditor.admin.js' => array(),
+ ),
+ 'css' => array(
+ $module_path . '/css/ckeditor.admin.css' => array(),
+ 'core/misc/ckeditor/skins/moono/editor.css' => array(),
+ ),
+ 'dependencies' => array(
+ array('system', 'jquery.once'),
+ array('system', 'jquery.ui.sortable'),
+ array('system', 'jquery.ui.draggable'),
+ ),
+ );
+ $libraries['drupal.ckeditor.stylescombo.admin'] = array(
+ 'title' => 'Only show the "stylescombo" plugin settings when its button is enabled.',
+ 'version' => VERSION,
+ 'js' => array(
+ $module_path . '/js/ckeditor.stylescombo.admin.js' => array(),
+ ),
+ 'dependencies' => array(
+ array('system', 'jquery.once'),
+ array('system', 'drupal.vertical-tabs'),
+ ),
+ );
+ $libraries['ckeditor'] = array(
+ 'title' => 'Loads the main CKEditor library.',
+ 'version' => '4.0.1',
+ 'js' => array(
+ 'core/misc/ckeditor/ckeditor.js' => array(),
+ ),
+ );
+
+ return $libraries;
+}
+
+/**
+ * Implements hook_theme().
+ */
+function ckeditor_theme() {
+ return array(
+ 'ckeditor_settings_toolbar' => array(
+ 'file' => 'ckeditor.admin.inc',
+ 'variables' => array('editor' => NULL, 'plugins' => NULL),
+ ),
+ );
+}
+
+/**
+ * Implements hook_page_build().
+ */
+function ckeditor_page_build(&$page) {
+ // Add our CSS file that adds common needed classes, such as align-left,
+ // align-right, underline, indent, etc.
+ $page['#attached']['library'][] = array('ckeditor', 'drupal.ckeditor.css');
+}
+
+/**
+ * Retrieves the default theme's CKEditor stylesheets defined in the .info file.
+ *
+ * Themes may specify iframe-specific CSS files for use with CKEditor by
+ * including a "ckeditor_stylesheets" key in the theme .info file.
+ *
+ * @code
+ * ckeditor_stylesheets[] = css/ckeditor-iframe.css
+ * @endcode
+ */
+function _ckeditor_theme_css($theme = NULL) {
+ $css = array();
+ if (!isset($theme)) {
+ $theme = variable_get('theme_default');
+ }
+ if ($theme_path = drupal_get_path('theme', $theme)) {
+ $info = system_get_info('theme', $theme);
+ if (isset($info['ckeditor_stylesheets'])) {
+ $css = $info['ckeditor_stylesheets'];
+ foreach ($css as $key => $path) {
+ $css[$key] = $theme_path . '/' . $path;
+ }
+ }
+ if (isset($info['base theme'])) {
+ $css = array_merge(_ckeditor_theme_css($info['base theme'], $css));
+ }
+ }
+ return $css;
+}
diff --git a/core/modules/ckeditor/css/ckeditor-iframe.css b/core/modules/ckeditor/css/ckeditor-iframe.css
new file mode 100644
index 0000000..54f4b3f
--- /dev/null
+++ b/core/modules/ckeditor/css/ckeditor-iframe.css
@@ -0,0 +1,18 @@
+/**
+ * CSS added to iframe-based instances only.
+ */
+body {
+ font-family: Arial, Verdana, sans-serif;
+ font-size: 12px;
+ color: #222;
+ background-color: #fff;
+ margin: 8px;
+}
+
+ol, ul, dl {
+ /* IE7: reset rtl list margin. (CKEditor issue #7334) */
+ *margin-right: 0px;
+ /* Preserved spaces for list items with text direction other than the list.
+ * (CKEditor issues #6249,#8049) */
+ padding: 0 40px;
+}
diff --git a/core/modules/ckeditor/css/ckeditor-rtl.css b/core/modules/ckeditor/css/ckeditor-rtl.css
new file mode 100644
index 0000000..59f97b8
--- /dev/null
+++ b/core/modules/ckeditor/css/ckeditor-rtl.css
@@ -0,0 +1,18 @@
+/**
+ * RTL styles used by CKEditor. Added to front-end theme and iframe editors.
+ */
+.align-left {
+ text-align: right;
+}
+.align-right {
+ text-align: left;
+}
+.indent1 {
+ margin: 0 40px 0 0;
+}
+.indent2 {
+ margin: 0 80px 0 0;
+}
+.indent3 {
+ margin: 0 120px 0 0;
+}
diff --git a/core/modules/ckeditor/css/ckeditor.admin.css b/core/modules/ckeditor/css/ckeditor.admin.css
new file mode 100644
index 0000000..62f30b0
--- /dev/null
+++ b/core/modules/ckeditor/css/ckeditor.admin.css
@@ -0,0 +1,173 @@
+/**
+ * @file
+ * Styles for configuration of CKEditor module.
+ *
+ * Many of these styles are adapted directly from the default CKEditor theme
+ * "moono".
+ */
+
+.ckeditor-toolbar-active {
+ border: 1px solid #b6b6b6;
+ padding: 6px 8px 2px;
+ box-shadow: 0 1px 0 white inset;
+ background: #cfd1cf;
+ background-image: -webkit-gradient(linear, left top, left bottom, from(whiteSmoke), to(#cfd1cf));
+ background-image: -moz-linear-gradient(top, whiteSmoke, #cfd1cf);
+ background-image: -o-linear-gradient(top, whiteSmoke, #cfd1cf);
+ background-image: -ms-linear-gradient(top, whiteSmoke, #cfd1cf);
+ background-image: linear-gradient(top, whiteSmoke, #cfd1cf);
+ filter: progid:DXImageTransform.Microsoft.gradient(gradientType=0, startColorstr='#fff5f5f5', endColorstr='#ffcfd1cf');
+ margin: 5px 0;
+ overflow: nowrap;
+}
+.ckeditor-toolbar-disabled ul.ckeditor-buttons {
+ border: 0;
+}
+
+.ckeditor-toolbar-disabled ul.ckeditor-buttons li {
+ margin: 5px;
+}
+
+.ckeditor-toolbar-disabled ul.ckeditor-buttons li,
+ul.ckeditor-buttons {
+ border: 1px solid #a6a6a6;
+ border-bottom-color: #979797;
+ border-radius: 3px;
+ box-shadow: 0 1px 0 rgba(255, 255, 255, .5), 0 0 2px rgba(255, 255, 255, .15) inset, 0 1px 0 rgba(255, 255, 255, .15) inset;
+}
+
+ul.ckeditor-buttons {
+ min-height: 26px;
+ min-width: 26px;
+ list-style: none;
+
+ float: left;
+ clear: left;
+ padding: 0;
+ margin: 0 6px 5px 0;
+ border: 1px solid #a6a6a6;
+ border-bottom-color: #979797;
+ border-radius: 3px;
+ box-shadow: 0 1px 0 rgba(255, 255, 255, .5), 0 0 2px rgba(255, 255, 255, .15) inset, 0 1px 0 rgba(255, 255, 255, .15) inset;
+}
+ul.ckeditor-buttons li {
+ display: inline-block;
+ height: 18px;
+ padding: 4px 6px;
+ outline: none;
+ cursor: move;
+ float: left;
+ border: 0;
+ white-space: nowrap;
+
+ background: #e4e4e4;
+ background-image: -webkit-gradient(linear,left top,left bottom,from(white),to(#e4e4e4));
+ background-image: -moz-linear-gradient(top,white,#e4e4e4);
+ background-image: -webkit-linear-gradient(top,white,#e4e4e4);
+ background-image: -o-linear-gradient(top,white,#e4e4e4);
+ background-image: -ms-linear-gradient(top,white,#e4e4e4);
+ background-image: linear-gradient(top,white,#e4e4e4);
+ filter: progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#ffffffff',endColorstr='#ffe4e4e4');
+}
+ul.ckeditor-buttons li:first-child {
+ border-top-left-radius: 2px;
+ border-bottom-left-radius: 2px;
+}
+ul.ckeditor-buttons li:last-child {
+ border-top-right-radius: 2px;
+ border-bottom-right-radius: 2px;
+}
+ul.ckeditor-buttons li.ckeditor-button-placeholder {
+ background: #333;
+ opacity: 0.3;
+}
+ul.ckeditor-multiple-buttons {
+ padding: 1px 2px;
+ margin: 5px;
+ list-style: none;
+ float: left;
+}
+ul.ckeditor-multiple-buttons li {
+ padding: 2px 0;
+ margin: 0;
+ display: inline-block;
+ height: 18px;
+ cursor: move;
+ float: left;
+}
+.ckeditor-multiple-label {
+ float: left;
+ padding: 10px 4px;
+}
+ul.ckeditor-buttons li.ckeditor-group-button-separator,
+ul.ckeditor-multiple-buttons li.ckeditor-group-button-separator {
+ background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAA0AAAAdCAMAAABG4xbVAAAAhFBMVEUAAACmpqampqampqb////l5eX////5+fmmpqatra2urq6vr6+1tbW2tra4uLi6urq8vLzb29ve3t7i4uLl5eXn5+fo6Ojp6enq6urr6+vs7Ozt7e3u7u7v7+/w8PDx8fHy8vLz8/P09PT19fX29vb39/f4+Pj5+fn6+vr7+/v8/Pz+/v7qIQO+AAAACHRSTlMATVmAi8XM29MuWToAAABjSURBVBiVrc5BCoAwDETRMKhtRBduev9LKm1xjItWRBBE6Nt9QkIwOTcUzk0Imi8aoMssxbgoTHMtqsFMLta0vPh2N49HyfdelPg6k9uvX/a+Bmggt1qJRNzQFVgjEnkUZDoBmH57VSypjg4AAAAASUVORK5CYII=) no-repeat center center;
+ width: 13px;
+ padding: 0;
+ height: 29px;
+ margin: -1px -3px -2px;
+ position: relative;
+ z-index: 10;
+}
+ul.ckeditor-buttons li.ckeditor-button-separator {
+ width: 2px;
+ padding: 0 4px;
+ height: 26px;
+ margin: 0 -4px;
+ position: relative;
+ z-index: 10;
+
+ background: #e4e4e4;
+ background-image: -webkit-gradient(linear, left top, left bottom, from(white), to(#e4e4e4));
+ background-image: -moz-linear-gradient(top, white, #e4e4e4);
+ background-image: -webkit-linear-gradient(top, white, #e4e4e4);
+ background-image: -o-linear-gradient(top, white, #e4e4e4);
+ background-image: -ms-linear-gradient(top, white, #e4e4e4);
+ background-image: linear-gradient(top, white, #e4e4e4);
+ filter: progid:DXImageTransform.Microsoft.gradient(gradientType=0, startColorstr='#ffffffff', endColorstr='#ffe4e4e4');
+}
+ul.ckeditor-multiple-buttons li.ckeditor-button-separator {
+ width: 2px;
+ padding: 0;
+ height: 26px;
+ margin: 0 10px;
+}
+.ckeditor-separator {
+ background-color: silver;
+ background-color: rgba(0, 0, 0, .2);
+ margin: 5px 0;
+ height: 18px;
+ width: 1px;
+ display: block;
+ -webkit-box-shadow: 1px 0 1px rgba(255, 255, 255, .5);
+ -moz-box-shadow: 1px 0 1px rgba(255,255,255,.5);
+ box-shadow: 1px 0 1px rgba(255, 255, 255, .5)
+}
+.ckeditor-button-arrow {
+ width: 0;
+ text-align: center;
+ border-left: 3px solid transparent;
+ border-right: 3px solid transparent;
+ border-top: 3px solid #333;
+ display: inline-block;
+ margin: 0 4px 2px;
+}
+
+.ckeditor-row-controls {
+ float: right;
+ font-size: 18px;
+ width: 40px;
+ text-align: right;
+}
+.ckeditor-row-controls a {
+ display: inline-block;
+ padding: 6px 2px;
+ height: 16px;
+ width: 16px;
+ line-height: 0.9;
+ font-weight: bold;
+ color: #333;
+}
+.ckeditor-row-controls a:hover {
+ text-decoration: none;
+}
diff --git a/core/modules/ckeditor/css/ckeditor.css b/core/modules/ckeditor/css/ckeditor.css
new file mode 100644
index 0000000..d5c6efe
--- /dev/null
+++ b/core/modules/ckeditor/css/ckeditor.css
@@ -0,0 +1,56 @@
+/**
+ * Common styles used by CKEditor. Added to front-end theme and iframe editors.
+ */
+.align-left {
+ text-align: left; /* RTL */
+}
+.align-right {
+ text-align: right; /* RTL */
+}
+.align-center {
+ text-align: center;
+}
+.align-justify {
+ text-align: justify;
+}
+img.align-left {
+ float: left; /* RTL */
+}
+img.align-right {
+ float: right; /* RTL */
+}
+img.align-center {
+ margin-left: auto;
+ margin-right: auto;
+ display: block;
+}
+img.full-width {
+ width: 100%;
+ height: auto;
+}
+.underline {
+ text-decoration: underline;
+}
+.indent1 {
+ margin: 0 0 0 40px; /* RTL */
+}
+.indent2 {
+ margin: 0 0 0 80px; /* RTL */
+}
+.indent3 {
+ margin: 0 0 0 120px; /* RTL */
+}
+.caption-left {
+ float: left; /* RTL */
+}
+.caption-right {
+ float: right; /* RTL */
+}
+.caption-center {
+ text-align: center;
+ margin-left: auto;
+ margin-right: auto;
+}
+.align-justify {
+ text-align: justify;
+}
diff --git a/core/modules/ckeditor/js/ckeditor.admin.js b/core/modules/ckeditor/js/ckeditor.admin.js
new file mode 100644
index 0000000..18c070f
--- /dev/null
+++ b/core/modules/ckeditor/js/ckeditor.admin.js
@@ -0,0 +1,111 @@
+(function ($, Drupal) {
+
+"use strict";
+
+Drupal.ckeditor = Drupal.ckeditor || {};
+
+Drupal.behaviors.ckeditorAdmin = {
+ attach: function (context, settings) {
+ var $context = $(context);
+ $(context).find('.ckeditor-toolbar-configuration').once('ckeditor-toolbar', function() {
+ var $wrapper = $(this);
+ var $textareaWrapper = $(this).find('.form-item-editor-settings-toolbar-buttons').hide();
+ var $textarea = $textareaWrapper.find('textarea');
+ var $toolbarAdmin = $(settings.ckeditor.toolbarAdmin);
+ var sortableSettings = {
+ connectWith: '.ckeditor-buttons',
+ placeholder: 'ckeditor-button-placeholder',
+ forcePlaceholderSize: true,
+ tolerance: 'pointer',
+ cursor: 'move',
+ stop: adminToolbarValue
+ };
+ $toolbarAdmin.insertAfter($textareaWrapper).find('.ckeditor-buttons').sortable(sortableSettings);
+ $toolbarAdmin.find('.ckeditor-multiple-buttons li').draggable({
+ connectToSortable: '.ckeditor-toolbar-active .ckeditor-buttons',
+ helper: 'clone'
+ });
+ $toolbarAdmin.on('click.ckeditorAddRow', 'a.ckeditor-row-add', adminToolbarAddRow);
+ $toolbarAdmin.on('click.ckeditorAddRow', 'a.ckeditor-row-remove', adminToolbarRemoveRow);
+ if ($toolbarAdmin.find('.ckeditor-toolbar-active ul').length > 1) {
+ $toolbarAdmin.find('a.ckeditor-row-remove').hide();
+ }
+
+ /**
+ * Add a new row of buttons.
+ */
+ function adminToolbarAddRow(event) {
+ var $this = $(this);
+ var $rows = $this.closest('.ckeditor-toolbar-active').find('.ckeditor-buttons');
+ $rows.last().clone().empty().insertAfter($rows.last()).sortable(sortableSettings);
+ $this.siblings('a').show();
+ redrawToolbarGradient();
+ event.preventDefault();
+ }
+
+ /**
+ * Remove a row of buttons.
+ */
+ function adminToolbarRemoveRow(event) {
+ var $this = $(this);
+ var $rows = $this.closest('.ckeditor-toolbar-active').find('.ckeditor-buttons');
+ if ($rows.length === 2) {
+ $this.hide();
+ }
+ if ($rows.length > 1) {
+ var $lastRow = $rows.last();
+ var $disabledButtons = $wrapper.find('.ckeditor-toolbar-disabled .ckeditor-buttons');
+ $lastRow.children(':not(.ckeditor-multiple-button)').prependTo($disabledButtons);
+ $lastRow.sortable('destroy').remove();
+ redrawToolbarGradient();
+ }
+ event.preventDefault();
+ }
+
+ /**
+ * Browser quirk work-around to redraw CSS3 gradients.
+ */
+ function redrawToolbarGradient() {
+ $wrapper.find('.ckeditor-toolbar-active').css('position', 'relative');
+ window.setTimeout(function() {
+ $wrapper.find('.ckeditor-toolbar-active').css('position', '');
+ }, 10);
+ }
+
+ /**
+ * jQuery Sortable stop event. Save updated toolbar positions to the
+ * textarea.
+ */
+ function adminToolbarValue(event, ui) {
+ // Update the toolbar config after updating a sortable.
+ var toolbarConfig = [];
+ $wrapper.find('.ckeditor-toolbar-active ul').each(function() {
+ var $rowButtons = $(this).find('li');
+ var rowConfig = [];
+ if ($rowButtons.length) {
+ $rowButtons.each(function() {
+ rowConfig.push(this.getAttribute('data-button-name'));
+ });
+ toolbarConfig.push(rowConfig);
+ }
+ });
+ $textarea.val(JSON.stringify(toolbarConfig, null, ' '));
+
+ // Determine whether we should trigger an event.
+ var from = $(event.target).parents('div[data-toolbar]').attr('data-toolbar');
+ var to = $(event.toElement).parents('div[data-toolbar]').attr('data-toolbar');
+ if (from !== to) {
+ $wrapper
+ .find('.ckeditor-toolbar-active')
+ .trigger('CKEditorToolbarChanged', [
+ (to === 'active') ? 'added' : 'removed',
+ $(ui.item).get(0).getAttribute('data-button-name')
+ ]);
+ }
+ }
+
+ });
+ }
+};
+
+})(jQuery, Drupal);
diff --git a/core/modules/ckeditor/js/ckeditor.js b/core/modules/ckeditor/js/ckeditor.js
new file mode 100644
index 0000000..7cfed59
--- /dev/null
+++ b/core/modules/ckeditor/js/ckeditor.js
@@ -0,0 +1,32 @@
+(function ($, Drupal, drupalSettings, CKEDITOR) {
+
+"use strict";
+
+Drupal.editors.ckeditor = {
+ attach: function (element, format) {
+ // Register and load additional CKEditor plugins as necessary.
+ if (format.editorSettings.externalPlugins) {
+ for (var pluginName in format.editorSettings.drupalExternalPlugins) {
+ if (format.editorSettings.drupalExternalPlugins.hasOwnProperty(pluginName)) {
+ CKEDITOR.plugins.addExternal(pluginName, format.editorSettings.drupalExternalPlugins[pluginName], '');
+ }
+ }
+ delete format.editorSettings.drupalExternalPlugins;
+ }
+ return !!CKEDITOR.replace(element, format.editorSettings);
+ },
+ detach: function (element, format, trigger) {
+ var editor = CKEDITOR.dom.element.get(element).getEditor();
+ if (editor) {
+ if (trigger === 'serialize') {
+ editor.updateElement();
+ }
+ else {
+ editor.destroy();
+ }
+ }
+ return !!editor;
+ }
+};
+
+})(jQuery, Drupal, drupalSettings, CKEDITOR);
diff --git a/core/modules/ckeditor/js/ckeditor.stylescombo.admin.js b/core/modules/ckeditor/js/ckeditor.stylescombo.admin.js
new file mode 100644
index 0000000..707bf4b
--- /dev/null
+++ b/core/modules/ckeditor/js/ckeditor.stylescombo.admin.js
@@ -0,0 +1,52 @@
+(function ($, Drupal) {
+
+"use strict";
+
+/**
+ * Shows the "stylescombo" plugin settings only when the button is enabled.
+ */
+Drupal.behaviors.ckeditorStylesComboSettingsVisibility = {
+ attach: function (context, settings) {
+ var $stylesComboVerticalTab = $('#edit-editor-settings-plugins-stylescombo').data('verticalTab');
+
+ // Hide if the "Styles" button is disabled.
+ if ($('.ckeditor-toolbar-disabled li[data-button-name="Styles"]').length === 1) {
+ $stylesComboVerticalTab.tabHide();
+ }
+
+ // React to added/removed toolbar buttons.
+ $(context)
+ .find('.ckeditor-toolbar-active')
+ .on('CKEditorToolbarChanged', function (e, action, button) {
+ if (button === 'Styles') {
+ if (action === 'added') {
+ $stylesComboVerticalTab.tabShow();
+ }
+ else {
+ $stylesComboVerticalTab.tabHide();
+ }
+ }
+ });
+ }
+};
+
+/**
+ * Provides the summary for the "stylescombo" plugin settings vertical tab.
+ */
+Drupal.behaviors.ckeditorStylesComboSettingsSummary = {
+ attach: function (context, settings) {
+ $('#edit-editor-settings-plugins-stylescombo')
+ .drupalSetSummary(function (context) {
+ var styles = $.trim($('#edit-editor-settings-plugins-stylescombo-styles').val());
+ if (styles.length === 0) {
+ return Drupal.t('No styles configured');
+ }
+ else {
+ var count = $.trim(styles).split("\n").length;
+ return Drupal.t('@count styles configured', { '@count': count});
+ }
+ });
+ }
+};
+
+})(jQuery, Drupal);
diff --git a/core/modules/ckeditor/lib/Drupal/ckeditor/CKEditorPluginBase.php b/core/modules/ckeditor/lib/Drupal/ckeditor/CKEditorPluginBase.php
new file mode 100644
index 0000000..4050be1
--- /dev/null
+++ b/core/modules/ckeditor/lib/Drupal/ckeditor/CKEditorPluginBase.php
@@ -0,0 +1,42 @@
+settings.
+ *
+ * @param \Drupal\editor\Plugin\Core\Entity\Editor $editor
+ * A configured text editor object.
+ *
+ * @return bool
+ */
+ public function isEnabled(Editor $editor);
+
+}
diff --git a/core/modules/ckeditor/lib/Drupal/ckeditor/CKEditorPluginInterface.php b/core/modules/ckeditor/lib/Drupal/ckeditor/CKEditorPluginInterface.php
new file mode 100644
index 0000000..601cafb
--- /dev/null
+++ b/core/modules/ckeditor/lib/Drupal/ckeditor/CKEditorPluginInterface.php
@@ -0,0 +1,69 @@
+settings, but be aware that
+ * it may not yet contain plugin-specific settings, because the user may not
+ * yet have configured the form.
+ * If there are plugin-specific settings (verify with isset()), they can be
+ * found at $editor->settings['plugins'][$plugin_id].
+ *
+ * @param \Drupal\editor\Plugin\Core\Entity\Editor $editor
+ * A configured text editor object.
+ * @return array
+ * A keyed array, whose keys will end up as keys under CKEDITOR.config.
+ */
+ public function getConfig(Editor $editor);
+}
diff --git a/core/modules/ckeditor/lib/Drupal/ckeditor/CKEditorPluginManager.php b/core/modules/ckeditor/lib/Drupal/ckeditor/CKEditorPluginManager.php
new file mode 100644
index 0000000..57a3a4b
--- /dev/null
+++ b/core/modules/ckeditor/lib/Drupal/ckeditor/CKEditorPluginManager.php
@@ -0,0 +1,158 @@
+discovery = new AnnotatedClassDiscovery('ckeditor', 'plugin');
+ $this->discovery = new ProcessDecorator($this->discovery, array($this, 'processDefinition'));
+ $this->discovery = new AlterDecorator($this->discovery, 'ckeditor_plugin_info');
+ $this->discovery = new CacheDecorator($this->discovery, 'ckeditor_plugin');
+ $this->factory = new DefaultFactory($this->discovery);
+ }
+
+ /**
+ * Determines which plug-ins are enabled.
+ *
+ * For CKEditor plugins that implement:
+ * - CKEditorPluginButtonsInterface, not CKEditorPluginContextualInterface,
+ * a plugin is enabled if at least one of its buttons is in the toolbar;
+ * - CKEditorPluginContextualInterface, not CKEditorPluginButtonsInterface,
+ * a plugin is enabled if its isEnabled() method returns TRUE
+ * - both of these interfaces, a plugin is enabled if either is the case.
+ *
+ * Internal plugins (those that are part of the bundled build of CKEditor) are
+ * excluded by default, since they are loaded implicitly. If you need to know
+ * even implicitly loaded (i.e. internal) plugins, then set the optional
+ * second parameter.
+ *
+ * @param \Drupal\editor\Plugin\Core\Entity\Editor $editor
+ * A configured text editor object.
+ * @param bool $include_internal_plugins
+ * Defaults to FALSE. When set to TRUE, plugins whose isInternal() method
+ * returns TRUE will also be included.
+ * @return array
+ * A list of the enabled CKEditor plugins, with the plugin IDs as keys and
+ * the Drupal root-relative plugin files as values.
+ * For internal plugins, the value is NULL.
+ */
+ public function getEnabledPlugins(Editor $editor, $include_internal_plugins = FALSE) {
+ $plugins = array_keys($this->getDefinitions());
+ $toolbar_buttons = array_unique(NestedArray::mergeDeepArray($editor->settings['toolbar']['buttons']));
+ $enabled_plugins = array();
+
+ foreach ($plugins as $plugin_id) {
+ $plugin = $this->createInstance($plugin_id);
+
+ if (!$include_internal_plugins && $plugin->isInternal()) {
+ continue;
+ }
+
+ $enabled = FALSE;
+ if ($plugin instanceof CKEditorPluginButtonsInterface) {
+ $plugin_buttons = array_keys($plugin->getButtons());
+ $enabled = (count(array_intersect($toolbar_buttons, $plugin_buttons)) > 0);
+ }
+ if (!$enabled && $plugin instanceof CKEditorPluginContextualInterface) {
+ $enabled = $plugin->isEnabled($editor);
+ }
+
+ if ($enabled) {
+ $enabled_plugins[$plugin_id] = ($plugin->isInternal()) ? NULL : $plugin->getFile();
+ }
+ }
+
+ // Always return plugins in the same order.
+ asort($enabled_plugins);
+
+ return $enabled_plugins;
+ }
+
+ /**
+ * Retrieves all plugins that implement CKEditorPluginButtonsInterface.
+ *
+ * @param \Drupal\editor\Plugin\Core\Entity\Editor $editor
+ * A configured text editor object.
+ * @return array
+ * A list of the CKEditor plugins that implement buttons, with the plugin
+ * IDs as keys and lists of button metadata (as implemented by getButtons())
+ * as values.
+ *
+ * @see CKEditorPluginButtonsInterface::getButtons()
+ */
+ public function getButtonsPlugins(Editor $editor) {
+ $plugins = array_keys($this->getDefinitions());
+ $buttons_plugins = array();
+
+ foreach ($plugins as $plugin_id) {
+ $plugin = $this->createInstance($plugin_id);
+ if ($plugin instanceof CKEditorPluginButtonsInterface) {
+ $buttons_plugins[$plugin_id] = $plugin->getButtons();
+ }
+ }
+
+ return $buttons_plugins;
+ }
+
+ /**
+ * Injects the CKEditor plugins settings forms as a vertical tabs subform.
+ *
+ * @param array &$form
+ * A reference to an associative array containing the structure of the form.
+ * @param array &$form_state
+ * A reference to a keyed array containing the current state of the form.
+ * @param \Drupal\editor\Plugin\Core\Entity\Editor $editor
+ * A configured text editor object.
+ */
+ public function injectPluginSettingsForm(array &$form, array &$form_state, Editor $editor) {
+ $definitions = $this->getDefinitions();
+
+ foreach (array_keys($definitions) as $plugin_id) {
+ $plugin = $this->createInstance($plugin_id);
+ if ($plugin instanceof CKEditorPluginConfigurableInterface) {
+ $plugin_settings_form = array();
+ $form['plugins'][$plugin_id] = array(
+ '#type' => 'details',
+ '#title' => $definitions[$plugin_id]['label'],
+ '#group' => 'editor][settings][plugin_settings',
+ );
+ $form['plugins'][$plugin_id] += $plugin->settingsForm($plugin_settings_form, $form_state, $editor);
+ }
+ }
+ }
+
+ /**
+ * Overrides Drupal\Component\Plugin\PluginManagerBase::processDefinition().
+ */
+ public function processDefinition(&$definition, $plugin_id) {
+ parent::processDefinition($definition, $plugin_id);
+
+ // @todo Remove this check once http://drupal.org/node/1780396 is resolved.
+ if (!module_exists($definition['module'])) {
+ $definition = NULL;
+ return;
+ }
+ }
+
+}
diff --git a/core/modules/ckeditor/lib/Drupal/ckeditor/CkeditorBundle.php b/core/modules/ckeditor/lib/Drupal/ckeditor/CkeditorBundle.php
new file mode 100644
index 0000000..eb8af12
--- /dev/null
+++ b/core/modules/ckeditor/lib/Drupal/ckeditor/CkeditorBundle.php
@@ -0,0 +1,26 @@
+register('plugin.manager.ckeditor.plugin', 'Drupal\ckeditor\CKEditorPluginManager');
+ }
+
+}
diff --git a/core/modules/ckeditor/lib/Drupal/ckeditor/Plugin/ckeditor/plugin/Internal.php b/core/modules/ckeditor/lib/Drupal/ckeditor/Plugin/ckeditor/plugin/Internal.php
new file mode 100644
index 0000000..0260abf
--- /dev/null
+++ b/core/modules/ckeditor/lib/Drupal/ckeditor/Plugin/ckeditor/plugin/Internal.php
@@ -0,0 +1,291 @@
+ TRUE,
+ 'indentClasses' => array('indent1', 'indent2', 'indent3'),
+ 'justifyClasses' => array('align-left', 'align-center', 'align-right', 'align-justify'),
+ 'coreStyles_underline' => array('element' => 'span', 'attributes' => array('class' => 'underline')),
+ 'removeDialogTabs' => 'image:Link;image:advanced;link:advanced',
+ 'resize_dir' => 'vertical',
+ 'keystrokes' => array(
+ // 0x11000 is CKEDITOR.CTRL, see http://docs.ckeditor.com/#!/api/CKEDITOR-property-CTRL.
+ array(0x110000 + 75, 'link'),
+ array(0x110000 + 76, NULL),
+ ),
+ );
+
+ // Next, add the format_tags setting, if its button is enabled.
+ $toolbar_buttons = array_unique(NestedArray::mergeDeepArray($editor->settings['toolbar']['buttons']));
+ if (in_array('Format', $toolbar_buttons)) {
+ $config['format_tags'] = $this->generateFormatTagsSetting($editor);
+ }
+
+ return $config;
+ }
+
+ /**
+ * Implements \Drupal\ckeditor\Plugin\CKEditorPluginButtonsInterface::getButtons().
+ */
+ public function getButtons() {
+ $button = function($name, $direction = 'ltr') {
+ return ' ';
+ };
+
+ return array(
+ // "basicstyles" plugin.
+ 'Bold' => array(
+ 'label' => t('Bold'),
+ 'image_alternative' => $button('bold'),
+ ),
+ 'Italic' => array(
+ 'label' => t('Italic'),
+ 'image_alternative' => $button('italic'),
+ ),
+ 'Underline' => array(
+ 'label' => t('Underline'),
+ 'image_alternative' => $button('underline'),
+ ),
+ 'Strike' => array(
+ 'label' => t('Strike-through'),
+ 'image_alternative' => $button('strike'),
+ ),
+ 'Superscript' => array(
+ 'label' => t('Superscript'),
+ 'image_alternative' => $button('superscript'),
+ ),
+ 'Subscript' => array(
+ 'label' => t('Subscript'),
+ 'image_alternative' => $button('subscript'),
+ ),
+ // "removeformat" plugin.
+ 'RemoveFormat' => array(
+ 'label' => t('Remove format'),
+ 'image_alternative' => $button('removeformat'),
+ ),
+ // "justify" plugin.
+ 'JustifyLeft' => array(
+ 'label' => t('Align left'),
+ 'image_alternative' => $button('justifyleft'),
+ ),
+ 'JustifyCenter' => array(
+ 'label' => t('Align center'),
+ 'image_alternative' => $button('justifycenter'),
+ ),
+ 'JustifyRight' => array(
+ 'label' => t('Align right'),
+ 'image_alternative' => $button('justifyright'),
+ ),
+ 'JustifyBlock' => array(
+ 'label' => t('Justify'),
+ 'image_alternative' => $button('justifyblock'),
+ ),
+ // "list" plugin.
+ 'BulletedList' => array(
+ 'label' => t('Bullet list'),
+ 'image_alternative' => $button('bulletedlist'),
+ 'image_alternative_rtl' => $button('bulletedlist', 'rtl'),
+ ),
+ 'NumberedList' => array(
+ 'label' => t('Numbered list'),
+ 'image_alternative' => $button('numberedlist'),
+ 'image_alternative_rtl' => $button('numberedlist', 'rtl'),
+ ),
+ // "indent" plugin.
+ 'Outdent' => array(
+ 'label' => t('Outdent'),
+ 'image_alternative' => $button('outdent'),
+ 'image_alternative_rtl' => $button('outdent', 'rtl'),
+ ),
+ 'Indent' => array(
+ 'label' => t('Indent'),
+ 'image_alternative' => $button('indent'),
+ 'image_alternative_rtl' => $button('indent', 'rtl'),
+ ),
+ // "undo" plugin.
+ 'Undo' => array(
+ 'label' => t('Undo'),
+ 'image_alternative' => $button('undo'),
+ 'image_alternative_rtl' => $button('undo', 'rtl'),
+ ),
+ 'Redo' => array(
+ 'label' => t('Redo'),
+ 'image_alternative' => $button('redo'),
+ 'image_alternative_rtl' => $button('redo', 'rtl'),
+ ),
+ // "link" plugin.
+ 'Link' => array(
+ 'label' => t('Link'),
+ 'image_alternative' => $button('link'),
+ ),
+ 'Unlink' => array(
+ 'label' => t('Unlink'),
+ 'image_alternative' => $button('unlink'),
+ ),
+ 'Anchor' => array(
+ 'label' => t('Anchor'),
+ 'image_alternative' => $button('anchor'),
+ 'image_alternative_rtl' => $button('anchor', 'rtl'),
+ ),
+ // "blockquote" plugin.
+ 'Blockquote' => array(
+ 'label' => t('Blockquote'),
+ 'image_alternative' => $button('blockquote'),
+ ),
+ // "horizontalrule" plugin
+ 'HorizontalRule' => array(
+ 'label' => t('Horizontal rule'),
+ 'image_alternative' => $button('horizontalrule'),
+ ),
+ // "clipboard" plugin.
+ 'Cut' => array(
+ 'label' => t('Cut'),
+ 'image_alternative' => $button('cut'),
+ 'image_alternative_rtl' => $button('cut', 'rtl'),
+ ),
+ 'Copy' => array(
+ 'label' => t('Copy'),
+ 'image_alternative' => $button('copy'),
+ 'image_alternative_rtl' => $button('copy', 'rtl'),
+ ),
+ 'Paste' => array(
+ 'label' => t('Paste'),
+ 'image_alternative' => $button('paste'),
+ 'image_alternative_rtl' => $button('paste', 'rtl'),
+ ),
+ // "pastetext" plugin.
+ 'PasteText' => array(
+ 'label' => t('Paste Text'),
+ 'image_alternative' => $button('pastetext'),
+ 'image_alternative_rtl' => $button('pastetext', 'rtl'),
+ ),
+ // "pastefromword" plugin.
+ 'PasteFromWord' => array(
+ 'label' => t('Paste from Word'),
+ 'image_alternative' => $button('pastefromword'),
+ 'image_alternative_rtl' => $button('pastefromword', 'rtl'),
+ ),
+ // "specialchar" plugin.
+ 'SpecialChar' => array(
+ 'label' => t('Character map'),
+ 'image_alternative' => $button('specialchar'),
+ ),
+ 'Format' => array(
+ 'label' => t('HTML block format'),
+ 'image_alternative' => '' . t('Format') . ' ',
+ ),
+ // "image" plugin.
+ 'Image' => array(
+ 'label' => t('Image'),
+ 'image_alternative' => $button('image'),
+ ),
+ // "table" plugin.
+ 'Table' => array(
+ 'label' => t('Table'),
+ 'image_alternative' => $button('table'),
+ ),
+ // "showblocks" plugin.
+ 'ShowBlocks' => array(
+ 'label' => t('Show blocks'),
+ 'image_alternative' => $button('showblocks'),
+ 'image_alternative_rtl' => $button('showblocks', 'rtl'),
+ ),
+ // "sourcearea" plugin.
+ 'Source' => array(
+ 'label' => t('Source code'),
+ 'image_alternative' => $button('source'),
+ ),
+ // "maximize" plugin.
+ 'Maximize' => array(
+ 'label' => t('Maximize'),
+ 'image_alternative' => $button('maximize'),
+ ),
+ // No plugin, separator "buttons" for toolbar builder UI use only.
+ '|' => array(
+ 'label' => t('Group separator'),
+ 'image_alternative' => ' ',
+ 'attributes' => array('class' => array('ckeditor-group-button-separator')),
+ 'multiple' => TRUE,
+ ),
+ '-' => array(
+ 'label' => t('Separator'),
+ 'image_alternative' => ' ',
+ 'attributes' => array('class' => array('ckeditor-button-separator')),
+ 'multiple' => TRUE,
+ ),
+ );
+ }
+
+ /**
+ * Builds the "format_tags" configuration part of the CKEditor JS settings.
+ *
+ * @see getConfig()
+ *
+ * @param \Drupal\editor\Plugin\Core\Entity\Editor $editor
+ * A configured text editor object.
+ * @return array
+ * An array containing the "format_tags" configuration.
+ */
+ protected function generateFormatTagsSetting(Editor $editor) {
+ // The tag is always allowed — HTML without
tags is nonsensical.
+ $format_tags = array('p');
+
+ // Given the list of possible format tags, automatically determine whether
+ // the current text format allows this tag, and thus whether it should show
+ // up in the "Format" dropdown.
+ $possible_format_tags = array('h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'pre');
+ foreach ($possible_format_tags as $tag) {
+ $input = '<' . $tag . '>TEST' . $tag . '>';
+ $output = trim(check_markup($input, $editor->format));
+ if ($input == $output) {
+ $format_tags[] = $tag;
+ }
+ }
+
+ return implode(';', $format_tags);
+ }
+
+}
diff --git a/core/modules/ckeditor/lib/Drupal/ckeditor/Plugin/ckeditor/plugin/StylesCombo.php b/core/modules/ckeditor/lib/Drupal/ckeditor/Plugin/ckeditor/plugin/StylesCombo.php
new file mode 100644
index 0000000..e40e5ca
--- /dev/null
+++ b/core/modules/ckeditor/lib/Drupal/ckeditor/Plugin/ckeditor/plugin/StylesCombo.php
@@ -0,0 +1,159 @@
+settings['toolbar']['buttons']));
+ if (in_array('Styles', $toolbar_buttons)) {
+ $styles = $editor->settings['plugins']['stylescombo']['styles'];
+ $config['stylesSet'] = $this->generateStylesSetSetting($styles);
+ }
+
+ return $config;
+ }
+
+ /**
+ * Implements \Drupal\ckeditor\Plugin\CKEditorPluginButtonsInterface::getButtons().
+ */
+ public function getButtons() {
+ return array(
+ 'Styles' => array(
+ 'label' => t('Font style'),
+ 'image_alternative' => '' . t('Styles') . ' ',
+ ),
+ );
+ }
+
+ /**
+ * Implements \Drupal\ckeditor\Plugin\CKEditorPluginConfigurableInterface::settingsForm().
+ */
+ public function settingsForm(array $form, array &$form_state, Editor $editor) {
+ // Defaults.
+ $config = array('styles' => '');
+ if (isset($editor->settings['plugins']['stylescombo'])) {
+ $config = $editor->settings['plugins']['stylescombo'];
+ }
+
+ $form['styles'] = array(
+ '#title' => t('Styles'),
+ '#title_display' => 'invisible',
+ '#type' => 'textarea',
+ '#default_value' => $config['styles'],
+ '#description' => t('A list of classes that will be provided in the "Styles" dropdown. Enter one class on each line in the format: label|element.class. Example: title|h1.title. These styles should be available in your theme\'s CKEditor stylesheets as well as in your theme\'s main CSS file.'),
+ '#attached' => array(
+ 'library' => array(array('ckeditor', 'drupal.ckeditor.stylescombo.admin')),
+ ),
+ '#element_validate' => array(
+ array($this, 'validateStylesValue'),
+ ),
+ );
+
+ return $form;
+ }
+
+ /**
+ * #element_validate handler for the "styles" element in settingsForm().
+ */
+ public function validateStylesValue(array $element, array &$form_state) {
+ if ($this->generateStylesSetSetting($element['#value']) === FALSE) {
+ form_error($element, t('The provided list of styles is syntactically incorrect.'));
+ }
+ }
+
+ /**
+ * Builds the "stylesSet" configuration part of the CKEditor JS settings.
+ *
+ * @see getConfig()
+ *
+ * @param string $styles
+ * The "styles" setting.
+ * @return array|FALSE
+ * An array containing the "stylesSet" configuration, or FALSE when the
+ * syntax is invalid.
+ */
+ protected function generateStylesSetSetting($styles) {
+ $styles_set = array();
+
+ // Early-return when empty.
+ $styles = trim($styles);
+ if (empty($styles)) {
+ return $styles_set;
+ }
+
+ $styles = str_replace(array("\r\n", "\r"), "\n", $styles);
+ foreach (explode("\n", $styles) as $style) {
+ $style = trim($style);
+
+ // Ignore empty lines in between non-empty lines.
+ if (empty($style)) {
+ continue;
+ }
+
+ // Validate syntax: label|element.class[.class...] pattern expected.
+ if (!preg_match('@^.+\\| *[a-zA-Z0-9]+ *(\\.[a-zA-Z0-9_-]+ *)+$@', $style)) {
+ return FALSE;
+ }
+
+ // Parse.
+ list($label, $selector) = explode('|', $style, 2);
+ $classes = explode('.', $selector);
+ $element = array_shift($classes);
+
+ // Build the data structure CKEditor's stylescombo plugin expects.
+ // @see http://docs.cksource.com/CKEditor_3.x/Developers_Guide/Styles
+ $styles_set[] = array(
+ 'name' => trim($label),
+ 'element' => trim($element),
+ 'attributes' => array(
+ 'class' => implode(' ', array_map('trim', $classes))
+ ),
+ );
+ }
+ return $styles_set;
+ }
+
+}
diff --git a/core/modules/ckeditor/lib/Drupal/ckeditor/Plugin/editor/editor/CKEditor.php b/core/modules/ckeditor/lib/Drupal/ckeditor/Plugin/editor/editor/CKEditor.php
new file mode 100644
index 0000000..379d00e
--- /dev/null
+++ b/core/modules/ckeditor/lib/Drupal/ckeditor/Plugin/editor/editor/CKEditor.php
@@ -0,0 +1,200 @@
+ array(
+ 'buttons' => array(
+ array(
+ 'Source', '|', 'Bold', 'Italic', '|',
+ 'NumberedList', 'BulletedList', 'Blockquote', '|',
+ 'JustifyLeft', 'JustifyCenter', 'JustifyRight', '|',
+ 'Link', 'Unlink', '|', 'Image', 'Maximize',
+ ),
+ ),
+ ),
+ 'plugins' => array(),
+ );
+ }
+
+ /**
+ * Implements \Drupal\editor\Plugin\EditorInterface::settingsForm().
+ */
+ public function settingsForm(array $form, array &$form_state, Editor $editor) {
+ $module_path = drupal_get_path('module', 'ckeditor');
+ $manager = drupal_container()->get('plugin.manager.ckeditor.plugin');
+
+ $form['toolbar'] = array(
+ '#type' => 'container',
+ '#attached' => array(
+ 'library' => array(array('ckeditor', 'drupal.ckeditor.admin')),
+ 'js' => array(
+ array(
+ 'type' => 'setting',
+ 'data' => array('ckeditor' => array(
+ 'toolbarAdmin' => theme('ckeditor_settings_toolbar', array('editor' => $editor, 'plugins' => $manager->getButtonsPlugins($editor))),
+ )),
+ )
+ ),
+ ),
+ '#attributes' => array('class' => array('ckeditor-toolbar-configuration')),
+ );
+ $form['toolbar']['buttons'] = array(
+ '#type' => 'textarea',
+ '#title' => t('Toolbar buttons'),
+ '#default_value' => json_encode($editor->settings['toolbar']['buttons']),
+ '#attributes' => array('class' => array('ckeditor-toolbar-textarea')),
+ );
+
+ // CKEditor plugin settings, if any.
+ $form['plugin_settings'] = array(
+ '#type' => 'vertical_tabs',
+ '#title' => t('Optional settings'),
+ );
+ $manager->injectPluginSettingsForm($form, $form_state, $editor);
+ if (count(element_children($form['plugins'])) === 0) {
+ unset($form['plugins']);
+ unset($form['plugin_settings']);
+ }
+
+ return $form;
+ }
+
+ /**
+ * Implements \Drupal\editor\Plugin\EditorInterface::settingsFormSubmit().
+ */
+ public function settingsFormSubmit(array $form, array &$form_state) {
+ // Modify the toolbar settings by reference. The values in
+ // $form_state['values']['editor']['settings'] will be saved directly by
+ // editor_form_filter_admin_format_submit().
+ $toolbar_settings = &$form_state['values']['editor']['settings']['toolbar'];
+
+ $toolbar_settings['buttons'] = json_decode($toolbar_settings['buttons'], FALSE);
+
+ // Remove the plugin settings' vertical tabs state; no need to save that.
+ if (isset($form_state['values']['editor']['settings']['plugins'])) {
+ unset($form_state['values']['editor']['settings']['plugin_settings']);
+ }
+ }
+
+ /**
+ * Implements \Drupal\editor\Plugin\EditorInterface::getJSSettings().
+ */
+ public function getJSSettings(Editor $editor) {
+ $language_interface = language(LANGUAGE_TYPE_INTERFACE);
+
+ $settings = array();
+ $manager = drupal_container()->get('plugin.manager.ckeditor.plugin');
+
+ // Get the settings for all enabled plugins, even the internal ones.
+ $enabled_plugins = array_keys($manager->getEnabledPlugins($editor, TRUE));
+ foreach ($enabled_plugins as $plugin_id) {
+ $plugin = $manager->createInstance($plugin_id);
+ $settings += $plugin->getConfig($editor);
+ }
+
+ // Next, set the most fundamental CKEditor settings.
+ $external_plugins = $manager->getEnabledPlugins($editor);
+ $settings += array(
+ 'toolbar' => $this->buildToolbarJSSetting($editor),
+ 'contentsCss' => $this->buildContentsCssJSSetting($editor),
+ 'extraPlugins' => implode(',', array_keys($external_plugins)),
+ 'language' => $language_interface->langcode,
+ );
+
+ // Finally, set Drupal-specific CKEditor settings.
+ $settings += array(
+ 'drupalExternalPlugins' => array_map('file_create_url', $external_plugins),
+ );
+
+ return $settings;
+ }
+
+ /**
+ * Implements \Drupal\editor\Plugin\EditorInterface::getLibraries().
+ */
+ public function getLibraries(Editor $editor) {
+ return array(
+ array('ckeditor', 'drupal.ckeditor'),
+ );
+ }
+
+ /**
+ * Builds the "toolbar" configuration part of the CKEditor JS settings.
+ *
+ * @see getJSSettings()
+ *
+ * @param \Drupal\editor\Plugin\Core\Entity\Editor $editor
+ * A configured text editor object.
+ * @return array
+ * An array containing the "toolbar" configuration.
+ */
+ public function buildToolbarJSSetting(Editor $editor) {
+ $toolbar = array();
+ foreach ($editor->settings['toolbar']['buttons'] as $row_number => $row) {
+ $button_group = array();
+ foreach ($row as $button_name) {
+ // Change the toolbar separators into groups.
+ if ($button_name === '|') {
+ $toolbar[] = $button_group;
+ $button_group = array();
+ }
+ else {
+ $button_group['items'][] = $button_name;
+ }
+ }
+ $toolbar[] = $button_group;
+ $toolbar[] = '/';
+ }
+
+ return $toolbar;
+ }
+
+ /**
+ * Builds the "contentsCss" configuration part of the CKEditor JS settings.
+ *
+ * @see getJSSettings()
+ *
+ * @param \Drupal\editor\Plugin\Core\Entity\Editor $editor
+ * A configured text editor object.
+ * @return array
+ * An array containing the "contentsCss" configuration.
+ */
+ public function buildContentsCssJSSetting(Editor $editor) {
+ $css = array(
+ drupal_get_path('module', 'ckeditor') . '/css/ckeditor.css',
+ drupal_get_path('module', 'ckeditor') . '/css/ckeditor-iframe.css',
+ );
+ $css = array_merge($css, _ckeditor_theme_css());
+ drupal_alter('ckeditor_css', $css, $editor);
+ $css = array_map('file_create_url', $css);
+
+ return array_values($css);
+ }
+
+}
diff --git a/core/modules/ckeditor/lib/Drupal/ckeditor/Tests/CKEditorAdminTest.php b/core/modules/ckeditor/lib/Drupal/ckeditor/Tests/CKEditorAdminTest.php
new file mode 100644
index 0000000..fa0adc9
--- /dev/null
+++ b/core/modules/ckeditor/lib/Drupal/ckeditor/Tests/CKEditorAdminTest.php
@@ -0,0 +1,142 @@
+ 'CKEditor administration',
+ 'description' => 'Tests administration of CKEditor.',
+ 'group' => 'CKEditor',
+ );
+ }
+
+ function setUp() {
+ parent::setUp();
+
+ // Create text format.
+ $filtered_html_format = entity_create('filter_format', array(
+ 'format' => 'filtered_html',
+ 'name' => 'Filtered HTML',
+ 'weight' => 0,
+ 'filters' => array(),
+ ));
+ $filtered_html_format->save();
+
+ // Create admin user.
+ $this->admin_user = $this->drupalCreateUser(array('administer filters'));
+ }
+
+ function testAdmin() {
+ $manager = drupal_container()->get('plugin.manager.editor');
+ $ckeditor = $manager->createInstance('ckeditor');
+
+ $this->drupalLogin($this->admin_user);
+ $this->drupalGet('admin/config/content/formats/filtered_html');
+
+ // Ensure no Editor config entity exists yet.
+ $editor = entity_load('editor', 'filtered_html');
+ $this->assertFalse($editor, 'No Editor config entity exists yet.');
+
+ // Verify the "Text Editor" when a text editor is available.
+ $select = $this->xpath('//select[@name="editor[editor]"]');
+ $select_is_disabled = $this->xpath('//select[@name="editor[editor]" and @disabled="disabled"]');
+ $options = $this->xpath('//select[@name="editor[editor]"]/option');
+ $this->assertTrue(count($select) === 1, 'The Text Editor select exists.');
+ $this->assertTrue(count($select_is_disabled) === 0, 'The Text Editor select is not disabled.');
+ $this->assertTrue(count($options) === 2, 'The Text Editor select has two options.');
+ $this->assertTrue(((string) $options[0]) === 'None', 'Option 1 in the Text Editor select is "None".');
+ $this->assertTrue(((string) $options[1]) === 'CKEditor', 'Option 2 in the Text Editor select is "CKEditor".');
+ $this->assertTrue(((string) $options[0]['selected']) === 'selected', 'Option 1 ("None") is selected.');
+
+ // Select the "CKEditor" editor and click the "Configure" button.
+ $edit = array(
+ 'editor[editor]' => 'ckeditor',
+ );
+ // $this->drupalPost(NULL, $edit, t('Configure'));
+ $this->drupalPostAjax(NULL, $edit, 'editor_configure');
+ $editor = entity_load('editor', 'filtered_html');
+ $this->assertFalse($editor, 'No Editor config entity exists yet.');
+
+ // Default configuration.
+ $default_buttons_value = '[["Source","|","Bold","Italic","|","NumberedList","BulletedList","Blockquote","|","JustifyLeft","JustifyCenter","JustifyRight","|","Link","Unlink","|","Image","Maximize"]]';
+ $this->assertFieldByName('editor[settings][toolbar][buttons]', $default_buttons_value, 'Buttons configuration');
+ $styles_textarea = $this->xpath('//textarea[@name="editor[settings][plugins][stylescombo][styles]"]');
+ $this->assertFieldByXPath('//textarea[@name="editor[settings][plugins][stylescombo][styles]"]', '', 'The styles textarea exists and is empty.');
+ $this->assertTrue(count($styles_textarea) === 1, 'The "styles" textarea exists.');
+ $this->drupalPost(NULL, $edit, t('Save configuration'));
+
+ // Ensure an Editor object exists now, with the proper settings.
+ $expected_settings = $ckeditor->getDefaultSettings();
+ $expected_settings['plugins']['stylescombo']['styles'] = '';
+ $editor = entity_load('editor', 'filtered_html');
+ $this->assertTrue($editor instanceof Editor, 'An Editor config entity exists now.');
+ $this->assertIdentical($expected_settings, $editor->settings, 'The Editor config entity has the correct settings.');
+
+ // Configure a plugin.
+ $edit = array(
+ 'editor[settings][plugins][stylescombo][styles]' => "Title|h1.title\nCallout|p.callout\n\n",
+ );
+ $this->drupalPost(NULL, $edit, t('Save configuration'));
+ $expected_settings['plugins']['stylescombo']['styles'] = "Title|h1.title\nCallout|p.callout\n\n";
+ $editor = entity_load('editor', 'filtered_html');
+ $this->assertTrue($editor instanceof Editor, 'An Editor config entity exists.');
+ $this->assertIdentical($expected_settings, $editor->settings, 'The Editor config entity has the correct settings.');
+
+ // "Drag and drop" buttons: remove one button, add a separator, add a button
+ // and finally, add a button to a new row.
+ $edit = array(
+ 'editor[settings][toolbar][buttons]' => '[["Source","|","Bold","Italic","|","NumberedList","BulletedList","Blockquote","|","JustifyLeft","JustifyCenter","JustifyRight","|","Link","Unlink","|","Image","|","Undo"],["Redo"]]'
+ );
+ $this->drupalPost(NULL, $edit, t('Save configuration'));
+ $expected_settings['toolbar']['buttons'][0][17] = '|';
+ $expected_settings['toolbar']['buttons'][0][18] = 'Undo';
+ $expected_settings['toolbar']['buttons'][1][] = 'Redo';
+ $editor = entity_load('editor', 'filtered_html');
+ $this->assertTrue($editor instanceof Editor, 'An Editor config entity exists.');
+ $this->assertIdentical($expected_settings, $editor->settings, 'The Editor config entity has the correct settings.');
+
+ // Now enable the ckeditor_test module, which provides one configurable
+ // CKEditor plugin — this should not affect the Editor config entity.
+ module_enable(array('ckeditor_test'));
+ drupal_container()->get('plugin.manager.ckeditor.plugin')->clearCachedDefinitions();
+ $this->drupalGet('admin/config/content/formats/filtered_html');
+ $ultra_llama_mode_checkbox = $this->xpath('//input[@type="checkbox" and @name="editor[settings][plugins][llama_contextual_and_button][ultra_llama_mode]" and not(@checked)]');
+ $this->assertTrue(count($ultra_llama_mode_checkbox) === 1, 'The "Ultra llama mode" checkbox exists and is not checked.');
+ $editor = entity_load('editor', 'filtered_html');
+ $this->assertTrue($editor instanceof Editor, 'An Editor config entity exists.');
+ $this->assertIdentical($expected_settings, $editor->settings, 'The Editor config entity has the correct settings.');
+
+ // Finally, check the "Ultra llama mode" checkbox.
+ $edit = array(
+ 'editor[settings][plugins][llama_contextual_and_button][ultra_llama_mode]' => '1',
+ );
+ $this->drupalPost(NULL, $edit, t('Save configuration'));
+ $ultra_llama_mode_checkbox = $this->xpath('//input[@type="checkbox" and @name="editor[settings][plugins][llama_contextual_and_button][ultra_llama_mode]" and @checked="checked"]');
+ $this->assertTrue(count($ultra_llama_mode_checkbox) === 1, 'The "Ultra llama mode" checkbox exists and is checked.');
+ $expected_settings['plugins']['llama_contextual_and_button']['ultra_llama_mode'] = '1';
+ $editor = entity_load('editor', 'filtered_html');
+ $this->assertTrue($editor instanceof Editor, 'An Editor config entity exists.');
+ $this->assertIdentical($expected_settings, $editor->settings, 'The Editor config entity has the correct settings.');
+ }
+
+}
diff --git a/core/modules/ckeditor/lib/Drupal/ckeditor/Tests/CKEditorLoadingTest.php b/core/modules/ckeditor/lib/Drupal/ckeditor/Tests/CKEditorLoadingTest.php
new file mode 100644
index 0000000..6ba92e4
--- /dev/null
+++ b/core/modules/ckeditor/lib/Drupal/ckeditor/Tests/CKEditorLoadingTest.php
@@ -0,0 +1,149 @@
+ 'CKEditor loading',
+ 'description' => 'Tests loading of CKEditor.',
+ 'group' => 'CKEditor',
+ );
+ }
+
+ function setUp() {
+ parent::setUp();
+
+ // Create text format, associate CKEditor.
+ $filtered_html_format = entity_create('filter_format', array(
+ 'format' => 'filtered_html',
+ 'name' => 'Filtered HTML',
+ 'weight' => 0,
+ 'filters' => array(),
+ ));
+ $filtered_html_format->save();
+ $editor = entity_create('editor', array(
+ 'format' => 'filtered_html',
+ 'editor' => 'ckeditor',
+ ));
+ $editor->save();
+
+ // Create node type.
+ $this->drupalCreateContentType(array(
+ 'type' => 'article',
+ 'name' => 'Article',
+ ));
+
+ // Create 2 users, each with access to different text formats:
+ // - "untrusted": plain_text
+ // - "normal": plain_text, filtered_html
+ $this->untrusted_user = $this->drupalCreateUser(array('create article content', 'edit any article content'));
+ $this->normal_user = $this->drupalCreateUser(array('create article content', 'edit any article content', 'use text format filtered_html'));
+ }
+
+ /**
+ * Tests loading of CKEditor CSS, JS and JS settings.
+ */
+ function testLoading() {
+ // The untrusted user:
+ // - has access to 1 text format (plain_text);
+ // - doesn't have access to the filtered_html text format, so: no text editor.
+ $this->drupalLogin($this->untrusted_user);
+ $this->drupalGet('node/add/article');
+ list($settings, $editor_settings_present, $editor_js_present, $body, $format_selector) = $this->getThingsToCheck();
+ $this->assertFalse($editor_settings_present, 'No Text Editor module settings.');
+ $this->assertFalse($editor_js_present, 'No Text Editor JavaScript.');
+ $this->assertTrue(count($body) === 1, 'A body field exists.');
+ $this->assertTrue(count($format_selector) === 0, 'No text format selector exists on the page.');
+ $hidden_input = $this->xpath('//input[@type="hidden" and contains(@class, "editor")]');
+ $this->assertTrue(count($hidden_input) === 0, 'A single text format hidden input does not exist on the page.');
+ $this->assertRaw(drupal_get_path('module', 'ckeditor') . '/css/ckeditor.css', 'CKEditor CSS is present, even if no text editor is available for the user');
+ $this->assertNoRaw(drupal_get_path('module', 'ckeditor') . '/js/ckeditor.js', 'CKEditor glue JS is absent.');
+
+ // Even on pages where there would never be a text editor, CKEditor CSS
+ // should be present, because it is blindly attached in hook_page_build().
+ $this->drupalGet('user');
+ $this->assertRaw(drupal_get_path('module', 'ckeditor') . '/css/ckeditor.css', 'CKEditor CSS is present, even if no text editor is available for the user.');
+ $this->assertNoRaw(drupal_get_path('module', 'ckeditor') . '/js/ckeditor.js', 'CKEditor glue JS is absent.');
+
+ // The normal user:
+ // - has access to 2 text formats;
+ // - does have access to the filtered_html text format, so: CKEditor.
+ $this->drupalLogin($this->normal_user);
+ $this->drupalGet('node/add/article');
+ list($settings, $editor_settings_present, $editor_js_present, $body, $format_selector) = $this->getThingsToCheck();
+ $ckeditor_plugin = drupal_container()->get('plugin.manager.editor')->createInstance('ckeditor');
+ $editor = entity_load('editor', 'filtered_html');
+ $expected = array('formats' => array('filtered_html' => array(
+ 'editor' => 'ckeditor',
+ 'editorSettings' => $ckeditor_plugin->getJSSettings($editor),
+ )));
+ $this->assertTrue($editor_settings_present, "Text Editor module's JavaScript settings are on the page.");
+ $this->assertIdentical($expected, $settings['editor'], "Text Editor module's JavaScript settings on the page are correct.");
+ $this->assertTrue($editor_js_present, 'Text Editor JavaScript is present.');
+ $this->assertTrue(count($body) === 1, 'A body field exists.');
+ $this->assertTrue(count($format_selector) === 1, 'A single text format selector exists on the page.');
+ $specific_format_selector = $this->xpath('//select[contains(@class, "filter-list") and contains(@class, "editor") and @data-editor-for="edit-body-und-0-value"]');
+ $this->assertTrue(count($specific_format_selector) === 1, 'A single text format selector exists on the page and has the "editor" class and a "data-editor-for" attribute with the correct value.');
+ $this->assertTrue(isset($settings['ajaxPageState']['css']['ckeditor.css']), 'CKEditor CSS is present.');
+ $this->assertTrue(isset($settings['ajaxPageState']['js']['core/modules/ckeditor/js/ckeditor.js']), 'CKEditor glue JS is present.');
+ $this->assertTrue(isset($settings['ajaxPageState']['js']['core/misc/ckeditor/ckeditor.js']), 'CKEditor lib JS is present.');
+
+ // Enable the ckeditor_test module, customize configuration. In this case,
+ // there is additional CSS and JS to be loaded.
+ // NOTE: the tests in CKEditorTest already ensure that changing the
+ // configuration also results in modified CKEditor configuration, so we
+ // don't test that here.
+ module_enable(array('ckeditor_test'));
+ drupal_container()->get('plugin.manager.ckeditor.plugin')->clearCachedDefinitions();
+ $editor->settings['toolbar']['buttons'][0][] = 'Llama';
+ $editor->settings['plugins']['internal']['link_shortcut'] = 'CTRL+K';
+ $editor->save();
+ $this->drupalGet('node/add/article');
+ list($settings, $editor_settings_present, $editor_js_present, $body, $format_selector) = $this->getThingsToCheck();
+ $expected = array('formats' => array('filtered_html' => array(
+ 'editor' => 'ckeditor',
+ 'editorSettings' => $ckeditor_plugin->getJSSettings($editor),
+ )));
+ $this->assertTrue($editor_settings_present, "Text Editor module's JavaScript settings are on the page.");
+ $this->assertIdentical($expected, $settings['editor'], "Text Editor module's JavaScript settings on the page are correct.");
+ $this->assertTrue($editor_js_present, 'Text Editor JavaScript is present.');
+ $this->assertTrue(isset($settings['ajaxPageState']['css']['ckeditor.css']), 'CKEditor CSS is present.');
+ $this->assertTrue(isset($settings['ajaxPageState']['js']['core/modules/ckeditor/js/ckeditor.js']), 'CKEditor glue JS is present.');
+ $this->assertTrue(isset($settings['ajaxPageState']['js']['core/misc/ckeditor/ckeditor.js']), 'CKEditor lib JS is present.');
+ }
+
+ protected function getThingsToCheck() {
+ $settings = $this->drupalGetSettings();
+ return array(
+ // JavaScript settings.
+ $settings,
+ // Editor.module's JS settings present.
+ isset($settings['editor']),
+ // Editor.module's JS present.
+ isset($settings['ajaxPageState']['js']['core/modules/editor/js/editor.js']),
+ // Body field.
+ $this->xpath('//textarea[@id="edit-body-und-0-value"]'),
+ // Format selector.
+ $this->xpath('//select[contains(@class, "filter-list")]'),
+ );
+ }
+}
diff --git a/core/modules/ckeditor/lib/Drupal/ckeditor/Tests/CKEditorPluginManagerTest.php b/core/modules/ckeditor/lib/Drupal/ckeditor/Tests/CKEditorPluginManagerTest.php
new file mode 100644
index 0000000..c3e8828
--- /dev/null
+++ b/core/modules/ckeditor/lib/Drupal/ckeditor/Tests/CKEditorPluginManagerTest.php
@@ -0,0 +1,123 @@
+ 'CKEditor plugin manager',
+ 'description' => 'Tests different ways of enabling CKEditor plugins.',
+ 'group' => 'CKEditor',
+ );
+ }
+
+ function setUp() {
+ parent::setUp();
+
+ // Install the Filter module.
+ $this->installSchema('system', 'url_alias');
+ $this->enableModules(array('user', 'filter'));
+
+ // Create text format, associate CKEditor.
+ $filtered_html_format = entity_create('filter_format', array(
+ 'format' => 'filtered_html',
+ 'name' => 'Filtered HTML',
+ 'weight' => 0,
+ 'filters' => array(),
+ ));
+ $filtered_html_format->save();
+ $editor = entity_create('editor', array(
+ 'format' => 'filtered_html',
+ 'editor' => 'ckeditor',
+ ));
+ $editor->save();
+ }
+
+ /**
+ * Tests the enabling of plugins.
+ */
+ function testEnabledPlugins() {
+ $this->manager = new CKEditorPluginManager();
+ $editor = entity_load('editor', 'filtered_html');
+
+ // Case 1: no CKEditor plugins.
+ $this->assertIdentical(array('internal', 'stylescombo'), array_keys($this->manager->getDefinitions()), 'No CKEditor plugins found besides the built-in ones.');
+ $this->assertIdentical(array(), $this->manager->getEnabledPlugins($editor), 'Only built-in plugins are enabled.');
+ $this->assertIdentical(array('internal' => NULL), $this->manager->getEnabledPlugins($editor, TRUE), 'Only the "internal" plugin is enabled.');
+
+ // Enable the CKEditor Test module, which has the Llama plugin (plus three
+ // variations of it, to cover all possible ways a plugin can be enabled) and
+ // clear the editor manager's cache so it is picked up.
+ $this->enableModules(array('ckeditor_test'));
+ $this->manager->clearCachedDefinitions();
+
+ // Case 2: CKEditor plugins are available.
+ $plugin_ids = array_keys($this->manager->getDefinitions());
+ sort($plugin_ids);
+ $this->assertIdentical(array('internal', 'llama', 'llama_button', 'llama_contextual', 'llama_contextual_and_button', 'stylescombo'), $plugin_ids, 'Additional CKEditor plugins found.');
+ $this->assertIdentical(array(), $this->manager->getEnabledPlugins($editor), 'Only the internal plugins are enabled.');
+ $this->assertIdentical(array('internal' => NULL), $this->manager->getEnabledPlugins($editor, TRUE), 'Only the "internal" plugin is enabled.');
+
+ // Case 3: enable each of the newly available plugins, if possible:
+ // a. Llama: cannot be enabled, since it does not implement
+ // CKEditorPluginContextualInterface nor CKEditorPluginButtonsInterface.
+ // b. LlamaContextual: enabled by adding the 'Underline' button, which is
+ // part of another plugin!
+ // c. LlamaButton: automatically enabled by adding its 'Llama' button.
+ // d. LlamaContextualAndButton: enabled by either b or c.
+ // Below, we will first enable the "Llama" button, which will cause the
+ // LlamaButton and LlamaContextualAndButton plugins to be enabled. Then we
+ // will remove the "Llama" button and add the "Underline" button, which will
+ // cause the LlamaContextual and LlamaContextualAndButton plugins to be
+ // enabled. Finally, we will add the "Underline" button back again, which
+ // would cause all three plugins to be enabled.
+ $original_toolbar = $editor->settings['toolbar']['buttons'][0];
+ $editor->settings['toolbar']['buttons'][0][] = 'Llama';
+ $editor->save();
+ $file = array();
+ $file['b'] = 'core/modules/ckeditor/tests/modules/js/llama_button.js';
+ $file['c'] = 'core/modules/ckeditor/tests/modules/js/llama_contextual.js';
+ $file['cb'] = 'core/modules/ckeditor/tests/modules/js/llama_contextual_and_button.js';
+ $expected = array('llama_button' => $file['b'], 'llama_contextual_and_button' => $file['cb']);
+ $this->assertIdentical($expected, $this->manager->getEnabledPlugins($editor), 'The LlamaButton and LlamaContextualAndButton plugins are enabled.');
+ $this->assertIdentical(array('internal' => NULL) + $expected, $this->manager->getEnabledPlugins($editor, TRUE), 'The LlamaButton and LlamaContextualAndButton plugins are enabled.');
+ $editor->settings['toolbar']['buttons'][0] = $original_toolbar;
+ $editor->settings['toolbar']['buttons'][0][] = 'Underline';
+ $editor->save();
+ $expected = array('llama_contextual' => $file['c'], 'llama_contextual_and_button' => $file['cb']);
+ $this->assertIdentical($expected, $this->manager->getEnabledPlugins($editor), 'The LLamaContextual and LlamaContextualAndButton plugins are enabled.');
+ $this->assertIdentical(array('internal' => NULL) + $expected, $this->manager->getEnabledPlugins($editor, TRUE), 'The LlamaContextual and LlamaContextualAndButton plugins are enabled.');
+ $editor->settings['toolbar']['buttons'][0][] = 'Llama';
+ $editor->save();
+ $expected = array('llama_button' => $file['b'], 'llama_contextual' => $file['c'], 'llama_contextual_and_button' => $file['cb']);
+ $this->assertIdentical($expected, $this->manager->getEnabledPlugins($editor), 'The LlamaButton, LlamaContextual and LlamaContextualAndButton plugins are enabled.');
+ $this->assertIdentical(array('internal' => NULL) + $expected, $this->manager->getEnabledPlugins($editor, TRUE), 'The LLamaButton, LlamaContextual and LlamaContextualAndButton plugins are enabled.');
+ }
+
+}
diff --git a/core/modules/ckeditor/lib/Drupal/ckeditor/Tests/CKEditorTest.php b/core/modules/ckeditor/lib/Drupal/ckeditor/Tests/CKEditorTest.php
new file mode 100644
index 0000000..6fd31cc
--- /dev/null
+++ b/core/modules/ckeditor/lib/Drupal/ckeditor/Tests/CKEditorTest.php
@@ -0,0 +1,261 @@
+ 'CKEditor text editor plugin',
+ 'description' => 'Tests all aspects of the CKEditor text editor plugin.',
+ 'group' => 'CKEditor',
+ );
+ }
+
+ function setUp() {
+ parent::setUp();
+
+ // Install the Filter module.
+ $this->installSchema('system', 'url_alias');
+ $this->enableModules(array('user', 'filter'));
+
+ // Create text format, associate CKEditor.
+ $filtered_html_format = entity_create('filter_format', array(
+ 'format' => 'filtered_html',
+ 'name' => 'Filtered HTML',
+ 'weight' => 0,
+ 'filters' => array(
+ 'filter_html' => array(
+ 'status' => 1,
+ ),
+ ),
+ ));
+ $filtered_html_format->save();
+ $editor = entity_create('editor', array(
+ 'format' => 'filtered_html',
+ 'editor' => 'ckeditor',
+ ));
+ $editor->save();
+
+ // Create "CKEditor" text editor plugin instance.
+ $manager = new EditorManager();
+ $this->ckeditor = $manager->createInstance('ckeditor');
+ }
+
+ /**
+ * Tests CKEditor::getJSSettings().
+ */
+ function testGetJSSettings() {
+ $editor = entity_load('editor', 'filtered_html');
+
+ // Default toolbar.
+ $expected_config = $this->getDefaultInternalConfig() + array(
+ 'toolbar' => $this->getDefaultToolbarConfig(),
+ 'contentsCss' => $this->getDefaultContentsCssConfig(),
+ 'extraPlugins' => '',
+ 'language' => 'en',
+ 'drupalExternalPlugins' => array(),
+ );
+ $this->assertIdentical($expected_config, $this->ckeditor->getJSSettings($editor), 'Generated JS settings are correct for default configuration.');
+
+ // Customize the configuration: add button, have two contextually enabled
+ // buttons, and configure a CKEditor plugin setting.
+ $this->enableModules(array('ckeditor_test'));
+ drupal_container()->get('plugin.manager.ckeditor.plugin')->clearCachedDefinitions();
+ $editor->settings['toolbar']['buttons'][0][] = 'Underline';
+ $editor->settings['toolbar']['buttons'][1][] = 'Format';
+ $editor->settings['plugins']['internal']['link_shortcut'] = 'CTRL+K';
+ $editor->save();
+ $expected_config['toolbar'][5]['items'][] = 'Underline';
+ $expected_config['toolbar'][7]['items'][] = 'Format';
+ $expected_config['toolbar'][8] = '/';
+ $expected_config['format_tags'] = 'p;h4;h5;h6';
+ $expected_config['extraPlugins'] = 'llama_contextual,llama_contextual_and_button';
+ $expected_config['drupalExternalPlugins']['llama_contextual'] = file_create_url('core/modules/ckeditor/tests/modules/js/llama_contextual.js');
+ $expected_config['drupalExternalPlugins']['llama_contextual_and_button'] = file_create_url('core/modules/ckeditor/tests/modules/js/llama_contextual_and_button.js');
+ $expected_config['contentsCss'][] = file_create_url('core/modules/ckeditor/tests/modules/ckeditor_test.css');
+ $expected_config['keystrokes'] = array(array(1114187, 'link'), array(1114188, NULL));
+ $this->assertEqual($expected_config, $this->ckeditor->getJSSettings($editor), 'Generated JS settings are correct for customized configuration.');
+
+ // Change the allowed HTML tags; the "format_tags" setting for CKEditor
+ // should automatically be updated as well.
+ $format = entity_load('filter_format', 'filtered_html');
+ $format->filters['filter_html']['settings']['allowed_html'] .= ' ';
+ $format->save();
+ $expected_config['format_tags'] = 'p;h3;h4;h5;h6;pre';
+ $this->assertEqual($expected_config, $this->ckeditor->getJSSettings($editor), 'Generated JS settings are correct for customized configuration.');
+ }
+
+ /**
+ * Tests CKEditor::buildToolbarJSSetting().
+ */
+ function testBuildToolbarJSSetting() {
+ $editor = entity_load('editor', 'filtered_html');
+
+ // Default toolbar.
+ $expected = $this->getDefaultToolbarConfig();
+ $this->assertIdentical($expected, $this->ckeditor->buildToolbarJSSetting($editor), '"toolbar" configuration part of JS settings built correctly for default toolbar.');
+
+ // Customize the configuration.
+ $editor->settings['toolbar']['buttons'][0][] = 'Underline';
+ $editor->save();
+ $expected[5]['items'][] = 'Underline';
+ $this->assertIdentical($expected, $this->ckeditor->buildToolbarJSSetting($editor), '"toolbar" configuration part of JS settings built correctly for customized toolbar.');
+
+ // Enable the editor_test module, customize further.
+ $this->enableModules(array('ckeditor_test'));
+ drupal_container()->get('plugin.manager.ckeditor.plugin')->clearCachedDefinitions();
+ $editor->settings['toolbar']['buttons'][0][] = 'Llama';
+ $editor->save();
+ $expected[5]['items'][] = 'Llama';
+ $this->assertIdentical($expected, $this->ckeditor->buildToolbarJSSetting($editor), '"toolbar" configuration part of JS settings built correctly for customized toolbar with contrib module-provided CKEditor plugin.');
+ }
+
+ /**
+ * Tests CKEditor::buildContentsCssJSSetting().
+ */
+ function testBuildContentsCssJSSetting() {
+ $editor = entity_load('editor', 'filtered_html');
+
+ // Default toolbar.
+ $expected = $this->getDefaultContentsCssConfig();
+ $this->assertIdentical($expected, $this->ckeditor->buildContentsCssJSSetting($editor), '"contentsCss" configuration part of JS settings built correctly for default toolbar.');
+
+ // Enable the editor_test module, which implements hook_ckeditor_css_alter().
+ $this->enableModules(array('ckeditor_test'));
+ $expected[] = file_create_url('core/modules/ckeditor/tests/modules/ckeditor_test.css');
+ $this->assertIdentical($expected, $this->ckeditor->buildContentsCssJSSetting($editor), '"contentsCss" configuration part of JS settings built correctly while a hook_ckeditor_css_alter() implementation exists.');
+
+ // @todo test coverage for _ckeditor_theme_css(), by including a custom theme in this test with a "ckeditor_stylesheets" entry in its .info file.
+ }
+
+ /**
+ * Tests Internal::getConfig().
+ */
+ function testInternalGetConfig() {
+ $editor = entity_load('editor', 'filtered_html');
+ $manager = drupal_container()->get('plugin.manager.ckeditor.plugin');
+ $internal_plugin = $manager->createInstance('internal');
+
+ // Default toolbar.
+ $expected = $this->getDefaultInternalConfig();
+ $this->assertIdentical($expected, $internal_plugin->getConfig($editor), '"Internal" plugin configuration built correctly for default toolbar.');
+
+ // Format dropdown/button enabled: new setting should be present.
+ $editor->settings['toolbar']['buttons'][0][] = 'Format';
+ $expected['format_tags'] = 'p;h4;h5;h6';
+ $this->assertIdentical($expected, $internal_plugin->getConfig($editor), '"Internal" plugin configuration built correctly for customized toolbar.');
+ }
+
+ /**
+ * Tests StylesCombo::getConfig().
+ */
+ function testStylesComboGetConfig() {
+ $editor = entity_load('editor', 'filtered_html');
+ $manager = drupal_container()->get('plugin.manager.ckeditor.plugin');
+ $stylescombo_plugin = $manager->createInstance('stylescombo');
+
+ // Default toolbar.
+ $expected = $this->getDefaultStylesComboConfig();
+ $this->assertIdentical($expected, $stylescombo_plugin->getConfig($editor), '"StylesCombo" plugin configuration built correctly for default toolbar.');
+
+ // Styles dropdown/button enabled: new setting should be present.
+ $editor->settings['toolbar']['buttons'][0][] = 'Styles';
+ $editor->settings['plugins']['stylescombo']['styles'] = '';
+ $editor->save();
+ $expected['stylesSet'] = array();
+ $this->assertIdentical($expected, $stylescombo_plugin->getConfig($editor), '"StylesCombo" plugin configuration built correctly for customized toolbar.');
+
+ // Configure the optional "styles" setting in odd ways that shouldn't affect
+ // the end result.
+ $editor->settings['plugins']['stylescombo']['styles'] = " \n";
+ $editor->save();
+ $this->assertIdentical($expected, $stylescombo_plugin->getConfig($editor));
+ $editor->settings['plugins']['stylescombo']['styles'] = "\r\n \n \r \n ";
+ $editor->save();
+ $this->assertIdentical($expected, $stylescombo_plugin->getConfig($editor), '"StylesCombo" plugin configuration built correctly for customized toolbar.');
+
+ // Now configure it properly, the end result should change.
+ $editor->settings['plugins']['stylescombo']['styles'] = "Title|h1.title\nCallout|p.mAgical.Callout";
+ $editor->save();
+ $expected['stylesSet'] = array(
+ array('name' => 'Title', 'element' => 'h1', 'attributes' => array('class' => 'title')),
+ array('name' => 'Callout', 'element' => 'p', 'attributes' => array('class' => 'mAgical Callout')),
+ );
+ $this->assertIdentical($expected, $stylescombo_plugin->getConfig($editor), '"StylesCombo" plugin configuration built correctly for customized toolbar.');
+
+ // Same configuration, but now interspersed with nonsense. Should yield the
+ // same result.
+ $editor->settings['plugins']['stylescombo']['styles'] = "Title | h1 .title\r \n\r \nCallout|p.mAgical .Callout\r";
+ $editor->save();
+ $this->assertIdentical($expected, $stylescombo_plugin->getConfig($editor), '"StylesCombo" plugin configuration built correctly for customized toolbar.');
+
+ // Invalid syntax should cause stylesSet to be set to FALSE.
+ $editor->settings['plugins']['stylescombo']['styles'] = "Title|h1";
+ $editor->save();
+ $expected['stylesSet'] = FALSE;
+ $this->assertIdentical($expected, $stylescombo_plugin->getConfig($editor), '"StylesCombo" plugin configuration built correctly for customized toolbar.');
+ }
+
+ protected function getDefaultInternalConfig() {
+ return array(
+ 'pasteFromWordPromptCleanup' => TRUE,
+ 'indentClasses' => array('indent1', 'indent2', 'indent3'),
+ 'justifyClasses' => array('align-left', 'align-center', 'align-right', 'align-justify'),
+ 'coreStyles_underline' => array('element' => 'span', 'attributes' => array('class' => 'underline')),
+ 'removeDialogTabs' => 'image:Link;image:advanced;link:advanced',
+ 'resize_dir' => 'vertical',
+ 'keystrokes' => array(array(0x110000 + 75, 'link'), array(0x110000 + 76, NULL)),
+ );
+ }
+
+ protected function getDefaultStylesComboConfig() {
+ return array();
+ }
+
+ protected function getDefaultToolbarConfig() {
+ return array(
+ 0 => array('items' => array('Source')),
+ 1 => array('items' => array('Bold', 'Italic')),
+ 2 => array('items' => array('NumberedList', 'BulletedList', 'Blockquote')),
+ 3 => array('items' => array('JustifyLeft', 'JustifyCenter', 'JustifyRight')),
+ 4 => array('items' => array('Link', 'Unlink')),
+ 5 => array('items' => array('Image', 'Maximize')),
+ 6 => '/'
+ );
+ }
+
+ protected function getDefaultContentsCssConfig() {
+ return array(
+ file_create_url('core/modules/ckeditor/css/ckeditor.css'),
+ file_create_url('core/modules/ckeditor/css/ckeditor-iframe.css')
+ );
+ }
+
+}
diff --git a/core/modules/ckeditor/tests/modules/ckeditor_test.info b/core/modules/ckeditor/tests/modules/ckeditor_test.info
new file mode 100644
index 0000000..4fad10f
--- /dev/null
+++ b/core/modules/ckeditor/tests/modules/ckeditor_test.info
@@ -0,0 +1,6 @@
+name = CKEditor test
+description = Support module for the CKEditor module tests.
+core = 8.x
+package = Testing
+version = VERSION
+hidden = TRUE
diff --git a/core/modules/ckeditor/tests/modules/ckeditor_test.module b/core/modules/ckeditor/tests/modules/ckeditor_test.module
new file mode 100644
index 0000000..e97c019
--- /dev/null
+++ b/core/modules/ckeditor/tests/modules/ckeditor_test.module
@@ -0,0 +1,15 @@
+ array(
+ 'label' => t('Insert Lllama'),
+ ),
+ );
+ }
+
+ /**
+ * Implements \Drupal\ckeditor\Plugin\CKEditorPluginInterface::getFile().
+ */
+ function getFile() {
+ return drupal_get_path('module', 'ckeditor_test') . '/js/llama_button.js';
+ }
+
+}
diff --git a/core/modules/ckeditor/tests/modules/lib/Drupal/ckeditor_test/Plugin/ckeditor/plugin/LlamaContextual.php b/core/modules/ckeditor/tests/modules/lib/Drupal/ckeditor_test/Plugin/ckeditor/plugin/LlamaContextual.php
new file mode 100644
index 0000000..fb681a8
--- /dev/null
+++ b/core/modules/ckeditor/tests/modules/lib/Drupal/ckeditor_test/Plugin/ckeditor/plugin/LlamaContextual.php
@@ -0,0 +1,47 @@
+settings['toolbar']['buttons'] as $row) {
+ if (in_array('Underline', $row)) {
+ return TRUE;
+ }
+ }
+ return FALSE;
+ }
+
+ /**
+ * Implements \Drupal\ckeditor\Plugin\CKEditorPluginInterface::getFile().
+ */
+ function getFile() {
+ return drupal_get_path('module', 'ckeditor_test') . '/js/llama_contextual.js';
+ }
+
+}
diff --git a/core/modules/ckeditor/tests/modules/lib/Drupal/ckeditor_test/Plugin/ckeditor/plugin/LlamaContextualAndButton.php b/core/modules/ckeditor/tests/modules/lib/Drupal/ckeditor_test/Plugin/ckeditor/plugin/LlamaContextualAndButton.php
new file mode 100644
index 0000000..a0d55ec
--- /dev/null
+++ b/core/modules/ckeditor/tests/modules/lib/Drupal/ckeditor_test/Plugin/ckeditor/plugin/LlamaContextualAndButton.php
@@ -0,0 +1,80 @@
+settings['toolbar']['buttons'] as $row) {
+ if (in_array('Underline', $row)) {
+ return TRUE;
+ }
+ }
+ return FALSE;
+ }
+
+ /**
+ * Implements \Drupal\ckeditor\Plugin\CKEditorPluginButtonsInterface::getButtons().
+ */
+ function getButtons() {
+ return array(
+ 'Llama' => array(
+ 'label' => t('Insert Llama'),
+ ),
+ );
+ }
+
+ /**
+ * Implements \Drupal\ckeditor\Plugin\CKEditorPluginInterface::getFile().
+ */
+ function getFile() {
+ return drupal_get_path('module', 'ckeditor_test') . '/js/llama_contextual_and_button.js';
+ }
+
+ /**
+ * Implements \Drupal\ckeditor\Plugin\CKEditorPluginConfigurableInterface::settingsForm().
+ */
+ function settingsForm(array $form, array &$form_state, Editor $editor) {
+ // Defaults.
+ $config = array('ultra_llama_mode' => FALSE);
+ if (isset($editor->settings['plugins']['llama_contextual_and_button'])) {
+ $config = $editor->settings['plugins']['llama_contextual_and_button'];
+ }
+
+ $form['ultra_llama_mode'] = array(
+ '#title' => t('Ultra llama mode'),
+ '#type' => 'checkbox',
+ '#default_value' => $config['ultra_llama_mode'],
+ );
+
+ return $form;
+ }
+
+}
diff --git a/core/modules/editor/editor.module b/core/modules/editor/editor.module
index a40c249..89fd11b 100644
--- a/core/modules/editor/editor.module
+++ b/core/modules/editor/editor.module
@@ -119,21 +119,26 @@ function editor_form_filter_admin_format_form_alter(&$form, &$form_state) {
// Associate a text editor with this text format.
$editor_options = $manager->listOptions();
$form['editor'] = array(
- '#type' => 'select',
+ '#type' => 'fieldset',
'#title' => t('Text editor'),
- '#options' => $editor_options,
- '#empty_option' => t('None'),
- '#default_value' => $editor ? $editor->editor : '',
// Position the editor selection before the filter settings (weight of 0),
// but after the filter label and name (weight of -20).
'#weight' => -9,
+ );
+ $form['editor']['editor'] = array(
+ '#type' => 'select',
+ '#title' => t('Current text editor'),
+ '#options' => $editor_options,
+ '#empty_option' => t('None'),
+ '#default_value' => $editor ? $editor->editor : '',
'#ajax' => array(
'trigger_as' => array('name' => 'editor_configure'),
'callback' => 'editor_form_filter_admin_form_ajax',
'wrapper' => 'editor-settings-wrapper',
),
+ '#weight' => -10,
);
- $form['editor_configure'] = array(
+ $form['editor']['configure'] = array(
'#type' => 'submit',
'#name' => 'editor_configure',
'#value' => t('Configure'),
@@ -143,17 +148,17 @@ function editor_form_filter_admin_format_form_alter(&$form, &$form_state) {
'callback' => 'editor_form_filter_admin_form_ajax',
'wrapper' => 'editor-settings-wrapper',
),
- '#weight' => -9,
+ '#weight' => -10,
'#attributes' => array('class' => array('js-hide')),
);
// If there aren't any options (other than "None"), disable the select list.
if (empty($editor_options)) {
- $form['editor']['#disabled'] = TRUE;
- $form['editor']['#description'] = t('This option is disabled because no modules that provide a text editor are currently enabled.');
+ $form['editor']['editor']['#disabled'] = TRUE;
+ $form['editor']['editor']['#description'] = t('This option is disabled because no modules that provide a text editor are currently enabled.');
}
- $form['editor_settings'] = array(
+ $form['editor']['settings'] = array(
'#tree' => TRUE,
'#weight' => -8,
'#type' => 'container',
@@ -165,8 +170,8 @@ function editor_form_filter_admin_format_form_alter(&$form, &$form_state) {
$plugin = $manager->createInstance($editor->editor);
$settings_form = array();
$settings_form['#element_validate'][] = array($plugin, 'settingsFormValidate');
- $form['editor_settings']['settings'] = $plugin->settingsForm($settings_form, $form_state, $editor);
- $form['editor_settings']['settings']['#parents'] = array('editor_settings');
+ $form['editor']['settings']['subform'] = $plugin->settingsForm($settings_form, $form_state, $editor);
+ $form['editor']['settings']['subform']['#parents'] = array('editor', 'settings');
$form['#submit'][] = array($plugin, 'settingsFormSubmit');
}
@@ -178,14 +183,14 @@ function editor_form_filter_admin_format_form_alter(&$form, &$form_state) {
*/
function editor_form_filter_admin_format_editor_configure($form, &$form_state) {
$editor = $form_state['editor'];
- if (isset($form_state['values']['editor'])) {
- if ($form_state['values']['editor'] === '') {
+ if (isset($form_state['values']['editor']['editor'])) {
+ if ($form_state['values']['editor']['editor'] === '') {
$form_state['editor'] = FALSE;
}
- elseif (empty($editor) || $form_state['values']['editor'] !== $editor->editor) {
+ elseif (empty($editor) || $form_state['values']['editor']['editor'] !== $editor->editor) {
$editor = entity_create('editor', array(
'format' => $form['#format']->format,
- 'editor' => $form_state['values']['editor'],
+ 'editor' => $form_state['values']['editor']['editor'],
));
$form_state['editor'] = $editor;
}
@@ -197,7 +202,7 @@ function editor_form_filter_admin_format_editor_configure($form, &$form_state) {
* AJAX callback handler for filter_admin_format_form().
*/
function editor_form_filter_admin_form_ajax($form, &$form_state) {
- return $form['editor_settings'];
+ return $form['editor']['settings'];
}
/**
@@ -212,8 +217,8 @@ function editor_form_filter_admin_format_submit($form, &$form_state) {
}
// Create a new editor or update the existing editor.
- if ($form_state['values']['editor'] !== '') {
- $form_state['editor']->settings = $form_state['values']['editor_settings'];
+ if ($form_state['values']['editor']['editor'] !== '') {
+ $form_state['editor']->settings = $form_state['values']['editor']['settings'];
$form_state['editor']->save();
}
}
diff --git a/core/modules/editor/lib/Drupal/editor/Plugin/EditorInterface.php b/core/modules/editor/lib/Drupal/editor/Plugin/EditorInterface.php
index 1fcbec3..939d877 100644
--- a/core/modules/editor/lib/Drupal/editor/Plugin/EditorInterface.php
+++ b/core/modules/editor/lib/Drupal/editor/Plugin/EditorInterface.php
@@ -52,7 +52,7 @@ function settingsForm(array $form, array &$form_state, Editor $editor);
* Validates the settings form for an editor.
*
* The contents of the editor settings are located in
- * $form_state['values']['editor_settings']. Calls to form_error() should
+ * $form_state['values']['editor']['settings']. Calls to form_error() should
* reflect this location in the settings form.
*
* @param array $form
@@ -65,7 +65,7 @@ function settingsFormValidate(array $form, array &$form_state);
/**
* Modifies any values in the form state to prepare them for saving.
*
- * Values in $form_state['values']['editor_settings'] are saved by Editor
+ * Values in $form_state['values']['editor']['settings'] are saved by Editor
* module in editor_form_filter_admin_format_submit().
*
* @param array $form
diff --git a/core/modules/editor/lib/Drupal/editor/Tests/EditorAdminTest.php b/core/modules/editor/lib/Drupal/editor/Tests/EditorAdminTest.php
index f3b834a..783fdd9 100644
--- a/core/modules/editor/lib/Drupal/editor/Tests/EditorAdminTest.php
+++ b/core/modules/editor/lib/Drupal/editor/Tests/EditorAdminTest.php
@@ -56,9 +56,9 @@ function testWithoutEditorAvailable() {
$this->assertTrue($roles_pos < $editor_pos && $editor_pos < $filters_pos, '"Text Editor" select appears in the correct location of the text format configuration UI.');
// Verify the .
- $select = $this->xpath('//select[@name="editor"]');
- $select_is_disabled = $this->xpath('//select[@name="editor" and @disabled="disabled"]');
- $options = $this->xpath('//select[@name="editor"]/option');
+ $select = $this->xpath('//select[@name="editor[editor]"]');
+ $select_is_disabled = $this->xpath('//select[@name="editor[editor]" and @disabled="disabled"]');
+ $options = $this->xpath('//select[@name="editor[editor]"]/option');
$this->assertTrue(count($select) === 1, 'The Text Editor select exists.');
$this->assertTrue(count($select_is_disabled) === 1, 'The Text Editor select is disabled.');
$this->assertTrue(count($options) === 1, 'The Text Editor select has only one option.');
@@ -72,9 +72,9 @@ function testWithoutEditorAvailable() {
$this->drupalGet('admin/config/content/formats/filtered_html');
// Verify the when a text editor is available.
- $select = $this->xpath('//select[@name="editor"]');
- $select_is_disabled = $this->xpath('//select[@name="editor" and @disabled="disabled"]');
- $options = $this->xpath('//select[@name="editor"]/option');
+ $select = $this->xpath('//select[@name="editor[editor]"]');
+ $select_is_disabled = $this->xpath('//select[@name="editor[editor]" and @disabled="disabled"]');
+ $options = $this->xpath('//select[@name="editor[editor]"]/option');
$this->assertTrue(count($select) === 1, 'The Text Editor select exists.');
$this->assertTrue(count($select_is_disabled) === 0, 'The Text Editor select is not disabled.');
$this->assertTrue(count($options) === 2, 'The Text Editor select has two options.');
@@ -86,15 +86,15 @@ function testWithoutEditorAvailable() {
// Select the "Unicorn Editor" editor and click the "Configure" button.
$edit = array(
- 'editor' => 'unicorn',
+ 'editor[editor]' => 'unicorn',
);
$this->drupalPostAjax(NULL, $edit, 'editor_configure');
- $unicorn_setting_foo = $this->xpath('//input[@name="editor_settings[foo]" and @type="text" and @value="bar"]');
+ $unicorn_setting_foo = $this->xpath('//input[@name="editor[settings][foo]" and @type="text" and @value="bar"]');
$this->assertTrue(count($unicorn_setting_foo), "Unicorn Editor's settings form is present.");
- $options = $this->xpath('//select[@name="editor"]/option');
+ $options = $this->xpath('//select[@name="editor[editor]"]/option');
// Now configure the setting to another value.
- $edit['editor_settings[foo]'] = 'baz';
+ $edit['editor[settings][foo]'] = 'baz';
$this->drupalPost(NULL, $edit, t('Save configuration'));
// Verify the editor configuration is saved correctly.
@@ -105,9 +105,9 @@ function testWithoutEditorAvailable() {
$this->assertIdentical($editor->settings['rainbows'], true, 'The text editor defaults added by hook_editor_settings_defaults() are retrieved correctly.');
$this->assertIdentical($editor->settings['sparkles'], false, 'The text editor defaults modified by hook_editor_settings_defaults_alter() are retrieved correctly.');
$this->drupalGet('admin/config/content/formats/filtered_html');
- $select = $this->xpath('//select[@name="editor"]');
- $select_is_disabled = $this->xpath('//select[@name="editor" and @disabled="disabled"]');
- $options = $this->xpath('//select[@name="editor"]/option');
+ $select = $this->xpath('//select[@name="editor[editor]"]');
+ $select_is_disabled = $this->xpath('//select[@name="editor[editor]" and @disabled="disabled"]');
+ $options = $this->xpath('//select[@name="editor[editor]"]/option');
$this->assertTrue(count($select) === 1, 'The Text Editor select exists.');
$this->assertTrue(count($select_is_disabled) === 0, 'The Text Editor select is not disabled.');
$this->assertTrue(count($options) === 2, 'The Text Editor select has two options.');