Move into nested docroot
This commit is contained in:
parent
83a0d3a149
commit
c8b70abde9
13405 changed files with 0 additions and 0 deletions
40
web/core/modules/ckeditor/src/Annotation/CKEditorPlugin.php
Normal file
40
web/core/modules/ckeditor/src/Annotation/CKEditorPlugin.php
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
<?php
|
||||
|
||||
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 hook_ckeditor_plugin_info_alter()
|
||||
* @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;
|
||||
|
||||
}
|
||||
54
web/core/modules/ckeditor/src/CKEditorPluginBase.php
Normal file
54
web/core/modules/ckeditor/src/CKEditorPluginBase.php
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
<?php
|
||||
|
||||
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 class 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).
|
||||
*
|
||||
* 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();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,57 @@
|
|||
<?php
|
||||
|
||||
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\CKEditorPluginCssInterface
|
||||
* @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,46 @@
|
|||
<?php
|
||||
|
||||
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\CKEditorPluginCssInterface
|
||||
* @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
|
||||
* A render array for the settings form.
|
||||
*/
|
||||
public function settingsForm(array $form, FormStateInterface $form_state, Editor $editor);
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,42 @@
|
|||
<?php
|
||||
|
||||
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 (for
|
||||
* instance, 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\CKEditorPluginCssInterface
|
||||
* @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);
|
||||
|
||||
}
|
||||
39
web/core/modules/ckeditor/src/CKEditorPluginCssInterface.php
Normal file
39
web/core/modules/ckeditor/src/CKEditorPluginCssInterface.php
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\ckeditor;
|
||||
|
||||
use Drupal\editor\Entity\Editor;
|
||||
|
||||
/**
|
||||
* Defines an interface for CKEditor plugins with associated CSS.
|
||||
*
|
||||
* This allows a CKEditor plugin to add additional CSS in iframe CKEditor
|
||||
* instances without needing to implement hook_ckeditor_css_alter().
|
||||
*
|
||||
* @see \Drupal\ckeditor\CKEditorPluginInterface
|
||||
* @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 CKEditorPluginCssInterface extends CKEditorPluginInterface {
|
||||
|
||||
/**
|
||||
* Retrieves enabled plugins' iframe instance CSS files.
|
||||
*
|
||||
* Note: this does not use a Drupal asset library because this CSS will be
|
||||
* loaded by CKEditor, not by Drupal.
|
||||
*
|
||||
* @param \Drupal\editor\Entity\Editor $editor
|
||||
* A configured text editor object.
|
||||
*
|
||||
* @return string[]
|
||||
* An array of CSS files. This is a flat list of file paths relative to
|
||||
* the Drupal root.
|
||||
*/
|
||||
public function getCssFiles(Editor $editor);
|
||||
|
||||
}
|
||||
99
web/core/modules/ckeditor/src/CKEditorPluginInterface.php
Normal file
99
web/core/modules/ckeditor/src/CKEditorPluginInterface.php
Normal file
|
|
@ -0,0 +1,99 @@
|
|||
<?php
|
||||
|
||||
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\CKEditorPluginCssInterface
|
||||
* @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);
|
||||
|
||||
}
|
||||
221
web/core/modules/ckeditor/src/CKEditorPluginManager.php
Normal file
221
web/core/modules/ckeditor/src/CKEditorPluginManager.php
Normal file
|
|
@ -0,0 +1,221 @@
|
|||
<?php
|
||||
|
||||
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\CKEditorPluginCssInterface
|
||||
* @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());
|
||||
$toolbar_buttons = $this->getEnabledButtons($editor);
|
||||
$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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the enabled toolbar buttons in the given text editor instance.
|
||||
*
|
||||
* @param \Drupal\editor\Entity\Editor $editor
|
||||
* A configured text editor object.
|
||||
*
|
||||
* @return string[]
|
||||
* A list of the toolbar buttons enabled in the given text editor instance.
|
||||
*/
|
||||
public static function getEnabledButtons(Editor $editor) {
|
||||
$toolbar_rows = [];
|
||||
$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']);
|
||||
}, []);
|
||||
}
|
||||
return array_unique(NestedArray::mergeDeepArray($toolbar_rows));
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 \Drupal\ckeditor\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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves enabled plugins' iframe instance CSS files, keyed by plugin ID.
|
||||
*
|
||||
* @param \Drupal\editor\Entity\Editor $editor
|
||||
* A configured text editor object.
|
||||
*
|
||||
* @return string[]
|
||||
* Enabled plugins CKEditor CSS files, with plugin IDs as keys and CSS file
|
||||
* paths relative to the Drupal root (as implemented by getCssFiles()) as
|
||||
* values.
|
||||
*
|
||||
* @see \Drupal\ckeditor\CKEditorPluginCssInterface::getCssFiles()
|
||||
*/
|
||||
public function getCssFiles(Editor $editor) {
|
||||
$enabled_plugins = array_keys($this->getEnabledPluginFiles($editor, TRUE));
|
||||
$css_files = array();
|
||||
|
||||
foreach ($enabled_plugins as $plugin_id) {
|
||||
$plugin = $this->createInstance($plugin_id);
|
||||
if ($plugin instanceof CKEditorPluginCssInterface) {
|
||||
$css_files[$plugin_id] = $plugin->getCssFiles($editor);
|
||||
}
|
||||
}
|
||||
|
||||
return $css_files;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,88 @@
|
|||
<?php
|
||||
|
||||
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/icons/drupalimage.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,107 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\ckeditor\Plugin\CKEditorPlugin;
|
||||
|
||||
use Drupal\Component\Plugin\PluginBase;
|
||||
use Drupal\editor\Entity\Editor;
|
||||
use Drupal\ckeditor\CKEditorPluginInterface;
|
||||
use Drupal\ckeditor\CKEditorPluginContextualInterface;
|
||||
use Drupal\ckeditor\CKEditorPluginCssInterface;
|
||||
|
||||
/**
|
||||
* Defines the "drupalimagecaption" plugin.
|
||||
*
|
||||
* @CKEditorPlugin(
|
||||
* id = "drupalimagecaption",
|
||||
* label = @Translation("Drupal image caption widget"),
|
||||
* module = "ckeditor"
|
||||
* )
|
||||
*/
|
||||
class DrupalImageCaption extends PluginBase implements CKEditorPluginInterface, CKEditorPluginContextualInterface, CKEditorPluginCssInterface {
|
||||
|
||||
/**
|
||||
* {@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}
|
||||
*/
|
||||
public function getCssFiles(Editor $editor) {
|
||||
return array(
|
||||
drupal_get_path('module', 'ckeditor') . '/css/plugins/drupalimagecaption/ckeditor.drupalimagecaption.css'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@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,62 @@
|
|||
<?php
|
||||
|
||||
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 . '/icons/drupallink.png',
|
||||
),
|
||||
'DrupalUnlink' => array(
|
||||
'label' => t('Unlink'),
|
||||
'image' => $path . '/icons/drupalunlink.png',
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
607
web/core/modules/ckeditor/src/Plugin/CKEditorPlugin/Internal.php
Normal file
607
web/core/modules/ckeditor/src/Plugin/CKEditorPlugin/Internal.php
Normal file
|
|
@ -0,0 +1,607 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\ckeditor\Plugin\CKEditorPlugin;
|
||||
|
||||
use Drupal\ckeditor\CKEditorPluginBase;
|
||||
use Drupal\ckeditor\CKEditorPluginContextualInterface;
|
||||
use Drupal\ckeditor\CKEditorPluginManager;
|
||||
use Drupal\Component\Utility\Html;
|
||||
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, CKEditorPluginContextualInterface {
|
||||
|
||||
/**
|
||||
* 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')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function isInternal() {
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function isEnabled(Editor $editor) {
|
||||
// This plugin represents the core CKEditor plugins. They're always enabled:
|
||||
// its configuration is always necessary.
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getFile() {
|
||||
// This plugin is already part of Drupal core's CKEditor build.
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
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,
|
||||
'disableNativeSpellChecker' => 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_buttons = CKEditorPluginManager::getEnabledButtons($editor);
|
||||
if (in_array('Format', $toolbar_buttons)) {
|
||||
$config['format_tags'] = $this->generateFormatTagsSetting($editor);
|
||||
}
|
||||
|
||||
return $config;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getButtons() {
|
||||
$button = function($name, $direction = 'ltr') {
|
||||
// In the markup below, we mostly use the name (which may include spaces),
|
||||
// but in one spot we use it as a CSS class, so strip spaces.
|
||||
// Note: this uses str_replace() instead of Html::cleanCssIdentifier()
|
||||
// because we must provide these class names exactly how CKEditor expects
|
||||
// them in its library, which cleanCssIdentifier() does not do.
|
||||
$class_name = str_replace(' ', '', $name);
|
||||
return [
|
||||
'#type' => 'inline_template',
|
||||
'#template' => '<a href="#" class="cke-icon-only cke_{{ direction }}" role="button" title="{{ name }}" aria-label="{{ name }}"><span class="cke_button_icon cke_button__{{ classname }}_icon">{{ name }}</span></a>',
|
||||
'#context' => [
|
||||
'direction' => $direction,
|
||||
'name' => $name,
|
||||
'classname' => $class_name,
|
||||
],
|
||||
];
|
||||
};
|
||||
|
||||
return array(
|
||||
// "basicstyles" plugin.
|
||||
'Bold' => array(
|
||||
'label' => t('Bold'),
|
||||
'image_alternative' => $button('bold'),
|
||||
'image_alternative_rtl' => $button('bold', 'rtl'),
|
||||
),
|
||||
'Italic' => array(
|
||||
'label' => t('Italic'),
|
||||
'image_alternative' => $button('italic'),
|
||||
'image_alternative_rtl' => $button('italic', 'rtl'),
|
||||
),
|
||||
'Underline' => array(
|
||||
'label' => t('Underline'),
|
||||
'image_alternative' => $button('underline'),
|
||||
'image_alternative_rtl' => $button('underline', 'rtl'),
|
||||
),
|
||||
'Strike' => array(
|
||||
'label' => t('Strike-through'),
|
||||
'image_alternative' => $button('strike'),
|
||||
'image_alternative_rtl' => $button('strike', 'rtl'),
|
||||
),
|
||||
'Superscript' => array(
|
||||
'label' => t('Superscript'),
|
||||
'image_alternative' => $button('super script'),
|
||||
'image_alternative_rtl' => $button('super script', 'rtl'),
|
||||
),
|
||||
'Subscript' => array(
|
||||
'label' => t('Subscript'),
|
||||
'image_alternative' => $button('sub script'),
|
||||
'image_alternative_rtl' => $button('sub script', 'rtl'),
|
||||
),
|
||||
// "removeformat" plugin.
|
||||
'RemoveFormat' => array(
|
||||
'label' => t('Remove format'),
|
||||
'image_alternative' => $button('remove format'),
|
||||
'image_alternative_rtl' => $button('remove format', 'rtl'),
|
||||
),
|
||||
// "justify" plugin.
|
||||
'JustifyLeft' => array(
|
||||
'label' => t('Align left'),
|
||||
'image_alternative' => $button('justify left'),
|
||||
'image_alternative_rtl' => $button('justify left', 'rtl'),
|
||||
),
|
||||
'JustifyCenter' => array(
|
||||
'label' => t('Align center'),
|
||||
'image_alternative' => $button('justify center'),
|
||||
'image_alternative_rtl' => $button('justify center', 'rtl'),
|
||||
),
|
||||
'JustifyRight' => array(
|
||||
'label' => t('Align right'),
|
||||
'image_alternative' => $button('justify right'),
|
||||
'image_alternative_rtl' => $button('justify right', 'rtl'),
|
||||
),
|
||||
'JustifyBlock' => array(
|
||||
'label' => t('Justify'),
|
||||
'image_alternative' => $button('justify block'),
|
||||
'image_alternative_rtl' => $button('justify block', 'rtl'),
|
||||
),
|
||||
// "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'),
|
||||
'image_alternative_rtl' => $button('blockquote', 'rtl'),
|
||||
),
|
||||
// "horizontalrule" plugin
|
||||
'HorizontalRule' => array(
|
||||
'label' => t('Horizontal rule'),
|
||||
'image_alternative' => $button('horizontal rule'),
|
||||
'image_alternative_rtl' => $button('horizontal rule', 'rtl'),
|
||||
),
|
||||
// "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'),
|
||||
'image_alternative_rtl' => $button('special char', 'rtl'),
|
||||
),
|
||||
'Format' => array(
|
||||
'label' => t('HTML block format'),
|
||||
'image_alternative' => [
|
||||
'#type' => 'inline_template',
|
||||
'#template' => '<a href="#" role="button" aria-label="{{ format_text }}"><span class="ckeditor-button-dropdown">{{ format_text }}<span class="ckeditor-button-arrow"></span></span></a>',
|
||||
'#context' => [
|
||||
'format_text' => t('Format'),
|
||||
],
|
||||
],
|
||||
),
|
||||
// "table" plugin.
|
||||
'Table' => array(
|
||||
'label' => t('Table'),
|
||||
'image_alternative' => $button('table'),
|
||||
'image_alternative_rtl' => $button('table', 'rtl'),
|
||||
),
|
||||
// "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'),
|
||||
'image_alternative_rtl' => $button('source', 'rtl'),
|
||||
),
|
||||
// "maximize" plugin.
|
||||
'Maximize' => array(
|
||||
'label' => t('Maximize'),
|
||||
'image_alternative' => $button('maximize'),
|
||||
'image_alternative_rtl' => $button('maximize', 'rtl'),
|
||||
),
|
||||
// No plugin, separator "button" for toolbar builder UI use only.
|
||||
'-' => array(
|
||||
'label' => t('Separator'),
|
||||
'image_alternative' => [
|
||||
'#type' => 'inline_template',
|
||||
'#template' => '<a href="#" role="button" aria-label="{{ button_separator_text }}" class="ckeditor-separator"></a>',
|
||||
'#context' => [
|
||||
'button_separator_text' => t('Button separator'),
|
||||
],
|
||||
],
|
||||
'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.
|
||||
*/
|
||||
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();
|
||||
$cid = 'ckeditor_internal_format_tags:' . $format->id();
|
||||
|
||||
if ($cached = $this->cache->get($cid)) {
|
||||
$format_tags = $cached->data;
|
||||
}
|
||||
else {
|
||||
// The <p> tag is always allowed — HTML without <p> tags is nonsensical.
|
||||
$format_tags = ['p'];
|
||||
|
||||
// Given the list of possible format tags, automatically determine whether
|
||||
// the current text format allows this tag, and thus whether it should show
|
||||
// up in the "Format" dropdown.
|
||||
$possible_format_tags = ['h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'pre'];
|
||||
foreach ($possible_format_tags as $tag) {
|
||||
$input = '<' . $tag . '>TEST</' . $tag . '>';
|
||||
$output = trim(check_markup($input, $editor->id()));
|
||||
if (Html::load($output)->getElementsByTagName($tag)->length !== 0) {
|
||||
$format_tags[] = $tag;
|
||||
}
|
||||
}
|
||||
$format_tags = implode(';', $format_tags);
|
||||
|
||||
// Cache the "format_tags" configuration. This cache item is infinitely
|
||||
// valid; it only changes whenever the text format is changed, hence it's
|
||||
// tagged with the text format's cache tag.
|
||||
$this->cache->set($cid, $format_tags, Cache::PERMANENT, $format->getCacheTags());
|
||||
}
|
||||
|
||||
return $format_tags;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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, for instance, to define any
|
||||
// style, no matter what the "*" tag's restrictions may be. If there
|
||||
// is 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)) {
|
||||
// Set defaults (these will be overridden below if more specific
|
||||
// values are present).
|
||||
$allowed[$tag] = array(
|
||||
'attributes' => FALSE,
|
||||
'styles' => FALSE,
|
||||
'classes' => FALSE,
|
||||
);
|
||||
// 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'])) {
|
||||
if (is_bool($allowed_attributes['style'])) {
|
||||
$allowed[$tag]['styles'] = $allowed_attributes['style'];
|
||||
}
|
||||
elseif (is_array($allowed_attributes['style'])) {
|
||||
$allowed_classes = $get_attribute_values($allowed_attributes['style'], TRUE);
|
||||
if (isset($allowed_classes)) {
|
||||
$allowed[$tag]['styles'] = $allowed_classes;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (isset($allowed_attributes['class'])) {
|
||||
if (is_bool($allowed_attributes['class'])) {
|
||||
$allowed[$tag]['classes'] = $allowed_attributes['class'];
|
||||
}
|
||||
elseif (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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ksort($allowed);
|
||||
ksort($disallowed);
|
||||
|
||||
return array($allowed, $disallowed);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
135
web/core/modules/ckeditor/src/Plugin/CKEditorPlugin/Language.php
Normal file
135
web/core/modules/ckeditor/src/Plugin/CKEditorPlugin/Language.php
Normal file
|
|
@ -0,0 +1,135 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\ckeditor\Plugin\CKEditorPlugin;
|
||||
|
||||
use Drupal\ckeditor\CKEditorPluginBase;
|
||||
use Drupal\ckeditor\CKEditorPluginConfigurableInterface;
|
||||
use Drupal\ckeditor\CKEditorPluginCssInterface;
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
use Drupal\Core\Language\LanguageManager;
|
||||
use Drupal\Core\Language\LanguageInterface;
|
||||
use Drupal\editor\Entity\Editor;
|
||||
|
||||
/**
|
||||
* Defines the "language" plugin.
|
||||
*
|
||||
* @CKEditorPlugin(
|
||||
* id = "language",
|
||||
* label = @Translation("Language")
|
||||
* )
|
||||
*/
|
||||
class Language extends CKEditorPluginBase implements CKEditorPluginConfigurableInterface, CKEditorPluginCssInterface {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function isInternal() {
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getFile() {
|
||||
// This plugin is already part of Drupal core's CKEditor build.
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getLibraries(Editor $editor) {
|
||||
return ['ckeditor/drupal.ckeditor.plugins.language'];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getConfig(Editor $editor) {
|
||||
$language_list = [];
|
||||
$config = ['language_list' => 'un'];
|
||||
$settings = $editor->getSettings();
|
||||
if (isset($settings['plugins']['language'])) {
|
||||
$config = $settings['plugins']['language'];
|
||||
}
|
||||
|
||||
$predefined_languages = ($config['language_list'] === 'all') ?
|
||||
LanguageManager::getStandardLanguageList() :
|
||||
LanguageManager::getUnitedNationsLanguageList();
|
||||
|
||||
// Generate the language_list setting as expected by the CKEditor Language
|
||||
// plugin, but key the values by the full language name so that we can sort
|
||||
// them later on.
|
||||
foreach ($predefined_languages as $langcode => $language) {
|
||||
$english_name = $language[0];
|
||||
$direction = empty($language[2]) ? NULL : $language[2];
|
||||
if ($direction === LanguageInterface::DIRECTION_RTL) {
|
||||
$language_list[$english_name] = $langcode . ':' . $english_name . ':rtl';
|
||||
}
|
||||
else {
|
||||
$language_list[$english_name] = $langcode . ':' . $english_name;
|
||||
}
|
||||
}
|
||||
|
||||
// Sort on full language name.
|
||||
ksort($language_list);
|
||||
$config = ['language_list' => array_values($language_list)];
|
||||
return $config;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getButtons() {
|
||||
return [
|
||||
'Language' => [
|
||||
'label' => $this->t('Language'),
|
||||
'image_alternative' => [
|
||||
'#type' => 'inline_template',
|
||||
'#template' => '<a href="#" class="cke-icon-only" role="button" title="' . $this->t('Language') . '" aria-label="' . $this->t('Language') . '"><span class="cke_button_icon cke_button__language_icon">' . $this->t('Language') . '</span></a>',
|
||||
],
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function settingsForm(array $form, FormStateInterface $form_state, Editor $editor) {
|
||||
// Defaults.
|
||||
$config = ['language_list' => 'un'];
|
||||
$settings = $editor->getSettings();
|
||||
if (isset($settings['plugins']['language'])) {
|
||||
$config = $settings['plugins']['language'];
|
||||
}
|
||||
|
||||
$predefined_languages = LanguageManager::getStandardLanguageList();
|
||||
$form['language_list'] = array(
|
||||
'#title' => $this->t('Language list'),
|
||||
'#title_display' => 'invisible',
|
||||
'#type' => 'select',
|
||||
'#options' => [
|
||||
'un' => $this->t("United Nations' official languages"),
|
||||
'all' => $this->t('All @count languages', ['@count' => count($predefined_languages)]),
|
||||
],
|
||||
'#default_value' => $config['language_list'],
|
||||
'#description' => $this->t('The list of languages to show in the language dropdown. The basic list will only show the <a href=":url">six official languages of the UN</a>. The extended list will show all @count languages that are available in Drupal.', [
|
||||
':url' => 'https://www.un.org/en/sections/about-un/official-languages',
|
||||
'@count' => count($predefined_languages),
|
||||
]),
|
||||
'#attached' => ['library' => ['ckeditor/drupal.ckeditor.language.admin']],
|
||||
);
|
||||
|
||||
return $form;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
function getCssFiles(Editor $editor) {
|
||||
return array(
|
||||
drupal_get_path('module', 'ckeditor') . '/css/plugins/language/ckeditor.language.css'
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,166 @@
|
|||
<?php
|
||||
|
||||
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 "stylescombo" plugin.
|
||||
*
|
||||
* @CKEditorPlugin(
|
||||
* id = "stylescombo",
|
||||
* label = @Translation("Styles dropdown")
|
||||
* )
|
||||
*/
|
||||
class StylesCombo extends CKEditorPluginBase implements CKEditorPluginConfigurableInterface {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function isInternal() {
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getFile() {
|
||||
// This plugin is already part of Drupal core's CKEditor build.
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getButtons() {
|
||||
return array(
|
||||
'Styles' => array(
|
||||
'label' => t('Font style'),
|
||||
'image_alternative' => [
|
||||
'#type' => 'inline_template',
|
||||
'#template' => '<a href="#" role="button" aria-label="{{ styles_text }}"><span class="ckeditor-button-dropdown">{{ styles_text }}<span class="ckeditor-button-arrow"></span></span></a>',
|
||||
'#context' => [
|
||||
'styles_text' => t('Styles'),
|
||||
],
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
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 or more classes on each line in the format: element.classA.classB|Label. Example: h1.title|Title. Advanced example: h1.fancy.title|Fancy 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) {
|
||||
$styles_setting = $this->generateStylesSetSetting($element['#value']);
|
||||
if ($styles_setting === FALSE) {
|
||||
$form_state->setError($element, t('The provided list of styles is syntactically incorrect.'));
|
||||
}
|
||||
else {
|
||||
$style_names = array_map(function ($style) { return $style['name']; }, $styles_setting);
|
||||
if (count($style_names) !== count(array_unique($style_names))) {
|
||||
$form_state->setError($element, t('Each style must have a unique label.'));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
|
||||
}
|
||||
432
web/core/modules/ckeditor/src/Plugin/Editor/CKEditor.php
Normal file
432
web/core/modules/ckeditor/src/Plugin/Editor/CKEditor.php
Normal file
|
|
@ -0,0 +1,432 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\ckeditor\Plugin\Editor;
|
||||
|
||||
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;
|
||||
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' => ['language' => ['language_list' => 'un']],
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function settingsForm(array $form, FormStateInterface $form_state, Editor $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' => (string) $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' (for instance, 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 = Editor::create(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(Editor $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.
|
||||
$root_relative_file_url = function ($uri) {
|
||||
return file_url_transform_relative(file_create_url($uri));
|
||||
};
|
||||
$settings += array(
|
||||
'drupalExternalPlugins' => array_map($root_relative_file_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.
|
||||
$files = scandir('core/assets/vendor/ckeditor/lang');
|
||||
foreach ($files as $file) {
|
||||
if ($file[0] !== '.' && preg_match('/\.js$/', $file)) {
|
||||
$langcode = basename($file, '.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.
|
||||
// For instance, 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(Editor $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(Editor $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(Editor $editor) {
|
||||
$css = array(
|
||||
drupal_get_path('module', 'ckeditor') . '/css/ckeditor-iframe.css',
|
||||
drupal_get_path('module', 'system') . '/css/components/align.module.css',
|
||||
);
|
||||
$this->moduleHandler->alter('ckeditor_css', $css, $editor);
|
||||
// Get a list of all enabled plugins' iframe instance CSS files.
|
||||
$plugins_css = array_reduce($this->ckeditorPluginManager->getCssFiles($editor), function($result, $item) {
|
||||
return array_merge($result, array_values($item));
|
||||
}, array());
|
||||
$css = array_merge($css, $plugins_css);
|
||||
$css = array_merge($css, _ckeditor_theme_css());
|
||||
$css = array_map('file_create_url', $css);
|
||||
$css = array_map('file_url_transform_relative', $css);
|
||||
|
||||
return array_values($css);
|
||||
}
|
||||
|
||||
}
|
||||
284
web/core/modules/ckeditor/src/Tests/CKEditorAdminTest.php
Normal file
284
web/core/modules/ckeditor/src/Tests/CKEditorAdminTest.php
Normal file
|
|
@ -0,0 +1,284 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\ckeditor\Tests;
|
||||
|
||||
use Drupal\Component\Serialization\Json;
|
||||
use Drupal\editor\Entity\Editor;
|
||||
use Drupal\filter\FilterFormatInterface;
|
||||
use Drupal\simpletest\WebTestBase;
|
||||
use Drupal\filter\Entity\FilterFormat;
|
||||
|
||||
/**
|
||||
* 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 = FilterFormat::create(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 = Editor::load('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' => 'Formatting',
|
||||
'items' => array('Bold', 'Italic',),
|
||||
),
|
||||
array(
|
||||
'name' => 'Links',
|
||||
'items' => array('DrupalLink', 'DrupalUnlink',),
|
||||
),
|
||||
array(
|
||||
'name' => 'Lists',
|
||||
'items' => array('BulletedList', 'NumberedList',),
|
||||
),
|
||||
array(
|
||||
'name' => 'Media',
|
||||
'items' => array('Blockquote', 'DrupalImage',),
|
||||
),
|
||||
array(
|
||||
'name' => 'Tools',
|
||||
'items' => array('Source',),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
'plugins' => ['language' => ['language_list' => 'un']],
|
||||
);
|
||||
$this->assertIdentical($this->castSafeStrings($ckeditor->getDefaultSettings()), $expected_default_settings);
|
||||
|
||||
// Keep the "CKEditor" editor selected and click the "Configure" button.
|
||||
$this->drupalPostAjaxForm(NULL, $edit, 'editor_configure');
|
||||
$editor = Editor::load('filtered_html');
|
||||
$this->assertFalse($editor, 'No Editor config entity exists yet.');
|
||||
|
||||
// Ensure that drupalSettings is correct.
|
||||
$ckeditor_settings_toolbar = array(
|
||||
'#theme' => 'ckeditor_settings_toolbar',
|
||||
'#editor' => Editor::create(['editor' => 'ckeditor']),
|
||||
'#plugins' => $this->container->get('plugin.manager.ckeditor.plugin')->getButtons(),
|
||||
);
|
||||
$this->assertEqual(
|
||||
$this->drupalSettings['ckeditor']['toolbarAdmin'],
|
||||
$this->container->get('renderer')->renderPlain($ckeditor_settings_toolbar),
|
||||
'CKEditor toolbar settings are rendered as part of drupalSettings.'
|
||||
);
|
||||
|
||||
// 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 = Editor::load('filtered_html');
|
||||
$this->assertTrue($editor instanceof Editor, 'An Editor config entity exists now.');
|
||||
$this->assertEqual($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 = Editor::load('filtered_html');
|
||||
$this->assertTrue($editor instanceof Editor, 'An Editor config entity exists.');
|
||||
$this->assertEqual($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 = Editor::load('filtered_html');
|
||||
$this->assertTrue($editor instanceof Editor, 'An Editor config entity exists.');
|
||||
$this->assertEqual($expected_settings, $editor->getSettings(), 'The Editor config entity has the correct settings.');
|
||||
|
||||
// Check that the markup we're setting for the toolbar buttons (actually in
|
||||
// JavaScript's drupalSettings, and Unicode-escaped) is correctly rendered.
|
||||
$this->drupalGet('admin/config/content/formats/manage/filtered_html');
|
||||
// Create function to encode HTML as we expect it in drupalSettings.
|
||||
$json_encode = function($html) {
|
||||
return trim(Json::encode($html), '"');
|
||||
};
|
||||
// Check the Button separator.
|
||||
$this->assertRaw($json_encode('<li data-drupal-ckeditor-button-name="-" class="ckeditor-button-separator ckeditor-multiple-button" data-drupal-ckeditor-type="separator"><a href="#" role="button" aria-label="Button separator" class="ckeditor-separator"></a></li>'));
|
||||
// Check the Format dropdown.
|
||||
$this->assertRaw($json_encode('<li data-drupal-ckeditor-button-name="Format" class="ckeditor-button"><a href="#" role="button" aria-label="Format"><span class="ckeditor-button-dropdown">Format<span class="ckeditor-button-arrow"></span></span></a></li>'));
|
||||
// Check the Styles dropdown.
|
||||
$this->assertRaw($json_encode('<li data-drupal-ckeditor-button-name="Styles" class="ckeditor-button"><a href="#" role="button" aria-label="Styles"><span class="ckeditor-button-dropdown">Styles<span class="ckeditor-button-arrow"></span></span></a></li>'));
|
||||
// Check strikethrough.
|
||||
$this->assertRaw($json_encode('<li data-drupal-ckeditor-button-name="Strike" class="ckeditor-button"><a href="#" class="cke-icon-only cke_ltr" role="button" title="strike" aria-label="strike"><span class="cke_button_icon cke_button__strike_icon">strike</span></a></li>'));
|
||||
|
||||
// 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 = Editor::load('filtered_html');
|
||||
$this->assertTrue($editor instanceof Editor, 'An Editor config entity exists.');
|
||||
$this->assertEqual($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 = Editor::load('filtered_html');
|
||||
$this->assertTrue($editor instanceof Editor, 'An Editor config entity exists.');
|
||||
$this->assertEqual($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 = FilterFormat::load('amazing_format');
|
||||
$this->assertFalse($filter_format, 'No FilterFormat config entity exists yet.');
|
||||
$editor = Editor::load('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);
|
||||
|
||||
// Regression test for https://www.drupal.org/node/2606460.
|
||||
$this->assertTrue(strpos($this->drupalSettings['ckeditor']['toolbarAdmin'], '<li data-drupal-ckeditor-button-name="Bold" class="ckeditor-button"><a href="#" class="cke-icon-only cke_ltr" role="button" title="bold" aria-label="bold"><span class="cke_button_icon cke_button__bold_icon">bold</span></a></li>') !== FALSE);
|
||||
|
||||
// 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 = FilterFormat::load('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 = Editor::load('amazing_format');
|
||||
$this->assertTrue($editor instanceof Editor, 'An Editor config entity exists now.');
|
||||
$this->assertEqual($this->castSafeStrings($expected_settings), $this->castSafeStrings($editor->getSettings()), 'The Editor config entity has the correct settings.');
|
||||
}
|
||||
|
||||
}
|
||||
245
web/core/modules/ckeditor/src/Tests/CKEditorLoadingTest.php
Normal file
245
web/core/modules/ckeditor/src/Tests/CKEditorLoadingTest.php
Normal file
|
|
@ -0,0 +1,245 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\ckeditor\Tests;
|
||||
|
||||
use Drupal\editor\Entity\Editor;
|
||||
use Drupal\simpletest\WebTestBase;
|
||||
use Drupal\filter\Entity\FilterFormat;
|
||||
|
||||
/**
|
||||
* 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 = FilterFormat::create(array(
|
||||
'format' => 'filtered_html',
|
||||
'name' => 'Filtered HTML',
|
||||
'weight' => 0,
|
||||
'filters' => array(),
|
||||
));
|
||||
$filtered_html_format->save();
|
||||
$editor = Editor::create([
|
||||
'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 = FilterFormat::create(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 = Editor::load('filtered_html');
|
||||
$expected = array('formats' => array('filtered_html' => array(
|
||||
'format' => 'filtered_html',
|
||||
'editor' => 'ckeditor',
|
||||
'editorSettings' => $this->castSafeStrings($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, $this->castSafeStrings($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' => $this->castSafeStrings($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, $this->castSafeStrings($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.');
|
||||
|
||||
// Assert that CKEditor uses Drupal's cache-busting query string by
|
||||
// comparing the setting sent with the page with the current query string.
|
||||
$settings = $this->getDrupalSettings();
|
||||
$expected = $settings['ckeditor']['timestamp'];
|
||||
$this->assertIdentical($expected, \Drupal::state()->get('system.css_js_query_string'), "CKEditor scripts cache-busting string is correct before flushing all caches.");
|
||||
// Flush all caches then make sure that $settings['ckeditor']['timestamp']
|
||||
// still matches.
|
||||
drupal_flush_all_caches();
|
||||
$this->assertIdentical($expected, \Drupal::state()->get('system.css_js_query_string'), "CKEditor scripts cache-busting string is correct after flushing all caches.");
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests presence of essential configuration even without Internal's buttons.
|
||||
*/
|
||||
protected function testLoadingWithoutInternalButtons() {
|
||||
// Change the CKEditor text editor configuration to only have link buttons.
|
||||
// This means:
|
||||
// - 0 buttons are from \Drupal\ckeditor\Plugin\CKEditorPlugin\Internal
|
||||
// - 2 buttons are from \Drupal\ckeditor\Plugin\CKEditorPlugin\DrupalLink
|
||||
$filtered_html_editor = Editor::load('filtered_html');
|
||||
$settings = $filtered_html_editor->getSettings();
|
||||
$settings['toolbar']['rows'] = [
|
||||
0 => [
|
||||
0 => [
|
||||
'name' => 'Links',
|
||||
'items' => [
|
||||
'DrupalLink',
|
||||
'DrupalUnlink',
|
||||
],
|
||||
],
|
||||
],
|
||||
];
|
||||
$filtered_html_editor->setSettings($settings)->save();
|
||||
|
||||
// Even when no buttons of \Drupal\ckeditor\Plugin\CKEditorPlugin\Internal
|
||||
// are in use, its configuration (Internal::getConfig()) is still essential:
|
||||
// this is configuration that is associated with the (custom, optimized)
|
||||
// build of CKEditor that Drupal core ships with. For example, it configures
|
||||
// CKEditor to not perform its default action of loading a config.js file,
|
||||
// to not convert special characters into HTML entities, and the allowedContent
|
||||
// setting to configure CKEditor's Advanced Content Filter.
|
||||
$this->drupalLogin($this->normalUser);
|
||||
$this->drupalGet('node/add/article');
|
||||
$editor_settings = $this->getDrupalSettings()['editor']['formats']['filtered_html']['editorSettings'];
|
||||
$this->assertTrue(isset($editor_settings['customConfig']));
|
||||
$this->assertTrue(isset($editor_settings['entities']));
|
||||
$this->assertTrue(isset($editor_settings['allowedContent']));
|
||||
$this->assertTrue(isset($editor_settings['disallowedContent']));
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests loading of theme's CKEditor stylesheets defined in the .info file.
|
||||
*/
|
||||
function testExternalStylesheets() {
|
||||
$theme_handler = \Drupal::service('theme_handler');
|
||||
// Case 1: Install theme which has an absolute external CSS URL.
|
||||
$theme_handler->install(['test_ckeditor_stylesheets_external']);
|
||||
$theme_handler->setDefault('test_ckeditor_stylesheets_external');
|
||||
$expected = [
|
||||
'https://fonts.googleapis.com/css?family=Open+Sans',
|
||||
];
|
||||
$this->assertIdentical($expected, _ckeditor_theme_css('test_ckeditor_stylesheets_external'));
|
||||
|
||||
// Case 2: Install theme which has an external protocol-relative CSS URL.
|
||||
$theme_handler->install(['test_ckeditor_stylesheets_protocol_relative']);
|
||||
$theme_handler->setDefault('test_ckeditor_stylesheets_protocol_relative');
|
||||
$expected = [
|
||||
'//fonts.googleapis.com/css?family=Open+Sans',
|
||||
];
|
||||
$this->assertIdentical($expected, _ckeditor_theme_css('test_ckeditor_stylesheets_protocol_relative'));
|
||||
|
||||
// Case 3: Install theme which has a relative CSS URL.
|
||||
$theme_handler->install(['test_ckeditor_stylesheets_relative']);
|
||||
$theme_handler->setDefault('test_ckeditor_stylesheets_relative');
|
||||
$expected = [
|
||||
'core/modules/system/tests/themes/test_ckeditor_stylesheets_relative/css/yokotsoko.css',
|
||||
];
|
||||
$this->assertIdentical($expected, _ckeditor_theme_css('test_ckeditor_stylesheets_relative'));
|
||||
}
|
||||
|
||||
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")]'),
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,99 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\ckeditor\Tests;
|
||||
|
||||
use Drupal\editor\Entity\Editor;
|
||||
use Drupal\simpletest\WebTestBase;
|
||||
use Drupal\filter\Entity\FilterFormat;
|
||||
|
||||
/**
|
||||
* Tests administration of the CKEditor StylesCombo plugin.
|
||||
*
|
||||
* @group ckeditor
|
||||
*/
|
||||
class CKEditorStylesComboAdminTest extends WebTestBase {
|
||||
|
||||
/**
|
||||
* Modules to enable.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public static $modules = ['filter', 'editor', 'ckeditor'];
|
||||
|
||||
/**
|
||||
* A user with the 'administer filters' permission.
|
||||
*
|
||||
* @var \Drupal\user\UserInterface
|
||||
*/
|
||||
protected $adminUser;
|
||||
|
||||
/**
|
||||
* A random generated format machine name.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $format;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp() {
|
||||
parent::setUp();
|
||||
|
||||
$this->format = strtolower($this->randomMachineName());
|
||||
$filter_format = FilterFormat::create([
|
||||
'format' => $this->format,
|
||||
'name' => $this->randomString(),
|
||||
'filters' => [],
|
||||
]);
|
||||
$filter_format->save();
|
||||
$editor = Editor::create([
|
||||
'format' => $this->format,
|
||||
'editor' => 'ckeditor',
|
||||
]);
|
||||
$editor->save();
|
||||
|
||||
$this->adminUser = $this->drupalCreateUser(['administer filters']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests StylesCombo settings for an existing text format.
|
||||
*/
|
||||
function testExistingFormat() {
|
||||
$ckeditor = $this->container->get('plugin.manager.editor')->createInstance('ckeditor');
|
||||
$default_settings = $ckeditor->getDefaultSettings();
|
||||
|
||||
$this->drupalLogin($this->adminUser);
|
||||
$this->drupalGet('admin/config/content/formats/manage/' . $this->format);
|
||||
|
||||
// Ensure an Editor config entity exists, with the proper settings.
|
||||
$expected_settings = $default_settings;
|
||||
$editor = Editor::load($this->format);
|
||||
$this->assertEqual($expected_settings, $editor->getSettings(), 'The Editor config entity has the correct settings.');
|
||||
|
||||
// Case 1: Configure the Styles plugin with different labels for each style,
|
||||
// and ensure the updated settings are saved.
|
||||
$this->drupalGet('admin/config/content/formats/manage/' . $this->format);
|
||||
$edit = [
|
||||
'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 = Editor::load($this->format);
|
||||
$this->assertEqual($expected_settings, $editor->getSettings(), 'The Editor config entity has the correct settings.');
|
||||
|
||||
// Case 2: Configure the Styles plugin with same labels for each style, and
|
||||
// ensure that an error is displayed and that the updated settings are not
|
||||
// saved.
|
||||
$this->drupalGet('admin/config/content/formats/manage/' . $this->format);
|
||||
$edit = [
|
||||
'editor[settings][plugins][stylescombo][styles]' => "h1.title|Title\np.callout|Title\n\n",
|
||||
];
|
||||
$this->drupalPostForm(NULL, $edit, t('Save configuration'));
|
||||
$this->assertRaw(t('Each style must have a unique label.'));
|
||||
$expected_settings['plugins']['stylescombo']['styles'] = "h1.title|Title\np.callout|Callout\n\n";
|
||||
$editor = Editor::load($this->format);
|
||||
$this->assertEqual($expected_settings, $editor->getSettings(), 'The Editor config entity has the correct settings.');
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,77 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\ckeditor\Tests;
|
||||
|
||||
|
||||
use Drupal\filter\Entity\FilterFormat;
|
||||
use Drupal\editor\Entity\Editor;
|
||||
use Drupal\simpletest\WebTestBase;
|
||||
use Drupal\Component\Serialization\Json;
|
||||
|
||||
/**
|
||||
* Tests CKEditor toolbar buttons when the language direction is RTL.
|
||||
*
|
||||
* @group ckeditor
|
||||
*/
|
||||
class CKEditorToolbarButtonTest extends WebTestBase {
|
||||
|
||||
/**
|
||||
* Modules to enable for this test.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public static $modules = ['filter', 'editor', 'ckeditor', 'locale'];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp() {
|
||||
parent::setUp();
|
||||
|
||||
// Create a text format and associate this with CKEditor.
|
||||
FilterFormat::create([
|
||||
'format' => 'full_html',
|
||||
'name' => 'Full HTML',
|
||||
'weight' => 1,
|
||||
'filters' => [],
|
||||
])->save();
|
||||
Editor::create([
|
||||
'format' => 'full_html',
|
||||
'editor' => 'ckeditor',
|
||||
])->save();
|
||||
|
||||
// Create a new user with admin rights.
|
||||
$this->admin_user = $this->drupalCreateUser([
|
||||
'administer languages',
|
||||
'access administration pages',
|
||||
'administer site configuration',
|
||||
'administer filters',
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Method tests CKEditor image buttons.
|
||||
*/
|
||||
public function testImageButtonDisplay() {
|
||||
$this->drupalLogin($this->admin_user);
|
||||
|
||||
// Install the Arabic language (which is RTL) and configure as the default.
|
||||
$edit = [];
|
||||
$edit['predefined_langcode'] = 'ar';
|
||||
$this->drupalPostForm('admin/config/regional/language/add', $edit, t('Add language'));
|
||||
|
||||
$edit = ['site_default_language' => 'ar'];
|
||||
$this->drupalPostForm('admin/config/regional/language', $edit, t('Save configuration'));
|
||||
// Once the default language is changed, go to the tested text format
|
||||
// configuration page.
|
||||
$this->drupalGet('admin/config/content/formats/manage/full_html');
|
||||
|
||||
// Check if any image button is loaded in CKEditor json.
|
||||
$json_encode = function($html) {
|
||||
return trim(Json::encode($html), '"');
|
||||
};
|
||||
$markup = $json_encode(file_url_transform_relative(file_create_url('core/modules/ckeditor/js/plugins/drupalimage/icons/drupalimage.png')));
|
||||
$this->assertRaw($markup);
|
||||
}
|
||||
|
||||
}
|
||||
Reference in a new issue