]> git.immae.eu Git - perso/Immae/Projets/packagist/piedsjaloux-ckeditor-component.git/blob - sources/core/filter.js
Add oembed
[perso/Immae/Projets/packagist/piedsjaloux-ckeditor-component.git] / sources / core / filter.js
1 /**
2 * @license Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved.
3 * For licensing, see LICENSE.md or http://ckeditor.com/license
4 */
5
6 ( function() {
7 'use strict';
8
9 var DTD = CKEDITOR.dtd,
10 // processElement flag - means that element has been somehow modified.
11 FILTER_ELEMENT_MODIFIED = 1,
12 // processElement flag - meaning explained in CKEDITOR.FILTER_SKIP_TREE doc.
13 FILTER_SKIP_TREE = 2,
14 copy = CKEDITOR.tools.copy,
15 trim = CKEDITOR.tools.trim,
16 TEST_VALUE = 'cke-test',
17 enterModeTags = [ '', 'p', 'br', 'div' ];
18
19 /**
20 * A flag indicating that the current element and all its ancestors
21 * should not be filtered.
22 *
23 * See {@link CKEDITOR.filter#addElementCallback} for more details.
24 *
25 * @since 4.4
26 * @readonly
27 * @property {Number} [=2]
28 * @member CKEDITOR
29 */
30 CKEDITOR.FILTER_SKIP_TREE = FILTER_SKIP_TREE;
31
32 /**
33 * Highly configurable class which implements input data filtering mechanisms
34 * and core functions used for the activation of editor features.
35 *
36 * A filter instance is always available under the {@link CKEDITOR.editor#filter}
37 * property and is used by the editor in its core features like filtering input data,
38 * applying data transformations, validating whether a feature may be enabled for
39 * the current setup. It may be configured in two ways:
40 *
41 * * By the user, with the {@link CKEDITOR.config#allowedContent} setting.
42 * * Automatically, by loaded features (toolbar items, commands, etc.).
43 *
44 * In both cases additional allowed content rules may be added by
45 * setting the {@link CKEDITOR.config#extraAllowedContent}
46 * configuration option.
47 *
48 * **Note**: Filter rules will be extended with the following elements
49 * depending on the {@link CKEDITOR.config#enterMode} and
50 * {@link CKEDITOR.config#shiftEnterMode} settings:
51 *
52 * * `'p'` – for {@link CKEDITOR#ENTER_P},
53 * * `'div'` – for {@link CKEDITOR#ENTER_DIV},
54 * * `'br'` – for {@link CKEDITOR#ENTER_BR}.
55 *
56 * **Read more** about the Advanced Content Filter in [guides](#!/guide/dev_advanced_content_filter).
57 *
58 * Filter may also be used as a standalone instance by passing
59 * {@link CKEDITOR.filter.allowedContentRules} instead of {@link CKEDITOR.editor}
60 * to the constructor:
61 *
62 * var filter = new CKEDITOR.filter( 'b' );
63 *
64 * filter.check( 'b' ); // -> true
65 * filter.check( 'i' ); // -> false
66 * filter.allow( 'i' );
67 * filter.check( 'i' ); // -> true
68 *
69 * @since 4.1
70 * @class
71 * @constructor Creates a filter class instance.
72 * @param {CKEDITOR.editor/CKEDITOR.filter.allowedContentRules} editorOrRules
73 */
74 CKEDITOR.filter = function( editorOrRules ) {
75 /**
76 * Whether custom {@link CKEDITOR.config#allowedContent} was set.
77 *
78 * This property does not apply to the standalone filter.
79 *
80 * @readonly
81 * @property {Boolean} customConfig
82 */
83
84 /**
85 * Array of rules added by the {@link #allow} method (including those
86 * loaded from {@link CKEDITOR.config#allowedContent} and
87 * {@link CKEDITOR.config#extraAllowedContent}).
88 *
89 * Rules in this array are in unified allowed content rules format.
90 *
91 * This property is useful for debugging issues with rules string parsing
92 * or for checking which rules were automatically added by editor features.
93 *
94 * @readonly
95 */
96 this.allowedContent = [];
97
98 /**
99 * Array of rules added by the {@link #disallow} method (including those
100 * loaded from {@link CKEDITOR.config#disallowedContent}).
101 *
102 * Rules in this array are in unified disallowed content rules format.
103 *
104 * This property is useful for debugging issues with rules string parsing
105 * or for checking which rules were automatically added by editor features.
106 *
107 * @since 4.4
108 * @readonly
109 */
110 this.disallowedContent = [];
111
112 /**
113 * Array of element callbacks. See {@link #addElementCallback}.
114 *
115 * @readonly
116 * @property {Function[]} [=null]
117 */
118 this.elementCallbacks = null;
119
120 /**
121 * Whether the filter is disabled.
122 *
123 * To disable the filter, set {@link CKEDITOR.config#allowedContent} to `true`
124 * or use the {@link #disable} method.
125 *
126 * @readonly
127 */
128 this.disabled = false;
129
130 /**
131 * Editor instance if not a standalone filter.
132 *
133 * @readonly
134 * @property {CKEDITOR.editor} [=null]
135 */
136 this.editor = null;
137
138 /**
139 * Filter's unique id. It can be used to find filter instance in
140 * {@link CKEDITOR.filter#instances CKEDITOR.filter.instance} object.
141 *
142 * @since 4.3
143 * @readonly
144 * @property {Number} id
145 */
146 this.id = CKEDITOR.tools.getNextNumber();
147
148 this._ = {
149 // Optimized allowed content rules.
150 allowedRules: {
151 elements: {},
152 generic: []
153 },
154 // Optimized disallowed content rules.
155 disallowedRules: {
156 elements: {},
157 generic: []
158 },
159 // Object: element name => array of transformations groups.
160 transformations: {},
161 cachedTests: {},
162 cachedChecks: {}
163 };
164
165 // Register filter instance.
166 CKEDITOR.filter.instances[ this.id ] = this;
167
168 if ( editorOrRules instanceof CKEDITOR.editor ) {
169 var editor = this.editor = editorOrRules;
170 this.customConfig = true;
171
172 var allowedContent = editor.config.allowedContent;
173
174 // Disable filter completely by setting config.allowedContent = true.
175 if ( allowedContent === true ) {
176 this.disabled = true;
177 return;
178 }
179
180 if ( !allowedContent )
181 this.customConfig = false;
182
183 this.allow( allowedContent, 'config', 1 );
184 this.allow( editor.config.extraAllowedContent, 'extra', 1 );
185
186 // Enter modes should extend filter rules (ENTER_P adds 'p' rule, etc.).
187 this.allow( enterModeTags[ editor.enterMode ] + ' ' + enterModeTags[ editor.shiftEnterMode ], 'default', 1 );
188
189 this.disallow( editor.config.disallowedContent );
190 }
191 // Rules object passed in editorOrRules argument - initialize standalone filter.
192 else {
193 this.customConfig = false;
194 this.allow( editorOrRules, 'default', 1 );
195 }
196 };
197
198 /**
199 * Object containing all filter instances stored under their
200 * {@link #id} properties.
201 *
202 * var filter = new CKEDITOR.filter( 'p' );
203 * filter === CKEDITOR.filter.instances[ filter.id ];
204 *
205 * @since 4.3
206 * @static
207 * @property instances
208 */
209 CKEDITOR.filter.instances = {};
210
211 CKEDITOR.filter.prototype = {
212 /**
213 * Adds allowed content rules to the filter.
214 *
215 * Read about rules formats in [Allowed Content Rules guide](#!/guide/dev_allowed_content_rules).
216 *
217 * // Add a basic rule for custom image feature (e.g. 'MyImage' button).
218 * editor.filter.allow( 'img[!src,alt]', 'MyImage' );
219 *
220 * // Add rules for two header styles allowed by 'HeadersCombo'.
221 * var header1Style = new CKEDITOR.style( { element: 'h1' } ),
222 * header2Style = new CKEDITOR.style( { element: 'h2' } );
223 * editor.filter.allow( [ header1Style, header2Style ], 'HeadersCombo' );
224 *
225 * @param {CKEDITOR.filter.allowedContentRules} newRules Rule(s) to be added.
226 * @param {String} [featureName] Name of a feature that allows this content (most often plugin/button/command name).
227 * @param {Boolean} [overrideCustom] By default this method will reject any rules
228 * if {@link CKEDITOR.config#allowedContent} is defined to avoid overriding it.
229 * Pass `true` to force rules addition.
230 * @returns {Boolean} Whether the rules were accepted.
231 */
232 allow: function( newRules, featureName, overrideCustom ) {
233 // Check arguments and constraints. Clear cache.
234 if ( !beforeAddingRule( this, newRules, overrideCustom ) )
235 return false;
236
237 var i, ret;
238
239 if ( typeof newRules == 'string' )
240 newRules = parseRulesString( newRules );
241 else if ( newRules instanceof CKEDITOR.style ) {
242 // If style has the cast method defined, use it and abort.
243 if ( newRules.toAllowedContentRules )
244 return this.allow( newRules.toAllowedContentRules( this.editor ), featureName, overrideCustom );
245
246 newRules = convertStyleToRules( newRules );
247 } else if ( CKEDITOR.tools.isArray( newRules ) ) {
248 for ( i = 0; i < newRules.length; ++i )
249 ret = this.allow( newRules[ i ], featureName, overrideCustom );
250 return ret; // Return last status.
251 }
252
253 addAndOptimizeRules( this, newRules, featureName, this.allowedContent, this._.allowedRules );
254
255 return true;
256 },
257
258 /**
259 * Applies this filter to passed {@link CKEDITOR.htmlParser.fragment} or {@link CKEDITOR.htmlParser.element}.
260 * The result of filtering is a DOM tree without disallowed content.
261 *
262 * // Create standalone filter passing 'p' and 'b' elements.
263 * var filter = new CKEDITOR.filter( 'p b' ),
264 * // Parse HTML string to pseudo DOM structure.
265 * fragment = CKEDITOR.htmlParser.fragment.fromHtml( '<p><b>foo</b> <i>bar</i></p>' ),
266 * writer = new CKEDITOR.htmlParser.basicWriter();
267 *
268 * filter.applyTo( fragment );
269 * fragment.writeHtml( writer );
270 * writer.getHtml(); // -> '<p><b>foo</b> bar</p>'
271 *
272 * @param {CKEDITOR.htmlParser.fragment/CKEDITOR.htmlParser.element} fragment Node to be filtered.
273 * @param {Boolean} [toHtml] Set to `true` if the filter is used together with {@link CKEDITOR.htmlDataProcessor#toHtml}.
274 * @param {Boolean} [transformOnly] If set to `true` only transformations will be applied. Content
275 * will not be filtered with allowed content rules.
276 * @param {Number} [enterMode] Enter mode used by the filter when deciding how to strip disallowed element.
277 * Defaults to {@link CKEDITOR.editor#activeEnterMode} for a editor's filter or to {@link CKEDITOR#ENTER_P} for standalone filter.
278 * @returns {Boolean} Whether some part of the `fragment` was removed by the filter.
279 */
280 applyTo: function( fragment, toHtml, transformOnly, enterMode ) {
281 if ( this.disabled )
282 return false;
283
284 var that = this,
285 toBeRemoved = [],
286 protectedRegexs = this.editor && this.editor.config.protectedSource,
287 processRetVal,
288 isModified = false,
289 filterOpts = {
290 doFilter: !transformOnly,
291 doTransform: true,
292 doCallbacks: true,
293 toHtml: toHtml
294 };
295
296 // Filter all children, skip root (fragment or editable-like wrapper used by data processor).
297 fragment.forEach( function( el ) {
298 if ( el.type == CKEDITOR.NODE_ELEMENT ) {
299 // Do not filter element with data-cke-filter="off" and all their descendants.
300 if ( el.attributes[ 'data-cke-filter' ] == 'off' )
301 return false;
302
303 // (http://dev.ckeditor.com/ticket/10260) Don't touch elements like spans with data-cke-* attribute since they're
304 // responsible e.g. for placing markers, bookmarks, odds and stuff.
305 // We love 'em and we don't wanna lose anything during the filtering.
306 // '|' is to avoid tricky joints like data-="foo" + cke-="bar". Yes, they're possible.
307 //
308 // NOTE: data-cke-* assigned elements are preserved only when filter is used with
309 // htmlDataProcessor.toHtml because we don't want to protect them when outputting data
310 // (toDataFormat).
311 if ( toHtml && el.name == 'span' && ~CKEDITOR.tools.objectKeys( el.attributes ).join( '|' ).indexOf( 'data-cke-' ) )
312 return;
313
314 processRetVal = processElement( that, el, toBeRemoved, filterOpts );
315 if ( processRetVal & FILTER_ELEMENT_MODIFIED )
316 isModified = true;
317 else if ( processRetVal & FILTER_SKIP_TREE )
318 return false;
319 }
320 else if ( el.type == CKEDITOR.NODE_COMMENT && el.value.match( /^\{cke_protected\}(?!\{C\})/ ) ) {
321 if ( !processProtectedElement( that, el, protectedRegexs, filterOpts ) )
322 toBeRemoved.push( el );
323 }
324 }, null, true );
325
326 if ( toBeRemoved.length )
327 isModified = true;
328
329 var node, element, check,
330 toBeChecked = [],
331 enterTag = enterModeTags[ enterMode || ( this.editor ? this.editor.enterMode : CKEDITOR.ENTER_P ) ],
332 parentDtd;
333
334 // Remove elements in reverse order - from leaves to root, to avoid conflicts.
335 while ( ( node = toBeRemoved.pop() ) ) {
336 if ( node.type == CKEDITOR.NODE_ELEMENT )
337 removeElement( node, enterTag, toBeChecked );
338 // This is a comment securing rejected element - remove it completely.
339 else
340 node.remove();
341 }
342
343 // Check elements that have been marked as possibly invalid.
344 while ( ( check = toBeChecked.pop() ) ) {
345 element = check.el;
346 // Element has been already removed.
347 if ( !element.parent )
348 continue;
349
350 // Handle custom elements as inline elements (http://dev.ckeditor.com/ticket/12683).
351 parentDtd = DTD[ element.parent.name ] || DTD.span;
352
353 switch ( check.check ) {
354 // Check if element itself is correct.
355 case 'it':
356 // Check if element included in $removeEmpty has no children.
357 if ( DTD.$removeEmpty[ element.name ] && !element.children.length )
358 removeElement( element, enterTag, toBeChecked );
359 // Check if that is invalid element.
360 else if ( !validateElement( element ) )
361 removeElement( element, enterTag, toBeChecked );
362 break;
363
364 // Check if element is in correct context. If not - remove element.
365 case 'el-up':
366 // Check if e.g. li is a child of body after ul has been removed.
367 if ( element.parent.type != CKEDITOR.NODE_DOCUMENT_FRAGMENT && !parentDtd[ element.name ] )
368 removeElement( element, enterTag, toBeChecked );
369 break;
370
371 // Check if element is in correct context. If not - remove parent.
372 case 'parent-down':
373 if ( element.parent.type != CKEDITOR.NODE_DOCUMENT_FRAGMENT && !parentDtd[ element.name ] )
374 removeElement( element.parent, enterTag, toBeChecked );
375 break;
376 }
377 }
378
379 return isModified;
380 },
381
382 /**
383 * Checks whether a {@link CKEDITOR.feature} can be enabled. Unlike {@link #addFeature},
384 * this method always checks the feature, even when the default configuration
385 * for {@link CKEDITOR.config#allowedContent} is used.
386 *
387 * // TODO example
388 *
389 * @param {CKEDITOR.feature} feature The feature to be tested.
390 * @returns {Boolean} Whether this feature can be enabled.
391 */
392 checkFeature: function( feature ) {
393 if ( this.disabled )
394 return true;
395
396 if ( !feature )
397 return true;
398
399 // Some features may want to register other features.
400 // E.g. a button may return a command bound to it.
401 if ( feature.toFeature )
402 feature = feature.toFeature( this.editor );
403
404 return !feature.requiredContent || this.check( feature.requiredContent );
405 },
406
407 /**
408 * Disables Advanced Content Filter.
409 *
410 * This method is meant to be used by plugins which are not
411 * compatible with the filter and in other cases in which the filter
412 * has to be disabled during the initialization phase or runtime.
413 *
414 * In other cases the filter can be disabled by setting
415 * {@link CKEDITOR.config#allowedContent} to `true`.
416 */
417 disable: function() {
418 this.disabled = true;
419 },
420
421 /**
422 * Adds disallowed content rules to the filter.
423 *
424 * Read about rules formats in the [Allowed Content Rules guide](#!/guide/dev_allowed_content_rules).
425 *
426 * // Disallow all styles on the image elements.
427 * editor.filter.disallow( 'img{*}' );
428 *
429 * // Disallow all span and div elements.
430 * editor.filter.disallow( 'span div' );
431 *
432 * @since 4.4
433 * @param {CKEDITOR.filter.disallowedContentRules} newRules Rule(s) to be added.
434 */
435 disallow: function( newRules ) {
436 // Check arguments and constraints. Clear cache.
437 // Note: we pass true in the 3rd argument, because disallow() should never
438 // be blocked by custom configuration.
439 if ( !beforeAddingRule( this, newRules, true ) )
440 return false;
441
442 if ( typeof newRules == 'string' )
443 newRules = parseRulesString( newRules );
444
445 addAndOptimizeRules( this, newRules, null, this.disallowedContent, this._.disallowedRules );
446
447 return true;
448 },
449
450 /**
451 * Adds an array of {@link CKEDITOR.feature} content forms. All forms
452 * will then be transformed to the first form which is allowed by the filter.
453 *
454 * editor.filter.allow( 'i; span{!font-style}' );
455 * editor.filter.addContentForms( [
456 * 'em',
457 * 'i',
458 * [ 'span', function( el ) {
459 * return el.styles[ 'font-style' ] == 'italic';
460 * } ]
461 * ] );
462 * // Now <em> and <span style="font-style:italic"> will be replaced with <i>
463 * // because this is the first allowed form.
464 * // <span> is allowed too, but it is the last form and
465 * // additionaly, the editor cannot transform an element based on
466 * // the array+function form).
467 *
468 * This method is used by the editor to register {@link CKEDITOR.feature#contentForms}
469 * when adding a feature with {@link #addFeature} or {@link CKEDITOR.editor#addFeature}.
470 *
471 * @param {Array} forms The content forms of a feature.
472 */
473 addContentForms: function( forms ) {
474 if ( this.disabled )
475 return;
476
477 if ( !forms )
478 return;
479
480 var i, form,
481 transfGroups = [],
482 preferredForm;
483
484 // First, find preferred form - this is, first allowed.
485 for ( i = 0; i < forms.length && !preferredForm; ++i ) {
486 form = forms[ i ];
487
488 // Check only strings and styles - array format isn't supported by #check().
489 if ( ( typeof form == 'string' || form instanceof CKEDITOR.style ) && this.check( form ) )
490 preferredForm = form;
491 }
492
493 // This feature doesn't have preferredForm, so ignore it.
494 if ( !preferredForm )
495 return;
496
497 for ( i = 0; i < forms.length; ++i )
498 transfGroups.push( getContentFormTransformationGroup( forms[ i ], preferredForm ) );
499
500 this.addTransformations( transfGroups );
501 },
502
503 /**
504 * Adds a callback which will be executed on every element
505 * that the filter reaches when filtering, before the element is filtered.
506 *
507 * By returning {@link CKEDITOR#FILTER_SKIP_TREE} it is possible to
508 * skip filtering of the current element and all its ancestors.
509 *
510 * editor.filter.addElementCallback( function( el ) {
511 * if ( el.hasClass( 'protected' ) )
512 * return CKEDITOR.FILTER_SKIP_TREE;
513 * } );
514 *
515 * **Note:** At this stage the element passed to the callback does not
516 * contain `attributes`, `classes`, and `styles` properties which are available
517 * temporarily on later stages of the filtering process. Therefore you need to
518 * use the pure {@link CKEDITOR.htmlParser.element} interface.
519 *
520 * @since 4.4
521 * @param {Function} callback The callback to be executed.
522 */
523 addElementCallback: function( callback ) {
524 // We want to keep it a falsy value, to speed up finding whether there are any callbacks.
525 if ( !this.elementCallbacks )
526 this.elementCallbacks = [];
527
528 this.elementCallbacks.push( callback );
529 },
530
531 /**
532 * Checks whether a feature can be enabled for the HTML restrictions in place
533 * for the current CKEditor instance, based on the HTML code the feature might
534 * generate and the minimal HTML code the feature needs to be able to generate.
535 *
536 * // TODO example
537 *
538 * @param {CKEDITOR.feature} feature
539 * @returns {Boolean} Whether this feature can be enabled.
540 */
541 addFeature: function( feature ) {
542 if ( this.disabled )
543 return true;
544
545 if ( !feature )
546 return true;
547
548 // Some features may want to register other features.
549 // E.g. a button may return a command bound to it.
550 if ( feature.toFeature )
551 feature = feature.toFeature( this.editor );
552
553 // If default configuration (will be checked inside #allow()),
554 // then add allowed content rules.
555 this.allow( feature.allowedContent, feature.name );
556
557 this.addTransformations( feature.contentTransformations );
558 this.addContentForms( feature.contentForms );
559
560 // If custom configuration or any DACRs, then check if required content is allowed.
561 if ( feature.requiredContent && ( this.customConfig || this.disallowedContent.length ) )
562 return this.check( feature.requiredContent );
563
564 return true;
565 },
566
567 /**
568 * Adds an array of content transformation groups. One group
569 * may contain many transformation rules, but only the first
570 * matching rule in a group is executed.
571 *
572 * A single transformation rule is an object with four properties:
573 *
574 * * `check` (optional) &ndash; if set and {@link CKEDITOR.filter} does
575 * not accept this {@link CKEDITOR.filter.contentRule}, this transformation rule
576 * will not be executed (it does not *match*). This value is passed
577 * to {@link #check}.
578 * * `element` (optional) &ndash; this string property tells the filter on which
579 * element this transformation can be run. It is optional, because
580 * the element name can be obtained from `check` (if it is a String format)
581 * or `left` (if it is a {@link CKEDITOR.style} instance).
582 * * `left` (optional) &ndash; a function accepting an element or a {@link CKEDITOR.style}
583 * instance verifying whether the transformation should be
584 * executed on this specific element. If it returns `false` or if an element
585 * does not match this style, this transformation rule does not *match*.
586 * * `right` &ndash; a function accepting an element and {@link CKEDITOR.filter.transformationsTools}
587 * or a string containing the name of the {@link CKEDITOR.filter.transformationsTools} method
588 * that should be called on an element.
589 *
590 * A shorthand format is also available. A transformation rule can be defined by
591 * a single string `'check:right'`. The string before `':'` will be used as
592 * the `check` property and the second part as the `right` property.
593 *
594 * Transformation rules can be grouped. The filter will try to apply
595 * the first rule in a group. If it *matches*, the filter will ignore subsequent rules and
596 * will move to the next group. If it does not *match*, the next rule will be checked.
597 *
598 * Examples:
599 *
600 * editor.filter.addTransformations( [
601 * // First group.
602 * [
603 * // First rule. If table{width} is allowed, it
604 * // executes {@link CKEDITOR.filter.transformationsTools#sizeToStyle} on a table element.
605 * 'table{width}: sizeToStyle',
606 * // Second rule should not be executed if the first was.
607 * 'table[width]: sizeToAttribute'
608 * ],
609 * // Second group.
610 * [
611 * // This rule will add the foo="1" attribute to all images that
612 * // do not have it.
613 * {
614 * element: 'img',
615 * left: function( el ) {
616 * return !el.attributes.foo;
617 * },
618 * right: function( el, tools ) {
619 * el.attributes.foo = '1';
620 * }
621 * }
622 * ]
623 * ] );
624 *
625 * // Case 1:
626 * // config.allowedContent = 'table{height,width}; tr td'.
627 * //
628 * // '<table style="height:100px; width:200px">...</table>' -> '<table style="height:100px; width:200px">...</table>'
629 * // '<table height="100" width="200">...</table>' -> '<table style="height:100px; width:200px">...</table>'
630 *
631 * // Case 2:
632 * // config.allowedContent = 'table[height,width]; tr td'.
633 * //
634 * // '<table style="height:100px; width:200px">...</table>' -> '<table height="100" width="200">...</table>'
635 * // '<table height="100" width="200">...</table>' -> '<table height="100" width="200"">...</table>'
636 *
637 * // Case 3:
638 * // config.allowedContent = 'table{width,height}[height,width]; tr td'.
639 * //
640 * // '<table style="height:100px; width:200px">...</table>' -> '<table style="height:100px; width:200px">...</table>'
641 * // '<table height="100" width="200">...</table>' -> '<table style="height:100px; width:200px">...</table>'
642 * //
643 * // Note: Both forms are allowed (size set by style and by attributes), but only
644 * // the first transformation is applied &mdash; the size is always transformed to a style.
645 * // This is because only the first transformation matching allowed content rules is applied.
646 *
647 * This method is used by the editor to add {@link CKEDITOR.feature#contentTransformations}
648 * when adding a feature by {@link #addFeature} or {@link CKEDITOR.editor#addFeature}.
649 *
650 * @param {Array} transformations
651 */
652 addTransformations: function( transformations ) {
653 if ( this.disabled )
654 return;
655
656 if ( !transformations )
657 return;
658
659 var optimized = this._.transformations,
660 group, i;
661
662 for ( i = 0; i < transformations.length; ++i ) {
663 group = optimizeTransformationsGroup( transformations[ i ] );
664
665 if ( !optimized[ group.name ] )
666 optimized[ group.name ] = [];
667
668 optimized[ group.name ].push( group.rules );
669 }
670 },
671
672 /**
673 * Checks whether the content defined in the `test` argument is allowed
674 * by this filter.
675 *
676 * If `strictCheck` is set to `false` (default value), this method checks
677 * if all parts of the `test` (styles, attributes, and classes) are
678 * accepted by the filter. If `strictCheck` is set to `true`, the test
679 * must also contain the required attributes, styles, and classes.
680 *
681 * For example:
682 *
683 * // Rule: 'img[!src,alt]'.
684 * filter.check( 'img[alt]' ); // -> true
685 * filter.check( 'img[alt]', true, true ); // -> false
686 *
687 * Second `check()` call returned `false` because `src` is required.
688 *
689 * **Note:** The `test` argument is of {@link CKEDITOR.filter.contentRule} type, which is
690 * a limited version of {@link CKEDITOR.filter.allowedContentRules}. Read more about it
691 * in the {@link CKEDITOR.filter.contentRule}'s documentation.
692 *
693 * @param {CKEDITOR.filter.contentRule} test
694 * @param {Boolean} [applyTransformations=true] Whether to use registered transformations.
695 * @param {Boolean} [strictCheck] Whether the filter should check if an element with exactly
696 * these properties is allowed.
697 * @returns {Boolean} Returns `true` if the content is allowed.
698 */
699 check: function( test, applyTransformations, strictCheck ) {
700 if ( this.disabled )
701 return true;
702
703 // If rules are an array, expand it and return the logical OR value of
704 // the rules.
705 if ( CKEDITOR.tools.isArray( test ) ) {
706 for ( var i = test.length ; i-- ; ) {
707 if ( this.check( test[ i ], applyTransformations, strictCheck ) )
708 return true;
709 }
710 return false;
711 }
712
713 var element, result, cacheKey;
714
715 if ( typeof test == 'string' ) {
716 cacheKey = test + '<' + ( applyTransformations === false ? '0' : '1' ) + ( strictCheck ? '1' : '0' ) + '>';
717
718 // Check if result of this check hasn't been already cached.
719 if ( cacheKey in this._.cachedChecks )
720 return this._.cachedChecks[ cacheKey ];
721
722 // Create test element from string.
723 element = mockElementFromString( test );
724 } else {
725 // Create test element from CKEDITOR.style.
726 element = mockElementFromStyle( test );
727 }
728
729 // Make a deep copy.
730 var clone = CKEDITOR.tools.clone( element ),
731 toBeRemoved = [],
732 transformations;
733
734 // Apply transformations to original element.
735 // Transformations will be applied to clone by the filter function.
736 if ( applyTransformations !== false && ( transformations = this._.transformations[ element.name ] ) ) {
737 for ( i = 0; i < transformations.length; ++i )
738 applyTransformationsGroup( this, element, transformations[ i ] );
739
740 // Transformations could modify styles or classes, so they need to be copied
741 // to attributes object.
742 updateAttributes( element );
743 }
744
745 // Filter clone of mocked element.
746 processElement( this, clone, toBeRemoved, {
747 doFilter: true,
748 doTransform: applyTransformations !== false,
749 skipRequired: !strictCheck,
750 skipFinalValidation: !strictCheck
751 } );
752
753 // Element has been marked for removal.
754 if ( toBeRemoved.length > 0 ) {
755 result = false;
756 // Compare only left to right, because clone may be only trimmed version of original element.
757 } else if ( !CKEDITOR.tools.objectCompare( element.attributes, clone.attributes, true ) ) {
758 result = false;
759 } else {
760 result = true;
761 }
762
763 // Cache result of this test - we can build cache only for string tests.
764 if ( typeof test == 'string' )
765 this._.cachedChecks[ cacheKey ] = result;
766
767 return result;
768 },
769
770 /**
771 * Returns first enter mode allowed by this filter rules. Modes are checked in `p`, `div`, `br` order.
772 * If none of tags is allowed this method will return {@link CKEDITOR#ENTER_BR}.
773 *
774 * @since 4.3
775 * @param {Number} defaultMode The default mode which will be checked as the first one.
776 * @param {Boolean} [reverse] Whether to check modes in reverse order (used for shift enter mode).
777 * @returns {Number} Allowed enter mode.
778 */
779 getAllowedEnterMode: ( function() {
780 var tagsToCheck = [ 'p', 'div', 'br' ],
781 enterModes = {
782 p: CKEDITOR.ENTER_P,
783 div: CKEDITOR.ENTER_DIV,
784 br: CKEDITOR.ENTER_BR
785 };
786
787 return function( defaultMode, reverse ) {
788 // Clone the array first.
789 var tags = tagsToCheck.slice(),
790 tag;
791
792 // Check the default mode first.
793 if ( this.check( enterModeTags[ defaultMode ] ) )
794 return defaultMode;
795
796 // If not reverse order, reverse array so we can pop() from it.
797 if ( !reverse )
798 tags = tags.reverse();
799
800 while ( ( tag = tags.pop() ) ) {
801 if ( this.check( tag ) )
802 return enterModes[ tag ];
803 }
804
805 return CKEDITOR.ENTER_BR;
806 };
807 } )(),
808
809 /**
810 * Returns a clone of this filter instance.
811 *
812 * @since 4.7.3
813 * @returns {CKEDITOR.filter}
814 */
815 clone: function() {
816 var ret = new CKEDITOR.filter(),
817 clone = CKEDITOR.tools.clone;
818
819 // Cloning allowed content related things.
820 ret.allowedContent = clone( this.allowedContent );
821 ret._.allowedRules = clone( this._.allowedRules );
822
823 // Disallowed content rules.
824 ret.disallowedContent = clone( this.disallowedContent );
825 ret._.disallowedRules = clone( this._.disallowedRules );
826
827 ret._.transformations = clone( this._.transformations );
828
829 ret.disabled = this.disabled;
830 ret.editor = this.editor;
831
832 return ret;
833 },
834
835 /**
836 * Destroys the filter instance and removes it from the global {@link CKEDITOR.filter#instances} object.
837 *
838 * @since 4.4.5
839 */
840 destroy: function() {
841 delete CKEDITOR.filter.instances[ this.id ];
842 // Deleting reference to filter instance should be enough,
843 // but since these are big objects it's safe to clean them up too.
844 delete this._;
845 delete this.allowedContent;
846 delete this.disallowedContent;
847 }
848 };
849
850 function addAndOptimizeRules( that, newRules, featureName, standardizedRules, optimizedRules ) {
851 var groupName, rule,
852 rulesToOptimize = [];
853
854 for ( groupName in newRules ) {
855 rule = newRules[ groupName ];
856
857 // { 'p h1': true } => { 'p h1': {} }.
858 if ( typeof rule == 'boolean' )
859 rule = {};
860 // { 'p h1': func } => { 'p h1': { match: func } }.
861 else if ( typeof rule == 'function' )
862 rule = { match: rule };
863 // Clone (shallow) rule, because we'll modify it later.
864 else
865 rule = copy( rule );
866
867 // If this is not an unnamed rule ({ '$1' => { ... } })
868 // move elements list to property.
869 if ( groupName.charAt( 0 ) != '$' )
870 rule.elements = groupName;
871
872 if ( featureName )
873 rule.featureName = featureName.toLowerCase();
874
875 standardizeRule( rule );
876
877 // Save rule and remember to optimize it.
878 standardizedRules.push( rule );
879 rulesToOptimize.push( rule );
880 }
881
882 optimizeRules( optimizedRules, rulesToOptimize );
883 }
884
885 // Apply ACR to an element.
886 // @param rule
887 // @param element
888 // @param status Object containing status of element's filtering.
889 // @param {Boolean} skipRequired If true don't check if element has all required properties.
890 function applyAllowedRule( rule, element, status, skipRequired ) {
891 // This rule doesn't match this element - skip it.
892 if ( rule.match && !rule.match( element ) )
893 return;
894
895 // If element doesn't have all required styles/attrs/classes
896 // this rule doesn't match it.
897 if ( !skipRequired && !hasAllRequired( rule, element ) )
898 return;
899
900 // If this rule doesn't validate properties only mark element as valid.
901 if ( !rule.propertiesOnly )
902 status.valid = true;
903
904 // Apply rule only when all attrs/styles/classes haven't been marked as valid.
905 if ( !status.allAttributes )
906 status.allAttributes = applyAllowedRuleToHash( rule.attributes, element.attributes, status.validAttributes );
907
908 if ( !status.allStyles )
909 status.allStyles = applyAllowedRuleToHash( rule.styles, element.styles, status.validStyles );
910
911 if ( !status.allClasses )
912 status.allClasses = applyAllowedRuleToArray( rule.classes, element.classes, status.validClasses );
913 }
914
915 // Apply itemsRule to items (only classes are kept in array).
916 // Push accepted items to validItems array.
917 // Return true when all items are valid.
918 function applyAllowedRuleToArray( itemsRule, items, validItems ) {
919 if ( !itemsRule )
920 return false;
921
922 // True means that all elements of array are accepted (the asterix was used for classes).
923 if ( itemsRule === true )
924 return true;
925
926 for ( var i = 0, l = items.length, item; i < l; ++i ) {
927 item = items[ i ];
928 if ( !validItems[ item ] )
929 validItems[ item ] = itemsRule( item );
930 }
931
932 return false;
933 }
934
935 function applyAllowedRuleToHash( itemsRule, items, validItems ) {
936 if ( !itemsRule )
937 return false;
938
939 if ( itemsRule === true )
940 return true;
941
942 for ( var name in items ) {
943 if ( !validItems[ name ] )
944 validItems[ name ] = itemsRule( name );
945 }
946
947 return false;
948 }
949
950 // Apply DACR rule to an element.
951 function applyDisallowedRule( rule, element, status ) {
952 // This rule doesn't match this element - skip it.
953 if ( rule.match && !rule.match( element ) )
954 return;
955
956 // No properties - it's an element only rule so it disallows entire element.
957 // Early return is handled in filterElement.
958 if ( rule.noProperties )
959 return false;
960
961 // Apply rule to attributes, styles and classes. Switch hadInvalid* to true if method returned true.
962 status.hadInvalidAttribute = applyDisallowedRuleToHash( rule.attributes, element.attributes ) || status.hadInvalidAttribute;
963 status.hadInvalidStyle = applyDisallowedRuleToHash( rule.styles, element.styles ) || status.hadInvalidStyle;
964 status.hadInvalidClass = applyDisallowedRuleToArray( rule.classes, element.classes ) || status.hadInvalidClass;
965 }
966
967 // Apply DACR to items (only classes are kept in array).
968 // @returns {Boolean} True if at least one of items was invalid (disallowed).
969 function applyDisallowedRuleToArray( itemsRule, items ) {
970 if ( !itemsRule )
971 return false;
972
973 var hadInvalid = false,
974 allDisallowed = itemsRule === true;
975
976 for ( var i = items.length; i--; ) {
977 if ( allDisallowed || itemsRule( items[ i ] ) ) {
978 items.splice( i, 1 );
979 hadInvalid = true;
980 }
981 }
982
983 return hadInvalid;
984 }
985
986 // Apply DACR to items (styles and attributes).
987 // @returns {Boolean} True if at least one of items was invalid (disallowed).
988 function applyDisallowedRuleToHash( itemsRule, items ) {
989 if ( !itemsRule )
990 return false;
991
992 var hadInvalid = false,
993 allDisallowed = itemsRule === true;
994
995 for ( var name in items ) {
996 if ( allDisallowed || itemsRule( name ) ) {
997 delete items[ name ];
998 hadInvalid = true;
999 }
1000 }
1001
1002 return hadInvalid;
1003 }
1004
1005 function beforeAddingRule( that, newRules, overrideCustom ) {
1006 if ( that.disabled )
1007 return false;
1008
1009 // Don't override custom user's configuration if not explicitly requested.
1010 if ( that.customConfig && !overrideCustom )
1011 return false;
1012
1013 if ( !newRules )
1014 return false;
1015
1016 // Clear cache, because new rules could change results of checks.
1017 that._.cachedChecks = {};
1018
1019 return true;
1020 }
1021
1022 // Convert CKEDITOR.style to filter's rule.
1023 function convertStyleToRules( style ) {
1024 var styleDef = style.getDefinition(),
1025 rules = {},
1026 rule,
1027 attrs = styleDef.attributes;
1028
1029 rules[ styleDef.element ] = rule = {
1030 styles: styleDef.styles,
1031 requiredStyles: styleDef.styles && CKEDITOR.tools.objectKeys( styleDef.styles )
1032 };
1033
1034 if ( attrs ) {
1035 attrs = copy( attrs );
1036 rule.classes = attrs[ 'class' ] ? attrs[ 'class' ].split( /\s+/ ) : null;
1037 rule.requiredClasses = rule.classes;
1038 delete attrs[ 'class' ];
1039 rule.attributes = attrs;
1040 rule.requiredAttributes = attrs && CKEDITOR.tools.objectKeys( attrs );
1041 }
1042
1043 return rules;
1044 }
1045
1046 // Convert all validator formats (string, array, object, boolean) to hash or boolean:
1047 // * true is returned for '*'/true validator,
1048 // * false is returned for empty validator (no validator at all (false/null) or e.g. empty array),
1049 // * object is returned in other cases.
1050 function convertValidatorToHash( validator, delimiter ) {
1051 if ( !validator )
1052 return false;
1053
1054 if ( validator === true )
1055 return validator;
1056
1057 if ( typeof validator == 'string' ) {
1058 validator = trim( validator );
1059 if ( validator == '*' )
1060 return true;
1061 else
1062 return CKEDITOR.tools.convertArrayToObject( validator.split( delimiter ) );
1063 }
1064 else if ( CKEDITOR.tools.isArray( validator ) ) {
1065 if ( validator.length )
1066 return CKEDITOR.tools.convertArrayToObject( validator );
1067 else
1068 return false;
1069 }
1070 // If object.
1071 else {
1072 var obj = {},
1073 len = 0;
1074
1075 for ( var i in validator ) {
1076 obj[ i ] = validator[ i ];
1077 len++;
1078 }
1079
1080 return len ? obj : false;
1081 }
1082 }
1083
1084 function executeElementCallbacks( element, callbacks ) {
1085 for ( var i = 0, l = callbacks.length, retVal; i < l; ++i ) {
1086 if ( ( retVal = callbacks[ i ]( element ) ) )
1087 return retVal;
1088 }
1089 }
1090
1091 // Extract required properties from "required" validator and "all" properties.
1092 // Remove exclamation marks from "all" properties.
1093 //
1094 // E.g.:
1095 // requiredClasses = { cl1: true }
1096 // (all) classes = { cl1: true, cl2: true, '!cl3': true }
1097 //
1098 // result:
1099 // returned = { cl1: true, cl3: true }
1100 // all = { cl1: true, cl2: true, cl3: true }
1101 //
1102 // This function returns false if nothing is required.
1103 function extractRequired( required, all ) {
1104 var unbang = [],
1105 empty = true,
1106 i;
1107
1108 if ( required )
1109 empty = false;
1110 else
1111 required = {};
1112
1113 for ( i in all ) {
1114 if ( i.charAt( 0 ) == '!' ) {
1115 i = i.slice( 1 );
1116 unbang.push( i );
1117 required[ i ] = true;
1118 empty = false;
1119 }
1120 }
1121
1122 while ( ( i = unbang.pop() ) ) {
1123 all[ i ] = all[ '!' + i ];
1124 delete all[ '!' + i ];
1125 }
1126
1127 return empty ? false : required;
1128 }
1129
1130 // Does the actual filtering by appling allowed content rules
1131 // to the element.
1132 //
1133 // @param {CKEDITOR.filter} that The context.
1134 // @param {CKEDITOR.htmlParser.element} element
1135 // @param {Object} opts The same as in processElement.
1136 function filterElement( that, element, opts ) {
1137 var name = element.name,
1138 privObj = that._,
1139 allowedRules = privObj.allowedRules.elements[ name ],
1140 genericAllowedRules = privObj.allowedRules.generic,
1141 disallowedRules = privObj.disallowedRules.elements[ name ],
1142 genericDisallowedRules = privObj.disallowedRules.generic,
1143 skipRequired = opts.skipRequired,
1144 status = {
1145 // Whether any of rules accepted element.
1146 // If not - it will be stripped.
1147 valid: false,
1148 // Objects containing accepted attributes, classes and styles.
1149 validAttributes: {},
1150 validClasses: {},
1151 validStyles: {},
1152 // Whether all are valid.
1153 // If we know that all element's attrs/classes/styles are valid
1154 // we can skip their validation, to improve performance.
1155 allAttributes: false,
1156 allClasses: false,
1157 allStyles: false,
1158 // Whether element had (before applying DACRs) at least one invalid attribute/class/style.
1159 hadInvalidAttribute: false,
1160 hadInvalidClass: false,
1161 hadInvalidStyle: false
1162 },
1163 i, l;
1164
1165 // Early return - if there are no rules for this element (specific or generic), remove it.
1166 if ( !allowedRules && !genericAllowedRules )
1167 return null;
1168
1169 // Could not be done yet if there were no transformations and if this
1170 // is real (not mocked) object.
1171 populateProperties( element );
1172
1173 // Note - this step modifies element's styles, classes and attributes.
1174 if ( disallowedRules ) {
1175 for ( i = 0, l = disallowedRules.length; i < l; ++i ) {
1176 // Apply rule and make an early return if false is returned what means
1177 // that element is completely disallowed.
1178 if ( applyDisallowedRule( disallowedRules[ i ], element, status ) === false )
1179 return null;
1180 }
1181 }
1182
1183 // Note - this step modifies element's styles, classes and attributes.
1184 if ( genericDisallowedRules ) {
1185 for ( i = 0, l = genericDisallowedRules.length; i < l; ++i )
1186 applyDisallowedRule( genericDisallowedRules[ i ], element, status );
1187 }
1188
1189 if ( allowedRules ) {
1190 for ( i = 0, l = allowedRules.length; i < l; ++i )
1191 applyAllowedRule( allowedRules[ i ], element, status, skipRequired );
1192 }
1193
1194 if ( genericAllowedRules ) {
1195 for ( i = 0, l = genericAllowedRules.length; i < l; ++i )
1196 applyAllowedRule( genericAllowedRules[ i ], element, status, skipRequired );
1197 }
1198
1199 return status;
1200 }
1201
1202 // Check whether element has all properties (styles,classes,attrs) required by a rule.
1203 function hasAllRequired( rule, element ) {
1204 if ( rule.nothingRequired )
1205 return true;
1206
1207 var i, req, reqs, existing;
1208
1209 if ( ( reqs = rule.requiredClasses ) ) {
1210 existing = element.classes;
1211 for ( i = 0; i < reqs.length; ++i ) {
1212 req = reqs[ i ];
1213 if ( typeof req == 'string' ) {
1214 if ( CKEDITOR.tools.indexOf( existing, req ) == -1 )
1215 return false;
1216 }
1217 // This means regexp.
1218 else {
1219 if ( !CKEDITOR.tools.checkIfAnyArrayItemMatches( existing, req ) )
1220 return false;
1221 }
1222 }
1223 }
1224
1225 return hasAllRequiredInHash( element.styles, rule.requiredStyles ) &&
1226 hasAllRequiredInHash( element.attributes, rule.requiredAttributes );
1227 }
1228
1229 // Check whether all items in required (array) exist in existing (object).
1230 function hasAllRequiredInHash( existing, required ) {
1231 if ( !required )
1232 return true;
1233
1234 for ( var i = 0, req; i < required.length; ++i ) {
1235 req = required[ i ];
1236 if ( typeof req == 'string' ) {
1237 if ( !( req in existing ) )
1238 return false;
1239 }
1240 // This means regexp.
1241 else {
1242 if ( !CKEDITOR.tools.checkIfAnyObjectPropertyMatches( existing, req ) )
1243 return false;
1244 }
1245 }
1246
1247 return true;
1248 }
1249
1250 // Create pseudo element that will be passed through filter
1251 // to check if tested string is allowed.
1252 function mockElementFromString( str ) {
1253 var element = parseRulesString( str ).$1,
1254 styles = element.styles,
1255 classes = element.classes;
1256
1257 element.name = element.elements;
1258 element.classes = classes = ( classes ? classes.split( /\s*,\s*/ ) : [] );
1259 element.styles = mockHash( styles );
1260 element.attributes = mockHash( element.attributes );
1261 element.children = [];
1262
1263 if ( classes.length )
1264 element.attributes[ 'class' ] = classes.join( ' ' );
1265 if ( styles )
1266 element.attributes.style = CKEDITOR.tools.writeCssText( element.styles );
1267
1268 return element;
1269 }
1270
1271 // Create pseudo element that will be passed through filter
1272 // to check if tested style is allowed.
1273 function mockElementFromStyle( style ) {
1274 var styleDef = style.getDefinition(),
1275 styles = styleDef.styles,
1276 attrs = styleDef.attributes || {};
1277
1278 if ( styles && !CKEDITOR.tools.isEmpty( styles ) ) {
1279 styles = copy( styles );
1280 attrs.style = CKEDITOR.tools.writeCssText( styles, true );
1281 } else {
1282 styles = {};
1283 }
1284
1285 return {
1286 name: styleDef.element,
1287 attributes: attrs,
1288 classes: attrs[ 'class' ] ? attrs[ 'class' ].split( /\s+/ ) : [],
1289 styles: styles,
1290 children: []
1291 };
1292 }
1293
1294 // Mock hash based on string.
1295 // 'a,b,c' => { a: 'cke-test', b: 'cke-test', c: 'cke-test' }
1296 // Used to mock styles and attributes objects.
1297 function mockHash( str ) {
1298 // It may be a null or empty string.
1299 if ( !str )
1300 return {};
1301
1302 var keys = str.split( /\s*,\s*/ ).sort(),
1303 obj = {};
1304
1305 while ( keys.length )
1306 obj[ keys.shift() ] = TEST_VALUE;
1307
1308 return obj;
1309 }
1310
1311 // Extract properties names from the object
1312 // and replace those containing wildcards with regexps.
1313 // Note: there's a room for performance improvement. Array of mixed types
1314 // breaks JIT-compiler optiomization what may invalidate compilation of pretty a lot of code.
1315 //
1316 // @returns An array of strings and regexps.
1317 function optimizeRequiredProperties( requiredProperties ) {
1318 var arr = [];
1319 for ( var propertyName in requiredProperties ) {
1320 if ( propertyName.indexOf( '*' ) > -1 )
1321 arr.push( new RegExp( '^' + propertyName.replace( /\*/g, '.*' ) + '$' ) );
1322 else
1323 arr.push( propertyName );
1324 }
1325 return arr;
1326 }
1327
1328 var validators = { styles: 1, attributes: 1, classes: 1 },
1329 validatorsRequired = {
1330 styles: 'requiredStyles',
1331 attributes: 'requiredAttributes',
1332 classes: 'requiredClasses'
1333 };
1334
1335 // Optimize a rule by replacing validators with functions
1336 // and rewriting requiredXXX validators to arrays.
1337 function optimizeRule( rule ) {
1338 var validatorName,
1339 requiredProperties,
1340 i;
1341
1342 for ( validatorName in validators )
1343 rule[ validatorName ] = validatorFunction( rule[ validatorName ] );
1344
1345 var nothingRequired = true;
1346 for ( i in validatorsRequired ) {
1347 validatorName = validatorsRequired[ i ];
1348 requiredProperties = optimizeRequiredProperties( rule[ validatorName ] );
1349 // Don't set anything if there are no required properties. This will allow to
1350 // save some memory by GCing all empty arrays (requiredProperties).
1351 if ( requiredProperties.length ) {
1352 rule[ validatorName ] = requiredProperties;
1353 nothingRequired = false;
1354 }
1355 }
1356
1357 rule.nothingRequired = nothingRequired;
1358 rule.noProperties = !( rule.attributes || rule.classes || rule.styles );
1359 }
1360
1361 // Add optimized version of rule to optimizedRules object.
1362 function optimizeRules( optimizedRules, rules ) {
1363 var elementsRules = optimizedRules.elements,
1364 genericRules = optimizedRules.generic,
1365 i, l, rule, element, priority;
1366
1367 for ( i = 0, l = rules.length; i < l; ++i ) {
1368 // Shallow copy. Do not modify original rule.
1369 rule = copy( rules[ i ] );
1370 priority = rule.classes === true || rule.styles === true || rule.attributes === true;
1371 optimizeRule( rule );
1372
1373 // E.g. "*(xxx)[xxx]" - it's a generic rule that
1374 // validates properties only.
1375 // Or '$1': { match: function() {...} }
1376 if ( rule.elements === true || rule.elements === null ) {
1377 // Add priority rules at the beginning.
1378 genericRules[ priority ? 'unshift' : 'push' ]( rule );
1379 }
1380 // If elements list was explicitly defined,
1381 // add this rule for every defined element.
1382 else {
1383 // We don't need elements validator for this kind of rule.
1384 var elements = rule.elements;
1385 delete rule.elements;
1386
1387 for ( element in elements ) {
1388 if ( !elementsRules[ element ] )
1389 elementsRules[ element ] = [ rule ];
1390 else
1391 elementsRules[ element ][ priority ? 'unshift' : 'push' ]( rule );
1392 }
1393 }
1394 }
1395 }
1396
1397 // < elements >< styles, attributes and classes >< separator >
1398 var rulePattern = /^([a-z0-9\-*\s]+)((?:\s*\{[!\w\-,\s\*]+\}\s*|\s*\[[!\w\-,\s\*]+\]\s*|\s*\([!\w\-,\s\*]+\)\s*){0,3})(?:;\s*|$)/i,
1399 groupsPatterns = {
1400 styles: /{([^}]+)}/,
1401 attrs: /\[([^\]]+)\]/,
1402 classes: /\(([^\)]+)\)/
1403 };
1404
1405 function parseRulesString( input ) {
1406 var match,
1407 props, styles, attrs, classes,
1408 rules = {},
1409 groupNum = 1;
1410
1411 input = trim( input );
1412
1413 while ( ( match = input.match( rulePattern ) ) ) {
1414 if ( ( props = match[ 2 ] ) ) {
1415 styles = parseProperties( props, 'styles' );
1416 attrs = parseProperties( props, 'attrs' );
1417 classes = parseProperties( props, 'classes' );
1418 } else {
1419 styles = attrs = classes = null;
1420 }
1421
1422 // Add as an unnamed rule, because there can be two rules
1423 // for one elements set defined in string format.
1424 rules[ '$' + groupNum++ ] = {
1425 elements: match[ 1 ],
1426 classes: classes,
1427 styles: styles,
1428 attributes: attrs
1429 };
1430
1431 // Move to the next group.
1432 input = input.slice( match[ 0 ].length );
1433 }
1434
1435 return rules;
1436 }
1437
1438 // Extract specified properties group (styles, attrs, classes) from
1439 // what stands after the elements list in string format of allowedContent.
1440 function parseProperties( properties, groupName ) {
1441 var group = properties.match( groupsPatterns[ groupName ] );
1442 return group ? trim( group[ 1 ] ) : null;
1443 }
1444
1445 function populateProperties( element ) {
1446 // Backup styles and classes, because they may be removed by DACRs.
1447 // We'll need them in updateElement().
1448 var styles = element.styleBackup = element.attributes.style,
1449 classes = element.classBackup = element.attributes[ 'class' ];
1450
1451 // Parse classes and styles if that hasn't been done before.
1452 if ( !element.styles )
1453 element.styles = CKEDITOR.tools.parseCssText( styles || '', 1 );
1454 if ( !element.classes )
1455 element.classes = classes ? classes.split( /\s+/ ) : [];
1456 }
1457
1458 // Filter element protected with a comment.
1459 // Returns true if protected content is ok, false otherwise.
1460 function processProtectedElement( that, comment, protectedRegexs, filterOpts ) {
1461 var source = decodeURIComponent( comment.value.replace( /^\{cke_protected\}/, '' ) ),
1462 protectedFrag,
1463 toBeRemoved = [],
1464 node, i, match;
1465
1466 // Protected element's and protected source's comments look exactly the same.
1467 // Check if what we have isn't a protected source instead of protected script/noscript.
1468 if ( protectedRegexs ) {
1469 for ( i = 0; i < protectedRegexs.length; ++i ) {
1470 if ( ( match = source.match( protectedRegexs[ i ] ) ) &&
1471 match[ 0 ].length == source.length // Check whether this pattern matches entire source
1472 // to avoid '<script>alert("<? 1 ?>")</script>' matching
1473 // the PHP's protectedSource regexp.
1474 )
1475 return true;
1476 }
1477 }
1478
1479 protectedFrag = CKEDITOR.htmlParser.fragment.fromHtml( source );
1480
1481 if ( protectedFrag.children.length == 1 && ( node = protectedFrag.children[ 0 ] ).type == CKEDITOR.NODE_ELEMENT )
1482 processElement( that, node, toBeRemoved, filterOpts );
1483
1484 // If protected element has been marked to be removed, return 'false' - comment was rejected.
1485 return !toBeRemoved.length;
1486 }
1487
1488 var unprotectElementsNamesRegexp = /^cke:(object|embed|param)$/,
1489 protectElementsNamesRegexp = /^(object|embed|param)$/;
1490
1491 // The actual function which filters, transforms and does other funny things with an element.
1492 //
1493 // @param {CKEDITOR.filter} that Context.
1494 // @param {CKEDITOR.htmlParser.element} element The element to be processed.
1495 // @param {Array} toBeRemoved Array into which elements rejected by the filter will be pushed.
1496 // @param {Boolean} [opts.doFilter] Whether element should be filtered.
1497 // @param {Boolean} [opts.doTransform] Whether transformations should be applied.
1498 // @param {Boolean} [opts.doCallbacks] Whether to execute element callbacks.
1499 // @param {Boolean} [opts.toHtml] Set to true if filter used together with htmlDP#toHtml
1500 // @param {Boolean} [opts.skipRequired] Whether element's required properties shouldn't be verified.
1501 // @param {Boolean} [opts.skipFinalValidation] Whether to not perform final element validation (a,img).
1502 // @returns {Number} Possible flags:
1503 // * FILTER_ELEMENT_MODIFIED,
1504 // * FILTER_SKIP_TREE.
1505 function processElement( that, element, toBeRemoved, opts ) {
1506 var status,
1507 retVal = 0,
1508 callbacksRetVal;
1509
1510 // Unprotect elements names previously protected by htmlDataProcessor
1511 // (see protectElementNames and protectSelfClosingElements functions).
1512 // Note: body, title, etc. are not protected by htmlDataP (or are protected and then unprotected).
1513 if ( opts.toHtml )
1514 element.name = element.name.replace( unprotectElementsNamesRegexp, '$1' );
1515
1516 // Execute element callbacks and return if one of them returned any value.
1517 if ( opts.doCallbacks && that.elementCallbacks ) {
1518 // For now we only support here FILTER_SKIP_TREE, so we can early return if retVal is truly value.
1519 if ( ( callbacksRetVal = executeElementCallbacks( element, that.elementCallbacks ) ) )
1520 return callbacksRetVal;
1521 }
1522
1523 // If transformations are set apply all groups.
1524 if ( opts.doTransform )
1525 transformElement( that, element );
1526
1527 if ( opts.doFilter ) {
1528 // Apply all filters.
1529 status = filterElement( that, element, opts );
1530
1531 // Handle early return from filterElement.
1532 if ( !status ) {
1533 toBeRemoved.push( element );
1534 return FILTER_ELEMENT_MODIFIED;
1535 }
1536
1537 // Finally, if after running all filter rules it still hasn't been allowed - remove it.
1538 if ( !status.valid ) {
1539 toBeRemoved.push( element );
1540 return FILTER_ELEMENT_MODIFIED;
1541 }
1542
1543 // Update element's attributes based on status of filtering.
1544 if ( updateElement( element, status ) )
1545 retVal = FILTER_ELEMENT_MODIFIED;
1546
1547 if ( !opts.skipFinalValidation && !validateElement( element ) ) {
1548 toBeRemoved.push( element );
1549 return FILTER_ELEMENT_MODIFIED;
1550 }
1551 }
1552
1553 // Protect previously unprotected elements.
1554 if ( opts.toHtml )
1555 element.name = element.name.replace( protectElementsNamesRegexp, 'cke:$1' );
1556
1557 return retVal;
1558 }
1559
1560 // Returns a regexp object which can be used to test if a property
1561 // matches one of wildcard validators.
1562 function regexifyPropertiesWithWildcards( validators ) {
1563 var patterns = [],
1564 i;
1565
1566 for ( i in validators ) {
1567 if ( i.indexOf( '*' ) > -1 )
1568 patterns.push( i.replace( /\*/g, '.*' ) );
1569 }
1570
1571 if ( patterns.length )
1572 return new RegExp( '^(?:' + patterns.join( '|' ) + ')$' );
1573 else
1574 return null;
1575 }
1576
1577 // Standardize a rule by converting all validators to hashes.
1578 function standardizeRule( rule ) {
1579 rule.elements = convertValidatorToHash( rule.elements, /\s+/ ) || null;
1580 rule.propertiesOnly = rule.propertiesOnly || ( rule.elements === true );
1581
1582 var delim = /\s*,\s*/,
1583 i;
1584
1585 for ( i in validators ) {
1586 rule[ i ] = convertValidatorToHash( rule[ i ], delim ) || null;
1587 rule[ validatorsRequired[ i ] ] = extractRequired( convertValidatorToHash(
1588 rule[ validatorsRequired[ i ] ], delim ), rule[ i ] ) || null;
1589 }
1590
1591 rule.match = rule.match || null;
1592 }
1593
1594 // Does the element transformation by applying registered
1595 // transformation rules.
1596 function transformElement( that, element ) {
1597 var transformations = that._.transformations[ element.name ],
1598 i;
1599
1600 if ( !transformations )
1601 return;
1602
1603 populateProperties( element );
1604
1605 for ( i = 0; i < transformations.length; ++i )
1606 applyTransformationsGroup( that, element, transformations[ i ] );
1607
1608 // Do not count on updateElement() which is called in processElement, because it:
1609 // * may not be called,
1610 // * may skip some properties when all are marked as valid.
1611 updateAttributes( element );
1612 }
1613
1614 // Copy element's styles and classes back to attributes array.
1615 function updateAttributes( element ) {
1616 var attrs = element.attributes,
1617 styles;
1618
1619 // Will be recreated later if any of styles/classes exists.
1620 delete attrs.style;
1621 delete attrs[ 'class' ];
1622
1623 if ( ( styles = CKEDITOR.tools.writeCssText( element.styles, true ) ) )
1624 attrs.style = styles;
1625
1626 if ( element.classes.length )
1627 attrs[ 'class' ] = element.classes.sort().join( ' ' );
1628 }
1629
1630 // Update element object based on status of filtering.
1631 // @returns Whether element was modified.
1632 function updateElement( element, status ) {
1633 var validAttrs = status.validAttributes,
1634 validStyles = status.validStyles,
1635 validClasses = status.validClasses,
1636 attrs = element.attributes,
1637 styles = element.styles,
1638 classes = element.classes,
1639 origClasses = element.classBackup,
1640 origStyles = element.styleBackup,
1641 name, origName, i,
1642 stylesArr = [],
1643 classesArr = [],
1644 internalAttr = /^data-cke-/,
1645 isModified = false;
1646
1647 // Will be recreated later if any of styles/classes were passed.
1648 delete attrs.style;
1649 delete attrs[ 'class' ];
1650 // Clean up.
1651 delete element.classBackup;
1652 delete element.styleBackup;
1653
1654 if ( !status.allAttributes ) {
1655 for ( name in attrs ) {
1656 // If not valid and not internal attribute delete it.
1657 if ( !validAttrs[ name ] ) {
1658 // Allow all internal attibutes...
1659 if ( internalAttr.test( name ) ) {
1660 // ... unless this is a saved attribute and the original one isn't allowed.
1661 if ( name != ( origName = name.replace( /^data-cke-saved-/, '' ) ) &&
1662 !validAttrs[ origName ]
1663 ) {
1664 delete attrs[ name ];
1665 isModified = true;
1666 }
1667 } else {
1668 delete attrs[ name ];
1669 isModified = true;
1670 }
1671 }
1672
1673 }
1674 }
1675
1676 if ( !status.allStyles || status.hadInvalidStyle ) {
1677 for ( name in styles ) {
1678 // We check status.allStyles because when there was a '*' ACR and some
1679 // DACR we have now both properties true - status.allStyles and status.hadInvalidStyle.
1680 // However unlike in the case when we only have '*' ACR, in which we can just copy original
1681 // styles, in this case we must copy only those styles which were not removed by DACRs.
1682 if ( status.allStyles || validStyles[ name ] )
1683 stylesArr.push( name + ':' + styles[ name ] );
1684 else
1685 isModified = true;
1686 }
1687 if ( stylesArr.length )
1688 attrs.style = stylesArr.sort().join( '; ' );
1689 }
1690 else if ( origStyles ) {
1691 attrs.style = origStyles;
1692 }
1693
1694 if ( !status.allClasses || status.hadInvalidClass ) {
1695 for ( i = 0; i < classes.length; ++i ) {
1696 // See comment for styles.
1697 if ( status.allClasses || validClasses[ classes[ i ] ] )
1698 classesArr.push( classes[ i ] );
1699 }
1700 if ( classesArr.length )
1701 attrs[ 'class' ] = classesArr.sort().join( ' ' );
1702
1703 if ( origClasses && classesArr.length < origClasses.split( /\s+/ ).length )
1704 isModified = true;
1705 }
1706 else if ( origClasses ) {
1707 attrs[ 'class' ] = origClasses;
1708 }
1709
1710 return isModified;
1711 }
1712
1713 function validateElement( element ) {
1714 switch ( element.name ) {
1715 case 'a':
1716 // Code borrowed from htmlDataProcessor, so ACF does the same clean up.
1717 if ( !( element.children.length || element.attributes.name || element.attributes.id ) )
1718 return false;
1719 break;
1720 case 'img':
1721 if ( !element.attributes.src )
1722 return false;
1723 break;
1724 }
1725
1726 return true;
1727 }
1728
1729 function validatorFunction( validator ) {
1730 if ( !validator )
1731 return false;
1732 if ( validator === true )
1733 return true;
1734
1735 // Note: We don't need to remove properties with wildcards from the validator object.
1736 // E.g. data-* is actually an edge case of /^data-.*$/, so when it's accepted
1737 // by `value in validator` it's ok.
1738 var regexp = regexifyPropertiesWithWildcards( validator );
1739
1740 return function( value ) {
1741 return value in validator || ( regexp && value.match( regexp ) );
1742 };
1743 }
1744
1745 //
1746 // REMOVE ELEMENT ---------------------------------------------------------
1747 //
1748
1749 // Check whether all children will be valid in new context.
1750 // Note: it doesn't verify if text node is valid, because
1751 // new parent should accept them.
1752 function checkChildren( children, newParentName ) {
1753 var allowed = DTD[ newParentName ];
1754
1755 for ( var i = 0, l = children.length, child; i < l; ++i ) {
1756 child = children[ i ];
1757 if ( child.type == CKEDITOR.NODE_ELEMENT && !allowed[ child.name ] )
1758 return false;
1759 }
1760
1761 return true;
1762 }
1763
1764 function createBr() {
1765 return new CKEDITOR.htmlParser.element( 'br' );
1766 }
1767
1768 // Whether this is an inline element or text.
1769 function inlineNode( node ) {
1770 return node.type == CKEDITOR.NODE_TEXT ||
1771 node.type == CKEDITOR.NODE_ELEMENT && DTD.$inline[ node.name ];
1772 }
1773
1774 function isBrOrBlock( node ) {
1775 return node.type == CKEDITOR.NODE_ELEMENT &&
1776 ( node.name == 'br' || DTD.$block[ node.name ] );
1777 }
1778
1779 // Try to remove element in the best possible way.
1780 //
1781 // @param {Array} toBeChecked After executing this function
1782 // this array will contain elements that should be checked
1783 // because they were marked as potentially:
1784 // * in wrong context (e.g. li in body),
1785 // * empty elements from $removeEmpty,
1786 // * incorrect img/a/other element validated by validateElement().
1787 function removeElement( element, enterTag, toBeChecked ) {
1788 var name = element.name;
1789
1790 if ( DTD.$empty[ name ] || !element.children.length ) {
1791 // Special case - hr in br mode should be replaced with br, not removed.
1792 if ( name == 'hr' && enterTag == 'br' )
1793 element.replaceWith( createBr() );
1794 else {
1795 // Parent might become an empty inline specified in $removeEmpty or empty a[href].
1796 if ( element.parent )
1797 toBeChecked.push( { check: 'it', el: element.parent } );
1798
1799 element.remove();
1800 }
1801 } else if ( DTD.$block[ name ] || name == 'tr' ) {
1802 if ( enterTag == 'br' )
1803 stripBlockBr( element, toBeChecked );
1804 else
1805 stripBlock( element, enterTag, toBeChecked );
1806 }
1807 // Special case - elements that may contain CDATA should be removed completely.
1808 else if ( name in { style: 1, script: 1 } )
1809 element.remove();
1810 // The rest of inline elements. May also be the last resort
1811 // for some special elements.
1812 else {
1813 // Parent might become an empty inline specified in $removeEmpty or empty a[href].
1814 if ( element.parent )
1815 toBeChecked.push( { check: 'it', el: element.parent } );
1816 element.replaceWithChildren();
1817 }
1818 }
1819
1820 // Strip element block, but leave its content.
1821 // Works in 'div' and 'p' enter modes.
1822 function stripBlock( element, enterTag, toBeChecked ) {
1823 var children = element.children;
1824
1825 // First, check if element's children may be wrapped with <p/div>.
1826 // Ignore that <p/div> may not be allowed in element.parent.
1827 // This will be fixed when removing parent or by toBeChecked rule.
1828 if ( checkChildren( children, enterTag ) ) {
1829 element.name = enterTag;
1830 element.attributes = {};
1831 // Check if this p/div was put in correct context.
1832 // If not - strip parent.
1833 toBeChecked.push( { check: 'parent-down', el: element } );
1834 return;
1835 }
1836
1837 var parent = element.parent,
1838 shouldAutoP = parent.type == CKEDITOR.NODE_DOCUMENT_FRAGMENT || parent.name == 'body',
1839 i, child, p, parentDtd;
1840
1841 for ( i = children.length; i > 0; ) {
1842 child = children[ --i ];
1843
1844 // If parent requires auto paragraphing and child is inline node,
1845 // insert this child into newly created paragraph.
1846 if ( shouldAutoP && inlineNode( child ) ) {
1847 if ( !p ) {
1848 p = new CKEDITOR.htmlParser.element( enterTag );
1849 p.insertAfter( element );
1850
1851 // Check if this p/div was put in correct context.
1852 // If not - strip parent.
1853 toBeChecked.push( { check: 'parent-down', el: p } );
1854 }
1855 p.add( child, 0 );
1856 }
1857 // Child which doesn't need to be auto paragraphed.
1858 else {
1859 p = null;
1860 parentDtd = DTD[ parent.name ] || DTD.span;
1861
1862 child.insertAfter( element );
1863 // If inserted into invalid context, mark it and check
1864 // after removing all elements.
1865 if ( parent.type != CKEDITOR.NODE_DOCUMENT_FRAGMENT &&
1866 child.type == CKEDITOR.NODE_ELEMENT &&
1867 !parentDtd[ child.name ]
1868 )
1869 toBeChecked.push( { check: 'el-up', el: child } );
1870 }
1871 }
1872
1873 // All children have been moved to element's parent, so remove it.
1874 element.remove();
1875 }
1876
1877 // Prepend/append block with <br> if isn't
1878 // already prepended/appended with <br> or block and
1879 // isn't first/last child of its parent.
1880 // Then replace element with its children.
1881 // <p>a</p><p>b</p> => <p>a</p><br>b => a<br>b
1882 function stripBlockBr( element ) {
1883 var br;
1884
1885 if ( element.previous && !isBrOrBlock( element.previous ) ) {
1886 br = createBr();
1887 br.insertBefore( element );
1888 }
1889
1890 if ( element.next && !isBrOrBlock( element.next ) ) {
1891 br = createBr();
1892 br.insertAfter( element );
1893 }
1894
1895 element.replaceWithChildren();
1896 }
1897
1898 //
1899 // TRANSFORMATIONS --------------------------------------------------------
1900 //
1901 var transformationsTools;
1902
1903 // Apply given transformations group to the element.
1904 function applyTransformationsGroup( filter, element, group ) {
1905 var i, rule;
1906
1907 for ( i = 0; i < group.length; ++i ) {
1908 rule = group[ i ];
1909
1910 // Test with #check or #left only if it's set.
1911 // Do not apply transformations because that creates infinite loop.
1912 if ( ( !rule.check || filter.check( rule.check, false ) ) &&
1913 ( !rule.left || rule.left( element ) ) ) {
1914 rule.right( element, transformationsTools );
1915 return; // Only first matching rule in a group is executed.
1916 }
1917 }
1918 }
1919
1920 // Check whether element matches CKEDITOR.style.
1921 // The element can be a "superset" of style,
1922 // e.g. it may have more classes, but need to have
1923 // at least those defined in style.
1924 function elementMatchesStyle( element, style ) {
1925 var def = style.getDefinition(),
1926 defAttrs = def.attributes,
1927 defStyles = def.styles,
1928 attrName, styleName,
1929 classes, classPattern, cl;
1930
1931 if ( element.name != def.element )
1932 return false;
1933
1934 for ( attrName in defAttrs ) {
1935 if ( attrName == 'class' ) {
1936 classes = defAttrs[ attrName ].split( /\s+/ );
1937 classPattern = element.classes.join( '|' );
1938 while ( ( cl = classes.pop() ) ) {
1939 if ( classPattern.indexOf( cl ) == -1 )
1940 return false;
1941 }
1942 } else {
1943 if ( element.attributes[ attrName ] != defAttrs[ attrName ] )
1944 return false;
1945 }
1946 }
1947
1948 for ( styleName in defStyles ) {
1949 if ( element.styles[ styleName ] != defStyles[ styleName ] )
1950 return false;
1951 }
1952
1953 return true;
1954 }
1955
1956 // Return transformation group for content form.
1957 // One content form makes one transformation rule in one group.
1958 function getContentFormTransformationGroup( form, preferredForm ) {
1959 var element, left;
1960
1961 if ( typeof form == 'string' )
1962 element = form;
1963 else if ( form instanceof CKEDITOR.style )
1964 left = form;
1965 else {
1966 element = form[ 0 ];
1967 left = form[ 1 ];
1968 }
1969
1970 return [ {
1971 element: element,
1972 left: left,
1973 right: function( el, tools ) {
1974 tools.transform( el, preferredForm );
1975 }
1976 } ];
1977 }
1978
1979 // Obtain element's name from transformation rule.
1980 // It will be defined by #element, or #check or #left (styleDef.element).
1981 function getElementNameForTransformation( rule, check ) {
1982 if ( rule.element )
1983 return rule.element;
1984 if ( check )
1985 return check.match( /^([a-z0-9]+)/i )[ 0 ];
1986 return rule.left.getDefinition().element;
1987 }
1988
1989 function getMatchStyleFn( style ) {
1990 return function( el ) {
1991 return elementMatchesStyle( el, style );
1992 };
1993 }
1994
1995 function getTransformationFn( toolName ) {
1996 return function( el, tools ) {
1997 tools[ toolName ]( el );
1998 };
1999 }
2000
2001 function optimizeTransformationsGroup( rules ) {
2002 var groupName, i, rule,
2003 check, left, right,
2004 optimizedRules = [];
2005
2006 for ( i = 0; i < rules.length; ++i ) {
2007 rule = rules[ i ];
2008
2009 if ( typeof rule == 'string' ) {
2010 rule = rule.split( /\s*:\s*/ );
2011 check = rule[ 0 ];
2012 left = null;
2013 right = rule[ 1 ];
2014 } else {
2015 check = rule.check;
2016 left = rule.left;
2017 right = rule.right;
2018 }
2019
2020 // Extract element name.
2021 if ( !groupName )
2022 groupName = getElementNameForTransformation( rule, check );
2023
2024 if ( left instanceof CKEDITOR.style )
2025 left = getMatchStyleFn( left );
2026
2027 optimizedRules.push( {
2028 // It doesn't make sense to test against name rule (e.g. 'table'), so don't save it.
2029 check: check == groupName ? null : check,
2030
2031 left: left,
2032
2033 // Handle shorthand format. E.g.: 'table[width]:sizeToAttribute'.
2034 right: typeof right == 'string' ? getTransformationFn( right ) : right
2035 } );
2036 }
2037
2038 return {
2039 name: groupName,
2040 rules: optimizedRules
2041 };
2042 }
2043
2044 /**
2045 * Singleton containing tools useful for transformation rules.
2046 *
2047 * @class CKEDITOR.filter.transformationsTools
2048 * @singleton
2049 */
2050 transformationsTools = CKEDITOR.filter.transformationsTools = {
2051 /**
2052 * Converts `width` and `height` attributes to styles.
2053 *
2054 * @param {CKEDITOR.htmlParser.element} element
2055 */
2056 sizeToStyle: function( element ) {
2057 this.lengthToStyle( element, 'width' );
2058 this.lengthToStyle( element, 'height' );
2059 },
2060
2061 /**
2062 * Converts `width` and `height` styles to attributes.
2063 *
2064 * @param {CKEDITOR.htmlParser.element} element
2065 */
2066 sizeToAttribute: function( element ) {
2067 this.lengthToAttribute( element, 'width' );
2068 this.lengthToAttribute( element, 'height' );
2069 },
2070
2071 /**
2072 * Converts length in the `attrName` attribute to a valid CSS length (like `width` or `height`).
2073 *
2074 * @param {CKEDITOR.htmlParser.element} element
2075 * @param {String} attrName Name of the attribute that will be converted.
2076 * @param {String} [styleName=attrName] Name of the style into which the attribute will be converted.
2077 */
2078 lengthToStyle: function( element, attrName, styleName ) {
2079 styleName = styleName || attrName;
2080
2081 if ( !( styleName in element.styles ) ) {
2082 var value = element.attributes[ attrName ];
2083
2084 if ( value ) {
2085 if ( ( /^\d+$/ ).test( value ) )
2086 value += 'px';
2087
2088 element.styles[ styleName ] = value;
2089 }
2090 }
2091
2092 delete element.attributes[ attrName ];
2093 },
2094
2095 /**
2096 * Converts length in the `styleName` style to a valid length attribute (like `width` or `height`).
2097 *
2098 * @param {CKEDITOR.htmlParser.element} element
2099 * @param {String} styleName The name of the style that will be converted.
2100 * @param {String} [attrName=styleName] The name of the attribute into which the style will be converted.
2101 */
2102 lengthToAttribute: function( element, styleName, attrName ) {
2103 attrName = attrName || styleName;
2104
2105 if ( !( attrName in element.attributes ) ) {
2106 var value = element.styles[ styleName ],
2107 match = value && value.match( /^(\d+)(?:\.\d*)?px$/ );
2108
2109 if ( match )
2110 element.attributes[ attrName ] = match[ 1 ];
2111 // Pass the TEST_VALUE used by filter#check when mocking element.
2112 else if ( value == TEST_VALUE )
2113 element.attributes[ attrName ] = TEST_VALUE;
2114 }
2115
2116 delete element.styles[ styleName ];
2117 },
2118
2119 /**
2120 * Converts the `align` attribute to the `float` style if not set. The attribute
2121 * is always removed.
2122 *
2123 * @param {CKEDITOR.htmlParser.element} element
2124 */
2125 alignmentToStyle: function( element ) {
2126 if ( !( 'float' in element.styles ) ) {
2127 var value = element.attributes.align;
2128
2129 if ( value == 'left' || value == 'right' )
2130 element.styles[ 'float' ] = value; // Uh... GCC doesn't like the 'float' prop name.
2131 }
2132
2133 delete element.attributes.align;
2134 },
2135
2136 /**
2137 * Converts the `float` style to the `align` attribute if not set.
2138 * The style is always removed.
2139 *
2140 * @param {CKEDITOR.htmlParser.element} element
2141 */
2142 alignmentToAttribute: function( element ) {
2143 if ( !( 'align' in element.attributes ) ) {
2144 var value = element.styles[ 'float' ];
2145
2146 if ( value == 'left' || value == 'right' )
2147 element.attributes.align = value;
2148 }
2149
2150 delete element.styles[ 'float' ]; // Uh... GCC doesn't like the 'float' prop name.
2151 },
2152
2153 /**
2154 * Converts the shorthand form of the `border` style to seperate styles.
2155 *
2156 * @param {CKEDITOR.htmlParser.element} element
2157 */
2158 splitBorderShorthand: function( element ) {
2159 if ( !element.styles.border ) {
2160 return;
2161 }
2162
2163 var widths = element.styles.border.match( /([\.\d]+\w+)/g ) || [ '0px' ];
2164 switch ( widths.length ) {
2165 case 1:
2166 element.styles[ 'border-width' ] = widths[0];
2167 break;
2168 case 2:
2169 mapStyles( [ 0, 1, 0, 1 ] );
2170 break;
2171 case 3:
2172 mapStyles( [ 0, 1, 2, 1 ] );
2173 break;
2174 case 4:
2175 mapStyles( [ 0, 1, 2, 3 ] );
2176 break;
2177 }
2178
2179 element.styles[ 'border-style' ] = element.styles[ 'border-style' ] ||
2180 ( element.styles.border.match( /(none|hidden|dotted|dashed|solid|double|groove|ridge|inset|outset|initial|inherit)/ ) || [] )[ 0 ];
2181 if ( !element.styles[ 'border-style' ] )
2182 delete element.styles[ 'border-style' ];
2183
2184 delete element.styles.border;
2185
2186 function mapStyles( map ) {
2187 element.styles['border-top-width'] = widths[ map[0] ];
2188 element.styles['border-right-width'] = widths[ map[1] ];
2189 element.styles['border-bottom-width'] = widths[ map[2] ];
2190 element.styles['border-left-width'] = widths[ map[3] ];
2191 }
2192 },
2193
2194 listTypeToStyle: function( element ) {
2195 if ( element.attributes.type ) {
2196 switch ( element.attributes.type ) {
2197 case 'a':
2198 element.styles[ 'list-style-type' ] = 'lower-alpha';
2199 break;
2200 case 'A':
2201 element.styles[ 'list-style-type' ] = 'upper-alpha';
2202 break;
2203 case 'i':
2204 element.styles[ 'list-style-type' ] = 'lower-roman';
2205 break;
2206 case 'I':
2207 element.styles[ 'list-style-type' ] = 'upper-roman';
2208 break;
2209 case '1':
2210 element.styles[ 'list-style-type' ] = 'decimal';
2211 break;
2212 default:
2213 element.styles[ 'list-style-type' ] = element.attributes.type;
2214 }
2215 }
2216 },
2217
2218 /**
2219 * Converts the shorthand form of the `margin` style to seperate styles.
2220 *
2221 * @param {CKEDITOR.htmlParser.element} element
2222 */
2223 splitMarginShorthand: function( element ) {
2224 if ( !element.styles.margin ) {
2225 return;
2226 }
2227
2228 var widths = element.styles.margin.match( /(\-?[\.\d]+\w+)/g ) || [ '0px' ];
2229 switch ( widths.length ) {
2230 case 1:
2231 mapStyles( [ 0, 0, 0, 0 ] );
2232 break;
2233 case 2:
2234 mapStyles( [ 0, 1, 0, 1 ] );
2235 break;
2236 case 3:
2237 mapStyles( [ 0, 1, 2, 1 ] );
2238 break;
2239 case 4:
2240 mapStyles( [ 0, 1, 2, 3 ] );
2241 break;
2242 }
2243
2244 delete element.styles.margin;
2245
2246 function mapStyles( map ) {
2247 element.styles['margin-top'] = widths[ map[0] ];
2248 element.styles['margin-right'] = widths[ map[1] ];
2249 element.styles['margin-bottom'] = widths[ map[2] ];
2250 element.styles['margin-left'] = widths[ map[3] ];
2251 }
2252 },
2253
2254 /**
2255 * Checks whether an element matches a given {@link CKEDITOR.style}.
2256 * The element can be a "superset" of a style, e.g. it may have
2257 * more classes, but needs to have at least those defined in the style.
2258 *
2259 * @param {CKEDITOR.htmlParser.element} element
2260 * @param {CKEDITOR.style} style
2261 */
2262 matchesStyle: elementMatchesStyle,
2263
2264 /**
2265 * Transforms an element to a given form.
2266 *
2267 * Form may be a:
2268 *
2269 * * {@link CKEDITOR.style},
2270 * * string &ndash; the new name of the element.
2271 *
2272 * @param {CKEDITOR.htmlParser.element} el
2273 * @param {CKEDITOR.style/String} form
2274 */
2275 transform: function( el, form ) {
2276 if ( typeof form == 'string' )
2277 el.name = form;
2278 // Form is an instance of CKEDITOR.style.
2279 else {
2280 var def = form.getDefinition(),
2281 defStyles = def.styles,
2282 defAttrs = def.attributes,
2283 attrName, styleName,
2284 existingClassesPattern, defClasses, cl;
2285
2286 el.name = def.element;
2287
2288 for ( attrName in defAttrs ) {
2289 if ( attrName == 'class' ) {
2290 existingClassesPattern = el.classes.join( '|' );
2291 defClasses = defAttrs[ attrName ].split( /\s+/ );
2292
2293 while ( ( cl = defClasses.pop() ) ) {
2294 if ( existingClassesPattern.indexOf( cl ) == -1 )
2295 el.classes.push( cl );
2296 }
2297 } else {
2298 el.attributes[ attrName ] = defAttrs[ attrName ];
2299 }
2300
2301 }
2302
2303 for ( styleName in defStyles ) {
2304 el.styles[ styleName ] = defStyles[ styleName ];
2305 }
2306 }
2307 }
2308 };
2309
2310 } )();
2311
2312 /**
2313 * Allowed content rules. This setting is used when
2314 * instantiating {@link CKEDITOR.editor#filter}.
2315 *
2316 * The following values are accepted:
2317 *
2318 * * {@link CKEDITOR.filter.allowedContentRules} &ndash; defined rules will be added
2319 * to the {@link CKEDITOR.editor#filter}.
2320 * * `true` &ndash; will disable the filter (data will not be filtered,
2321 * all features will be activated). Reading [security best practices](#!/guide/dev_best_practices) before setting `true` is recommended.
2322 * * default &ndash; the filter will be configured by loaded features
2323 * (toolbar items, commands, etc.).
2324 *
2325 * In all cases filter configuration may be extended by
2326 * {@link CKEDITOR.config#extraAllowedContent}. This option may be especially
2327 * useful when you want to use the default `allowedContent` value
2328 * along with some additional rules.
2329 *
2330 * CKEDITOR.replace( 'textarea_id', {
2331 * allowedContent: 'p b i; a[!href]',
2332 * on: {
2333 * instanceReady: function( evt ) {
2334 * var editor = evt.editor;
2335 *
2336 * editor.filter.check( 'h1' ); // -> false
2337 * editor.setData( '<h1><i>Foo</i></h1><p class="left"><span>Bar</span> <a href="http://foo.bar">foo</a></p>' );
2338 * // Editor contents will be:
2339 * '<p><i>Foo</i></p><p>Bar <a href="http://foo.bar">foo</a></p>'
2340 * }
2341 * }
2342 * } );
2343 *
2344 * It is also possible to disallow some already allowed content. It is especially
2345 * useful when you want to "trim down" the content allowed by default by
2346 * editor features. To do that, use the {@link #disallowedContent} option.
2347 *
2348 * Read more in the [documentation](#!/guide/dev_acf)
2349 * and see the [SDK sample](http://sdk.ckeditor.com/samples/acf.html).
2350 *
2351 * @since 4.1
2352 * @cfg {CKEDITOR.filter.allowedContentRules/Boolean} [allowedContent=null]
2353 * @member CKEDITOR.config
2354 */
2355
2356 /**
2357 * This option makes it possible to set additional allowed
2358 * content rules for {@link CKEDITOR.editor#filter}.
2359 *
2360 * It is especially useful in combination with the default
2361 * {@link CKEDITOR.config#allowedContent} value:
2362 *
2363 * CKEDITOR.replace( 'textarea_id', {
2364 * plugins: 'wysiwygarea,toolbar,format',
2365 * extraAllowedContent: 'b i',
2366 * on: {
2367 * instanceReady: function( evt ) {
2368 * var editor = evt.editor;
2369 *
2370 * editor.filter.check( 'h1' ); // -> true (thanks to Format combo)
2371 * editor.filter.check( 'b' ); // -> true (thanks to extraAllowedContent)
2372 * editor.setData( '<h1><i>Foo</i></h1><p class="left"><b>Bar</b> <a href="http://foo.bar">foo</a></p>' );
2373 * // Editor contents will be:
2374 * '<h1><i>Foo</i></h1><p><b>Bar</b> foo</p>'
2375 * }
2376 * }
2377 * } );
2378 *
2379 * Read more in the [documentation](#!/guide/dev_acf-section-automatic-mode-and-allow-additional-tags%2Fproperties)
2380 * and see the [SDK sample](http://sdk.ckeditor.com/samples/acf.html).
2381 * See also {@link CKEDITOR.config#allowedContent} for more details.
2382 *
2383 * @since 4.1
2384 * @cfg {Object/String} extraAllowedContent
2385 * @member CKEDITOR.config
2386 */
2387
2388 /**
2389 * Disallowed content rules. They have precedence over {@link #allowedContent allowed content rules}.
2390 * Read more in the [Disallowed Content guide](#!/guide/dev_disallowed_content).
2391 *
2392 * Read more in the [documentation](#!/guide/dev_acf-section-automatic-mode-but-disallow-certain-tags%2Fproperties)
2393 * and see the [SDK sample](http://sdk.ckeditor.com/samples/acf.html).
2394 * See also {@link CKEDITOR.config#allowedContent} and {@link CKEDITOR.config#extraAllowedContent}.
2395 *
2396 * @since 4.4
2397 * @cfg {CKEDITOR.filter.disallowedContentRules} disallowedContent
2398 * @member CKEDITOR.config
2399 */
2400
2401 /**
2402 * This event is fired when {@link CKEDITOR.filter} has stripped some
2403 * content from the data that was loaded (e.g. by {@link CKEDITOR.editor#method-setData}
2404 * method or in the source mode) or inserted (e.g. when pasting or using the
2405 * {@link CKEDITOR.editor#method-insertHtml} method).
2406 *
2407 * This event is useful when testing whether the {@link CKEDITOR.config#allowedContent}
2408 * setting is sufficient and correct for a system that is migrating to CKEditor 4.1
2409 * (where the [Advanced Content Filter](#!/guide/dev_advanced_content_filter) was introduced).
2410 *
2411 * @since 4.1
2412 * @event dataFiltered
2413 * @member CKEDITOR.editor
2414 * @param {CKEDITOR.editor} editor This editor instance.
2415 */
2416
2417 /**
2418 * Virtual class which is the [Allowed Content Rules](#!/guide/dev_allowed_content_rules) formats type.
2419 *
2420 * Possible formats are:
2421 *
2422 * * the [string format](#!/guide/dev_allowed_content_rules-section-2),
2423 * * the [object format](#!/guide/dev_allowed_content_rules-section-3),
2424 * * a {@link CKEDITOR.style} instance &ndash; used mainly for integrating plugins with Advanced Content Filter,
2425 * * an array of the above formats.
2426 *
2427 * @since 4.1
2428 * @class CKEDITOR.filter.allowedContentRules
2429 * @abstract
2430 */
2431
2432 /**
2433 * Virtual class representing the {@link CKEDITOR.filter#disallow} argument and a type of
2434 * the {@link CKEDITOR.config#disallowedContent} option.
2435 *
2436 * This is a simplified version of the {@link CKEDITOR.filter.allowedContentRules} type.
2437 * Only the string format and object format are accepted. Required properties
2438 * are not allowed in this format.
2439 *
2440 * Read more in the [Disallowed Content guide](#!/guide/dev_disallowed_content).
2441 *
2442 * @since 4.4
2443 * @class CKEDITOR.filter.disallowedContentRules
2444 * @abstract
2445 */
2446
2447 /**
2448 * Virtual class representing {@link CKEDITOR.filter#check} argument.
2449 *
2450 * This is a simplified version of the {@link CKEDITOR.filter.allowedContentRules} type.
2451 * It may contain only one element and its styles, classes, and attributes. Only the
2452 * string format and a {@link CKEDITOR.style} instances are accepted. Required properties
2453 * are not allowed in this format.
2454 *
2455 * Example:
2456 *
2457 * 'img[src,alt](foo)' // Correct rule.
2458 * 'ol, ul(!foo)' // Incorrect rule. Multiple elements and required
2459 * // properties are not supported.
2460 *
2461 * @since 4.1
2462 * @class CKEDITOR.filter.contentRule
2463 * @abstract
2464 */
2465
2466 /**
2467 * Interface that may be automatically implemented by any
2468 * instance of any class which has at least the `name` property and
2469 * can be meant as an editor feature.
2470 *
2471 * For example:
2472 *
2473 * * "Bold" command, button, and keystroke &ndash; it does not mean exactly
2474 * `<strong>` or `<b>` but just the ability to create bold text.
2475 * * "Format" drop-down list &ndash; it also does not imply any HTML tag.
2476 * * "Link" command, button, and keystroke.
2477 * * "Image" command, button, and dialog window.
2478 *
2479 * Thus most often a feature is an instance of one of the following classes:
2480 *
2481 * * {@link CKEDITOR.command}
2482 * * {@link CKEDITOR.ui.button}
2483 * * {@link CKEDITOR.ui.richCombo}
2484 *
2485 * None of them have a `name` property explicitly defined, but
2486 * it is set by {@link CKEDITOR.editor#addCommand} and {@link CKEDITOR.ui#add}.
2487 *
2488 * During editor initialization all features that the editor should activate
2489 * should be passed to {@link CKEDITOR.editor#addFeature} (shorthand for {@link CKEDITOR.filter#addFeature}).
2490 *
2491 * This method checks if a feature can be activated (see {@link #requiredContent}) and if yes,
2492 * then it registers allowed content rules required by this feature (see {@link #allowedContent}) along
2493 * with two kinds of transformations: {@link #contentForms} and {@link #contentTransformations}.
2494 *
2495 * By default all buttons that are included in [toolbar layout configuration](#!/guide/dev_toolbar)
2496 * are checked and registered with {@link CKEDITOR.editor#addFeature}, all styles available in the
2497 * 'Format' and 'Styles' drop-down lists are checked and registered too and so on.
2498 *
2499 * @since 4.1
2500 * @class CKEDITOR.feature
2501 * @abstract
2502 */
2503
2504 /**
2505 * HTML code that can be generated by this feature.
2506 *
2507 * For example a basic image feature (image button displaying the image dialog window)
2508 * may allow `'img[!src,alt,width,height]'`.
2509 *
2510 * During the feature activation this value is passed to {@link CKEDITOR.filter#allow}.
2511 *
2512 * @property {CKEDITOR.filter.allowedContentRules} [allowedContent=null]
2513 */
2514
2515 /**
2516 * Minimal HTML code that this feature must be allowed to
2517 * generate in order to work.
2518 *
2519 * For example a basic image feature (image button displaying the image dialog window)
2520 * needs `'img[src,alt]'` in order to be activated.
2521 *
2522 * During the feature validation this value is passed to {@link CKEDITOR.filter#check}.
2523 *
2524 * If this value is not provided, a feature will be always activated.
2525 *
2526 * @property {CKEDITOR.filter.contentRule} [requiredContent=null]
2527 */
2528
2529 /**
2530 * The name of the feature.
2531 *
2532 * It is used for example to identify which {@link CKEDITOR.filter#allowedContent}
2533 * rule was added for which feature.
2534 *
2535 * @property {String} name
2536 */
2537
2538 /**
2539 * Feature content forms to be registered in the {@link CKEDITOR.editor#filter}
2540 * during the feature activation.
2541 *
2542 * See {@link CKEDITOR.filter#addContentForms} for more details.
2543 *
2544 * @property [contentForms=null]
2545 */
2546
2547 /**
2548 * Transformations (usually for content generated by this feature, but not necessarily)
2549 * that will be registered in the {@link CKEDITOR.editor#filter} during the feature activation.
2550 *
2551 * See {@link CKEDITOR.filter#addTransformations} for more details.
2552 *
2553 * @property [contentTransformations=null]
2554 */
2555
2556 /**
2557 * Returns a feature that this feature needs to register.
2558 *
2559 * In some cases, during activation, one feature may need to register
2560 * another feature. For example a {@link CKEDITOR.ui.button} often registers
2561 * a related command. See {@link CKEDITOR.ui.button#toFeature}.
2562 *
2563 * This method is executed when a feature is passed to the {@link CKEDITOR.editor#addFeature}.
2564 *
2565 * @method toFeature
2566 * @returns {CKEDITOR.feature}
2567 */