class CKEditor

Same name in other branches
  1. 9 core/modules/ckeditor/src/Plugin/Editor/CKEditor.php \Drupal\ckeditor\Plugin\Editor\CKEditor

Defines a CKEditor-based text editor for Drupal.

Plugin annotation


@Editor(
  id = "ckeditor",
  label = @Translation("CKEditor"),
  supports_content_filtering = TRUE,
  supports_inline_editing = TRUE,
  is_xss_safe = FALSE,
  supported_element_types = {
    "textarea"
  }
)

Hierarchy

Expanded class hierarchy of CKEditor

1 file declares its use of CKEditor
CKEditorTest.php in core/modules/ckeditor/tests/src/Kernel/CKEditorTest.php
62 string references to 'CKEditor'
BigPipeRegressionTest::testCommentForm_2698811 in core/modules/big_pipe/tests/src/FunctionalJavascript/BigPipeRegressionTest.php
Ensure comment form works with history and big_pipe modules.
ckeditor.info.yml in core/modules/ckeditor/ckeditor.info.yml
core/modules/ckeditor/ckeditor.info.yml
CKEditor::buildConfigurationForm in core/modules/ckeditor/src/Plugin/Editor/CKEditor.php
Form constructor.
CKEditor::buildContentsCssJSSetting in core/modules/ckeditor/src/Plugin/Editor/CKEditor.php
Builds the "contentsCss" configuration part of the CKEditor JS settings.
CKEditorAdminTest::testExistingFormat in core/modules/ckeditor/tests/src/Functional/CKEditorAdminTest.php
Tests configuring a text editor for an existing text format.

... See full list

File

core/modules/ckeditor/src/Plugin/Editor/CKEditor.php, line 31

Namespace

Drupal\ckeditor\Plugin\Editor
View source
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;
    
    /**
     * The state key/value store.
     *
     * @var \Drupal\Core\State\StateInterface
     */
    protected $state;
    
    /**
     * Constructs a \Drupal\ckeditor\Plugin\Editor\CKEditor 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.
     * @param \Drupal\Core\State\StateInterface $state
     *   The state key/value store.
     */
    public function __construct(array $configuration, $plugin_id, $plugin_definition, CKEditorPluginManager $ckeditor_plugin_manager, ModuleHandlerInterface $module_handler, LanguageManagerInterface $language_manager, RendererInterface $renderer, StateInterface $state = NULL) {
        parent::__construct($configuration, $plugin_id, $plugin_definition);
        $this->ckeditorPluginManager = $ckeditor_plugin_manager;
        $this->moduleHandler = $module_handler;
        $this->languageManager = $language_manager;
        $this->renderer = $renderer;
        if ($state === NULL) {
            @trigger_error('Calling CKEditor::__construct() without the $state argument is deprecated in drupal:8.8.0. The $state argument is required in drupal:9.0.0. See https://www.drupal.org/node/3075102.', E_USER_DEPRECATED);
            $state = \Drupal::service('state');
        }
        $this->state = $state;
    }
    
    /**
     * {@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'), $container->get('state'));
    }
    
    /**
     * {@inheritdoc}
     */
    public function getDefaultSettings() {
        return [
            'toolbar' => [
                'rows' => [
                    // Button groups.
[
                        [
                            'name' => $this->t('Formatting'),
                            'items' => [
                                'Bold',
                                'Italic',
                            ],
                        ],
                        [
                            'name' => $this->t('Links'),
                            'items' => [
                                'DrupalLink',
                                'DrupalUnlink',
                            ],
                        ],
                        [
                            'name' => $this->t('Lists'),
                            'items' => [
                                'BulletedList',
                                'NumberedList',
                            ],
                        ],
                        [
                            'name' => $this->t('Media'),
                            'items' => [
                                'Blockquote',
                                'DrupalImage',
                            ],
                        ],
                        [
                            'name' => $this->t('Tools'),
                            'items' => [
                                'Source',
                            ],
                        ],
                    ],
                ],
            ],
            'plugins' => [
                'language' => [
                    'language_list' => 'un',
                ],
            ],
        ];
    }
    
    /**
     * {@inheritdoc}
     */
    public function buildConfigurationForm(array $form, FormStateInterface $form_state) {
        $editor = $form_state->get('editor');
        $settings = $editor->getSettings();
        $ckeditor_settings_toolbar = [
            '#theme' => 'ckeditor_settings_toolbar',
            '#editor' => $editor,
            '#plugins' => $this->ckeditorPluginManager
                ->getButtons(),
        ];
        $form['toolbar'] = [
            '#type' => 'container',
            '#attached' => [
                'library' => [
                    'ckeditor/drupal.ckeditor.admin',
                ],
                'drupalSettings' => [
                    'ckeditor' => [
                        'toolbarAdmin' => (string) $this->renderer
                            ->renderPlain($ckeditor_settings_toolbar),
                    ],
                ],
            ],
            '#attributes' => [
                'class' => [
                    'ckeditor-toolbar-configuration',
                ],
            ],
        ];
        $form['toolbar']['button_groups'] = [
            '#type' => 'textarea',
            '#title' => $this->t('Toolbar buttons'),
            '#default_value' => json_encode($settings['toolbar']['rows']),
            '#attributes' => [
                'class' => [
                    'ckeditor-toolbar-textarea',
                ],
            ],
        ];
        // CKEditor plugin settings, if any.
        $form['plugin_settings'] = [
            '#type' => 'vertical_tabs',
            '#title' => $this->t('CKEditor plugin settings'),
            '#attributes' => [
                '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 = [];
        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));
        }, []);
        // Build a fake Editor object, which we'll use to generate JavaScript
        // settings for this fake Editor instance.
        $fake_editor = Editor::create([
            'format' => $editor->id(),
            'editor' => 'ckeditor',
            'settings' => [
                // Single toolbar row, single button group, all existing buttons.
'toolbar' => [
                    'rows' => [
                        0 => [
                            0 => [
                                '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'] = [
            '#markup' => '<div id="ckeditor-hidden" class="hidden"></div>',
            '#attached' => [
                'drupalSettings' => [
                    'ckeditor' => [
                        'hiddenCKEditorConfig' => $config,
                    ],
                ],
            ],
        ];
        return $form;
    }
    
    /**
     * {@inheritdoc}
     */
    public function validateConfigurationForm(array &$form, FormStateInterface $form_state) {
    }
    
    /**
     * {@inheritdoc}
     */
    public function submitConfigurationForm(array &$form, FormStateInterface $form_state) {
        // 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.
        $form_state->setValue([
            'toolbar',
            'rows',
        ], json_decode($form_state->getValue([
            'toolbar',
            'button_groups',
        ]), TRUE));
        $form_state->unsetValue([
            'toolbar',
            'button_groups',
        ]);
        // Remove the plugin settings' vertical tabs state; no need to save that.
        if ($form_state->hasValue('plugins')) {
            $form_state->unsetValue('plugin_settings');
        }
    }
    
    /**
     * {@inheritdoc}
     */
    public function getJSSettings(Editor $editor) {
        $settings = [];
        // 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 += [
            '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 += [
            '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 = [];
            // 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() : [];
        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 = [
            '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 = [];
        $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 = [
            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));
        }, []);
        $css = array_merge($css, $plugins_css);
        $css = array_merge($css, _ckeditor_theme_css());
        $query_string = $this->state
            ->get('system.css_js_query_string', '0');
        $css = array_map(function ($item) use ($query_string) {
            $query_string_separator = strpos($item, '?') !== FALSE ? '&' : '?';
            return $item . $query_string_separator . $query_string;
        }, $css);
        $css = array_map('file_create_url', $css);
        $css = array_map('file_url_transform_relative', $css);
        return array_values($css);
    }

}

Members

Title Sort descending Modifiers Object type Summary Overriden Title Overrides
CKEditor::$ckeditorPluginManager protected property The CKEditor plugin manager.
CKEditor::$languageManager protected property The language manager.
CKEditor::$moduleHandler protected property The module handler to invoke hooks on.
CKEditor::$renderer protected property The renderer.
CKEditor::$state protected property The state key/value store.
CKEditor::buildConfigurationForm public function Form constructor. Overrides EditorBase::buildConfigurationForm
CKEditor::buildContentsCssJSSetting public function Builds the &quot;contentsCss&quot; configuration part of the CKEditor JS settings.
CKEditor::buildToolbarJSSetting public function Builds the &quot;toolbar&quot; configuration part of the CKEditor JS settings.
CKEditor::create public static function Creates an instance of the plugin. Overrides ContainerFactoryPluginInterface::create
CKEditor::getDefaultSettings public function Returns the default settings for this configurable text editor. Overrides EditorBase::getDefaultSettings
CKEditor::getJSSettings public function Returns JavaScript settings to be attached. Overrides EditorPluginInterface::getJSSettings
CKEditor::getLangcodes public function Returns a list of language codes supported by CKEditor.
CKEditor::getLibraries public function Returns libraries to be attached. Overrides EditorPluginInterface::getLibraries
CKEditor::submitConfigurationForm public function Form submission handler. Overrides EditorBase::submitConfigurationForm
CKEditor::validateConfigurationForm public function Form validation handler. Overrides EditorBase::validateConfigurationForm
CKEditor::__construct public function Constructs a \Drupal\ckeditor\Plugin\Editor\CKEditor object.
PluginInspectionInterface::getPluginDefinition public function Gets the definition of the plugin implementation. 6
PluginInspectionInterface::getPluginId public function Gets the plugin_id of the plugin instance. 2

Buggy or inaccurate documentation? Please file an issue. Need support? Need help programming? Connect with the Drupal community.