"Fossies" - the Fresh Open Source Software Archive

Member "drupal-8.9.10/core/modules/ckeditor/src/Plugin/Editor/CKEditor.php" (26 Nov 2020, 15992 Bytes) of package /linux/www/drupal-8.9.10.tar.gz:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) PHP source code syntax highlighting (style: standard) with prefixed line numbers and code folding option. Alternatively you can here view or download the uninterpreted source code file. For more information about "CKEditor.php" see the Fossies "Dox" file reference documentation.

    1 <?php
    2 
    3 namespace Drupal\ckeditor\Plugin\Editor;
    4 
    5 use Drupal\Core\Extension\ModuleHandlerInterface;
    6 use Drupal\ckeditor\CKEditorPluginManager;
    7 use Drupal\Core\Form\FormStateInterface;
    8 use Drupal\Core\Language\LanguageManagerInterface;
    9 use Drupal\Core\Render\Element;
   10 use Drupal\Core\Render\RendererInterface;
   11 use Drupal\Core\State\StateInterface;
   12 use Drupal\editor\Plugin\EditorBase;
   13 use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
   14 use Drupal\editor\Entity\Editor;
   15 use Symfony\Component\DependencyInjection\ContainerInterface;
   16 
   17 /**
   18  * Defines a CKEditor-based text editor for Drupal.
   19  *
   20  * @Editor(
   21  *   id = "ckeditor",
   22  *   label = @Translation("CKEditor"),
   23  *   supports_content_filtering = TRUE,
   24  *   supports_inline_editing = TRUE,
   25  *   is_xss_safe = FALSE,
   26  *   supported_element_types = {
   27  *     "textarea"
   28  *   }
   29  * )
   30  */
   31 class CKEditor extends EditorBase implements ContainerFactoryPluginInterface {
   32 
   33   /**
   34    * The module handler to invoke hooks on.
   35    *
   36    * @var \Drupal\Core\Extension\ModuleHandlerInterface
   37    */
   38   protected $moduleHandler;
   39 
   40   /**
   41    * The language manager.
   42    *
   43    * @var \Drupal\Core\Language\LanguageManagerInterface
   44    */
   45   protected $languageManager;
   46 
   47   /**
   48    * The CKEditor plugin manager.
   49    *
   50    * @var \Drupal\ckeditor\CKEditorPluginManager
   51    */
   52   protected $ckeditorPluginManager;
   53 
   54   /**
   55    * The renderer.
   56    *
   57    * @var \Drupal\Core\Render\RendererInterface
   58    */
   59   protected $renderer;
   60 
   61   /**
   62    * The state key/value store.
   63    *
   64    * @var \Drupal\Core\State\StateInterface
   65    */
   66   protected $state;
   67 
   68   /**
   69    * Constructs a \Drupal\ckeditor\Plugin\Editor\CKEditor object.
   70    *
   71    * @param array $configuration
   72    *   A configuration array containing information about the plugin instance.
   73    * @param string $plugin_id
   74    *   The plugin_id for the plugin instance.
   75    * @param mixed $plugin_definition
   76    *   The plugin implementation definition.
   77    * @param \Drupal\ckeditor\CKEditorPluginManager $ckeditor_plugin_manager
   78    *   The CKEditor plugin manager.
   79    * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
   80    *   The module handler to invoke hooks on.
   81    * @param \Drupal\Core\Language\LanguageManagerInterface $language_manager
   82    *   The language manager.
   83    * @param \Drupal\Core\Render\RendererInterface $renderer
   84    *   The renderer.
   85    * @param \Drupal\Core\State\StateInterface $state
   86    *   The state key/value store.
   87    */
   88   public function __construct(array $configuration, $plugin_id, $plugin_definition, CKEditorPluginManager $ckeditor_plugin_manager, ModuleHandlerInterface $module_handler, LanguageManagerInterface $language_manager, RendererInterface $renderer, StateInterface $state = NULL) {
   89     parent::__construct($configuration, $plugin_id, $plugin_definition);
   90     $this->ckeditorPluginManager = $ckeditor_plugin_manager;
   91     $this->moduleHandler = $module_handler;
   92     $this->languageManager = $language_manager;
   93     $this->renderer = $renderer;
   94     if ($state === NULL) {
   95       @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);
   96       $state = \Drupal::service('state');
   97     }
   98     $this->state = $state;
   99   }
  100 
  101   /**
  102    * {@inheritdoc}
  103    */
  104   public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
  105     return new static(
  106       $configuration,
  107       $plugin_id,
  108       $plugin_definition,
  109       $container->get('plugin.manager.ckeditor.plugin'),
  110       $container->get('module_handler'),
  111       $container->get('language_manager'),
  112       $container->get('renderer'),
  113       $container->get('state')
  114     );
  115   }
  116 
  117   /**
  118    * {@inheritdoc}
  119    */
  120   public function getDefaultSettings() {
  121     return [
  122       'toolbar' => [
  123         'rows' => [
  124           // Button groups.
  125           [
  126             [
  127               'name' => $this->t('Formatting'),
  128               'items' => ['Bold', 'Italic'],
  129             ],
  130             [
  131               'name' => $this->t('Links'),
  132               'items' => ['DrupalLink', 'DrupalUnlink'],
  133             ],
  134             [
  135               'name' => $this->t('Lists'),
  136               'items' => ['BulletedList', 'NumberedList'],
  137             ],
  138             [
  139               'name' => $this->t('Media'),
  140               'items' => ['Blockquote', 'DrupalImage'],
  141             ],
  142             [
  143               'name' => $this->t('Tools'),
  144               'items' => ['Source'],
  145             ],
  146           ],
  147         ],
  148       ],
  149       'plugins' => ['language' => ['language_list' => 'un']],
  150     ];
  151   }
  152 
  153   /**
  154    * {@inheritdoc}
  155    */
  156   public function buildConfigurationForm(array $form, FormStateInterface $form_state) {
  157     $editor = $form_state->get('editor');
  158     $settings = $editor->getSettings();
  159 
  160     $ckeditor_settings_toolbar = [
  161       '#theme' => 'ckeditor_settings_toolbar',
  162       '#editor' => $editor,
  163       '#plugins' => $this->ckeditorPluginManager->getButtons(),
  164     ];
  165     $form['toolbar'] = [
  166       '#type' => 'container',
  167       '#attached' => [
  168         'library' => ['ckeditor/drupal.ckeditor.admin'],
  169         'drupalSettings' => [
  170           'ckeditor' => [
  171             'toolbarAdmin' => (string) $this->renderer->renderPlain($ckeditor_settings_toolbar),
  172           ],
  173         ],
  174       ],
  175       '#attributes' => ['class' => ['ckeditor-toolbar-configuration']],
  176     ];
  177 
  178     $form['toolbar']['button_groups'] = [
  179       '#type' => 'textarea',
  180       '#title' => $this->t('Toolbar buttons'),
  181       '#default_value' => json_encode($settings['toolbar']['rows']),
  182       '#attributes' => ['class' => ['ckeditor-toolbar-textarea']],
  183     ];
  184 
  185     // CKEditor plugin settings, if any.
  186     $form['plugin_settings'] = [
  187       '#type' => 'vertical_tabs',
  188       '#title' => $this->t('CKEditor plugin settings'),
  189       '#attributes' => [
  190         'id' => 'ckeditor-plugin-settings',
  191       ],
  192     ];
  193     $this->ckeditorPluginManager->injectPluginSettingsForm($form, $form_state, $editor);
  194     if (count(Element::children($form['plugins'])) === 0) {
  195       unset($form['plugins']);
  196       unset($form['plugin_settings']);
  197     }
  198 
  199     // Hidden CKEditor instance. We need a hidden CKEditor instance with all
  200     // plugins enabled, so we can retrieve CKEditor's per-feature metadata (on
  201     // which tags, attributes, styles and classes are enabled). This metadata is
  202     // necessary for certain filters' (for instance, the html_filter filter)
  203     // settings to be updated accordingly.
  204     // Get a list of all external plugins and their corresponding files.
  205     $plugins = array_keys($this->ckeditorPluginManager->getDefinitions());
  206     $all_external_plugins = [];
  207     foreach ($plugins as $plugin_id) {
  208       $plugin = $this->ckeditorPluginManager->createInstance($plugin_id);
  209       if (!$plugin->isInternal()) {
  210         $all_external_plugins[$plugin_id] = $plugin->getFile();
  211       }
  212     }
  213     // Get a list of all buttons that are provided by all plugins.
  214     $all_buttons = array_reduce($this->ckeditorPluginManager->getButtons(), function ($result, $item) {
  215       return array_merge($result, array_keys($item));
  216     }, []);
  217     // Build a fake Editor object, which we'll use to generate JavaScript
  218     // settings for this fake Editor instance.
  219     $fake_editor = Editor::create([
  220       'format' => $editor->id(),
  221       'editor' => 'ckeditor',
  222       'settings' => [
  223         // Single toolbar row, single button group, all existing buttons.
  224         'toolbar' => [
  225           'rows' => [
  226             0 => [
  227               0 => [
  228                 'name' => 'All existing buttons',
  229                 'items' => $all_buttons,
  230               ],
  231             ],
  232           ],
  233         ],
  234         'plugins' => $settings['plugins'],
  235       ],
  236     ]);
  237     $config = $this->getJSSettings($fake_editor);
  238     // Remove the ACF configuration that is generated based on filter settings,
  239     // because otherwise we cannot retrieve per-feature metadata.
  240     unset($config['allowedContent']);
  241     $form['hidden_ckeditor'] = [
  242       '#markup' => '<div id="ckeditor-hidden" class="hidden"></div>',
  243       '#attached' => [
  244         'drupalSettings' => ['ckeditor' => ['hiddenCKEditorConfig' => $config]],
  245       ],
  246     ];
  247 
  248     return $form;
  249   }
  250 
  251   /**
  252    * {@inheritdoc}
  253    */
  254   public function validateConfigurationForm(array &$form, FormStateInterface $form_state) {
  255   }
  256 
  257   /**
  258    * {@inheritdoc}
  259    */
  260   public function submitConfigurationForm(array &$form, FormStateInterface $form_state) {
  261     // The rows key is not built into the form structure, so decode the button
  262     // groups data into this new key and remove the button_groups key.
  263     $form_state->setValue(['toolbar', 'rows'], json_decode($form_state->getValue(['toolbar', 'button_groups']), TRUE));
  264     $form_state->unsetValue(['toolbar', 'button_groups']);
  265 
  266     // Remove the plugin settings' vertical tabs state; no need to save that.
  267     if ($form_state->hasValue('plugins')) {
  268       $form_state->unsetValue('plugin_settings');
  269     }
  270   }
  271 
  272   /**
  273    * {@inheritdoc}
  274    */
  275   public function getJSSettings(Editor $editor) {
  276     $settings = [];
  277 
  278     // Get the settings for all enabled plugins, even the internal ones.
  279     $enabled_plugins = array_keys($this->ckeditorPluginManager->getEnabledPluginFiles($editor, TRUE));
  280     foreach ($enabled_plugins as $plugin_id) {
  281       $plugin = $this->ckeditorPluginManager->createInstance($plugin_id);
  282       $settings += $plugin->getConfig($editor);
  283     }
  284 
  285     // Fall back on English if no matching language code was found.
  286     $display_langcode = 'en';
  287 
  288     // Map the interface language code to a CKEditor translation if interface
  289     // translation is enabled.
  290     if ($this->moduleHandler->moduleExists('locale')) {
  291       $ckeditor_langcodes = $this->getLangcodes();
  292       $language_interface = $this->languageManager->getCurrentLanguage();
  293       if (isset($ckeditor_langcodes[$language_interface->getId()])) {
  294         $display_langcode = $ckeditor_langcodes[$language_interface->getId()];
  295       }
  296     }
  297 
  298     // Next, set the most fundamental CKEditor settings.
  299     $external_plugin_files = $this->ckeditorPluginManager->getEnabledPluginFiles($editor);
  300     $settings += [
  301       'toolbar' => $this->buildToolbarJSSetting($editor),
  302       'contentsCss' => $this->buildContentsCssJSSetting($editor),
  303       'extraPlugins' => implode(',', array_keys($external_plugin_files)),
  304       'language' => $display_langcode,
  305       // Configure CKEditor to not load styles.js. The StylesCombo plugin will
  306       // set stylesSet according to the user's settings, if the "Styles" button
  307       // is enabled. We cannot get rid of this until CKEditor will stop loading
  308       // styles.js by default.
  309       // See http://dev.ckeditor.com/ticket/9992#comment:9.
  310       'stylesSet' => FALSE,
  311     ];
  312 
  313     // Finally, set Drupal-specific CKEditor settings.
  314     $root_relative_file_url = function ($uri) {
  315       return file_url_transform_relative(file_create_url($uri));
  316     };
  317     $settings += [
  318       'drupalExternalPlugins' => array_map($root_relative_file_url, $external_plugin_files),
  319     ];
  320 
  321     // Parse all CKEditor plugin JavaScript files for translations.
  322     if ($this->moduleHandler->moduleExists('locale')) {
  323       locale_js_translate(array_values($external_plugin_files));
  324     }
  325 
  326     ksort($settings);
  327 
  328     return $settings;
  329   }
  330 
  331   /**
  332    * Returns a list of language codes supported by CKEditor.
  333    *
  334    * @return array
  335    *   An associative array keyed by language codes.
  336    */
  337   public function getLangcodes() {
  338     // Cache the file system based language list calculation because this would
  339     // be expensive to calculate all the time. The cache is cleared on core
  340     // upgrades which is the only situation the CKEditor file listing should
  341     // change.
  342     $langcode_cache = \Drupal::cache()->get('ckeditor.langcodes');
  343     if (!empty($langcode_cache)) {
  344       $langcodes = $langcode_cache->data;
  345     }
  346     if (empty($langcodes)) {
  347       $langcodes = [];
  348       // Collect languages included with CKEditor based on file listing.
  349       $files = scandir('core/assets/vendor/ckeditor/lang');
  350       foreach ($files as $file) {
  351         if ($file[0] !== '.' && preg_match('/\.js$/', $file)) {
  352           $langcode = basename($file, '.js');
  353           $langcodes[$langcode] = $langcode;
  354         }
  355       }
  356       \Drupal::cache()->set('ckeditor.langcodes', $langcodes);
  357     }
  358 
  359     // Get language mapping if available to map to Drupal language codes.
  360     // This is configurable in the user interface and not expensive to get, so
  361     // we don't include it in the cached language list.
  362     $language_mappings = $this->moduleHandler->moduleExists('language') ? language_get_browser_drupal_langcode_mappings() : [];
  363     foreach ($langcodes as $langcode) {
  364       // If this language code is available in a Drupal mapping, use that to
  365       // compute a possibility for matching from the Drupal langcode to the
  366       // CKEditor langcode.
  367       // For instance, CKEditor uses the langcode 'no' for Norwegian, Drupal
  368       // uses 'nb'. This would then remove the 'no' => 'no' mapping and replace
  369       // it with 'nb' => 'no'. Now Drupal knows which CKEditor translation to
  370       // load.
  371       if (isset($language_mappings[$langcode]) && !isset($langcodes[$language_mappings[$langcode]])) {
  372         $langcodes[$language_mappings[$langcode]] = $langcode;
  373         unset($langcodes[$langcode]);
  374       }
  375     }
  376 
  377     return $langcodes;
  378   }
  379 
  380   /**
  381    * {@inheritdoc}
  382    */
  383   public function getLibraries(Editor $editor) {
  384     $libraries = [
  385       'ckeditor/drupal.ckeditor',
  386     ];
  387 
  388     // Get the required libraries for any enabled plugins.
  389     $enabled_plugins = array_keys($this->ckeditorPluginManager->getEnabledPluginFiles($editor));
  390     foreach ($enabled_plugins as $plugin_id) {
  391       $plugin = $this->ckeditorPluginManager->createInstance($plugin_id);
  392       $additional_libraries = array_diff($plugin->getLibraries($editor), $libraries);
  393       $libraries = array_merge($libraries, $additional_libraries);
  394     }
  395 
  396     return $libraries;
  397   }
  398 
  399   /**
  400    * Builds the "toolbar" configuration part of the CKEditor JS settings.
  401    *
  402    * @see getJSSettings()
  403    *
  404    * @param \Drupal\editor\Entity\Editor $editor
  405    *   A configured text editor object.
  406    *
  407    * @return array
  408    *   An array containing the "toolbar" configuration.
  409    */
  410   public function buildToolbarJSSetting(Editor $editor) {
  411     $toolbar = [];
  412 
  413     $settings = $editor->getSettings();
  414     foreach ($settings['toolbar']['rows'] as $row) {
  415       foreach ($row as $group) {
  416         $toolbar[] = $group;
  417       }
  418       $toolbar[] = '/';
  419     }
  420     return $toolbar;
  421   }
  422 
  423   /**
  424    * Builds the "contentsCss" configuration part of the CKEditor JS settings.
  425    *
  426    * @see getJSSettings()
  427    *
  428    * @param \Drupal\editor\Entity\Editor $editor
  429    *   A configured text editor object.
  430    *
  431    * @return array
  432    *   An array containing the "contentsCss" configuration.
  433    */
  434   public function buildContentsCssJSSetting(Editor $editor) {
  435     $css = [
  436       drupal_get_path('module', 'ckeditor') . '/css/ckeditor-iframe.css',
  437       drupal_get_path('module', 'system') . '/css/components/align.module.css',
  438     ];
  439     $this->moduleHandler->alter('ckeditor_css', $css, $editor);
  440     // Get a list of all enabled plugins' iframe instance CSS files.
  441     $plugins_css = array_reduce($this->ckeditorPluginManager->getCssFiles($editor), function ($result, $item) {
  442       return array_merge($result, array_values($item));
  443     }, []);
  444     $css = array_merge($css, $plugins_css);
  445     $css = array_merge($css, _ckeditor_theme_css());
  446     $query_string = $this->state->get('system.css_js_query_string', '0');
  447     $css = array_map(function ($item) use ($query_string) {
  448       $query_string_separator = (strpos($item, '?') !== FALSE) ? '&' : '?';
  449       return $item . $query_string_separator . $query_string;
  450     }, $css);
  451     $css = array_map('file_create_url', $css);
  452     $css = array_map('file_url_transform_relative', $css);
  453 
  454     return array_values($css);
  455   }
  456 
  457 }