Newer
Older
Dries Buytaert
committed
<?php
/**
* @file
* Contains \Drupal\ckeditor\Plugin\CKEditorPlugin\Internal.
Dries Buytaert
committed
*/
namespace Drupal\ckeditor\Plugin\CKEditorPlugin;
Dries Buytaert
committed
use Drupal\ckeditor\CKEditorPluginBase;
use Drupal\Component\Utility\NestedArray;
use Drupal\ckeditor\Annotation\CKEditorPlugin;
Dries Buytaert
committed
use Drupal\Core\Annotation\Translation;
use Drupal\editor\Plugin\Core\Entity\Editor;
/**
* Defines the "internal" plugin (i.e. core plugins part of our CKEditor build).
*
* @CKEditorPlugin(
Dries Buytaert
committed
* id = "internal",
* label = @Translation("CKEditor core")
Dries Buytaert
committed
22
23
24
25
26
27
28
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
* )
*/
class Internal extends CKEditorPluginBase {
/**
* Implements \Drupal\ckeditor\Plugin\CKEditorPluginInterface::isInternal().
*/
public function isInternal() {
return TRUE;
}
/**
* Implements \Drupal\ckeditor\Plugin\CKEditorPluginInterface::getFile().
*/
public function getFile() {
// This plugin is already part of Drupal core's CKEditor build.
return FALSE;
}
/**
* Implements \Drupal\ckeditor\Plugin\CKEditorPluginInterface::getConfig().
*/
public function getConfig(Editor $editor) {
// Reasonable defaults that provide expected basic behavior.
$config = array(
'customConfig' => '', // Don't load CKEditor's config.js file.
'pasteFromWordPromptCleanup' => TRUE,
'removeDialogTabs' => 'image:Link;image:advanced;link:advanced',
'resize_dir' => 'vertical',
'keystrokes' => array(
// 0x11000 is CKEDITOR.CTRL, see http://docs.ckeditor.com/#!/api/CKEDITOR-property-CTRL.
array(0x110000 + 75, 'link'),
array(0x110000 + 76, NULL),
),
);
Dries Buytaert
committed
// Add the allowedContent setting, which ensures CKEditor only allows tags
// and attributes that are allowed by the text format for this text editor.
$config['allowedContent'] = $this->generateAllowedContentSetting($editor);
// Add the format_tags setting, if its button is enabled.
Dries Buytaert
committed
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
$toolbar_buttons = array_unique(NestedArray::mergeDeepArray($editor->settings['toolbar']['buttons']));
if (in_array('Format', $toolbar_buttons)) {
$config['format_tags'] = $this->generateFormatTagsSetting($editor);
}
return $config;
}
/**
* Implements \Drupal\ckeditor\Plugin\CKEditorPluginButtonsInterface::getButtons().
*/
public function getButtons() {
$button = function($name, $direction = 'ltr') {
return '<a href="#" class="cke-icon-only cke_' . $direction . '" role="button" title="' . $name . '" aria-label="' . $name . '"><span class="cke_button_icon cke_button__' . str_replace(' ', '', $name) . '_icon">' . $name . '</span></a>';
};
return array(
// "basicstyles" plugin.
'Bold' => array(
'label' => t('Bold'),
'image_alternative' => $button('bold'),
),
'Italic' => array(
'label' => t('Italic'),
'image_alternative' => $button('italic'),
),
'Strike' => array(
'label' => t('Strike-through'),
'image_alternative' => $button('strike'),
),
'Superscript' => array(
'label' => t('Superscript'),
'image_alternative' => $button('super script'),
),
'Subscript' => array(
'label' => t('Subscript'),
'image_alternative' => $button('sub script'),
),
// "removeformat" plugin.
'RemoveFormat' => array(
'label' => t('Remove format'),
'image_alternative' => $button('remove format'),
),
// "list" plugin.
'BulletedList' => array(
'label' => t('Bullet list'),
'image_alternative' => $button('bulleted list'),
'image_alternative_rtl' => $button('bulleted list', 'rtl'),
),
'NumberedList' => array(
'label' => t('Numbered list'),
'image_alternative' => $button('numbered list'),
'image_alternative_rtl' => $button('numbered list', 'rtl'),
),
// "indent" plugin.
'Outdent' => array(
'label' => t('Outdent'),
'image_alternative' => $button('outdent'),
'image_alternative_rtl' => $button('outdent', 'rtl'),
),
'Indent' => array(
'label' => t('Indent'),
'image_alternative' => $button('indent'),
'image_alternative_rtl' => $button('indent', 'rtl'),
),
// "undo" plugin.
'Undo' => array(
'label' => t('Undo'),
'image_alternative' => $button('undo'),
'image_alternative_rtl' => $button('undo', 'rtl'),
),
'Redo' => array(
'label' => t('Redo'),
'image_alternative' => $button('redo'),
'image_alternative_rtl' => $button('redo', 'rtl'),
),
// "link" plugin.
'Link' => array(
'label' => t('Link'),
'image_alternative' => $button('link'),
),
'Unlink' => array(
'label' => t('Unlink'),
'image_alternative' => $button('unlink'),
),
'Anchor' => array(
'label' => t('Anchor'),
'image_alternative' => $button('anchor'),
'image_alternative_rtl' => $button('anchor', 'rtl'),
),
// "blockquote" plugin.
'Blockquote' => array(
'label' => t('Blockquote'),
'image_alternative' => $button('blockquote'),
),
// "horizontalrule" plugin
'HorizontalRule' => array(
'label' => t('Horizontal rule'),
'image_alternative' => $button('horizontal rule'),
),
// "clipboard" plugin.
'Cut' => array(
'label' => t('Cut'),
'image_alternative' => $button('cut'),
'image_alternative_rtl' => $button('cut', 'rtl'),
),
'Copy' => array(
'label' => t('Copy'),
'image_alternative' => $button('copy'),
'image_alternative_rtl' => $button('copy', 'rtl'),
),
'Paste' => array(
'label' => t('Paste'),
'image_alternative' => $button('paste'),
'image_alternative_rtl' => $button('paste', 'rtl'),
),
// "pastetext" plugin.
'PasteText' => array(
'label' => t('Paste Text'),
'image_alternative' => $button('paste text'),
'image_alternative_rtl' => $button('paste text', 'rtl'),
),
// "pastefromword" plugin.
'PasteFromWord' => array(
'label' => t('Paste from Word'),
'image_alternative' => $button('paste from word'),
'image_alternative_rtl' => $button('paste from word', 'rtl'),
),
// "specialchar" plugin.
'SpecialChar' => array(
'label' => t('Character map'),
'image_alternative' => $button('special char'),
),
'Format' => array(
'label' => t('HTML block format'),
'image_alternative' => '<a href="#" role="button" aria-label="' . t('Format') . '"><span class="ckeditor-button-dropdown">' . t('Format') . '<span class="ckeditor-button-arrow"></span></span></a>',
),
// "image" plugin.
'Image' => array(
'label' => t('Image'),
'image_alternative' => $button('image'),
),
// "table" plugin.
'Table' => array(
'label' => t('Table'),
'image_alternative' => $button('table'),
),
// "showblocks" plugin.
'ShowBlocks' => array(
'label' => t('Show blocks'),
'image_alternative' => $button('show blocks'),
'image_alternative_rtl' => $button('show blocks', 'rtl'),
),
// "sourcearea" plugin.
'Source' => array(
'label' => t('Source code'),
'image_alternative' => $button('source'),
),
// "maximize" plugin.
'Maximize' => array(
'label' => t('Maximize'),
'image_alternative' => $button('maximize'),
),
// No plugin, separator "buttons" for toolbar builder UI use only.
'|' => array(
'label' => t('Group separator'),
'image_alternative' => '<a href="#" role="button" aria-label="' . t('Button group separator') . '" class="ckeditor-group-separator"></a>',
'attributes' => array('class' => array('ckeditor-group-button-separator')),
'multiple' => TRUE,
),
'-' => array(
'label' => t('Separator'),
'image_alternative' => '<a href="#" role="button" aria-label="' . t('Button separator') . '" class="ckeditor-separator"></a>',
'attributes' => array('class' => array('ckeditor-button-separator')),
'multiple' => TRUE,
),
);
}
/**
* Builds the "format_tags" configuration part of the CKEditor JS settings.
*
* @see getConfig()
*
* @param \Drupal\editor\Plugin\Core\Entity\Editor $editor
* A configured text editor object.
Dries Buytaert
committed
*
Dries Buytaert
committed
* @return array
* An array containing the "format_tags" configuration.
*/
protected function generateFormatTagsSetting(Editor $editor) {
// The <p> tag is always allowed — HTML without <p> tags is nonsensical.
$format_tags = array('p');
// Given the list of possible format tags, automatically determine whether
// the current text format allows this tag, and thus whether it should show
// up in the "Format" dropdown.
$possible_format_tags = array('h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'pre');
foreach ($possible_format_tags as $tag) {
$input = '<' . $tag . '>TEST</' . $tag . '>';
$output = trim(check_markup($input, $editor->format));
if ($input == $output) {
$format_tags[] = $tag;
}
}
return implode(';', $format_tags);
}
Dries Buytaert
committed
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
/**
* Builds the "allowedContent" configuration part of the CKEditor JS settings.
*
* This ensures that CKEditor obeys the HTML restrictions defined by Drupal's
* filter system, by enabling CKEditor's Advanced Content Filter (ACF)
* functionality: http://ckeditor.com/blog/CKEditor-4.1-RC-Released.
*
* @see getConfig()
*
* @param \Drupal\editor\Plugin\Core\Entity\Editor $editor
* A configured text editor object.
*
* @return string|TRUE
* The "allowedContent" configuration: a well-formatted string or TRUE. The
* latter indicates that anything is allowed.
*/
protected function generateAllowedContentSetting(Editor $editor) {
// When nothing is disallowed, set allowedContent to true.
$filter_types = filter_get_filter_types_by_format($editor->format);
if (!in_array(FILTER_TYPE_HTML_RESTRICTOR, $filter_types)) {
return TRUE;
}
// Generate setting that accurately reflects allowed tags and attributes.
else {
$get_allowed_attribute_values = function($attribute_values) {
$values = array_keys(array_filter($attribute_values, function($value) {
return $value !== FALSE;
}));
if (count($values)) {
return implode(',', $values);
}
else {
return NULL;
}
};
$html_restrictions = filter_get_html_restrictions_by_format($editor->format);
// When all HTML is allowed, also set allowedContent to true.
if ($html_restrictions === FALSE) {
return TRUE;
}
$setting = array();
foreach ($html_restrictions['allowed'] as $tag => $attributes) {
// Tell CKEditor the tag is allowed, but no attributes.
if ($attributes === FALSE) {
$setting[$tag] = array(
'attributes' => FALSE,
'styles' => FALSE,
'classes' => FALSE,
);
}
// Tell CKEditor the tag is allowed, as well as any attribute on it. The
// "style" and "class" attributes are handled separately by CKEditor:
// they are disallowed even if you specify it in the list of allowed
// attributes, unless you state specific values for them that are
// allowed. Or, in this case: any value for them is allowed.
elseif ($attributes === TRUE) {
$setting[$tag] = array(
'attributes' => TRUE,
'styles' => TRUE,
'classes' => TRUE,
);
// We've just marked that any value for the "style" and "class"
// attributes is allowed. However, that may not be the case: the "*"
// tag may still apply restrictions.
// Since CKEditor's ACF follows the following principle:
// Once validated, an element or its property cannot be
// invalidated by another rule.
// That means that the most permissive setting wins. Which means that
// it will still be allowed by CKEditor to e.g. define any style, no
// matter what the "*" tag's restrictions may be. If there's a setting
// for either the "style" or "class" attribute, it cannot possibly be
// more permissive than what was set above. Hence: inherit from the
// "*" tag where possible.
if (isset($html_restrictions['allowed']['*'])) {
$wildcard = $html_restrictions['allowed']['*'];
if (isset($wildcard['style'])) {
if (!is_array($wildcard['style'])) {
$setting[$tag]['styles'] = $wildcard['style'];
}
else {
$allowed_styles = $get_allowed_attribute_values($wildcard['style']);
if (isset($allowed_styles)) {
$setting[$tag]['styles'] = $allowed_styles;
}
else {
unset($setting[$tag]['styles']);
}
}
}
if (isset($wildcard['class'])) {
if (!is_array($wildcard['class'])) {
$setting[$tag]['classes'] = $wildcard['class'];
}
else {
$allowed_classes = $get_allowed_attribute_values($wildcard['class']);
if (isset($allowed_classes)) {
$setting[$tag]['classes'] = $allowed_classes;
}
else {
unset($setting[$tag]['classes']);
}
}
}
}
}
// Tell CKEditor the tag is allowed, along with some tags.
elseif (is_array($attributes)) {
// CKEditor does not yet support blacklisting, so ignore those.
// @todo Update this once http://dev.ckeditor.com/ticket/10276 lands.
$attributes = array_filter($attributes, function($value) {
return $value !== FALSE;
});
// Configure allowed attributes, allowed "style" attribute values and
// allowed "class" attribute values.
// CKEditor only allows specific values for the "class" and "style"
// attributes; so ignore restrictions on other attributes, which
// Drupal filters may provide.
// NOTE: A Drupal contrib module can subclass this class, override the
// getConfig() method, and override the JavaScript at
// Drupal.editors.ckeditor to somehow make validation of values for
// attributes other than "class" and "style" work.
if (count($attributes)) {
$setting[$tag]['attributes'] = implode(',', array_keys($attributes));
}
if (isset($attributes['style']) && is_array($attributes['style'])) {
$allowed_styles = $get_allowed_attribute_values($attributes['style']);
if (isset($allowed_values)) {
$setting[$tag]['styles'] = $allowed_styles;
}
}
if (isset($attributes['class']) && is_array($attributes['class'])) {
$allowed_classes = $get_allowed_attribute_values($attributes['class']);
if (isset($allowed_classes)) {
$setting[$tag]['classes'] = $allowed_classes;
}
}
}
}
return $setting;
}
}