diff --git a/core/modules/ckeditor/js/ckeditor.js b/core/modules/ckeditor/js/ckeditor.js
index b8318be..928f149 100644
--- a/core/modules/ckeditor/js/ckeditor.js
+++ b/core/modules/ckeditor/js/ckeditor.js
@@ -5,7 +5,7 @@
Drupal.editors.ckeditor = {
attach: function (element, format) {
- var externalPlugins = format.editorSettings.externalPlugins;
+ var externalPlugins = format.editorSettings.drupalExternalPlugins;
// Register and load additional CKEditor plugins as necessary.
if (externalPlugins) {
for (var pluginName in externalPlugins) {
diff --git a/core/modules/ckeditor/js/plugins/drupaldialog/plugin.js b/core/modules/ckeditor/js/plugins/drupaldialog/plugin.js
new file mode 100644
index 0000000..b02ef8c
--- /dev/null
+++ b/core/modules/ckeditor/js/plugins/drupaldialog/plugin.js
@@ -0,0 +1,26 @@
+/**
+ * @file Drupal Dialog plugin.
+ *
+ * This file is a placeholder until we can implements true Drupal-side dialog
+ * handling, pending the patch at
+ *
+ * Note that this file follows coding standards for the CKEditor project rather
+ * than traditional Drupal coding standards for JavaScript. This keeps
+ * compatibility high when adapting code between the built-in plugins and the
+ * custom plugins that Drupal provides.
+ *
+ * @see http://dev.ckeditor.com/wiki/CodingStyle
+ */
+
+(function()
+{
+
+CKEDITOR.plugins.add( 'drupaldialog',
+{
+ init : function( editor )
+ {
+ var pluginName = 'drupaldialog';
+ }
+});
+
+})();
diff --git a/core/modules/ckeditor/js/plugins/drupalimage/image.png b/core/modules/ckeditor/js/plugins/drupalimage/image.png
new file mode 100644
index 0000000..3e07ac8
--- /dev/null
+++ b/core/modules/ckeditor/js/plugins/drupalimage/image.png
Binary files differ
diff --git a/core/modules/ckeditor/js/plugins/drupalimage/plugin.js b/core/modules/ckeditor/js/plugins/drupalimage/plugin.js
new file mode 100644
index 0000000..10e1557
--- /dev/null
+++ b/core/modules/ckeditor/js/plugins/drupalimage/plugin.js
@@ -0,0 +1,319 @@
+/**
+ * @file Drupal Image plugin.
+ *
+ * Note that this file follows coding standards for the CKEditor project rather
+ * than traditional Drupal coding standards for JavaScript. This keeps
+ * compatibility high when adapting code between the built-in plugins and the
+ * custom plugins that Drupal provides.
+ *
+ * @see http://dev.ckeditor.com/wiki/CodingStyle
+ */
+
+(function()
+{
+
+CKEDITOR.plugins.add( 'drupalimage',
+{
+ // @todo: Remove dependency on normal image dialog, build Drupal-based dialog.
+ requires: [ 'dialog' ],
+
+ init : function( editor )
+ {
+ var pluginName = 'drupalimage';
+
+ // Register the normal image dialog.
+ CKEDITOR.dialog.add( 'image', Drupal.settings.basePath + Drupal.settings.ckeditor.modulePath + '/lib/ckeditor/plugins/image/dialogs/image.js' );
+
+ // Register the toolbar button.
+ editor.ui.addButton( 'DrupalImage',
+ {
+ label : editor.lang.common.image,
+ command : 'image',
+ icon : Drupal.settings.basePath + Drupal.settings.ckeditor.modulePath + '/js/plugins/drupalimage/image.png',
+ });
+
+ // Register the image command.
+ editor.addCommand( 'image', new CKEDITOR.dialogCommand( 'image' ) );
+
+ // Double clicking an image opens its properties.
+ editor.on( 'doubleclick', function( evt )
+ {
+ var element = evt.data.element;
+
+ if ( element.is( 'img' ) && !element.data( 'cke-realelement' ) && !element.isReadOnly() )
+ evt.data.dialog = 'image';
+ });
+
+ // If the "menu" plugin is loaded, register the menu items.
+ if ( editor.addMenuItems )
+ {
+ editor.addMenuItems(
+ {
+ image :
+ {
+ label : editor.lang.image.menu,
+ command : 'image',
+ group : 'image'
+ }
+ });
+ }
+
+ // If the "contextmenu" plugin is loaded, register the listeners.
+ if ( editor.contextMenu )
+ {
+ editor.contextMenu.addListener( function( element, selection )
+ {
+ if ( getSelectedImage( editor, element ) )
+ return { image : CKEDITOR.TRISTATE_OFF };
+ });
+ }
+
+ // Customize the built-in dialog for images.
+ CKEDITOR.on( 'dialogDefinition', function( evt ) {
+ if ( evt.data.name == 'image' )
+ {
+ var infoPane = evt.data.definition.getContents('info');
+
+ // Remove unneeded options that write style attributes.
+ infoPane.remove('txtHSpace');
+ infoPane.remove('txtVSpace');
+ infoPane.remove('txtBorder');
+
+ // Add option for center alignment if supported.
+ var alignOption = infoPane.get('cmbAlign');
+ if ( CKEDITOR.config.drupalimage_justifyClasses )
+ alignOption.items.push([ editor.lang.common.alignCenter, 'center' ]);
+
+ // Adjust the alignment options to use our alignment classes.
+ alignOption.setup = function ( type, element ) {
+ // Set alignment value based on active alignment state.
+ var value = '';
+ var possibleButtons = [ 'left', 'center', 'right', 'block' ];
+ for (var n = 0; n < 4; n++) {
+ var currentCommand = editor.getCommand('justify' + possibleButtons[n]);
+ if ( currentCommand && currentCommand.state === CKEDITOR.TRISTATE_ON )
+ value = possibleButtons[n];
+ }
+ if ( value )
+ this.setValue( value );
+ this.setupValue = value;
+ }
+ alignOption.commit = function( type, element, internalCommit ) {
+ // Constants from image plugin dialog.js.
+ var IMAGE = 1, LINK = 2, PREVIEW = 4, CLEANUP = 8;
+ var value = this.getValue();
+ switch ( type )
+ {
+ case IMAGE:
+ if ( value !== this.setupValue )
+ {
+ if ( value )
+ {
+ var alignCommand = editor.getCommand('justify' + value );
+ if ( alignCommand.state === CKEDITOR.TRISTATE_OFF )
+ alignCommand.exec();
+ }
+ else if ( this.setupValue )
+ {
+ var alignCommand = editor.getCommand('justify' + this.setupValue );
+ if ( alignCommand.state === CKEDITOR.TRISTATE_ON )
+ alignCommand.exec();
+ }
+ }
+ break;
+ case PREVIEW:
+ setImageAlignment( element, value );
+ break;
+ case CLEANUP:
+ setImageAlignment( element, false );
+ break;
+ }
+ }
+ }
+ });
+
+ },
+ afterInit : function( editor )
+ {
+ // Use normal height/width attributes for images instead of styles (#5547).
+ // We may be able to remove this code once Drupal provides its own modal
+ // dialogs for manipulating images, but resizing an image via resize handles
+ // may make it necessary still.
+ var dataProcessor = editor.dataProcessor;
+ var htmlFilter = dataProcessor && dataProcessor.htmlFilter;
+ htmlFilter.addRules(
+ {
+ elements :
+ {
+ img : function( element )
+ {
+ var style = element.attributes.style, match, width, height;
+ if ( style )
+ {
+ // Get the width from the style.
+ match = /(?:^|\s)width\s*:\s*(\d+)px/i.exec( style ),
+ width = match && match[1];
+ // Get the height from the style.
+ match = /(?:^|\s)height\s*:\s*(\d+)px/i.exec( style );
+ height = match && match[1];
+ if ( width.length )
+ {
+ style = style.replace( /(?:^|\s)width\s*:\s*(\d+)px;?/i , '' );
+ element.attributes.width = width;
+ }
+ if ( height.length )
+ {
+ style = style.replace( /(?:^|\s)height\s*:\s*(\d+)px;?/i , '' );
+ element.attributes.height = height;
+ }
+ if (style)
+ element.attributes.style = style;
+ else
+ delete element.attributes.style;
+ }
+ }
+ }
+ }
+ );
+
+ // Customize the behavior of the alignment commands. (#7430)
+ setupAlignCommand( 'left' );
+ setupAlignCommand( 'right' );
+ setupAlignCommand( 'center' );
+ setupAlignCommand( 'block' );
+
+ function setupAlignCommand( value )
+ {
+ var command = editor.getCommand( 'justify' + value );
+ if ( command )
+ {
+ command.on( 'exec', function( evt )
+ {
+ var img = getSelectedImage( editor ), align;
+ if ( img )
+ {
+ // Remove the state of the previous alignment button.
+ previousAlignment = getImageAlignment( img );
+ if (previousAlignment)
+ {
+ var previousCommand = editor.getCommand( 'justify' + previousAlignment );
+ if ( previousCommand.state === CKEDITOR.TRISTATE_ON)
+ previousCommand.setState(CKEDITOR.TRISTATE_OFF);
+ }
+ // Set alignment and activate the current alignment button.
+ if ( setImageAlignment( img, value ) ) {
+ command.setState(CKEDITOR.TRISTATE_ON);
+ }
+ evt.cancel();
+ }
+ });
+
+ command.on( 'refresh', function( evt )
+ {
+ var img = getSelectedImage( editor ), align;
+ if ( img )
+ {
+ align = getImageAlignment( img );
+
+ this.setState(
+ ( align == value ) ? CKEDITOR.TRISTATE_ON :
+ ( CKEDITOR.config.drupalimage_justifyClasses || value == 'right' || value == 'left' ) ? CKEDITOR.TRISTATE_OFF :
+ CKEDITOR.TRISTATE_DISABLED );
+
+ evt.cancel();
+ }
+ });
+ }
+ }
+ }
+});
+
+function getSelectedImage( editor, element )
+{
+ if ( !element )
+ {
+ var sel = editor.getSelection();
+ var selectedText = sel.getSelectedText().replace(/^\s\s*/, '').replace(/\s\s*$/, '');
+ var isElement = sel.getType() === CKEDITOR.SELECTION_ELEMENT;
+ var isEmptySelection = sel.getType() === CKEDITOR.SELECTION_TEXT && selectedText.length === 0;
+ element = ( isElement || isEmptySelection ) && sel.getSelectedElement();
+ }
+
+ if ( element && element.is( 'img' ) && !element.data( 'cke-realelement' ) && !element.isReadOnly() )
+ return element;
+}
+
+function getImageAlignment( element )
+{
+ // Get alignment based on precedence of display used in browsers:
+ // 1) inline style, 2) class style, then 3) align attribute.
+ var align = element.getStyle( 'float' );
+
+ if ( align == 'inherit' || align == 'none' )
+ align = 0;
+
+ if ( !align && CKEDITOR.config.drupalimage_justifyClasses ) {
+ var justifyClasses = CKEDITOR.config.drupalimage_justifyClasses;
+ var justifyNames = [ 'left', 'center', 'right', 'block' ];
+ for (var classPosition = 0; classPosition < 4; classPosition++) {
+ if (element.hasClass(justifyClasses[classPosition]))
+ align = justifyNames[classPosition];
+ }
+ }
+
+ if ( !align )
+ align = element.getAttribute( 'align' );
+
+ return align;
+}
+
+function setImageAlignment( img, value )
+{
+ align = getImageAlignment( img );
+ if ( CKEDITOR.config.drupalimage_justifyClasses )
+ {
+ var justifyClasses = CKEDITOR.config.drupalimage_justifyClasses;
+ var justifyNames = [ 'left', 'center', 'right', 'block' ];
+ var justifyOldPosition = CKEDITOR.tools.indexOf( justifyNames, align );
+ var justifyOldClassName = justifyOldPosition === -1 ? null : justifyClasses[justifyOldPosition];
+ var justifyNewPosition = CKEDITOR.tools.indexOf( justifyNames, value );
+ var justifyNewClassName = justifyNewPosition === -1 ? null : justifyClasses[justifyNewPosition];
+ }
+
+ // If this image is already aligned, remove existing alignment.
+ if ( align )
+ {
+ img.removeStyle( 'float' );
+ img.removeAttribute( 'align' );
+ if ( justifyOldClassName )
+ img.removeClass( justifyOldClassName );
+ }
+
+ // If changing the alignment to a new value, set the new style.
+ if ( value && align !== value )
+ {
+ if ( justifyNewClassName )
+ img.addClass( justifyNewClassName );
+ else
+ img.setStyle( 'float', value );
+ }
+
+ return align !== value;
+}
+
+})();
+
+/**
+ * List of classes to use for aligning images. Each class will be used when
+ * an image is selected and the normal justify toolbar buttons are clicked. The
+ * array of classes should contain 4 members, in the following order: left,
+ * center, right, justify. If the list of classes is null, CSS style attributes
+ * will be used instead.
+ *
+ * @type Array
+ * @default true
+ * @example
+ * // Disable the list of classes and use styles instead.
+ * config.drupalimage_justifyClasses = null;
+ */
+CKEDITOR.config.drupalimage_justifyClasses = [ 'align-left', 'align-center', 'align-right', 'full-width' ];
diff --git a/core/modules/ckeditor/lib/Drupal/ckeditor/CKEditorPluginBase.php b/core/modules/ckeditor/lib/Drupal/ckeditor/CKEditorPluginBase.php
index 4050be1..cbe4499 100644
--- a/core/modules/ckeditor/lib/Drupal/ckeditor/CKEditorPluginBase.php
+++ b/core/modules/ckeditor/lib/Drupal/ckeditor/CKEditorPluginBase.php
@@ -30,7 +30,7 @@
* @see CKEditorPluginContextualInterface
* @see CKEditorPluginConfigurableInterface
*/
-abstract class CKEditorPluginBase extends PluginBase implements CKEditorPluginInterface, CKEditorPluginButtonsInterface {
+abstract class CKEditorPluginBase extends PluginBase implements CKEditorPluginInterface {
/**
* Implements \Drupal\ckeditor\Plugin\CKEditorPluginInterface::isInternal().
@@ -39,4 +39,11 @@
return FALSE;
}
+ /**
+ * Implements \Drupal\ckeditor\Plugin\CKEditorPluginInterface::getDependencies().
+ */
+ function getDependencies() {
+ return array();
+ }
+
}
diff --git a/core/modules/ckeditor/lib/Drupal/ckeditor/CKEditorPluginInterface.php b/core/modules/ckeditor/lib/Drupal/ckeditor/CKEditorPluginInterface.php
index 601cafb..6357655 100644
--- a/core/modules/ckeditor/lib/Drupal/ckeditor/CKEditorPluginInterface.php
+++ b/core/modules/ckeditor/lib/Drupal/ckeditor/CKEditorPluginInterface.php
@@ -41,6 +41,15 @@
public function isInternal();
/**
+ * Returns a list of plugins this plugin requires.
+ *
+ * @return array
+ * An unindexed array of plugin names this plugin requires. Each plugin is
+ * is identified by its annotated ID.
+ */
+ public function getDependencies();
+
+ /**
* Returns the Drupal root-relative file path to the plugin JavaScript file.
*
* Note: this does not use a Drupal library because this uses CKEditor's API,
diff --git a/core/modules/ckeditor/lib/Drupal/ckeditor/CKEditorPluginManager.php b/core/modules/ckeditor/lib/Drupal/ckeditor/CKEditorPluginManager.php
index 57a3a4b..ae797b1 100644
--- a/core/modules/ckeditor/lib/Drupal/ckeditor/CKEditorPluginManager.php
+++ b/core/modules/ckeditor/lib/Drupal/ckeditor/CKEditorPluginManager.php
@@ -61,6 +61,7 @@
$plugins = array_keys($this->getDefinitions());
$toolbar_buttons = array_unique(NestedArray::mergeDeepArray($editor->settings['toolbar']['buttons']));
$enabled_plugins = array();
+ $additional_plugins = array();
foreach ($plugins as $plugin_id) {
$plugin = $this->createInstance($plugin_id);
@@ -70,19 +71,29 @@
}
$enabled = FALSE;
+ // Enable this plugin if it provides a button that has been enabled.
if ($plugin instanceof CKEditorPluginButtonsInterface) {
$plugin_buttons = array_keys($plugin->getButtons());
$enabled = (count(array_intersect($toolbar_buttons, $plugin_buttons)) > 0);
}
+ // Otherwise enable this plugin if it declares itself as enabled.
if (!$enabled && $plugin instanceof CKEditorPluginContextualInterface) {
$enabled = $plugin->isEnabled($editor);
}
if ($enabled) {
$enabled_plugins[$plugin_id] = ($plugin->isInternal()) ? NULL : $plugin->getFile();
+ // Check if this plugin has dependencies that also need to be enabled.
+ $additional_plugins = array_merge($additional_plugins, array_diff($plugin->getDependencies(), $additional_plugins));
}
}
+ // Add the list of dependent plugins.
+ foreach ($additional_plugins as $plugin_id) {
+ $plugin = $this->createInstance($plugin_id);
+ $enabled_plugins[$plugin_id] = ($plugin->isInternal()) ? NULL : $plugin->getFile();
+ }
+
// Always return plugins in the same order.
asort($enabled_plugins);
diff --git a/core/modules/ckeditor/lib/Drupal/ckeditor/Plugin/ckeditor/plugin/DrupalDialog.php b/core/modules/ckeditor/lib/Drupal/ckeditor/Plugin/ckeditor/plugin/DrupalDialog.php
new file mode 100644
index 0000000..ebbe722
--- /dev/null
+++ b/core/modules/ckeditor/lib/Drupal/ckeditor/Plugin/ckeditor/plugin/DrupalDialog.php
@@ -0,0 +1,40 @@
+ array(
+ 'label' => t('Image'),
+ 'image' => drupal_get_path('module', 'ckeditor') . '/js/plugins/drupalimage/image.png',
+ ),
+ );
+ }
+
+}
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
index 86fe66f..16e58c6 100644
--- a/core/modules/ckeditor/lib/Drupal/ckeditor/Plugin/ckeditor/plugin/Internal.php
+++ b/core/modules/ckeditor/lib/Drupal/ckeditor/Plugin/ckeditor/plugin/Internal.php
@@ -8,6 +8,7 @@
namespace Drupal\ckeditor\Plugin\ckeditor\plugin;
use Drupal\ckeditor\CKEditorPluginBase;
+use Drupal\ckeditor\CKEditorPluginButtonsInterface;
use Drupal\Component\Utility\NestedArray;
use Drupal\Core\Annotation\Plugin;
use Drupal\Core\Annotation\Translation;
@@ -22,7 +23,7 @@
* module = "ckeditor"
* )
*/
-class Internal extends CKEditorPluginBase {
+class Internal extends CKEditorPluginBase implements CKEditorPluginButtonsInterface {
/**
* Implements \Drupal\ckeditor\Plugin\CKEditorPluginInterface::isInternal().
@@ -193,11 +194,6 @@
'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(
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
index 9f09a11..ec00a9a 100644
--- a/core/modules/ckeditor/lib/Drupal/ckeditor/Plugin/ckeditor/plugin/StylesCombo.php
+++ b/core/modules/ckeditor/lib/Drupal/ckeditor/Plugin/ckeditor/plugin/StylesCombo.php
@@ -8,6 +8,7 @@
namespace Drupal\ckeditor\Plugin\ckeditor\plugin;
use Drupal\ckeditor\CKEditorPluginBase;
+use Drupal\ckeditor\CKEditorPluginButtonsInterface;
use Drupal\ckeditor\CKEditorPluginConfigurableInterface;
use Drupal\Component\Utility\NestedArray;
use Drupal\Core\Annotation\Plugin;
@@ -23,7 +24,7 @@
* module = "ckeditor"
* )
*/
-class StylesCombo extends CKEditorPluginBase implements CKEditorPluginConfigurableInterface {
+class StylesCombo extends CKEditorPluginBase implements CKEditorPluginButtonsInterface, CKEditorPluginConfigurableInterface {
/**
* Implements \Drupal\ckeditor\Plugin\CKEditorPluginInterface::isInternal().
diff --git a/core/modules/ckeditor/lib/StylesCombo.php b/core/modules/ckeditor/lib/StylesCombo.php
new file mode 100644
index 0000000..9f09a11
--- /dev/null
+++ b/core/modules/ckeditor/lib/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: element.class|Label. Example: h1.title|Title.
These styles should be available in your theme\'s 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: element.class[.class...]|label pattern expected.
+ if (!preg_match('@^ *[a-zA-Z0-9]+ *(\\.[a-zA-Z0-9_-]+ *)+\\| *.+ *$@', $style)) {
+ return FALSE;
+ }
+
+ // Parse.
+ list($selector, $label) = explode('|', $style);
+ $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;
+ }
+
+}