View source  
  <?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\Core\State\StateInterface;
use Drupal\editor\Plugin\EditorBase;
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
use Drupal\editor\Entity\Editor;
use Symfony\Component\DependencyInjection\ContainerInterface;
class CKEditor extends EditorBase implements ContainerFactoryPluginInterface {
  
  protected $moduleHandler;
  
  protected $languageManager;
  
  protected $ckeditorPluginManager;
  
  protected $renderer;
  
  protected $state;
  
  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;
  }
  
  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'));
  }
  
  public function getDefaultSettings() {
    return [
      'toolbar' => [
        'rows' => [
          
          [
            [
              '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',
        ],
      ],
    ];
  }
  
  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',
        ],
      ],
    ];
    
    $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']);
    }
    
    $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();
      }
    }
    
    $all_buttons = array_reduce($this->ckeditorPluginManager
      ->getButtons(), function ($result, $item) {
      return array_merge($result, array_keys($item));
    }, []);
    
    $fake_editor = Editor::create([
      'format' => $editor
        ->id(),
      'editor' => 'ckeditor',
      'settings' => [
        
        'toolbar' => [
          'rows' => [
            0 => [
              0 => [
                'name' => 'All existing buttons',
                'items' => $all_buttons,
              ],
            ],
          ],
        ],
        'plugins' => $settings['plugins'],
      ],
    ]);
    $config = $this
      ->getJSSettings($fake_editor);
    
    unset($config['allowedContent']);
    $form['hidden_ckeditor'] = [
      '#markup' => '<div id="ckeditor-hidden" class="hidden"></div>',
      '#attached' => [
        'drupalSettings' => [
          'ckeditor' => [
            'hiddenCKEditorConfig' => $config,
          ],
        ],
      ],
    ];
    return $form;
  }
  
  public function validateConfigurationForm(array &$form, FormStateInterface $form_state) {
  }
  
  public function submitConfigurationForm(array &$form, FormStateInterface $form_state) {
    
    $form_state
      ->setValue([
      'toolbar',
      'rows',
    ], json_decode($form_state
      ->getValue([
      'toolbar',
      'button_groups',
    ]), TRUE));
    $form_state
      ->unsetValue([
      'toolbar',
      'button_groups',
    ]);
    
    if ($form_state
      ->hasValue('plugins')) {
      $form_state
        ->unsetValue('plugin_settings');
    }
  }
  
  public function getJSSettings(Editor $editor) {
    $settings = [];
    
    $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);
    }
    
    $display_langcode = 'en';
    
    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()];
      }
    }
    
    $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,
      
      'stylesSet' => FALSE,
    ];
    
    $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),
    ];
    
    if ($this->moduleHandler
      ->moduleExists('locale')) {
      locale_js_translate(array_values($external_plugin_files));
    }
    ksort($settings);
    return $settings;
  }
  
  public function getLangcodes() {
    
    $langcode_cache = \Drupal::cache()
      ->get('ckeditor.langcodes');
    if (!empty($langcode_cache)) {
      $langcodes = $langcode_cache->data;
    }
    if (empty($langcodes)) {
      $langcodes = [];
      
      $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);
    }
    
    $language_mappings = $this->moduleHandler
      ->moduleExists('language') ? language_get_browser_drupal_langcode_mappings() : [];
    foreach ($langcodes as $langcode) {
      
      if (isset($language_mappings[$langcode]) && !isset($langcodes[$language_mappings[$langcode]])) {
        $langcodes[$language_mappings[$langcode]] = $langcode;
        unset($langcodes[$langcode]);
      }
    }
    return $langcodes;
  }
  
  public function getLibraries(Editor $editor) {
    $libraries = [
      'ckeditor/drupal.ckeditor',
    ];
    
    $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;
  }
  
  public function buildToolbarJSSetting(Editor $editor) {
    $toolbar = [];
    $settings = $editor
      ->getSettings();
    foreach ($settings['toolbar']['rows'] as $row) {
      foreach ($row as $group) {
        $toolbar[] = $group;
      }
      $toolbar[] = '/';
    }
    return $toolbar;
  }
  
  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);
    
    $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);
  }
}