Drupal 8.0.0 beta 12. More info: https://www.drupal.org/node/2514176
This commit is contained in:
commit
9921556621
13277 changed files with 1459781 additions and 0 deletions
44
core/modules/ckeditor/src/Annotation/CKEditorPlugin.php
Normal file
44
core/modules/ckeditor/src/Annotation/CKEditorPlugin.php
Normal file
|
@ -0,0 +1,44 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\ckeditor\Annotation\CKEditorPlugin.
|
||||
*/
|
||||
|
||||
namespace Drupal\ckeditor\Annotation;
|
||||
|
||||
use Drupal\Component\Annotation\Plugin;
|
||||
|
||||
/**
|
||||
* Defines a CKEditorPlugin annotation object.
|
||||
*
|
||||
* Plugin Namespace: Plugin\CKEditorPlugin
|
||||
*
|
||||
* For a working example, see \Drupal\ckeditor\Plugin\CKEditorPlugin\DrupalImage
|
||||
*
|
||||
* @see \Drupal\ckeditor\CKEditorPluginInterface
|
||||
* @see \Drupal\ckeditor\CKEditorPluginBase
|
||||
* @see \Drupal\ckeditor\CKEditorPluginManager
|
||||
* @see plugin_api
|
||||
*
|
||||
* @Annotation
|
||||
*/
|
||||
class CKEditorPlugin extends Plugin {
|
||||
|
||||
/**
|
||||
* The plugin ID.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $id;
|
||||
|
||||
/**
|
||||
* The human-readable name of the CKEditor plugin.
|
||||
*
|
||||
* @ingroup plugin_translatable
|
||||
*
|
||||
* @var \Drupal\Core\Annotation\Translation
|
||||
*/
|
||||
public $label;
|
||||
|
||||
}
|
58
core/modules/ckeditor/src/CKEditorPluginBase.php
Normal file
58
core/modules/ckeditor/src/CKEditorPluginBase.php
Normal file
|
@ -0,0 +1,58 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\ckeditor\CKEditorPluginBase.
|
||||
*/
|
||||
|
||||
namespace Drupal\ckeditor;
|
||||
|
||||
use Drupal\Core\Plugin\PluginBase;
|
||||
use Drupal\editor\Entity\Editor;
|
||||
|
||||
/**
|
||||
* Defines a base CKEditor plugin implementation.
|
||||
*
|
||||
* No other CKEditor plugins can be internal, unless a different CKEditor build
|
||||
* than the one provided by Drupal core is used. Most CKEditor plugins don't
|
||||
* need to provide additional settings forms.
|
||||
*
|
||||
* This base assumes that your plugin has buttons that you want to be enabled
|
||||
* through the toolbar builder UI. It is still possible to also implement the
|
||||
* CKEditorPluginContextualInterface (for contextual enabling) and
|
||||
* CKEditorPluginConfigurableInterface interfaces (for configuring plugin
|
||||
* settings) though.
|
||||
*
|
||||
* NOTE: the Drupal plugin ID should correspond to the CKEditor plugin name.
|
||||
*
|
||||
* @see \Drupal\ckeditor\CKEditorPluginInterface
|
||||
* @see \Drupal\ckeditor\CKEditorPluginButtonsInterface
|
||||
* @see \Drupal\ckeditor\CKEditorPluginContextualInterface
|
||||
* @see \Drupal\ckeditor\CKEditorPluginConfigurableInterface
|
||||
* @see \Drupal\ckeditor\CKEditorPluginManager
|
||||
* @see \Drupal\ckeditor\Annotation\CKEditorPlugin
|
||||
* @see plugin_api
|
||||
*/
|
||||
abstract class CKEditorPluginBase extends PluginBase implements CKEditorPluginInterface, CKEditorPluginButtonsInterface {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
function isInternal() {
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
function getDependencies(Editor $editor) {
|
||||
return array();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
function getLibraries(Editor $editor) {
|
||||
return array();
|
||||
}
|
||||
}
|
61
core/modules/ckeditor/src/CKEditorPluginButtonsInterface.php
Normal file
61
core/modules/ckeditor/src/CKEditorPluginButtonsInterface.php
Normal file
|
@ -0,0 +1,61 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\ckeditor\CKEditorPluginButtonsInterface.
|
||||
*/
|
||||
|
||||
namespace Drupal\ckeditor;
|
||||
|
||||
/**
|
||||
* Defines an interface for CKEditor plugins with buttons.
|
||||
*
|
||||
* This allows a CKEditor plugin to define which buttons it provides, so that
|
||||
* users can configure a CKEditor toolbar instance via the toolbar builder UI.
|
||||
* If at least one button that this plugin provides is added to the toolbar via
|
||||
* the toolbar builder UI, then this plugin will be enabled automatically.
|
||||
*
|
||||
* If a CKEditor plugin implements this interface, it can still also implement
|
||||
* CKEditorPluginContextualInterface if it wants a button to conditionally be
|
||||
* added as well. The downside of conditionally adding buttons is that the user
|
||||
* cannot see these buttons in the toolbar builder UI.
|
||||
*
|
||||
* @see \Drupal\ckeditor\CKEditorPluginInterface
|
||||
* @see \Drupal\ckeditor\CKEditorPluginContextualInterface
|
||||
* @see \Drupal\ckeditor\CKEditorPluginConfigurableInterface
|
||||
* @see \Drupal\ckeditor\CKEditorPluginBase
|
||||
* @see \Drupal\ckeditor\CKEditorPluginManager
|
||||
* @see \Drupal\ckeditor\Annotation\CKEditorPlugin
|
||||
* @see plugin_api
|
||||
*/
|
||||
interface CKEditorPluginButtonsInterface extends CKEditorPluginInterface {
|
||||
|
||||
/**
|
||||
* Returns the buttons that this plugin provides, along with metadata.
|
||||
*
|
||||
* The metadata is used by the CKEditor module to generate a visual CKEditor
|
||||
* toolbar builder UI.
|
||||
*
|
||||
* @return array
|
||||
* An array of buttons that are provided by this plugin. This will
|
||||
* only be used in the administrative section for assembling the toolbar.
|
||||
* Each button should by keyed by its CKEditor button name, and should
|
||||
* contain an array of button properties, including:
|
||||
* - label: A human-readable, translated button name.
|
||||
* - image: An image for the button to be used in the toolbar.
|
||||
* - image_rtl: If the image needs to have a right-to-left version, specify
|
||||
* an alternative file that will be used in RTL editors.
|
||||
* - image_alternative: If this button does not render as an image, specify
|
||||
* an HTML string representing the contents of this button.
|
||||
* - image_alternative_rtl: Similar to image_alternative, but a
|
||||
* right-to-left version.
|
||||
* - attributes: An array of HTML attributes which should be added to this
|
||||
* button when rendering the button in the administrative section for
|
||||
* assembling the toolbar.
|
||||
* - multiple: Boolean value indicating if this button may be added multiple
|
||||
* times to the toolbar. This typically is only applicable for dividers
|
||||
* and group indicators.
|
||||
*/
|
||||
public function getButtons();
|
||||
|
||||
}
|
|
@ -0,0 +1,50 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\ckeditor\CKEditorPluginConfigurableInterface.
|
||||
*/
|
||||
|
||||
namespace Drupal\ckeditor;
|
||||
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
use Drupal\editor\Entity\Editor;
|
||||
|
||||
/**
|
||||
* Defines an interface for configurable CKEditor plugins.
|
||||
*
|
||||
* This allows a CKEditor plugin to define a settings form. These settings can
|
||||
* then be automatically passed on to the corresponding CKEditor instance via
|
||||
* CKEditorPluginInterface::getConfig().
|
||||
*
|
||||
* @see \Drupal\ckeditor\CKEditorPluginInterface
|
||||
* @see \Drupal\ckeditor\CKEditorPluginButtonsInterface
|
||||
* @see \Drupal\ckeditor\CKEditorPluginContextualInterface
|
||||
* @see \Drupal\ckeditor\CKEditorPluginBase
|
||||
* @see \Drupal\ckeditor\CKEditorPluginManager
|
||||
* @see \Drupal\ckeditor\Annotation\CKEditorPlugin
|
||||
* @see plugin_api
|
||||
*/
|
||||
interface CKEditorPluginConfigurableInterface extends CKEditorPluginInterface {
|
||||
|
||||
/**
|
||||
* Returns a settings form to configure this CKEditor plugin.
|
||||
*
|
||||
* If the plugin's behavior depends on extensive options and/or external data,
|
||||
* then the implementing module can choose to provide a separate, global
|
||||
* configuration page rather than per-text-editor settings. In that case, this
|
||||
* form should provide a link to the separate settings page.
|
||||
*
|
||||
* @param array $form
|
||||
* An empty form array to be populated with a configuration form, if any.
|
||||
* @param \Drupal\Core\Form\FormStateInterface $form_state
|
||||
* The state of the entire filter administration form.
|
||||
* @param \Drupal\editor\Entity\Editor $editor
|
||||
* A configured text editor object.
|
||||
*
|
||||
* @return array|FALSE
|
||||
* A render array for the settings form, or FALSE if there is none.
|
||||
*/
|
||||
public function settingsForm(array $form, FormStateInterface $form_state, Editor $editor);
|
||||
|
||||
}
|
|
@ -0,0 +1,45 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\ckeditor\CKEditorPluginContextualInterface.
|
||||
*/
|
||||
|
||||
namespace Drupal\ckeditor;
|
||||
|
||||
use Drupal\editor\Entity\Editor;
|
||||
|
||||
/**
|
||||
* Defines an interface for contextually enabled CKEditor plugins.
|
||||
*
|
||||
* Contextually enabled CKEditor plugins can be enabled via an explicit setting,
|
||||
* or enable themselves based on the configuration of another setting, such as
|
||||
* enabling based on a particular button being present in the toolbar.
|
||||
*
|
||||
* If a contextually enabled CKEditor plugin must also be configurable (e.g. in
|
||||
* the case where it must be enabled based on an explicit setting), then one
|
||||
* must also implement the CKEditorPluginConfigurableInterface interface.
|
||||
*
|
||||
* @see \Drupal\ckeditor\CKEditorPluginInterface
|
||||
* @see \Drupal\ckeditor\CKEditorPluginButtonsInterface
|
||||
* @see \Drupal\ckeditor\CKEditorPluginConfigurableInterface
|
||||
* @see \Drupal\ckeditor\CKEditorPluginBase
|
||||
* @see \Drupal\ckeditor\CKEditorPluginManager
|
||||
* @see \Drupal\ckeditor\Annotation\CKEditorPlugin
|
||||
* @see plugin_api
|
||||
*/
|
||||
interface CKEditorPluginContextualInterface extends CKEditorPluginInterface {
|
||||
|
||||
/**
|
||||
* Checks if this plugin should be enabled based on the editor configuration.
|
||||
*
|
||||
* The editor's settings can be retrieved via $editor->getSettings().
|
||||
*
|
||||
* @param \Drupal\editor\Entity\Editor $editor
|
||||
* A configured text editor object.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isEnabled(Editor $editor);
|
||||
|
||||
}
|
102
core/modules/ckeditor/src/CKEditorPluginInterface.php
Normal file
102
core/modules/ckeditor/src/CKEditorPluginInterface.php
Normal file
|
@ -0,0 +1,102 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\ckeditor\CKEditorPluginInterface.
|
||||
*/
|
||||
|
||||
namespace Drupal\ckeditor;
|
||||
|
||||
use Drupal\Component\Plugin\PluginInspectionInterface;
|
||||
use Drupal\editor\Entity\Editor;
|
||||
|
||||
/**
|
||||
* Defines an interface for (loading of) CKEditor plugins.
|
||||
*
|
||||
* This is the most basic CKEditor plugin interface; it provides the bare
|
||||
* minimum information. Solely implementing this interface is not sufficient to
|
||||
* be able to enable the plugin though — a CKEditor plugin can either be enabled
|
||||
* automatically when a button it provides is present in the toolbar, or when
|
||||
* some programmatically defined condition is true. In the former case,
|
||||
* implement the CKEditorPluginButtonsInterface interface, in the latter case,
|
||||
* implement the CKEditorPluginContextualInterface interface. It is also
|
||||
* possible to implement both, for advanced use cases.
|
||||
*
|
||||
* Finally, if your plugin must be configurable, you can also implement the
|
||||
* CKEditorPluginConfigurableInterface interface.
|
||||
*
|
||||
* @see \Drupal\ckeditor\CKEditorPluginButtonsInterface
|
||||
* @see \Drupal\ckeditor\CKEditorPluginContextualInterface
|
||||
* @see \Drupal\ckeditor\CKEditorPluginConfigurableInterface
|
||||
* @see \Drupal\ckeditor\CKEditorPluginBase
|
||||
* @see \Drupal\ckeditor\CKEditorPluginManager
|
||||
* @see \Drupal\ckeditor\Annotation\CKEditorPlugin
|
||||
* @see plugin_api
|
||||
*/
|
||||
interface CKEditorPluginInterface extends PluginInspectionInterface {
|
||||
|
||||
/**
|
||||
* Indicates if this plugin is part of the optimized CKEditor build.
|
||||
*
|
||||
* Plugins marked as internal are implicitly loaded as part of CKEditor.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isInternal();
|
||||
|
||||
/**
|
||||
* Returns a list of plugins this plugin requires.
|
||||
*
|
||||
* @param \Drupal\editor\Entity\Editor $editor
|
||||
* A configured text editor object.
|
||||
* @return array
|
||||
* An unindexed array of plugin names this plugin requires. Each plugin is
|
||||
* is identified by its annotated ID.
|
||||
*/
|
||||
public function getDependencies(Editor $editor);
|
||||
|
||||
/**
|
||||
* Returns a list of libraries this plugin requires.
|
||||
*
|
||||
* These libraries will be attached to the text_format element on which the
|
||||
* editor is being loaded.
|
||||
*
|
||||
* @param \Drupal\editor\Entity\Editor $editor
|
||||
* A configured text editor object.
|
||||
* @return array
|
||||
* An array of libraries suitable for usage in a render API #attached
|
||||
* property.
|
||||
*/
|
||||
public function getLibraries(Editor $editor);
|
||||
|
||||
/**
|
||||
* 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,
|
||||
* see http://docs.cksource.com/ckeditor_api/symbols/CKEDITOR.resourceManager.html#addExternal.
|
||||
*
|
||||
* @return string|FALSE
|
||||
* The Drupal root-relative path to the file, FALSE if an internal plugin.
|
||||
*/
|
||||
public function getFile();
|
||||
|
||||
/**
|
||||
* Returns the additions to CKEDITOR.config for a specific CKEditor instance.
|
||||
*
|
||||
* The editor's settings can be retrieved via $editor->getSettings(), 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
|
||||
* @code
|
||||
* $settings = $editor->getSettings();
|
||||
* $plugin_specific_settings = $settings['plugins'][$plugin_id];
|
||||
* @endcode
|
||||
*
|
||||
* @param \Drupal\editor\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);
|
||||
}
|
185
core/modules/ckeditor/src/CKEditorPluginManager.php
Normal file
185
core/modules/ckeditor/src/CKEditorPluginManager.php
Normal file
|
@ -0,0 +1,185 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\ckeditor\CKEditorPluginManager.
|
||||
*/
|
||||
|
||||
namespace Drupal\ckeditor;
|
||||
|
||||
use Drupal\Component\Utility\NestedArray;
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
use Drupal\Core\Plugin\DefaultPluginManager;
|
||||
use Drupal\Core\Cache\CacheBackendInterface;
|
||||
use Drupal\Core\Extension\ModuleHandlerInterface;
|
||||
use Drupal\editor\Entity\Editor;
|
||||
|
||||
/**
|
||||
* Provides a CKEditor Plugin plugin manager.
|
||||
*
|
||||
* @see \Drupal\ckeditor\CKEditorPluginInterface
|
||||
* @see \Drupal\ckeditor\CKEditorPluginButtonsInterface
|
||||
* @see \Drupal\ckeditor\CKEditorPluginContextualInterface
|
||||
* @see \Drupal\ckeditor\CKEditorPluginConfigurableInterface
|
||||
* @see \Drupal\ckeditor\CKEditorPluginBase
|
||||
* @see \Drupal\ckeditor\Annotation\CKEditorPlugin
|
||||
* @see plugin_api
|
||||
*/
|
||||
class CKEditorPluginManager extends DefaultPluginManager {
|
||||
|
||||
/**
|
||||
* Constructs a CKEditorPluginManager object.
|
||||
*
|
||||
* @param \Traversable $namespaces
|
||||
* An object that implements \Traversable which contains the root paths
|
||||
* keyed by the corresponding namespace to look for plugin implementations.
|
||||
* @param \Drupal\Core\Cache\CacheBackendInterface $cache_backend
|
||||
* Cache backend instance to use.
|
||||
* @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
|
||||
* The module handler to invoke the alter hook with.
|
||||
*/
|
||||
public function __construct(\Traversable $namespaces, CacheBackendInterface $cache_backend, ModuleHandlerInterface $module_handler) {
|
||||
parent::__construct('Plugin/CKEditorPlugin', $namespaces, $module_handler, 'Drupal\ckeditor\CKEditorPluginInterface', 'Drupal\ckeditor\Annotation\CKEditorPlugin');
|
||||
$this->alterInfo('ckeditor_plugin_info');
|
||||
$this->setCacheBackend($cache_backend, 'ckeditor_plugins');
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves enabled plugins' files, keyed by plugin ID.
|
||||
*
|
||||
* 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\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 getEnabledPluginFiles(Editor $editor, $include_internal_plugins = FALSE) {
|
||||
$plugins = array_keys($this->getDefinitions());
|
||||
// Flatten each row.
|
||||
$toolbar_rows = array();
|
||||
$settings = $editor->getSettings();
|
||||
foreach ($settings['toolbar']['rows'] as $row_number => $row) {
|
||||
$toolbar_rows[] = array_reduce($settings['toolbar']['rows'][$row_number], function (&$result, $button_group) {
|
||||
return array_merge($result, $button_group['items']);
|
||||
}, array());
|
||||
}
|
||||
$toolbar_buttons = array_unique(NestedArray::mergeDeepArray($toolbar_rows));
|
||||
$enabled_plugins = array();
|
||||
$additional_plugins = array();
|
||||
|
||||
foreach ($plugins as $plugin_id) {
|
||||
$plugin = $this->createInstance($plugin_id);
|
||||
|
||||
if (!$include_internal_plugins && $plugin->isInternal()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$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($editor), $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);
|
||||
|
||||
return $enabled_plugins;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves all available CKEditor buttons, keyed by plugin ID.
|
||||
*
|
||||
* @return array
|
||||
* All available CKEditor buttons, with plugin IDs as keys and button
|
||||
* metadata (as implemented by getButtons()) as values.
|
||||
*
|
||||
* @see CKEditorPluginButtonsInterface::getButtons()
|
||||
*/
|
||||
public function getButtons() {
|
||||
$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 \Drupal\Core\Form\FormStateInterface $form_state
|
||||
* The current state of the form.
|
||||
* @param \Drupal\editor\Entity\Editor $editor
|
||||
* A configured text editor object.
|
||||
*/
|
||||
public function injectPluginSettingsForm(array &$form, FormStateInterface $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'],
|
||||
'#open' => TRUE,
|
||||
'#group' => 'editor][settings][plugin_settings',
|
||||
'#attributes' => array(
|
||||
'data-ckeditor-plugin-id' => $plugin_id,
|
||||
),
|
||||
);
|
||||
// Provide enough metadata for the drupal.ckeditor.admin library to
|
||||
// allow it to automatically show/hide the vertical tab containing the
|
||||
// settings for this plugin. Only do this if it's a CKEditor plugin that
|
||||
// just provides buttons, don't do this if it's a contextually enabled
|
||||
// CKEditor plugin. After all, in the latter case, we can't know when
|
||||
// its settings should be shown!
|
||||
if ($plugin instanceof CKEditorPluginButtonsInterface && !$plugin instanceof CKEditorPluginContextualInterface) {
|
||||
$form['plugins'][$plugin_id]['#attributes']['data-ckeditor-buttons'] = implode(' ', array_keys($plugin->getButtons()));
|
||||
}
|
||||
$form['plugins'][$plugin_id] += $plugin->settingsForm($plugin_settings_form, $form_state, $editor);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,93 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\ckeditor\Plugin\CKEditorPlugin\DrupalImage.
|
||||
*/
|
||||
|
||||
namespace Drupal\ckeditor\Plugin\CKEditorPlugin;
|
||||
|
||||
use Drupal\ckeditor\CKEditorPluginBase;
|
||||
use Drupal\ckeditor\CKEditorPluginConfigurableInterface;
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
use Drupal\editor\Entity\Editor;
|
||||
|
||||
/**
|
||||
* Defines the "drupalimage" plugin.
|
||||
*
|
||||
* @CKEditorPlugin(
|
||||
* id = "drupalimage",
|
||||
* label = @Translation("Image"),
|
||||
* module = "ckeditor"
|
||||
* )
|
||||
*/
|
||||
class DrupalImage extends CKEditorPluginBase implements CKEditorPluginConfigurableInterface {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getFile() {
|
||||
return drupal_get_path('module', 'ckeditor') . '/js/plugins/drupalimage/plugin.js';
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getLibraries(Editor $editor) {
|
||||
return array(
|
||||
'core/drupal.ajax',
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getConfig(Editor $editor) {
|
||||
return array(
|
||||
'drupalImage_dialogTitleAdd' => t('Insert Image'),
|
||||
'drupalImage_dialogTitleEdit' => t('Edit Image'),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getButtons() {
|
||||
return array(
|
||||
'DrupalImage' => array(
|
||||
'label' => t('Image'),
|
||||
'image' => drupal_get_path('module', 'ckeditor') . '/js/plugins/drupalimage/image.png',
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @see \Drupal\editor\Form\EditorImageDialog
|
||||
* @see editor_image_upload_settings_form()
|
||||
*/
|
||||
public function settingsForm(array $form, FormStateInterface $form_state, Editor $editor) {
|
||||
$form_state->loadInclude('editor', 'admin.inc');
|
||||
$form['image_upload'] = editor_image_upload_settings_form($editor);
|
||||
$form['image_upload']['#attached']['library'][] = 'ckeditor/drupal.ckeditor.drupalimage.admin';
|
||||
$form['image_upload']['#element_validate'][] = array($this, 'validateImageUploadSettings');
|
||||
return $form;
|
||||
}
|
||||
|
||||
/**
|
||||
* #element_validate handler for the "image_upload" element in settingsForm().
|
||||
*
|
||||
* Moves the text editor's image upload settings from the DrupalImage plugin's
|
||||
* own settings into $editor->image_upload.
|
||||
*
|
||||
* @see \Drupal\editor\Form\EditorImageDialog
|
||||
* @see editor_image_upload_settings_form()
|
||||
*/
|
||||
function validateImageUploadSettings(array $element, FormStateInterface $form_state) {
|
||||
$settings = &$form_state->getValue(array('editor', 'settings', 'plugins', 'drupalimage', 'image_upload'));
|
||||
$form_state->get('editor')->setImageUploadSettings($settings);
|
||||
$form_state->unsetValue(array('editor', 'settings', 'plugins', 'drupalimage'));
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,102 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\ckeditor\Plugin\CKEditorPlugin\DrupalImageCaption.
|
||||
*/
|
||||
|
||||
namespace Drupal\ckeditor\Plugin\CKEditorPlugin;
|
||||
|
||||
use Drupal\Component\Plugin\PluginBase;
|
||||
use Drupal\editor\Entity\Editor;
|
||||
use Drupal\ckeditor\CKEditorPluginInterface;
|
||||
use Drupal\ckeditor\CKEditorPluginContextualInterface;
|
||||
|
||||
/**
|
||||
* Defines the "drupalimagecaption" plugin.
|
||||
*
|
||||
* @CKEditorPlugin(
|
||||
* id = "drupalimagecaption",
|
||||
* label = @Translation("Drupal image caption widget"),
|
||||
* module = "ckeditor"
|
||||
* )
|
||||
*/
|
||||
class DrupalImageCaption extends PluginBase implements CKEditorPluginInterface, CKEditorPluginContextualInterface {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function isInternal() {
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getDependencies(Editor $editor) {
|
||||
return array();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getLibraries(Editor $editor) {
|
||||
return array(
|
||||
'ckeditor/drupal.ckeditor.plugins.drupalimagecaption',
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getFile() {
|
||||
return drupal_get_path('module', 'ckeditor') . '/js/plugins/drupalimagecaption/plugin.js';
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getConfig(Editor $editor) {
|
||||
$format = $editor->getFilterFormat();
|
||||
return array(
|
||||
'image2_captionedClass' => 'caption caption-img',
|
||||
'image2_alignClasses' => array('align-left', 'align-center', 'align-right'),
|
||||
'drupalImageCaption_captionPlaceholderText' => t('Enter caption here'),
|
||||
// Only enable those parts of DrupalImageCaption for which the
|
||||
// corresponding Drupal text filters are enabled.
|
||||
'drupalImageCaption_captionFilterEnabled' => $format->filters('filter_caption')->status,
|
||||
'drupalImageCaption_alignFilterEnabled' => $format->filters('filter_align')->status,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
function isEnabled(Editor $editor) {
|
||||
if (!$editor->hasAssociatedFilterFormat()) {
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
// Automatically enable this plugin if the text format associated with this
|
||||
// text editor uses the filter_align or filter_caption filter and the
|
||||
// DrupalImage button is enabled.
|
||||
$format = $editor->getFilterFormat();
|
||||
if ($format->filters('filter_align')->status || $format->filters('filter_caption')->status) {
|
||||
$enabled = FALSE;
|
||||
$settings = $editor->getSettings();
|
||||
foreach ($settings['toolbar']['rows'] as $row) {
|
||||
foreach ($row as $group) {
|
||||
foreach ($group['items'] as $button) {
|
||||
if ($button === 'DrupalImage') {
|
||||
$enabled = TRUE;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return $enabled;
|
||||
}
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,67 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\ckeditor\Plugin\CKEditorPlugin\DrupalLink.
|
||||
*/
|
||||
|
||||
namespace Drupal\ckeditor\Plugin\CKEditorPlugin;
|
||||
|
||||
use Drupal\ckeditor\CKEditorPluginBase;
|
||||
use Drupal\editor\Entity\Editor;
|
||||
|
||||
/**
|
||||
* Defines the "drupallink" plugin.
|
||||
*
|
||||
* @CKEditorPlugin(
|
||||
* id = "drupallink",
|
||||
* label = @Translation("Drupal link"),
|
||||
* module = "ckeditor"
|
||||
* )
|
||||
*/
|
||||
class DrupalLink extends CKEditorPluginBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getFile() {
|
||||
return drupal_get_path('module', 'ckeditor') . '/js/plugins/drupallink/plugin.js';
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getLibraries(Editor $editor) {
|
||||
return array(
|
||||
'core/drupal.ajax',
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getConfig(Editor $editor) {
|
||||
return array(
|
||||
'drupalLink_dialogTitleAdd' => t('Add Link'),
|
||||
'drupalLink_dialogTitleEdit' => t('Edit Link'),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getButtons() {
|
||||
$path = drupal_get_path('module', 'ckeditor') . '/js/plugins/drupallink';
|
||||
return array(
|
||||
'DrupalLink' => array(
|
||||
'label' => t('Link'),
|
||||
'image' => $path . '/link.png',
|
||||
),
|
||||
'DrupalUnlink' => array(
|
||||
'label' => t('Unlink'),
|
||||
'image' => $path . '/unlink.png',
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
}
|
522
core/modules/ckeditor/src/Plugin/CKEditorPlugin/Internal.php
Normal file
522
core/modules/ckeditor/src/Plugin/CKEditorPlugin/Internal.php
Normal file
|
@ -0,0 +1,522 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\ckeditor\Plugin\CKEditorPlugin\Internal.
|
||||
*/
|
||||
|
||||
namespace Drupal\ckeditor\Plugin\CKEditorPlugin;
|
||||
|
||||
use Drupal\ckeditor\CKEditorPluginBase;
|
||||
use Drupal\Component\Utility\NestedArray;
|
||||
use Drupal\Core\Cache\Cache;
|
||||
use Drupal\Core\Cache\CacheBackendInterface;
|
||||
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
|
||||
use Drupal\editor\Entity\Editor;
|
||||
use Drupal\filter\Plugin\FilterInterface;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
|
||||
/**
|
||||
* Defines the "internal" plugin (i.e. core plugins part of our CKEditor build).
|
||||
*
|
||||
* @CKEditorPlugin(
|
||||
* id = "internal",
|
||||
* label = @Translation("CKEditor core")
|
||||
* )
|
||||
*/
|
||||
class Internal extends CKEditorPluginBase implements ContainerFactoryPluginInterface {
|
||||
|
||||
/**
|
||||
* The cache backend.
|
||||
*
|
||||
* @var \Drupal\Core\Cache\CacheBackendInterface
|
||||
*/
|
||||
protected $cache;
|
||||
|
||||
/**
|
||||
* Constructs a \Drupal\ckeditor\Plugin\CKEditorPlugin\Internal object.
|
||||
*
|
||||
* @param array $configuration
|
||||
* A configuration array containing information about the plugin instance.
|
||||
* @param string $plugin_id
|
||||
* The plugin_id for the plugin instance.
|
||||
* @param mixed $plugin_definition
|
||||
* The plugin implementation definition.
|
||||
* @param \Drupal\Core\Cache\CacheBackendInterface $cache_backend
|
||||
* The cache backend.
|
||||
*/
|
||||
public function __construct(array $configuration, $plugin_id, $plugin_definition, CacheBackendInterface $cache_backend) {
|
||||
$this->cache = $cache_backend;
|
||||
parent::__construct($configuration, $plugin_id, $plugin_definition);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an instance of the plugin.
|
||||
*
|
||||
* @param \Symfony\Component\DependencyInjection\ContainerInterface $container
|
||||
* The container to pull out services used in the plugin.
|
||||
* @param array $configuration
|
||||
* A configuration array containing information about the plugin instance.
|
||||
* @param string $plugin_id
|
||||
* The plugin ID for the plugin instance.
|
||||
* @param mixed $plugin_definition
|
||||
* The plugin implementation definition.
|
||||
*
|
||||
* @return static
|
||||
* Returns an instance of this plugin.
|
||||
*/
|
||||
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
|
||||
return new static(
|
||||
$configuration,
|
||||
$plugin_id,
|
||||
$plugin_definition,
|
||||
$container->get('cache.default')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements \Drupal\ckeditor\Plugin\CKEditorPluginInterface::isInternal().
|
||||
*/
|
||||
public function isInternal() {
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements \Drupal\ckeditor\Plugin\CKEditorPluginInterface::getFile().
|
||||
*/
|
||||
public function getFile() {
|
||||
// This plugin is already part of Drupal core's CKEditor build.
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements \Drupal\ckeditor\Plugin\CKEditorPluginInterface::getConfig().
|
||||
*/
|
||||
public function getConfig(Editor $editor) {
|
||||
// Reasonable defaults that provide expected basic behavior.
|
||||
$config = array(
|
||||
'customConfig' => '', // Don't load CKEditor's config.js file.
|
||||
'pasteFromWordPromptCleanup' => TRUE,
|
||||
'resize_dir' => 'vertical',
|
||||
'justifyClasses' => array('text-align-left', 'text-align-center', 'text-align-right', 'text-align-justify'),
|
||||
'entities' => FALSE,
|
||||
);
|
||||
|
||||
// Add the allowedContent setting, which ensures CKEditor only allows tags
|
||||
// and attributes that are allowed by the text format for this text editor.
|
||||
list($config['allowedContent'], $config['disallowedContent']) = $this->generateACFSettings($editor);
|
||||
|
||||
// Add the format_tags setting, if its button is enabled.
|
||||
$toolbar_rows = array();
|
||||
$settings = $editor->getSettings();
|
||||
foreach ($settings['toolbar']['rows'] as $row_number => $row) {
|
||||
$toolbar_rows[] = array_reduce($settings['toolbar']['rows'][$row_number], function (&$result, $button_group) {
|
||||
return array_merge($result, $button_group['items']);
|
||||
}, array());
|
||||
}
|
||||
$toolbar_buttons = array_unique(NestedArray::mergeDeepArray($toolbar_rows));
|
||||
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 '<a href="#" class="cke-icon-only cke_' . $direction . '" role="button" title="' . $name . '" aria-label="' . $name . '"><span class="cke_button_icon cke_button__' . str_replace(' ', '', $name) . '_icon">' . $name . '</span></a>';
|
||||
};
|
||||
|
||||
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('super script'),
|
||||
),
|
||||
'Subscript' => array(
|
||||
'label' => t('Subscript'),
|
||||
'image_alternative' => $button('sub script'),
|
||||
),
|
||||
// "removeformat" plugin.
|
||||
'RemoveFormat' => array(
|
||||
'label' => t('Remove format'),
|
||||
'image_alternative' => $button('remove format'),
|
||||
),
|
||||
// "justify" plugin.
|
||||
'JustifyLeft' => array(
|
||||
'label' => t('Align left'),
|
||||
'image_alternative' => $button('justify left'),
|
||||
),
|
||||
'JustifyCenter' => array(
|
||||
'label' => t('Align center'),
|
||||
'image_alternative' => $button('justify center'),
|
||||
),
|
||||
'JustifyRight' => array(
|
||||
'label' => t('Align right'),
|
||||
'image_alternative' => $button('justify right'),
|
||||
),
|
||||
'JustifyBlock' => array(
|
||||
'label' => t('Justify'),
|
||||
'image_alternative' => $button('justify block'),
|
||||
),
|
||||
// "list" plugin.
|
||||
'BulletedList' => array(
|
||||
'label' => t('Bullet list'),
|
||||
'image_alternative' => $button('bulleted list'),
|
||||
'image_alternative_rtl' => $button('bulleted list', 'rtl'),
|
||||
),
|
||||
'NumberedList' => array(
|
||||
'label' => t('Numbered list'),
|
||||
'image_alternative' => $button('numbered list'),
|
||||
'image_alternative_rtl' => $button('numbered list', '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'),
|
||||
),
|
||||
// "blockquote" plugin.
|
||||
'Blockquote' => array(
|
||||
'label' => t('Blockquote'),
|
||||
'image_alternative' => $button('blockquote'),
|
||||
),
|
||||
// "horizontalrule" plugin
|
||||
'HorizontalRule' => array(
|
||||
'label' => t('Horizontal rule'),
|
||||
'image_alternative' => $button('horizontal rule'),
|
||||
),
|
||||
// "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('paste text'),
|
||||
'image_alternative_rtl' => $button('paste text', 'rtl'),
|
||||
),
|
||||
// "pastefromword" plugin.
|
||||
'PasteFromWord' => array(
|
||||
'label' => t('Paste from Word'),
|
||||
'image_alternative' => $button('paste from word'),
|
||||
'image_alternative_rtl' => $button('paste from word', 'rtl'),
|
||||
),
|
||||
// "specialchar" plugin.
|
||||
'SpecialChar' => array(
|
||||
'label' => t('Character map'),
|
||||
'image_alternative' => $button('special char'),
|
||||
),
|
||||
'Format' => array(
|
||||
'label' => t('HTML block format'),
|
||||
'image_alternative' => '<a href="#" role="button" aria-label="' . t('Format') . '"><span class="ckeditor-button-dropdown">' . t('Format') . '<span class="ckeditor-button-arrow"></span></span></a>',
|
||||
),
|
||||
// "table" plugin.
|
||||
'Table' => array(
|
||||
'label' => t('Table'),
|
||||
'image_alternative' => $button('table'),
|
||||
),
|
||||
// "showblocks" plugin.
|
||||
'ShowBlocks' => array(
|
||||
'label' => t('Show blocks'),
|
||||
'image_alternative' => $button('show blocks'),
|
||||
'image_alternative_rtl' => $button('show blocks', '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 "button" for toolbar builder UI use only.
|
||||
'-' => array(
|
||||
'label' => t('Separator'),
|
||||
'image_alternative' => '<a href="#" role="button" aria-label="' . t('Button separator') . '" class="ckeditor-separator"></a>',
|
||||
'attributes' => array(
|
||||
'class' => array('ckeditor-button-separator'),
|
||||
'data-drupal-ckeditor-type' => 'separator',
|
||||
),
|
||||
'multiple' => TRUE,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds the "format_tags" configuration part of the CKEditor JS settings.
|
||||
*
|
||||
* @see getConfig()
|
||||
*
|
||||
* @param \Drupal\editor\Entity\Editor $editor
|
||||
* A configured text editor object.
|
||||
*
|
||||
* @return array
|
||||
* An array containing the "format_tags" configuration.
|
||||
*
|
||||
* @see ckeditor_rebuild()
|
||||
* @see ckeditor_filter_format_insert()
|
||||
* @see ckeditor_filter_format_update()
|
||||
*/
|
||||
protected function generateFormatTagsSetting(Editor $editor) {
|
||||
// When no text format is associated yet, assume no tag is allowed.
|
||||
// @see \Drupal\Editor\EditorInterface::hasAssociatedFilterFormat()
|
||||
if (!$editor->hasAssociatedFilterFormat()) {
|
||||
return array();
|
||||
}
|
||||
|
||||
$format = $editor->getFilterFormat();
|
||||
// The <p> tag is always allowed — HTML without <p> tags is nonsensical.
|
||||
$default = 'p';
|
||||
return \Drupal::state()->get('ckeditor_internal_format_tags:' . $format->id(), $default);
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds the ACF part of the CKEditor JS settings.
|
||||
*
|
||||
* This ensures that CKEditor obeys the HTML restrictions defined by Drupal's
|
||||
* filter system, by enabling CKEditor's Advanced Content Filter (ACF)
|
||||
* functionality: http://ckeditor.com/blog/CKEditor-4.1-RC-Released.
|
||||
*
|
||||
* @see getConfig()
|
||||
*
|
||||
* @param \Drupal\editor\Entity\Editor $editor
|
||||
* A configured text editor object.
|
||||
*
|
||||
* @return array
|
||||
* An array with two values:
|
||||
* - the first value is the "allowedContent" setting: a well-formatted array
|
||||
* or TRUE. The latter indicates that anything is allowed.
|
||||
* - the second value is the "disallowedContent" setting: a well-formatted
|
||||
* array or FALSE. The latter indicates that nothing is disallowed.
|
||||
*/
|
||||
protected function generateACFSettings(Editor $editor) {
|
||||
// When no text format is associated yet, assume nothing is disallowed, so
|
||||
// set allowedContent to true.
|
||||
if (!$editor->hasAssociatedFilterFormat()) {
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
$format = $editor->getFilterFormat();
|
||||
$filter_types = $format->getFilterTypes();
|
||||
|
||||
// When nothing is disallowed, set allowedContent to true.
|
||||
if (!in_array(FilterInterface::TYPE_HTML_RESTRICTOR, $filter_types)) {
|
||||
return array(TRUE, FALSE);
|
||||
}
|
||||
// Generate setting that accurately reflects allowed tags and attributes.
|
||||
else {
|
||||
$get_attribute_values = function($attribute_values, $allowed_values) {
|
||||
$values = array_keys(array_filter($attribute_values, function($value) use ($allowed_values) {
|
||||
if ($allowed_values) {
|
||||
return $value !== FALSE;
|
||||
}
|
||||
else {
|
||||
return $value === FALSE;
|
||||
}
|
||||
}));
|
||||
if (count($values)) {
|
||||
return implode(',', $values);
|
||||
}
|
||||
else {
|
||||
return NULL;
|
||||
}
|
||||
};
|
||||
|
||||
$html_restrictions = $format->getHtmlRestrictions();
|
||||
// When all HTML is allowed, also set allowedContent to true and
|
||||
// disallowedContent to false.
|
||||
if ($html_restrictions === FALSE) {
|
||||
return array(TRUE, FALSE);
|
||||
}
|
||||
$allowed = array();
|
||||
$disallowed = array();
|
||||
if (isset($html_restrictions['forbidden_tags'])) {
|
||||
foreach ($html_restrictions['forbidden_tags'] as $tag) {
|
||||
$disallowed[$tag] = TRUE;
|
||||
}
|
||||
}
|
||||
foreach ($html_restrictions['allowed'] as $tag => $attributes) {
|
||||
// Tell CKEditor the tag is allowed, but no attributes.
|
||||
if ($attributes === FALSE) {
|
||||
$allowed[$tag] = array(
|
||||
'attributes' => FALSE,
|
||||
'styles' => FALSE,
|
||||
'classes' => FALSE,
|
||||
);
|
||||
}
|
||||
// Tell CKEditor the tag is allowed, as well as any attribute on it. The
|
||||
// "style" and "class" attributes are handled separately by CKEditor:
|
||||
// they are disallowed even if you specify it in the list of allowed
|
||||
// attributes, unless you state specific values for them that are
|
||||
// allowed. Or, in this case: any value for them is allowed.
|
||||
elseif ($attributes === TRUE) {
|
||||
$allowed[$tag] = array(
|
||||
'attributes' => TRUE,
|
||||
'styles' => TRUE,
|
||||
'classes' => TRUE,
|
||||
);
|
||||
// We've just marked that any value for the "style" and "class"
|
||||
// attributes is allowed. However, that may not be the case: the "*"
|
||||
// tag may still apply restrictions.
|
||||
// Since CKEditor's ACF follows the following principle:
|
||||
// Once validated, an element or its property cannot be
|
||||
// invalidated by another rule.
|
||||
// That means that the most permissive setting wins. Which means that
|
||||
// it will still be allowed by CKEditor to e.g. define any style, no
|
||||
// matter what the "*" tag's restrictions may be. If there's a setting
|
||||
// for either the "style" or "class" attribute, it cannot possibly be
|
||||
// more permissive than what was set above. Hence: inherit from the
|
||||
// "*" tag where possible.
|
||||
if (isset($html_restrictions['allowed']['*'])) {
|
||||
$wildcard = $html_restrictions['allowed']['*'];
|
||||
if (isset($wildcard['style'])) {
|
||||
if (!is_array($wildcard['style'])) {
|
||||
$allowed[$tag]['styles'] = $wildcard['style'];
|
||||
}
|
||||
else {
|
||||
$allowed_styles = $get_attribute_values($wildcard['style'], TRUE);
|
||||
if (isset($allowed_styles)) {
|
||||
$allowed[$tag]['styles'] = $allowed_styles;
|
||||
}
|
||||
else {
|
||||
unset($allowed[$tag]['styles']);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (isset($wildcard['class'])) {
|
||||
if (!is_array($wildcard['class'])) {
|
||||
$allowed[$tag]['classes'] = $wildcard['class'];
|
||||
}
|
||||
else {
|
||||
$allowed_classes = $get_attribute_values($wildcard['class'], TRUE);
|
||||
if (isset($allowed_classes)) {
|
||||
$allowed[$tag]['classes'] = $allowed_classes;
|
||||
}
|
||||
else {
|
||||
unset($allowed[$tag]['classes']);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// Tell CKEditor the tag is allowed, along with some tags.
|
||||
elseif (is_array($attributes)) {
|
||||
// Configure allowed attributes, allowed "style" attribute values and
|
||||
// allowed "class" attribute values.
|
||||
// CKEditor only allows specific values for the "class" and "style"
|
||||
// attributes; so ignore restrictions on other attributes, which
|
||||
// Drupal filters may provide.
|
||||
// NOTE: A Drupal contrib module can subclass this class, override the
|
||||
// getConfig() method, and override the JavaScript at
|
||||
// Drupal.editors.ckeditor to somehow make validation of values for
|
||||
// attributes other than "class" and "style" work.
|
||||
$allowed_attributes = array_filter($attributes, function($value) {
|
||||
return $value !== FALSE;
|
||||
});
|
||||
if (count($allowed_attributes)) {
|
||||
$allowed[$tag]['attributes'] = implode(',', array_keys($allowed_attributes));
|
||||
}
|
||||
if (isset($allowed_attributes['style']) && is_array($allowed_attributes['style'])) {
|
||||
$allowed_styles = $get_attribute_values($allowed_attributes['style'], TRUE);
|
||||
if (isset($allowed_styles)) {
|
||||
$allowed[$tag]['styles'] = $allowed_styles;
|
||||
}
|
||||
}
|
||||
if (isset($allowed_attributes['class']) && is_array($allowed_attributes['class'])) {
|
||||
$allowed_classes = $get_attribute_values($allowed_attributes['class'], TRUE);
|
||||
if (isset($allowed_classes)) {
|
||||
$allowed[$tag]['classes'] = $allowed_classes;
|
||||
}
|
||||
}
|
||||
|
||||
// Handle disallowed attributes analogously. However, to handle *dis-
|
||||
// allowed* attribute values, we must look at *allowed* attributes'
|
||||
// disallowed attribute values! After all, a disallowed attribute
|
||||
// implies that all of its possible attribute values are disallowed,
|
||||
// thus we must look at the disallowed attribute values on allowed
|
||||
// attributes.
|
||||
$disallowed_attributes = array_filter($attributes, function($value) {
|
||||
return $value === FALSE;
|
||||
});
|
||||
if (count($disallowed_attributes)) {
|
||||
// No need to blacklist the 'class' or 'style' attributes; CKEditor
|
||||
// handles them separately (if no specific class or style attribute
|
||||
// values are allowed, then those attributes are disallowed).
|
||||
if (isset($disallowed_attributes['class'])) {
|
||||
unset($disallowed_attributes['class']);
|
||||
}
|
||||
if (isset($disallowed_attributes['style'])) {
|
||||
unset($disallowed_attributes['style']);
|
||||
}
|
||||
$disallowed[$tag]['attributes'] = implode(',', array_keys($disallowed_attributes));
|
||||
}
|
||||
if (isset($allowed_attributes['style']) && is_array($allowed_attributes['style'])) {
|
||||
$disallowed_styles = $get_attribute_values($allowed_attributes['style'], FALSE);
|
||||
if (isset($disallowed_styles)) {
|
||||
$disallowed[$tag]['styles'] = $disallowed_styles;
|
||||
}
|
||||
}
|
||||
if (isset($allowed_attributes['class']) && is_array($allowed_attributes['class'])) {
|
||||
$disallowed_classes = $get_attribute_values($allowed_attributes['class'], FALSE);
|
||||
if (isset($disallowed_classes)) {
|
||||
$disallowed[$tag]['classes'] = $disallowed_classes;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return array($allowed, $disallowed);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
159
core/modules/ckeditor/src/Plugin/CKEditorPlugin/StylesCombo.php
Normal file
159
core/modules/ckeditor/src/Plugin/CKEditorPlugin/StylesCombo.php
Normal file
|
@ -0,0 +1,159 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\ckeditor\Plugin\CKEditorPlugin\StylesCombo.
|
||||
*/
|
||||
|
||||
namespace Drupal\ckeditor\Plugin\CKEditorPlugin;
|
||||
|
||||
use Drupal\ckeditor\CKEditorPluginBase;
|
||||
use Drupal\ckeditor\CKEditorPluginConfigurableInterface;
|
||||
use Drupal\Component\Utility\NestedArray;
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
use Drupal\editor\Entity\Editor;
|
||||
|
||||
/**
|
||||
* Defines the "stylescombo" plugin.
|
||||
*
|
||||
* @CKEditorPlugin(
|
||||
* id = "stylescombo",
|
||||
* label = @Translation("Styles dropdown")
|
||||
* )
|
||||
*/
|
||||
class StylesCombo extends CKEditorPluginBase implements CKEditorPluginConfigurableInterface {
|
||||
|
||||
/**
|
||||
* Implements \Drupal\ckeditor\Plugin\CKEditorPluginInterface::isInternal().
|
||||
*/
|
||||
public function isInternal() {
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements \Drupal\ckeditor\Plugin\CKEditorPluginInterface::getFile().
|
||||
*/
|
||||
public function getFile() {
|
||||
// This plugin is already part of Drupal core's CKEditor build.
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements \Drupal\ckeditor\Plugin\CKEditorPluginInterface::getConfig().
|
||||
*/
|
||||
public function getConfig(Editor $editor) {
|
||||
$config = array();
|
||||
$settings = $editor->getSettings();
|
||||
if (!isset($settings['plugins']['stylescombo']['styles'])) {
|
||||
return $config;
|
||||
}
|
||||
$styles = $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' => '<a href="#" role="button" aria-label="' . t('Styles') . '"><span class="ckeditor-button-dropdown">' . t('Styles') . '<span class="ckeditor-button-arrow"></span></span></a>',
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements \Drupal\ckeditor\Plugin\CKEditorPluginConfigurableInterface::settingsForm().
|
||||
*/
|
||||
public function settingsForm(array $form, FormStateInterface $form_state, Editor $editor) {
|
||||
// Defaults.
|
||||
$config = array('styles' => '');
|
||||
$settings = $editor->getSettings();
|
||||
if (isset($settings['plugins']['stylescombo'])) {
|
||||
$config = $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.<br />These styles should be available in your theme\'s CSS file.'),
|
||||
'#attached' => array(
|
||||
'library' => 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, FormStateInterface $form_state) {
|
||||
if ($this->generateStylesSetSetting($element['#value']) === FALSE) {
|
||||
$form_state->setError($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...]|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
|
||||
$configured_style = array(
|
||||
'name' => trim($label),
|
||||
'element' => trim($element),
|
||||
);
|
||||
if (!empty($classes)) {
|
||||
$configured_style['attributes'] = array(
|
||||
'class' => implode(' ', array_map('trim', $classes))
|
||||
);
|
||||
}
|
||||
$styles_set[] = $configured_style;
|
||||
}
|
||||
return $styles_set;
|
||||
}
|
||||
|
||||
}
|
426
core/modules/ckeditor/src/Plugin/Editor/CKEditor.php
Normal file
426
core/modules/ckeditor/src/Plugin/Editor/CKEditor.php
Normal file
|
@ -0,0 +1,426 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\ckeditor\Plugin\Editor\CKEditor.
|
||||
*/
|
||||
|
||||
namespace Drupal\ckeditor\Plugin\Editor;
|
||||
|
||||
use Drupal\Core\Cache\CacheBackendInterface;
|
||||
use Drupal\Core\Extension\ModuleHandlerInterface;
|
||||
use Drupal\ckeditor\CKEditorPluginManager;
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
use Drupal\Core\Language\LanguageManagerInterface;
|
||||
use Drupal\Core\Render\Element;
|
||||
use Drupal\Core\Render\RendererInterface;
|
||||
use Drupal\editor\Plugin\EditorBase;
|
||||
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
|
||||
use Drupal\editor\Entity\Editor as EditorEntity;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
|
||||
/**
|
||||
* Defines a CKEditor-based text editor for Drupal.
|
||||
*
|
||||
* @Editor(
|
||||
* id = "ckeditor",
|
||||
* label = @Translation("CKEditor"),
|
||||
* supports_content_filtering = TRUE,
|
||||
* supports_inline_editing = TRUE,
|
||||
* is_xss_safe = FALSE,
|
||||
* supported_element_types = {
|
||||
* "textarea"
|
||||
* }
|
||||
* )
|
||||
*/
|
||||
class CKEditor extends EditorBase implements ContainerFactoryPluginInterface {
|
||||
|
||||
/**
|
||||
* The module handler to invoke hooks on.
|
||||
*
|
||||
* @var \Drupal\Core\Extension\ModuleHandlerInterface
|
||||
*/
|
||||
protected $moduleHandler;
|
||||
|
||||
/**
|
||||
* The language manager.
|
||||
*
|
||||
* @var \Drupal\Core\Language\LanguageManagerInterface
|
||||
*/
|
||||
protected $languageManager;
|
||||
|
||||
/**
|
||||
* The CKEditor plugin manager.
|
||||
*
|
||||
* @var \Drupal\ckeditor\CKEditorPluginManager
|
||||
*/
|
||||
protected $ckeditorPluginManager;
|
||||
|
||||
/**
|
||||
* The renderer.
|
||||
*
|
||||
* @var \Drupal\Core\Render\RendererInterface
|
||||
*/
|
||||
protected $renderer;
|
||||
|
||||
/**
|
||||
* Constructs a Drupal\Component\Plugin\PluginBase object.
|
||||
*
|
||||
* @param array $configuration
|
||||
* A configuration array containing information about the plugin instance.
|
||||
* @param string $plugin_id
|
||||
* The plugin_id for the plugin instance.
|
||||
* @param mixed $plugin_definition
|
||||
* The plugin implementation definition.
|
||||
* @param \Drupal\ckeditor\CKEditorPluginManager $ckeditor_plugin_manager
|
||||
* The CKEditor plugin manager.
|
||||
* @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
|
||||
* The module handler to invoke hooks on.
|
||||
* @param \Drupal\Core\Language\LanguageManagerInterface $language_manager
|
||||
* The language manager.
|
||||
* @param \Drupal\Core\Render\RendererInterface $renderer
|
||||
* The renderer.
|
||||
*/
|
||||
public function __construct(array $configuration, $plugin_id, $plugin_definition, CKEditorPluginManager $ckeditor_plugin_manager, ModuleHandlerInterface $module_handler, LanguageManagerInterface $language_manager, RendererInterface $renderer) {
|
||||
parent::__construct($configuration, $plugin_id, $plugin_definition);
|
||||
$this->ckeditorPluginManager = $ckeditor_plugin_manager;
|
||||
$this->moduleHandler = $module_handler;
|
||||
$this->languageManager = $language_manager;
|
||||
$this->renderer = $renderer;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
|
||||
return new static(
|
||||
$configuration,
|
||||
$plugin_id,
|
||||
$plugin_definition,
|
||||
$container->get('plugin.manager.ckeditor.plugin'),
|
||||
$container->get('module_handler'),
|
||||
$container->get('language_manager'),
|
||||
$container->get('renderer')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getDefaultSettings() {
|
||||
return array(
|
||||
'toolbar' => array(
|
||||
'rows' => array(
|
||||
// Button groups.
|
||||
array(
|
||||
array(
|
||||
'name' => t('Formatting'),
|
||||
'items' => array('Bold', 'Italic',),
|
||||
),
|
||||
array(
|
||||
'name' => t('Links'),
|
||||
'items' => array('DrupalLink', 'DrupalUnlink',),
|
||||
),
|
||||
array(
|
||||
'name' => t('Lists'),
|
||||
'items' => array('BulletedList', 'NumberedList',),
|
||||
),
|
||||
array(
|
||||
'name' => t('Media'),
|
||||
'items' => array('Blockquote', 'DrupalImage',),
|
||||
),
|
||||
array(
|
||||
'name' => t('Tools'),
|
||||
'items' => array('Source',),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
'plugins' => array(),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function settingsForm(array $form, FormStateInterface $form_state, EditorEntity $editor) {
|
||||
$settings = $editor->getSettings();
|
||||
|
||||
$ckeditor_settings_toolbar = array(
|
||||
'#theme' => 'ckeditor_settings_toolbar',
|
||||
'#editor' => $editor,
|
||||
'#plugins' => $this->ckeditorPluginManager->getButtons(),
|
||||
);
|
||||
$form['toolbar'] = array(
|
||||
'#type' => 'container',
|
||||
'#attached' => array(
|
||||
'library' => array('ckeditor/drupal.ckeditor.admin'),
|
||||
'drupalSettings' => [
|
||||
'ckeditor' => [
|
||||
'toolbarAdmin' => $this->renderer->renderPlain($ckeditor_settings_toolbar),
|
||||
],
|
||||
],
|
||||
),
|
||||
'#attributes' => array('class' => array('ckeditor-toolbar-configuration')),
|
||||
);
|
||||
|
||||
$form['toolbar']['button_groups'] = array(
|
||||
'#type' => 'textarea',
|
||||
'#title' => t('Toolbar buttons'),
|
||||
'#default_value' => json_encode($settings['toolbar']['rows']),
|
||||
'#attributes' => array('class' => array('ckeditor-toolbar-textarea')),
|
||||
);
|
||||
|
||||
// CKEditor plugin settings, if any.
|
||||
$form['plugin_settings'] = array(
|
||||
'#type' => 'vertical_tabs',
|
||||
'#title' => t('CKEditor plugin settings'),
|
||||
'#attributes' => array(
|
||||
'id' => 'ckeditor-plugin-settings',
|
||||
),
|
||||
);
|
||||
$this->ckeditorPluginManager->injectPluginSettingsForm($form, $form_state, $editor);
|
||||
if (count(Element::children($form['plugins'])) === 0) {
|
||||
unset($form['plugins']);
|
||||
unset($form['plugin_settings']);
|
||||
}
|
||||
|
||||
// Hidden CKEditor instance. We need a hidden CKEditor instance with all
|
||||
// plugins enabled, so we can retrieve CKEditor's per-feature metadata (on
|
||||
// which tags, attributes, styles and classes are enabled). This metadata is
|
||||
// necessary for certain filters' (e.g. the html_filter filter) settings to
|
||||
// be updated accordingly.
|
||||
// Get a list of all external plugins and their corresponding files.
|
||||
$plugins = array_keys($this->ckeditorPluginManager->getDefinitions());
|
||||
$all_external_plugins = array();
|
||||
foreach ($plugins as $plugin_id) {
|
||||
$plugin = $this->ckeditorPluginManager->createInstance($plugin_id);
|
||||
if (!$plugin->isInternal()) {
|
||||
$all_external_plugins[$plugin_id] = $plugin->getFile();
|
||||
}
|
||||
}
|
||||
// Get a list of all buttons that are provided by all plugins.
|
||||
$all_buttons = array_reduce($this->ckeditorPluginManager->getButtons(), function($result, $item) {
|
||||
return array_merge($result, array_keys($item));
|
||||
}, array());
|
||||
// Build a fake Editor object, which we'll use to generate JavaScript
|
||||
// settings for this fake Editor instance.
|
||||
$fake_editor = entity_create('editor', array(
|
||||
'format' => $editor->id(),
|
||||
'editor' => 'ckeditor',
|
||||
'settings' => array(
|
||||
// Single toolbar row, single button group, all existing buttons.
|
||||
'toolbar' => array(
|
||||
'rows' => array(
|
||||
0 => array(
|
||||
0 => array(
|
||||
'name' => 'All existing buttons',
|
||||
'items' => $all_buttons,
|
||||
)
|
||||
)
|
||||
),
|
||||
),
|
||||
'plugins' => $settings['plugins'],
|
||||
),
|
||||
));
|
||||
$config = $this->getJSSettings($fake_editor);
|
||||
// Remove the ACF configuration that is generated based on filter settings,
|
||||
// because otherwise we cannot retrieve per-feature metadata.
|
||||
unset($config['allowedContent']);
|
||||
$form['hidden_ckeditor'] = array(
|
||||
'#markup' => '<div id="ckeditor-hidden" class="hidden"></div>',
|
||||
'#attached' => array(
|
||||
'drupalSettings' => ['ckeditor' => ['hiddenCKEditorConfig' => $config]],
|
||||
),
|
||||
);
|
||||
|
||||
return $form;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function settingsFormSubmit(array $form, FormStateInterface $form_state) {
|
||||
// Modify the toolbar settings by reference. The values in
|
||||
// $form_state->getValue(array('editor', 'settings')) will be saved directly
|
||||
// by editor_form_filter_admin_format_submit().
|
||||
$toolbar_settings = &$form_state->getValue(array('editor', 'settings', 'toolbar'));
|
||||
|
||||
// The rows key is not built into the form structure, so decode the button
|
||||
// groups data into this new key and remove the button_groups key.
|
||||
$toolbar_settings['rows'] = json_decode($toolbar_settings['button_groups'], TRUE);
|
||||
unset($toolbar_settings['button_groups']);
|
||||
|
||||
// Remove the plugin settings' vertical tabs state; no need to save that.
|
||||
if ($form_state->hasValue(array('editor', 'settings', 'plugins'))) {
|
||||
$form_state->unsetValue(array('editor', 'settings', 'plugin_settings'));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getJSSettings(EditorEntity $editor) {
|
||||
$settings = array();
|
||||
|
||||
// Get the settings for all enabled plugins, even the internal ones.
|
||||
$enabled_plugins = array_keys($this->ckeditorPluginManager->getEnabledPluginFiles($editor, TRUE));
|
||||
foreach ($enabled_plugins as $plugin_id) {
|
||||
$plugin = $this->ckeditorPluginManager->createInstance($plugin_id);
|
||||
$settings += $plugin->getConfig($editor);
|
||||
}
|
||||
|
||||
// Fall back on English if no matching language code was found.
|
||||
$display_langcode = 'en';
|
||||
|
||||
// Map the interface language code to a CKEditor translation if interface
|
||||
// translation is enabled.
|
||||
if ($this->moduleHandler->moduleExists('locale')) {
|
||||
$ckeditor_langcodes = $this->getLangcodes();
|
||||
$language_interface = $this->languageManager->getCurrentLanguage();
|
||||
if (isset($ckeditor_langcodes[$language_interface->getId()])) {
|
||||
$display_langcode = $ckeditor_langcodes[$language_interface->getId()];
|
||||
}
|
||||
}
|
||||
|
||||
// Next, set the most fundamental CKEditor settings.
|
||||
$external_plugin_files = $this->ckeditorPluginManager->getEnabledPluginFiles($editor);
|
||||
$settings += array(
|
||||
'toolbar' => $this->buildToolbarJSSetting($editor),
|
||||
'contentsCss' => $this->buildContentsCssJSSetting($editor),
|
||||
'extraPlugins' => implode(',', array_keys($external_plugin_files)),
|
||||
'language' => $display_langcode,
|
||||
// Configure CKEditor to not load styles.js. The StylesCombo plugin will
|
||||
// set stylesSet according to the user's settings, if the "Styles" button
|
||||
// is enabled. We cannot get rid of this until CKEditor will stop loading
|
||||
// styles.js by default.
|
||||
// See http://dev.ckeditor.com/ticket/9992#comment:9.
|
||||
'stylesSet' => FALSE,
|
||||
);
|
||||
|
||||
// Finally, set Drupal-specific CKEditor settings.
|
||||
$settings += array(
|
||||
'drupalExternalPlugins' => array_map('file_create_url', $external_plugin_files),
|
||||
);
|
||||
|
||||
// Parse all CKEditor plugin JavaScript files for translations.
|
||||
if ($this->moduleHandler->moduleExists('locale')) {
|
||||
locale_js_translate(array_values($external_plugin_files));
|
||||
}
|
||||
|
||||
ksort($settings);
|
||||
|
||||
return $settings;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a list of language codes supported by CKEditor.
|
||||
*
|
||||
* @return array
|
||||
* An associative array keyed by language codes.
|
||||
*/
|
||||
public function getLangcodes() {
|
||||
// Cache the file system based language list calculation because this would
|
||||
// be expensive to calculate all the time. The cache is cleared on core
|
||||
// upgrades which is the only situation the CKEditor file listing should
|
||||
// change.
|
||||
$langcode_cache = \Drupal::cache()->get('ckeditor.langcodes');
|
||||
if (!empty($langcode_cache)) {
|
||||
$langcodes = $langcode_cache->data;
|
||||
}
|
||||
if (empty($langcodes)) {
|
||||
$langcodes = array();
|
||||
// Collect languages included with CKEditor based on file listing.
|
||||
$ckeditor_languages = new \GlobIterator(\Drupal::root() . '/core/assets/vendor/ckeditor/lang/*.js');
|
||||
foreach ($ckeditor_languages as $language_file) {
|
||||
$langcode = $language_file->getBasename('.js');
|
||||
$langcodes[$langcode] = $langcode;
|
||||
}
|
||||
\Drupal::cache()->set('ckeditor.langcodes', $langcodes);
|
||||
}
|
||||
|
||||
// Get language mapping if available to map to Drupal language codes.
|
||||
// This is configurable in the user interface and not expensive to get, so
|
||||
// we don't include it in the cached language list.
|
||||
$language_mappings = $this->moduleHandler->moduleExists('language') ? language_get_browser_drupal_langcode_mappings() : array();
|
||||
foreach ($langcodes as $langcode) {
|
||||
// If this language code is available in a Drupal mapping, use that to
|
||||
// compute a possibility for matching from the Drupal langcode to the
|
||||
// CKEditor langcode.
|
||||
// e.g. CKEditor uses the langcode 'no' for Norwegian, Drupal uses 'nb'.
|
||||
// This would then remove the 'no' => 'no' mapping and replace it with
|
||||
// 'nb' => 'no'. Now Drupal knows which CKEditor translation to load.
|
||||
if (isset($language_mappings[$langcode]) && !isset($langcodes[$language_mappings[$langcode]])) {
|
||||
$langcodes[$language_mappings[$langcode]] = $langcode;
|
||||
unset($langcodes[$langcode]);
|
||||
}
|
||||
}
|
||||
|
||||
return $langcodes;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getLibraries(EditorEntity $editor) {
|
||||
$libraries = array(
|
||||
'ckeditor/drupal.ckeditor',
|
||||
);
|
||||
|
||||
// Get the required libraries for any enabled plugins.
|
||||
$enabled_plugins = array_keys($this->ckeditorPluginManager->getEnabledPluginFiles($editor));
|
||||
foreach ($enabled_plugins as $plugin_id) {
|
||||
$plugin = $this->ckeditorPluginManager->createInstance($plugin_id);
|
||||
$additional_libraries = array_diff($plugin->getLibraries($editor), $libraries);
|
||||
$libraries = array_merge($libraries, $additional_libraries);
|
||||
}
|
||||
|
||||
return $libraries;
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds the "toolbar" configuration part of the CKEditor JS settings.
|
||||
*
|
||||
* @see getJSSettings()
|
||||
*
|
||||
* @param \Drupal\editor\Entity\Editor $editor
|
||||
* A configured text editor object.
|
||||
* @return array
|
||||
* An array containing the "toolbar" configuration.
|
||||
*/
|
||||
public function buildToolbarJSSetting(EditorEntity $editor) {
|
||||
$toolbar = array();
|
||||
|
||||
$settings = $editor->getSettings();
|
||||
foreach ($settings['toolbar']['rows'] as $row) {
|
||||
foreach ($row as $group) {
|
||||
$toolbar[] = $group;
|
||||
}
|
||||
$toolbar[] = '/';
|
||||
}
|
||||
return $toolbar;
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds the "contentsCss" configuration part of the CKEditor JS settings.
|
||||
*
|
||||
* @see getJSSettings()
|
||||
*
|
||||
* @param \Drupal\editor\Entity\Editor $editor
|
||||
* A configured text editor object.
|
||||
* @return array
|
||||
* An array containing the "contentsCss" configuration.
|
||||
*/
|
||||
public function buildContentsCssJSSetting(EditorEntity $editor) {
|
||||
$css = array(
|
||||
drupal_get_path('module', 'ckeditor') . '/css/ckeditor-iframe.css',
|
||||
drupal_get_path('module', 'system') . '/css/system.module.css',
|
||||
);
|
||||
$this->moduleHandler->alter('ckeditor_css', $css, $editor);
|
||||
$css = array_merge($css, _ckeditor_theme_css());
|
||||
$css = array_map('file_create_url', $css);
|
||||
|
||||
return array_values($css);
|
||||
}
|
||||
|
||||
}
|
256
core/modules/ckeditor/src/Tests/CKEditorAdminTest.php
Normal file
256
core/modules/ckeditor/src/Tests/CKEditorAdminTest.php
Normal file
|
@ -0,0 +1,256 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\ckeditor\Tests\CKEditorAdminTest.
|
||||
*/
|
||||
|
||||
namespace Drupal\ckeditor\Tests;
|
||||
|
||||
use Drupal\editor\Entity\Editor;
|
||||
use Drupal\filter\FilterFormatInterface;
|
||||
use Drupal\simpletest\WebTestBase;
|
||||
|
||||
/**
|
||||
* Tests administration of CKEditor.
|
||||
*
|
||||
* @group ckeditor
|
||||
*/
|
||||
class CKEditorAdminTest extends WebTestBase {
|
||||
|
||||
/**
|
||||
* Modules to enable.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public static $modules = array('filter', 'editor', 'ckeditor');
|
||||
|
||||
/**
|
||||
* A user with the 'administer filters' permission.
|
||||
*
|
||||
* @var \Drupal\user\UserInterface
|
||||
*/
|
||||
protected $adminUser;
|
||||
|
||||
protected 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->adminUser = $this->drupalCreateUser(array('administer filters'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests configuring a text editor for an existing text format.
|
||||
*/
|
||||
function testExistingFormat() {
|
||||
$ckeditor = $this->container->get('plugin.manager.editor')->createInstance('ckeditor');
|
||||
|
||||
$this->drupalLogin($this->adminUser);
|
||||
$this->drupalGet('admin/config/content/formats/manage/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" <select> 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 "Save configuration" button.
|
||||
$edit = array(
|
||||
'editor[editor]' => 'ckeditor',
|
||||
);
|
||||
$this->drupalPostForm(NULL, $edit, t('Save configuration'));
|
||||
$this->assertRaw(t('You must configure the selected text editor.'));
|
||||
|
||||
// Ensure the CKEditor editor returns the expected default settings.
|
||||
$expected_default_settings = array(
|
||||
'toolbar' => array(
|
||||
'rows' => array(
|
||||
// Button groups
|
||||
array(
|
||||
array(
|
||||
'name' => t('Formatting'),
|
||||
'items' => array('Bold', 'Italic',),
|
||||
),
|
||||
array(
|
||||
'name' => t('Links'),
|
||||
'items' => array('DrupalLink', 'DrupalUnlink',),
|
||||
),
|
||||
array(
|
||||
'name' => t('Lists'),
|
||||
'items' => array('BulletedList', 'NumberedList',),
|
||||
),
|
||||
array(
|
||||
'name' => t('Media'),
|
||||
'items' => array('Blockquote', 'DrupalImage',),
|
||||
),
|
||||
array(
|
||||
'name' => t('Tools'),
|
||||
'items' => array('Source',),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
'plugins' => array(),
|
||||
);
|
||||
$this->assertIdentical($ckeditor->getDefaultSettings(), $expected_default_settings);
|
||||
|
||||
// Keep the "CKEditor" editor selected and click the "Configure" button.
|
||||
$this->drupalPostAjaxForm(NULL, $edit, 'editor_configure');
|
||||
$editor = entity_load('editor', 'filtered_html');
|
||||
$this->assertFalse($editor, 'No Editor config entity exists yet.');
|
||||
|
||||
// Ensure the toolbar buttons configuration value is initialized to the
|
||||
// expected default value.
|
||||
$expected_buttons_value = json_encode($expected_default_settings['toolbar']['rows']);
|
||||
$this->assertFieldByName('editor[settings][toolbar][button_groups]', $expected_buttons_value);
|
||||
|
||||
// Ensure the styles textarea exists and is initialized empty.
|
||||
$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.');
|
||||
|
||||
// Submit the form to save the selection of CKEditor as the chosen editor.
|
||||
$this->drupalPostForm(NULL, $edit, t('Save configuration'));
|
||||
|
||||
// Ensure an Editor object exists now, with the proper settings.
|
||||
$expected_settings = $expected_default_settings;
|
||||
$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->getSettings(), 'The Editor config entity has the correct settings.');
|
||||
|
||||
// Configure the Styles plugin, and ensure the updated settings are saved.
|
||||
$this->drupalGet('admin/config/content/formats/manage/filtered_html');
|
||||
$edit = array(
|
||||
'editor[settings][plugins][stylescombo][styles]' => "h1.title|Title\np.callout|Callout\n\n",
|
||||
);
|
||||
$this->drupalPostForm(NULL, $edit, t('Save configuration'));
|
||||
$expected_settings['plugins']['stylescombo']['styles'] = "h1.title|Title\np.callout|Callout\n\n";
|
||||
$editor = entity_load('editor', 'filtered_html');
|
||||
$this->assertTrue($editor instanceof Editor, 'An Editor config entity exists.');
|
||||
$this->assertIdentical($expected_settings, $editor->getSettings(), 'The Editor config entity has the correct settings.');
|
||||
|
||||
// Change the buttons that appear on the toolbar (in JavaScript, this is
|
||||
// done via drag and drop, but here we can only emulate the end result of
|
||||
// that interaction). Test multiple toolbar rows and a divider within a row.
|
||||
$this->drupalGet('admin/config/content/formats/manage/filtered_html');
|
||||
$expected_settings['toolbar']['rows'][0][] = array(
|
||||
'name' => 'Action history',
|
||||
'items' => array('Undo', '|', 'Redo', 'JustifyCenter'),
|
||||
);
|
||||
$edit = array(
|
||||
'editor[settings][toolbar][button_groups]' => json_encode($expected_settings['toolbar']['rows']),
|
||||
);
|
||||
$this->drupalPostForm(NULL, $edit, t('Save configuration'));
|
||||
$editor = entity_load('editor', 'filtered_html');
|
||||
$this->assertTrue($editor instanceof Editor, 'An Editor config entity exists.');
|
||||
$this->assertIdentical($expected_settings, $editor->getSettings(), '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.
|
||||
\Drupal::service('module_installer')->install(array('ckeditor_test'));
|
||||
$this->resetAll();
|
||||
$this->container->get('plugin.manager.ckeditor.plugin')->clearCachedDefinitions();
|
||||
$this->drupalGet('admin/config/content/formats/manage/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->getSettings(), 'The Editor config entity has the correct settings.');
|
||||
|
||||
// Finally, check the "Ultra llama mode" checkbox.
|
||||
$this->drupalGet('admin/config/content/formats/manage/filtered_html');
|
||||
$edit = array(
|
||||
'editor[settings][plugins][llama_contextual_and_button][ultra_llama_mode]' => '1',
|
||||
);
|
||||
$this->drupalPostForm(NULL, $edit, t('Save configuration'));
|
||||
$this->drupalGet('admin/config/content/formats/manage/filtered_html');
|
||||
$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'] = TRUE;
|
||||
$editor = entity_load('editor', 'filtered_html');
|
||||
$this->assertTrue($editor instanceof Editor, 'An Editor config entity exists.');
|
||||
$this->assertIdentical($expected_settings, $editor->getSettings());
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests configuring a text editor for a new text format.
|
||||
*
|
||||
* This test only needs to ensure that the basics of the CKEditor
|
||||
* configuration form work; details are tested in testExistingFormat().
|
||||
*/
|
||||
function testNewFormat() {
|
||||
$this->drupalLogin($this->adminUser);
|
||||
$this->drupalGet('admin/config/content/formats/add');
|
||||
|
||||
// Verify the "Text Editor" <select> 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.');
|
||||
|
||||
// Name our fancy new text format, select the "CKEditor" editor and click
|
||||
// the "Configure" button.
|
||||
$edit = array(
|
||||
'name' => 'My amazing text format',
|
||||
'format' => 'amazing_format',
|
||||
'editor[editor]' => 'ckeditor',
|
||||
);
|
||||
$this->drupalPostAjaxForm(NULL, $edit, 'editor_configure');
|
||||
$filter_format = entity_load('filter_format', 'amazing_format');
|
||||
$this->assertFalse($filter_format, 'No FilterFormat config entity exists yet.');
|
||||
$editor = entity_load('editor', 'amazing_format');
|
||||
$this->assertFalse($editor, 'No Editor config entity exists yet.');
|
||||
|
||||
// Ensure the toolbar buttons configuration value is initialized to the
|
||||
// default value.
|
||||
$ckeditor = $this->container->get('plugin.manager.editor')->createInstance('ckeditor');
|
||||
$default_settings = $ckeditor->getDefaultSettings();
|
||||
$expected_buttons_value = json_encode($default_settings['toolbar']['rows']);
|
||||
$this->assertFieldByName('editor[settings][toolbar][button_groups]', $expected_buttons_value);
|
||||
|
||||
// Ensure the styles textarea exists and is initialized empty.
|
||||
$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.');
|
||||
|
||||
// Submit the form to create both a new text format and an associated text
|
||||
// editor.
|
||||
$this->drupalPostForm(NULL, $edit, t('Save configuration'));
|
||||
|
||||
// Ensure a FilterFormat object exists now.
|
||||
$filter_format = entity_load('filter_format', 'amazing_format');
|
||||
$this->assertTrue($filter_format instanceof FilterFormatInterface, 'A FilterFormat config entity exists now.');
|
||||
|
||||
// Ensure an Editor object exists now, with the proper settings.
|
||||
$expected_settings = $default_settings;
|
||||
$expected_settings['plugins']['stylescombo']['styles'] = '';
|
||||
$editor = entity_load('editor', 'amazing_format');
|
||||
$this->assertTrue($editor instanceof Editor, 'An Editor config entity exists now.');
|
||||
$this->assertIdentical($expected_settings, $editor->getSettings(), 'The Editor config entity has the correct settings.');
|
||||
}
|
||||
|
||||
}
|
168
core/modules/ckeditor/src/Tests/CKEditorLoadingTest.php
Normal file
168
core/modules/ckeditor/src/Tests/CKEditorLoadingTest.php
Normal file
|
@ -0,0 +1,168 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\ckeditor\Tests\CKEditorLoadingTest.
|
||||
*/
|
||||
|
||||
namespace Drupal\ckeditor\Tests;
|
||||
|
||||
use Drupal\simpletest\WebTestBase;
|
||||
|
||||
/**
|
||||
* Tests loading of CKEditor.
|
||||
*
|
||||
* @group ckeditor
|
||||
*/
|
||||
class CKEditorLoadingTest extends WebTestBase {
|
||||
|
||||
/**
|
||||
* Modules to enable.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public static $modules = array('filter', 'editor', 'ckeditor', 'node');
|
||||
|
||||
/**
|
||||
* An untrusted user with access to only the 'plain_text' format.
|
||||
*
|
||||
* @var \Drupal\user\UserInterface
|
||||
*/
|
||||
protected $untrustedUser;
|
||||
|
||||
/**
|
||||
* A normal user with access to the 'plain_text' and 'filtered_html' formats.
|
||||
*
|
||||
* @var \Drupal\user\UserInterface
|
||||
*/
|
||||
protected $normalUser;
|
||||
|
||||
protected 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 a second format without an associated editor so a drop down select
|
||||
// list is created when selecting formats.
|
||||
$full_html_format = entity_create('filter_format', array(
|
||||
'format' => 'full_html',
|
||||
'name' => 'Full HTML',
|
||||
'weight' => 1,
|
||||
'filters' => array(),
|
||||
));
|
||||
$full_html_format->save();
|
||||
|
||||
// Create node type.
|
||||
$this->drupalCreateContentType(array(
|
||||
'type' => 'article',
|
||||
'name' => 'Article',
|
||||
));
|
||||
|
||||
$this->untrustedUser = $this->drupalCreateUser(array('create article content', 'edit any article content'));
|
||||
$this->normalUser = $this->drupalCreateUser(array('create article content', 'edit any article content', 'use text format filtered_html', 'use text format full_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->untrustedUser);
|
||||
$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->assertNoRaw(drupal_get_path('module', 'ckeditor') . '/js/ckeditor.js', 'CKEditor glue JS is absent.');
|
||||
|
||||
// On pages where there would never be a text editor, CKEditor JS is absent.
|
||||
$this->drupalGet('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->normalUser);
|
||||
$this->drupalGet('node/add/article');
|
||||
list($settings, $editor_settings_present, $editor_js_present, $body, $format_selector) = $this->getThingsToCheck();
|
||||
$ckeditor_plugin = $this->container->get('plugin.manager.editor')->createInstance('ckeditor');
|
||||
$editor = entity_load('editor', 'filtered_html');
|
||||
$expected = array('formats' => array('filtered_html' => array(
|
||||
'format' => 'filtered_html',
|
||||
'editor' => 'ckeditor',
|
||||
'editorSettings' => $ckeditor_plugin->getJSSettings($editor),
|
||||
'editorSupportsContentFiltering' => TRUE,
|
||||
'isXssSafe' => FALSE,
|
||||
)));
|
||||
$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 @data-editor-for="edit-body-0-value"]');
|
||||
$this->assertTrue(count($specific_format_selector) === 1, 'A single text format selector exists on the page and has a "data-editor-for" attribute with the correct value.');
|
||||
$this->assertTrue(in_array('ckeditor/drupal.ckeditor', explode(',', $settings['ajaxPageState']['libraries'])), 'CKEditor glue library 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.
|
||||
\Drupal::service('module_installer')->install(array('ckeditor_test'));
|
||||
$this->container->get('plugin.manager.ckeditor.plugin')->clearCachedDefinitions();
|
||||
$editor_settings = $editor->getSettings();
|
||||
$editor_settings['toolbar']['rows'][0][0]['items'][] = 'Llama';
|
||||
$editor->setSettings($editor_settings);
|
||||
$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(
|
||||
'format' => 'filtered_html',
|
||||
'editor' => 'ckeditor',
|
||||
'editorSettings' => $ckeditor_plugin->getJSSettings($editor),
|
||||
'editorSupportsContentFiltering' => TRUE,
|
||||
'isXssSafe' => FALSE,
|
||||
)));
|
||||
$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(in_array('ckeditor/drupal.ckeditor', explode(',', $settings['ajaxPageState']['libraries'])), 'CKEditor glue library is present.');
|
||||
}
|
||||
|
||||
protected function getThingsToCheck() {
|
||||
$settings = $this->getDrupalSettings();
|
||||
return array(
|
||||
// JavaScript settings.
|
||||
$settings,
|
||||
// Editor.module's JS settings present.
|
||||
isset($settings['editor']),
|
||||
// Editor.module's JS present. Note: ckeditor/drupal.ckeditor depends on
|
||||
// editor/drupal.editor, hence presence of the former implies presence of
|
||||
// the latter.
|
||||
isset($settings['ajaxPageState']['libraries']) && in_array('ckeditor/drupal.ckeditor', explode(',', $settings['ajaxPageState']['libraries'])),
|
||||
// Body field.
|
||||
$this->xpath('//textarea[@id="edit-body-0-value"]'),
|
||||
// Format selector.
|
||||
$this->xpath('//select[contains(@class, "filter-list")]'),
|
||||
);
|
||||
}
|
||||
}
|
127
core/modules/ckeditor/src/Tests/CKEditorPluginManagerTest.php
Normal file
127
core/modules/ckeditor/src/Tests/CKEditorPluginManagerTest.php
Normal file
|
@ -0,0 +1,127 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\ckeditor\Tests\CKEditorPluginManagerTest.
|
||||
*/
|
||||
|
||||
namespace Drupal\ckeditor\Tests;
|
||||
|
||||
use Drupal\simpletest\KernelTestBase;
|
||||
use Drupal\ckeditor\CKEditorPluginManager;
|
||||
|
||||
/**
|
||||
* Tests different ways of enabling CKEditor plugins.
|
||||
*
|
||||
* @group ckeditor
|
||||
*/
|
||||
class CKEditorPluginManagerTest extends KernelTestBase {
|
||||
|
||||
/**
|
||||
* Modules to enable.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public static $modules = array('system', 'user', 'filter', 'editor', 'ckeditor');
|
||||
|
||||
/**
|
||||
* The manager for "CKEditor plugin" plugins.
|
||||
*
|
||||
* @var \Drupal\Component\Plugin\PluginManagerInterface
|
||||
*/
|
||||
protected $manager;
|
||||
|
||||
protected function setUp() {
|
||||
parent::setUp();
|
||||
|
||||
// Install the Filter module.
|
||||
$this->installSchema('system', 'url_alias');
|
||||
|
||||
// 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 = $this->container->get('plugin.manager.ckeditor.plugin');
|
||||
$editor = entity_load('editor', 'filtered_html');
|
||||
|
||||
// Case 1: no CKEditor plugins.
|
||||
$definitions = array_keys($this->manager->getDefinitions());
|
||||
sort($definitions);
|
||||
$this->assertIdentical(array('drupalimage', 'drupalimagecaption', 'drupallink', 'internal', 'stylescombo'), $definitions, 'No CKEditor plugins found besides the built-in ones.');
|
||||
$enabled_plugins = array(
|
||||
'drupalimage' => 'core/modules/ckeditor/js/plugins/drupalimage/plugin.js',
|
||||
'drupallink' => 'core/modules/ckeditor/js/plugins/drupallink/plugin.js',
|
||||
);
|
||||
$this->assertIdentical($enabled_plugins, $this->manager->getEnabledPluginFiles($editor), 'Only built-in plugins are enabled.');
|
||||
$this->assertIdentical(array('internal' => NULL) + $enabled_plugins, $this->manager->getEnabledPluginFiles($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 = $this->container->get('plugin.manager.ckeditor.plugin');
|
||||
$this->manager->clearCachedDefinitions();
|
||||
|
||||
// Case 2: CKEditor plugins are available.
|
||||
$plugin_ids = array_keys($this->manager->getDefinitions());
|
||||
sort($plugin_ids);
|
||||
$this->assertIdentical(array('drupalimage', 'drupalimagecaption', 'drupallink', 'internal', 'llama', 'llama_button', 'llama_contextual', 'llama_contextual_and_button', 'stylescombo'), $plugin_ids, 'Additional CKEditor plugins found.');
|
||||
$this->assertIdentical($enabled_plugins, $this->manager->getEnabledPluginFiles($editor), 'Only the internal plugins are enabled.');
|
||||
$this->assertIdentical(array('internal' => NULL) + $enabled_plugins, $this->manager->getEnabledPluginFiles($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 'Strike' 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 "Strike" button, which will
|
||||
// cause the LlamaContextual and LlamaContextualAndButton plugins to be
|
||||
// enabled. Finally, we will add the "Strike" button back again, which would
|
||||
// cause all three plugins to be enabled.
|
||||
$settings = $editor->getSettings();
|
||||
$original_toolbar = $settings['toolbar'];
|
||||
$settings['toolbar']['rows'][0][0]['items'][] = 'Llama';
|
||||
$editor->setSettings($settings);
|
||||
$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 = $enabled_plugins + array('llama_button' => $file['b'], 'llama_contextual_and_button' => $file['cb']);
|
||||
$this->assertIdentical($expected, $this->manager->getEnabledPluginFiles($editor), 'The LlamaButton and LlamaContextualAndButton plugins are enabled.');
|
||||
$this->assertIdentical(array('internal' => NULL) + $expected, $this->manager->getEnabledPluginFiles($editor, TRUE), 'The LlamaButton and LlamaContextualAndButton plugins are enabled.');
|
||||
$settings['toolbar'] = $original_toolbar;
|
||||
$settings['toolbar']['rows'][0][0]['items'][] = 'Strike';
|
||||
$editor->setSettings($settings);
|
||||
$editor->save();
|
||||
$expected = $enabled_plugins + array('llama_contextual' => $file['c'], 'llama_contextual_and_button' => $file['cb']);
|
||||
$this->assertIdentical($expected, $this->manager->getEnabledPluginFiles($editor), 'The LLamaContextual and LlamaContextualAndButton plugins are enabled.');
|
||||
$this->assertIdentical(array('internal' => NULL) + $expected, $this->manager->getEnabledPluginFiles($editor, TRUE), 'The LlamaContextual and LlamaContextualAndButton plugins are enabled.');
|
||||
$settings['toolbar']['rows'][0][0]['items'][] = 'Llama';
|
||||
$editor->setSettings($settings);
|
||||
$editor->save();
|
||||
$expected = $enabled_plugins + array('llama_button' => $file['b'], 'llama_contextual' => $file['c'], 'llama_contextual_and_button' => $file['cb']);
|
||||
$this->assertIdentical($expected, $this->manager->getEnabledPluginFiles($editor), 'The LlamaButton, LlamaContextual and LlamaContextualAndButton plugins are enabled.');
|
||||
$this->assertIdentical(array('internal' => NULL) + $expected, $this->manager->getEnabledPluginFiles($editor, TRUE), 'The LLamaButton, LlamaContextual and LlamaContextualAndButton plugins are enabled.');
|
||||
}
|
||||
|
||||
}
|
482
core/modules/ckeditor/src/Tests/CKEditorTest.php
Normal file
482
core/modules/ckeditor/src/Tests/CKEditorTest.php
Normal file
|
@ -0,0 +1,482 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\ckeditor\Tests\CKEditorTest.
|
||||
*/
|
||||
|
||||
namespace Drupal\ckeditor\Tests;
|
||||
|
||||
use Drupal\simpletest\KernelTestBase;
|
||||
use Drupal\language\Entity\ConfigurableLanguage;
|
||||
use Drupal\editor\Entity\Editor;
|
||||
|
||||
/**
|
||||
* Tests for the 'CKEditor' text editor plugin.
|
||||
*
|
||||
* @group ckeditor
|
||||
*/
|
||||
class CKEditorTest extends KernelTestBase {
|
||||
|
||||
/**
|
||||
* Modules to enable.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public static $modules = array('system', 'user', 'filter', 'editor', 'ckeditor', 'filter_test');
|
||||
|
||||
/**
|
||||
* An instance of the "CKEditor" text editor plugin.
|
||||
*
|
||||
* @var \Drupal\ckeditor\Plugin\Editor\CKEditor;
|
||||
*/
|
||||
protected $ckeditor;
|
||||
|
||||
/**
|
||||
* The Editor Plugin Manager.
|
||||
*
|
||||
* @var \Drupal\editor\Plugin\EditorManager;
|
||||
*/
|
||||
protected $manager;
|
||||
|
||||
protected function setUp() {
|
||||
parent::setUp();
|
||||
|
||||
// Install the Filter module.
|
||||
$this->installSchema('system', 'url_alias');
|
||||
|
||||
// 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,
|
||||
'settings' => array(
|
||||
'allowed_html' => '<h4> <h5> <h6> <p> <br> <strong> <a>',
|
||||
)
|
||||
),
|
||||
),
|
||||
));
|
||||
$filtered_html_format->save();
|
||||
$editor = entity_create('editor', array(
|
||||
'format' => 'filtered_html',
|
||||
'editor' => 'ckeditor',
|
||||
));
|
||||
$editor->save();
|
||||
|
||||
// Create "CKEditor" text editor plugin instance.
|
||||
$this->ckeditor = $this->container->get('plugin.manager.editor')->createInstance('ckeditor');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests CKEditor::getJSSettings().
|
||||
*/
|
||||
function testGetJSSettings() {
|
||||
$editor = entity_load('editor', 'filtered_html');
|
||||
|
||||
// Default toolbar.
|
||||
$expected_config = $this->getDefaultInternalConfig() + array(
|
||||
'drupalImage_dialogTitleAdd' => 'Insert Image',
|
||||
'drupalImage_dialogTitleEdit' => 'Edit Image',
|
||||
'drupalLink_dialogTitleAdd' => 'Add Link',
|
||||
'drupalLink_dialogTitleEdit' => 'Edit Link',
|
||||
'allowedContent' => $this->getDefaultAllowedContentConfig(),
|
||||
'disallowedContent' => $this->getDefaultDisallowedContentConfig(),
|
||||
'toolbar' => $this->getDefaultToolbarConfig(),
|
||||
'contentsCss' => $this->getDefaultContentsCssConfig(),
|
||||
'extraPlugins' => 'drupalimage,drupallink',
|
||||
'language' => 'en',
|
||||
'stylesSet' => FALSE,
|
||||
'drupalExternalPlugins' => array(
|
||||
'drupalimage' => file_create_url('core/modules/ckeditor/js/plugins/drupalimage/plugin.js'),
|
||||
'drupallink' => file_create_url('core/modules/ckeditor/js/plugins/drupallink/plugin.js'),
|
||||
),
|
||||
);
|
||||
ksort($expected_config);
|
||||
$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'));
|
||||
$this->container->get('plugin.manager.editor')->clearCachedDefinitions();
|
||||
$this->ckeditor = $this->container->get('plugin.manager.editor')->createInstance('ckeditor');
|
||||
$this->container->get('plugin.manager.ckeditor.plugin')->clearCachedDefinitions();
|
||||
// KernelTestBase::enableModules() unfortunately doesn't invoke
|
||||
// hook_rebuild() just like a "real" Drupal site would. Do it manually.
|
||||
\Drupal::moduleHandler()->invoke('ckeditor', 'rebuild');
|
||||
$settings = $editor->getSettings();
|
||||
$settings['toolbar']['rows'][0][0]['items'][] = 'Strike';
|
||||
$settings['toolbar']['rows'][0][0]['items'][] = 'Format';
|
||||
$editor->setSettings($settings);
|
||||
$editor->save();
|
||||
$expected_config['toolbar'][0]['items'][] = 'Strike';
|
||||
$expected_config['toolbar'][0]['items'][] = 'Format';
|
||||
$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');
|
||||
ksort($expected_config);
|
||||
$this->assertIdentical($expected_config, $this->ckeditor->getJSSettings($editor), 'Generated JS settings are correct for customized configuration.');
|
||||
|
||||
// Change the allowed HTML tags; the "allowedContent" and "format_tags"
|
||||
// settings for CKEditor should automatically be updated as well.
|
||||
$format = $editor->getFilterFormat();
|
||||
$format->filters('filter_html')->settings['allowed_html'] .= '<pre> <h3>';
|
||||
$format->save();
|
||||
|
||||
$expected_config['allowedContent']['pre'] = array('attributes' => TRUE, 'styles' => FALSE, 'classes' => TRUE);
|
||||
$expected_config['allowedContent']['h3'] = array('attributes' => TRUE, 'styles' => FALSE, 'classes' => TRUE);
|
||||
$expected_config['format_tags'] = 'p;h3;h4;h5;h6;pre';
|
||||
$this->assertIdentical($expected_config, $this->ckeditor->getJSSettings($editor), 'Generated JS settings are correct for customized configuration.');
|
||||
|
||||
// Disable the filter_html filter: allow *all *tags.
|
||||
$format->setFilterConfig('filter_html', array('status' => 0));
|
||||
$format->save();
|
||||
|
||||
$expected_config['allowedContent'] = TRUE;
|
||||
$expected_config['disallowedContent'] = FALSE;
|
||||
$expected_config['format_tags'] = 'p;h1;h2;h3;h4;h5;h6;pre';
|
||||
$this->assertIdentical($expected_config, $this->ckeditor->getJSSettings($editor), 'Generated JS settings are correct for customized configuration.');
|
||||
|
||||
// Enable the filter_test_restrict_tags_and_attributes filter.
|
||||
$format->setFilterConfig('filter_test_restrict_tags_and_attributes', array(
|
||||
'status' => 1,
|
||||
'settings' => array(
|
||||
'restrictions' => array(
|
||||
'allowed' => array(
|
||||
'p' => TRUE,
|
||||
'a' => array(
|
||||
'href' => TRUE,
|
||||
'rel' => array('nofollow' => TRUE),
|
||||
'class' => array('external' => TRUE),
|
||||
'target' => array('_blank' => FALSE),
|
||||
),
|
||||
'span' => array(
|
||||
'class' => array('dodo' => FALSE),
|
||||
'property' => array('dc:*' => TRUE),
|
||||
'rel' => array('foaf:*' => FALSE),
|
||||
'style' => array('underline' => FALSE, 'color' => FALSE, 'font-size' => TRUE),
|
||||
),
|
||||
'*' => array(
|
||||
'style' => FALSE,
|
||||
'on*' => FALSE,
|
||||
'class' => array('is-a-hipster-llama' => TRUE, 'and-more' => TRUE),
|
||||
'data-*' => TRUE,
|
||||
),
|
||||
'del' => FALSE,
|
||||
),
|
||||
),
|
||||
),
|
||||
));
|
||||
$format->save();
|
||||
|
||||
$expected_config['allowedContent'] = array(
|
||||
'p' => array(
|
||||
'attributes' => TRUE,
|
||||
'styles' => FALSE,
|
||||
'classes' => 'is-a-hipster-llama,and-more',
|
||||
),
|
||||
'a' => array(
|
||||
'attributes' => 'href,rel,class,target',
|
||||
'classes' => 'external',
|
||||
),
|
||||
'span' => array(
|
||||
'attributes' => 'class,property,rel,style',
|
||||
'styles' => 'font-size',
|
||||
),
|
||||
'*' => array(
|
||||
'attributes' => 'class,data-*',
|
||||
'classes' => 'is-a-hipster-llama,and-more',
|
||||
),
|
||||
'del' => array(
|
||||
'attributes' => FALSE,
|
||||
'styles' => FALSE,
|
||||
'classes' => FALSE,
|
||||
),
|
||||
);
|
||||
$expected_config['disallowedContent'] = array(
|
||||
'span' => array(
|
||||
'styles' => 'underline,color',
|
||||
'classes' => 'dodo',
|
||||
),
|
||||
'*' => array(
|
||||
'attributes' => 'on*',
|
||||
),
|
||||
);
|
||||
$expected_config['format_tags'] = 'p';
|
||||
ksort($expected_config);
|
||||
$this->assertIdentical($expected_config, $this->ckeditor->getJSSettings($editor), 'Generated JS settings are correct for customized configuration.');
|
||||
|
||||
// Assert that we're robust enough to withstand people messing with State
|
||||
// manually.
|
||||
\Drupal::state()->delete('ckeditor_internal_format_tags:' . $format->id());
|
||||
$this->assertIdentical($expected_config, $this->ckeditor->getJSSettings($editor), 'Even when somebody manually deleted the key-value pair in State with the pre-calculated format_tags setting, it returns "p" — because the <p> tag is always allowed.');
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
$settings = $editor->getSettings();
|
||||
$settings['toolbar']['rows'][0][0]['items'][] = 'Strike';
|
||||
$editor->setSettings($settings);
|
||||
$editor->save();
|
||||
$expected[0]['items'][] = 'Strike';
|
||||
$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'));
|
||||
$this->container->get('plugin.manager.ckeditor.plugin')->clearCachedDefinitions();
|
||||
// Override the label of a toolbar component.
|
||||
$settings['toolbar']['rows'][0][0]['name'] = 'JunkScience';
|
||||
$settings['toolbar']['rows'][0][0]['items'][] = 'Llama';
|
||||
$editor->setSettings($settings);
|
||||
$editor->save();
|
||||
$expected[0]['name'] = 'JunkScience';
|
||||
$expected[0]['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.');
|
||||
|
||||
// Enable the Bartik theme, which specifies a CKEditor stylesheet.
|
||||
\Drupal::service('theme_handler')->install(['bartik']);
|
||||
$this->config('system.theme')->set('default', 'bartik')->save();
|
||||
$expected[] = file_create_url('core/themes/bartik/css/base/elements.css');
|
||||
$expected[] = file_create_url('core/themes/bartik/css/components/captions.css');
|
||||
$expected[] = file_create_url('core/themes/bartik/css/components/content.css');
|
||||
$expected[] = file_create_url('core/themes/bartik/css/components/table.css');
|
||||
$this->assertIdentical($expected, $this->ckeditor->buildContentsCssJSSetting($editor), '"contentsCss" configuration part of JS settings built correctly while a theme providing a CKEditor stylesheet exists.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests Internal::getConfig().
|
||||
*/
|
||||
function testInternalGetConfig() {
|
||||
$editor = entity_load('editor', 'filtered_html');
|
||||
$internal_plugin = $this->container->get('plugin.manager.ckeditor.plugin')->createInstance('internal');
|
||||
|
||||
// Default toolbar.
|
||||
$expected = $this->getDefaultInternalConfig();
|
||||
$expected['disallowedContent'] = $this->getDefaultDisallowedContentConfig();
|
||||
$expected['allowedContent'] = $this->getDefaultAllowedContentConfig();
|
||||
$this->assertEqual($expected, $internal_plugin->getConfig($editor), '"Internal" plugin configuration built correctly for default toolbar.');
|
||||
|
||||
// Format dropdown/button enabled: new setting should be present.
|
||||
$settings = $editor->getSettings();
|
||||
$settings['toolbar']['rows'][0][0]['items'][] = 'Format';
|
||||
$editor->setSettings($settings);
|
||||
$expected['format_tags'] = 'p;h4;h5;h6';
|
||||
$this->assertEqual($expected, $internal_plugin->getConfig($editor), '"Internal" plugin configuration built correctly for customized toolbar.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests StylesCombo::getConfig().
|
||||
*/
|
||||
function testStylesComboGetConfig() {
|
||||
$editor = entity_load('editor', 'filtered_html');
|
||||
$stylescombo_plugin = $this->container->get('plugin.manager.ckeditor.plugin')->createInstance('stylescombo');
|
||||
|
||||
// Styles dropdown/button enabled: new setting should be present.
|
||||
$settings = $editor->getSettings();
|
||||
$settings['toolbar']['rows'][0][0]['items'][] = 'Styles';
|
||||
$settings['plugins']['stylescombo']['styles'] = '';
|
||||
$editor->setSettings($settings);
|
||||
$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.
|
||||
$settings['plugins']['stylescombo']['styles'] = " \n";
|
||||
$editor->setSettings($settings);
|
||||
$editor->save();
|
||||
$this->assertIdentical($expected, $stylescombo_plugin->getConfig($editor));
|
||||
$settings['plugins']['stylescombo']['styles'] = "\r\n \n \r \n ";
|
||||
$editor->setSettings($settings);
|
||||
$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.
|
||||
$settings['plugins']['stylescombo']['styles'] = "h1.title|Title\np.mAgical.Callout|Callout";
|
||||
$editor->setSettings($settings);
|
||||
$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.
|
||||
$settings['plugins']['stylescombo']['styles'] = " h1 .title | Title \r \n\r \np.mAgical .Callout|Callout\r";
|
||||
$editor->setSettings($settings);
|
||||
$editor->save();
|
||||
$this->assertIdentical($expected, $stylescombo_plugin->getConfig($editor), '"StylesCombo" plugin configuration built correctly for customized toolbar.');
|
||||
|
||||
// Slightly different configuration: class names are optional.
|
||||
$settings['plugins']['stylescombo']['styles'] = " h1 | Title ";
|
||||
$editor->setSettings($settings);
|
||||
$editor->save();
|
||||
$expected['stylesSet'] = array(array('name' => 'Title', 'element' => 'h1'));
|
||||
$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.
|
||||
$settings['plugins']['stylescombo']['styles'] = "h1";
|
||||
$editor->setSettings($settings);
|
||||
$editor->save();
|
||||
$expected['stylesSet'] = FALSE;
|
||||
$this->assertIdentical($expected, $stylescombo_plugin->getConfig($editor), '"StylesCombo" plugin configuration built correctly for customized toolbar.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests language list availability in CKEditor.
|
||||
*/
|
||||
function testLanguages() {
|
||||
// Get CKEditor supported language codes and spot-check.
|
||||
$this->enableModules(array('language'));
|
||||
$this->installConfig(array('language'));
|
||||
$langcodes = $this->ckeditor->getLangcodes();
|
||||
|
||||
// Language codes transformed with browser mappings.
|
||||
$this->assertTrue($langcodes['pt-pt'] == 'pt', '"pt" properly resolved');
|
||||
$this->assertTrue($langcodes['zh-hans'] == 'zh-cn', '"zh-hans" properly resolved');
|
||||
|
||||
// Language code both in Drupal and CKEditor.
|
||||
$this->assertTrue($langcodes['gl'] == 'gl', '"gl" properly resolved');
|
||||
|
||||
// Language codes only in CKEditor.
|
||||
$this->assertTrue($langcodes['en-au'] == 'en-au', '"en-au" properly resolved');
|
||||
$this->assertTrue($langcodes['sr-latn'] == 'sr-latn', '"sr-latn" properly resolved');
|
||||
|
||||
// No locale module, so even though languages are enabled, CKEditor should
|
||||
// still be in English.
|
||||
$this->assertCKEditorLanguage('en');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that CKEditor plugins participate in JS translation.
|
||||
*/
|
||||
function testJSTranslation() {
|
||||
$this->enableModules(array('language', 'locale'));
|
||||
$this->installSchema('locale', 'locales_source');
|
||||
$this->installSchema('locale', 'locales_location');
|
||||
$this->installSchema('locale', 'locales_target');
|
||||
$editor = Editor::load('filtered_html');
|
||||
$this->ckeditor->getJSSettings($editor);
|
||||
$localeStorage = $this->container->get('locale.storage');
|
||||
$string = $localeStorage->findString(array('source' => 'Edit Link', 'context' => ''));
|
||||
$this->assertTrue(!empty($string), 'String from JavaScript file saved.');
|
||||
|
||||
// With locale module, CKEditor should not adhere to the language selected.
|
||||
$this->assertCKEditorLanguage();
|
||||
}
|
||||
|
||||
/**
|
||||
* Assert that CKEditor picks the expected language when French is default.
|
||||
*
|
||||
* @param string $langcode
|
||||
* Language code to assert for. Defaults to French. That is the default
|
||||
* language set in this assertion.
|
||||
*/
|
||||
protected function assertCKEditorLanguage($langcode = 'fr') {
|
||||
// Set French as the site default language.
|
||||
ConfigurableLanguage::createFromLangcode('fr')->save();
|
||||
$this->config('system.site')->set('default_langcode', 'fr')->save();
|
||||
|
||||
// Reset the language manager so new negotiations attempts will fall back on
|
||||
// French. Reinject the language manager CKEditor to use the current one.
|
||||
$this->container->get('language_manager')->reset();
|
||||
$this->ckeditor = $this->container->get('plugin.manager.editor')->createInstance('ckeditor');
|
||||
|
||||
// Test that we now get the expected language.
|
||||
$editor = Editor::load('filtered_html');
|
||||
$settings = $this->ckeditor->getJSSettings($editor);
|
||||
$this->assertEqual($settings['language'], $langcode);
|
||||
}
|
||||
|
||||
protected function getDefaultInternalConfig() {
|
||||
return array(
|
||||
'customConfig' => '',
|
||||
'pasteFromWordPromptCleanup' => TRUE,
|
||||
'resize_dir' => 'vertical',
|
||||
'justifyClasses' => array('text-align-left', 'text-align-center', 'text-align-right', 'text-align-justify'),
|
||||
'entities' => FALSE,
|
||||
);
|
||||
}
|
||||
|
||||
protected function getDefaultAllowedContentConfig() {
|
||||
return array(
|
||||
'h4' => array('attributes' => TRUE, 'styles' => FALSE, 'classes' => TRUE),
|
||||
'h5' => array('attributes' => TRUE, 'styles' => FALSE, 'classes' => TRUE),
|
||||
'h6' => array('attributes' => TRUE, 'styles' => FALSE, 'classes' => TRUE),
|
||||
'p' => array('attributes' => TRUE, 'styles' => FALSE, 'classes' => TRUE),
|
||||
'br' => array('attributes' => TRUE, 'styles' => FALSE, 'classes' => TRUE),
|
||||
'strong' => array('attributes' => TRUE, 'styles' => FALSE, 'classes' => TRUE),
|
||||
'a' => array('attributes' => TRUE, 'styles' => FALSE, 'classes' => TRUE),
|
||||
);
|
||||
}
|
||||
|
||||
protected function getDefaultDisallowedContentConfig() {
|
||||
return array(
|
||||
'*' => array('attributes' => 'on*'),
|
||||
);
|
||||
}
|
||||
|
||||
protected function getDefaultToolbarConfig() {
|
||||
return array(
|
||||
array(
|
||||
'name' => t('Formatting'),
|
||||
'items' => array('Bold', 'Italic',),
|
||||
),
|
||||
array(
|
||||
'name' => t('Links'),
|
||||
'items' => array('DrupalLink', 'DrupalUnlink',),
|
||||
),
|
||||
array(
|
||||
'name' => t('Lists'),
|
||||
'items' => array('BulletedList', 'NumberedList',),
|
||||
),
|
||||
array(
|
||||
'name' => t('Media'),
|
||||
'items' => array('Blockquote', 'DrupalImage',),
|
||||
),
|
||||
array(
|
||||
'name' => t('Tools'),
|
||||
'items' => array('Source',),
|
||||
),
|
||||
'/',
|
||||
);
|
||||
}
|
||||
|
||||
protected function getDefaultContentsCssConfig() {
|
||||
return array(
|
||||
file_create_url('core/modules/ckeditor/css/ckeditor-iframe.css'),
|
||||
file_create_url('core/modules/system/css/system.module.css'),
|
||||
);
|
||||
}
|
||||
|
||||
}
|
Reference in a new issue