3 namespace Drupal\ckeditor\Plugin\CKEditorPlugin;
5 use Drupal\ckeditor\CKEditorPluginBase;
6 use Drupal\ckeditor\CKEditorPluginConfigurableInterface;
7 use Drupal\Core\Form\FormStateInterface;
8 use Drupal\editor\Entity\Editor;
11 * Defines the "stylescombo" plugin.
15 * label = @Translation("Styles dropdown")
18 class StylesCombo extends CKEditorPluginBase implements CKEditorPluginConfigurableInterface {
23 public function isInternal() {
30 public function getFile() {
31 // This plugin is already part of Drupal core's CKEditor build.
38 public function getConfig(Editor $editor) {
40 $settings = $editor->getSettings();
41 if (!isset($settings['plugins']['stylescombo']['styles'])) {
44 $styles = $settings['plugins']['stylescombo']['styles'];
45 $config['stylesSet'] = $this->generateStylesSetSetting($styles);
52 public function getButtons() {
55 'label' => $this->t('Font style'),
56 'image_alternative' => [
57 '#type' => 'inline_template',
58 '#template' => '<a href="#" role="button" aria-label="{{ styles_text }}"><span class="ckeditor-button-dropdown">{{ styles_text }}<span class="ckeditor-button-arrow"></span></span></a>',
60 'styles_text' => $this->t('Styles'),
70 public function settingsForm(array $form, FormStateInterface $form_state, Editor $editor) {
72 $config = ['styles' => ''];
73 $settings = $editor->getSettings();
74 if (isset($settings['plugins']['stylescombo'])) {
75 $config = $settings['plugins']['stylescombo'];
79 '#title' => $this->t('Styles'),
80 '#title_display' => 'invisible',
81 '#type' => 'textarea',
82 '#default_value' => $config['styles'],
83 '#description' => $this->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.'),
85 'library' => ['ckeditor/drupal.ckeditor.stylescombo.admin'],
87 '#element_validate' => [
88 [$this, 'validateStylesValue'],
96 * #element_validate handler for the "styles" element in settingsForm().
98 public function validateStylesValue(array $element, FormStateInterface $form_state) {
99 $styles_setting = $this->generateStylesSetSetting($element['#value']);
100 if ($styles_setting === FALSE) {
101 $form_state->setError($element, $this->t('The provided list of styles is syntactically incorrect.'));
104 $style_names = array_map(function ($style) {
105 return $style['name'];
107 if (count($style_names) !== count(array_unique($style_names))) {
108 $form_state->setError($element, $this->t('Each style must have a unique label.'));
114 * Builds the "stylesSet" configuration part of the CKEditor JS settings.
118 * @param string $styles
119 * The "styles" setting.
120 * @return array|false
121 * An array containing the "stylesSet" configuration, or FALSE when the
124 protected function generateStylesSetSetting($styles) {
127 // Early-return when empty.
128 $styles = trim($styles);
129 if (empty($styles)) {
133 $styles = str_replace(["\r\n", "\r"], "\n", $styles);
134 foreach (explode("\n", $styles) as $style) {
135 $style = trim($style);
137 // Ignore empty lines in between non-empty lines.
142 // Validate syntax: element[.class...]|label pattern expected.
143 if (!preg_match('@^ *[a-zA-Z0-9]+ *(\\.[a-zA-Z0-9_-]+ *)*\\| *.+ *$@', $style)) {
148 list($selector, $label) = explode('|', $style);
149 $classes = explode('.', $selector);
150 $element = array_shift($classes);
152 // Build the data structure CKEditor's stylescombo plugin expects.
153 // @see http://docs.cksource.com/CKEditor_3.x/Developers_Guide/Styles
154 $configured_style = [
155 'name' => trim($label),
156 'element' => trim($element),
158 if (!empty($classes)) {
159 $configured_style['attributes'] = [
160 'class' => implode(' ', array_map('trim', $classes)),
163 $styles_set[] = $configured_style;