CKEditor.php 10.2 KB
Newer Older
1 2 3 4
<?php

/**
 * @file
5
 * Contains \Drupal\ckeditor\Plugin\Editor\CKEditor.
6 7
 */

8
namespace Drupal\ckeditor\Plugin\Editor;
9

10
use Drupal\ckeditor\CKEditorPluginManager;
11
use Drupal\Core\Language\Language;
12
use Drupal\editor\Plugin\EditorBase;
13
use Drupal\editor\Annotation\Editor;
14
use Drupal\Core\Annotation\Translation;
15
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
16
use Drupal\editor\Plugin\Core\Entity\Editor as EditorEntity;
17
use Symfony\Component\DependencyInjection\ContainerInterface;
18 19 20 21

/**
 * Defines a CKEditor-based text editor for Drupal.
 *
22
 * @Editor(
23 24
 *   id = "ckeditor",
 *   label = @Translation("CKEditor"),
25
 *   supports_inline_editing = TRUE
26 27
 * )
 */
28
class CKEditor extends EditorBase implements ContainerFactoryPluginInterface {
29 30

  /**
31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62
   * The CKEditor plugin manager.
   *
   * @var \Drupal\ckeditor\CKEditorPluginManager
   */
  protected $ckeditorPluginManager;

  /**
   * 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 array $plugin_definition
   *   The plugin implementation definition.
   * @param \Drupal\ckeditor\CKEditorPluginManager $ckeditor_plugin_manager
   *   The CKEditor plugin manager.
   */
  public function __construct(array $configuration, $plugin_id, array $plugin_definition, CKEditorPluginManager $ckeditor_plugin_manager) {
    parent::__construct($configuration, $plugin_id, $plugin_definition);
    $this->ckeditorPluginManager = $ckeditor_plugin_manager;
  }

  /**
   * {@inheritdoc}
   */
  public static function create(ContainerInterface $container, array $configuration, $plugin_id, array $plugin_definition) {
    return new static($configuration, $plugin_id, $plugin_definition, $container->get('plugin.manager.ckeditor.plugin'));
  }

  /**
   * {@inheritdoc}
63 64 65 66 67 68 69
   */
  public function getDefaultSettings() {
    return array(
      'toolbar' => array(
        'buttons' => array(
          array(
            'Bold', 'Italic',
70
            '|', 'DrupalLink', 'DrupalUnlink',
71
            '|', 'BulletedList', 'NumberedList',
72
            '|', 'Blockquote', 'DrupalImage',
73 74 75 76 77 78 79 80 81
            '|', 'Source',
          ),
        ),
      ),
      'plugins' => array(),
    );
  }

  /**
82
   * {@inheritdoc}
83
   */
84
  public function settingsForm(array $form, array &$form_state, EditorEntity $editor) {
85
    $module_path = drupal_get_path('module', 'ckeditor');
86 87 88
    $ckeditor_settings_toolbar = array(
      '#theme' => 'ckeditor_settings_toolbar',
      '#editor' => $editor,
89
      '#plugins' => $this->ckeditorPluginManager->getButtonsPlugins(),
90
    );
91 92 93 94 95 96 97 98
    $form['toolbar'] = array(
      '#type' => 'container',
      '#attached' => array(
        'library' => array(array('ckeditor', 'drupal.ckeditor.admin')),
        'js' => array(
          array(
            'type' => 'setting',
            'data' => array('ckeditor' => array(
99
              'toolbarAdmin' => drupal_render($ckeditor_settings_toolbar),
100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116
            )),
          )
        ),
      ),
      '#attributes' => array('class' => array('ckeditor-toolbar-configuration')),
    );
    $form['toolbar']['buttons'] = array(
      '#type' => 'textarea',
      '#title' => t('Toolbar buttons'),
      '#default_value' => json_encode($editor->settings['toolbar']['buttons']),
      '#attributes' => array('class' => array('ckeditor-toolbar-textarea')),
    );

    // CKEditor plugin settings, if any.
    $form['plugin_settings'] = array(
      '#type' => 'vertical_tabs',
    );
117
    $this->ckeditorPluginManager->injectPluginSettingsForm($form, $form_state, $editor);
118 119 120 121 122
    if (count(element_children($form['plugins'])) === 0) {
      unset($form['plugins']);
      unset($form['plugin_settings']);
    }

123 124 125 126 127 128
    // Hidden CKEditor instance. We need a hidden CKEditor instance with all
    // plugins enabled, so we can retrieve CKEditor's per-feature metadata (on
    // which tags, attributes, styles and classes are enabled). This metadata is
    // necessary for certain filters' (e.g. the html_filter filter) settings to
    // be updated accordingly.
    // Get a list of all external plugins and their corresponding files.
129
    $plugins = array_keys($this->ckeditorPluginManager->getDefinitions());
130 131
    $all_external_plugins = array();
    foreach ($plugins as $plugin_id) {
132
      $plugin = $this->ckeditorPluginManager->createInstance($plugin_id);
133 134 135 136 137
      if (!$plugin->isInternal()) {
        $all_external_plugins[$plugin_id] = $plugin->getFile();
      }
    }
    // Get a list of all buttons that are provided by all plugins.
138
    $all_buttons = array_reduce($this->ckeditorPluginManager->getButtonsPlugins(), function($result, $item) {
139 140 141 142 143 144 145 146 147 148 149 150 151
      return array_merge($result, array_keys($item));
    }, array());
    // Build a fake Editor object, which we'll use to generate JavaScript
    // settings for this fake Editor instance.
    $fake_editor = entity_create('editor', array(
      'format' => '',
      'editor' => 'ckeditor',
      'settings' => array(
        // Single toolbar row that contains all existing buttons.
        'toolbar' => array('buttons' => array(0 => $all_buttons)),
        'plugins' => $editor->settings['plugins'],
      ),
    ));
152 153 154 155
    $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']);
156 157 158 159 160 161 162
    $form['hidden_ckeditor'] = array(
      '#markup' => '<div id="ckeditor-hidden" class="element-hidden"></div>',
      '#attached' => array(
        'js' => array(
          array(
            'type' => 'setting',
            'data' => array('ckeditor' => array(
163
              'hiddenCKEditorConfig' => $config,
164 165 166 167 168 169
            )),
          ),
        ),
      ),
    );

170 171 172 173
    return $form;
  }

  /**
174
   * {@inheritdoc}
175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190
   */
  public function settingsFormSubmit(array $form, array &$form_state) {
    // Modify the toolbar settings by reference. The values in
    // $form_state['values']['editor']['settings'] will be saved directly by
    // editor_form_filter_admin_format_submit().
    $toolbar_settings = &$form_state['values']['editor']['settings']['toolbar'];

    $toolbar_settings['buttons'] = json_decode($toolbar_settings['buttons'], FALSE);

    // Remove the plugin settings' vertical tabs state; no need to save that.
    if (isset($form_state['values']['editor']['settings']['plugins'])) {
      unset($form_state['values']['editor']['settings']['plugin_settings']);
    }
  }

  /**
191
   * {@inheritdoc}
192
   */
193
  public function getJSSettings(EditorEntity $editor) {
194
    $language_interface = language(Language::TYPE_INTERFACE);
195 196 197 198

    $settings = array();

    // Get the settings for all enabled plugins, even the internal ones.
199
    $enabled_plugins = array_keys($this->ckeditorPluginManager->getEnabledPlugins($editor, TRUE));
200
    foreach ($enabled_plugins as $plugin_id) {
201
      $plugin = $this->ckeditorPluginManager->createInstance($plugin_id);
202 203 204 205
      $settings += $plugin->getConfig($editor);
    }

    // Next, set the most fundamental CKEditor settings.
206
    $external_plugins = $this->ckeditorPluginManager->getEnabledPlugins($editor);
207 208 209 210
    $settings += array(
      'toolbar' => $this->buildToolbarJSSetting($editor),
      'contentsCss' => $this->buildContentsCssJSSetting($editor),
      'extraPlugins' => implode(',', array_keys($external_plugins)),
211 212
      // @todo: Remove image and link plugins from CKEditor build.
      'removePlugins' => 'image,link',
213
      'language' => $language_interface->id,
214 215 216 217 218 219
      // 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,
220 221 222 223 224 225 226
    );

    // Finally, set Drupal-specific CKEditor settings.
    $settings += array(
      'drupalExternalPlugins' => array_map('file_create_url', $external_plugins),
    );

227 228
    ksort($settings);

229 230 231 232
    return $settings;
  }

  /**
233
   * {@inheritdoc}
234
   */
235
  public function getLibraries(EditorEntity $editor) {
236
    $libraries = array(
237 238
      array('ckeditor', 'drupal.ckeditor'),
    );
239 240 241 242 243 244 245 246 247 248 249 250

    // Get the required libraries for any enabled plugins.
    $enabled_plugins = array_keys($this->ckeditorPluginManager->getEnabledPlugins($editor));
    foreach ($enabled_plugins as $plugin_id) {
      $plugin = $this->ckeditorPluginManager->createInstance($plugin_id);
      $additional_libraries = array_udiff($plugin->getLibraries($editor), $libraries, function($a, $b) {
        return $a[0] === $b[0] && $a[1] === $b[1] ? 0 : 1;
      });
      $libraries = array_merge($libraries, $additional_libraries);
    }

    return $libraries;
251 252 253 254 255 256 257 258 259 260 261 262
  }

  /**
   * Builds the "toolbar" configuration part of the CKEditor JS settings.
   *
   * @see getJSSettings()
   *
   * @param \Drupal\editor\Plugin\Core\Entity\Editor $editor
   *   A configured text editor object.
   * @return array
   *   An array containing the "toolbar" configuration.
   */
263
  public function buildToolbarJSSetting(EditorEntity $editor) {
264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293
    $toolbar = array();
    foreach ($editor->settings['toolbar']['buttons'] as $row_number => $row) {
      $button_group = array();
      foreach ($row as $button_name) {
        // Change the toolbar separators into groups.
        if ($button_name === '|') {
          $toolbar[] = $button_group;
          $button_group = array();
        }
        else {
          $button_group['items'][] = $button_name;
        }
      }
      $toolbar[] = $button_group;
      $toolbar[] = '/';
    }

    return $toolbar;
  }

  /**
   * Builds the "contentsCss" configuration part of the CKEditor JS settings.
   *
   * @see getJSSettings()
   *
   * @param \Drupal\editor\Plugin\Core\Entity\Editor $editor
   *   A configured text editor object.
   * @return array
   *   An array containing the "contentsCss" configuration.
   */
294
  public function buildContentsCssJSSetting(EditorEntity $editor) {
295 296 297 298 299 300 301 302 303 304 305
    $css = array(
      drupal_get_path('module', 'ckeditor') . '/css/ckeditor-iframe.css',
    );
    $css = array_merge($css, _ckeditor_theme_css());
    drupal_alter('ckeditor_css', $css, $editor);
    $css = array_map('file_create_url', $css);

    return array_values($css);
  }

}