2 * @license Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved.
3 * For licensing, see LICENSE.md or http://ckeditor.com/license
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.
14 copy
= CKEDITOR
.tools
.copy
,
15 trim
= CKEDITOR
.tools
.trim
,
16 TEST_VALUE
= 'cke-test',
17 enterModeTags
= [ '', 'p', 'br', 'div' ];
20 * A flag indicating that the current element and all its ancestors
21 * should not be filtered.
23 * See {@link CKEDITOR.filter#addElementCallback} for more details.
27 * @property {Number} [=2]
30 CKEDITOR
.FILTER_SKIP_TREE
= FILTER_SKIP_TREE
;
33 * Highly configurable class which implements input data filtering mechanisms
34 * and core functions used for the activation of editor features.
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:
41 * * By the user, with the {@link CKEDITOR.config#allowedContent} setting.
42 * * Automatically, by loaded features (toolbar items, commands, etc.).
44 * In both cases additional allowed content rules may be added by
45 * setting the {@link CKEDITOR.config#extraAllowedContent}
46 * configuration option.
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:
52 * * `'p'` – for {@link CKEDITOR#ENTER_P},
53 * * `'div'` – for {@link CKEDITOR#ENTER_DIV},
54 * * `'br'` – for {@link CKEDITOR#ENTER_BR}.
56 * **Read more** about the Advanced Content Filter in [guides](#!/guide/dev_advanced_content_filter).
58 * Filter may also be used as a standalone instance by passing
59 * {@link CKEDITOR.filter.allowedContentRules} instead of {@link CKEDITOR.editor}
62 * var filter = new CKEDITOR.filter( 'b' );
64 * filter.check( 'b' ); // -> true
65 * filter.check( 'i' ); // -> false
66 * filter.allow( 'i' );
67 * filter.check( 'i' ); // -> true
71 * @constructor Creates a filter class instance.
72 * @param {CKEDITOR.editor/CKEDITOR.filter.allowedContentRules} editorOrRules
74 CKEDITOR
.filter = function( editorOrRules
) {
76 * Whether custom {@link CKEDITOR.config#allowedContent} was set.
78 * This property does not apply to the standalone filter.
81 * @property {Boolean} customConfig
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}).
89 * Rules in this array are in unified allowed content rules format.
91 * This property is useful for debugging issues with rules string parsing
92 * or for checking which rules were automatically added by editor features.
96 this.allowedContent
= [];
99 * Array of rules added by the {@link #disallow} method (including those
100 * loaded from {@link CKEDITOR.config#disallowedContent}).
102 * Rules in this array are in unified disallowed content rules format.
104 * This property is useful for debugging issues with rules string parsing
105 * or for checking which rules were automatically added by editor features.
110 this.disallowedContent
= [];
113 * Array of element callbacks. See {@link #addElementCallback}.
116 * @property {Function[]} [=null]
118 this.elementCallbacks
= null;
121 * Whether the filter is disabled.
123 * To disable the filter, set {@link CKEDITOR.config#allowedContent} to `true`
124 * or use the {@link #disable} method.
128 this.disabled
= false;
131 * Editor instance if not a standalone filter.
134 * @property {CKEDITOR.editor} [=null]
139 * Filter's unique id. It can be used to find filter instance in
140 * {@link CKEDITOR.filter#instances CKEDITOR.filter.instance} object.
144 * @property {Number} id
146 this.id
= CKEDITOR
.tools
.getNextNumber();
149 // Optimized allowed content rules.
154 // Optimized disallowed content rules.
159 // Object: element name => array of transformations groups.
165 // Register filter instance.
166 CKEDITOR
.filter
.instances
[ this.id
] = this;
168 if ( editorOrRules
instanceof CKEDITOR
.editor
) {
169 var editor
= this.editor
= editorOrRules
;
170 this.customConfig
= true;
172 var allowedContent
= editor
.config
.allowedContent
;
174 // Disable filter completely by setting config.allowedContent = true.
175 if ( allowedContent
=== true ) {
176 this.disabled
= true;
180 if ( !allowedContent
)
181 this.customConfig
= false;
183 this.allow( allowedContent
, 'config', 1 );
184 this.allow( editor
.config
.extraAllowedContent
, 'extra', 1 );
186 // Enter modes should extend filter rules (ENTER_P adds 'p' rule, etc.).
187 this.allow( enterModeTags
[ editor
.enterMode
] + ' ' + enterModeTags
[ editor
.shiftEnterMode
], 'default', 1 );
189 this.disallow( editor
.config
.disallowedContent
);
191 // Rules object passed in editorOrRules argument - initialize standalone filter.
193 this.customConfig
= false;
194 this.allow( editorOrRules
, 'default', 1 );
199 * Object containing all filter instances stored under their
200 * {@link #id} properties.
202 * var filter = new CKEDITOR.filter( 'p' );
203 * filter === CKEDITOR.filter.instances[ filter.id ];
207 * @property instances
209 CKEDITOR
.filter
.instances
= {};
211 CKEDITOR
.filter
.prototype = {
213 * Adds allowed content rules to the filter.
215 * Read about rules formats in [Allowed Content Rules guide](#!/guide/dev_allowed_content_rules).
217 * // Add a basic rule for custom image feature (e.g. 'MyImage' button).
218 * editor.filter.allow( 'img[!src,alt]', 'MyImage' );
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' );
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.
232 allow: function( newRules
, featureName
, overrideCustom
) {
233 // Check arguments and constraints. Clear cache.
234 if ( !beforeAddingRule( this, newRules
, overrideCustom
) )
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
);
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.
253 addAndOptimizeRules( this, newRules
, featureName
, this.allowedContent
, this._
.allowedRules
);
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.
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();
268 * filter.applyTo( fragment );
269 * fragment.writeHtml( writer );
270 * writer.getHtml(); // -> '<p><b>foo</b> bar</p>'
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.
280 applyTo: function( fragment
, toHtml
, transformOnly
, enterMode
) {
286 protectedRegexs
= this.editor
&& this.editor
.config
.protectedSource
,
290 doFilter: !transformOnly
,
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' )
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.
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
311 if ( toHtml
&& el
.name
== 'span' && ~CKEDITOR
.tools
.objectKeys( el
.attributes
).join( '|' ).indexOf( 'data-cke-' ) )
314 processRetVal
= processElement( that
, el
, toBeRemoved
, filterOpts
);
315 if ( processRetVal
& FILTER_ELEMENT_MODIFIED
)
317 else if ( processRetVal
& FILTER_SKIP_TREE
)
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
);
326 if ( toBeRemoved
.length
)
329 var node
, element
, check
,
331 enterTag
= enterModeTags
[ enterMode
|| ( this.editor
? this.editor
.enterMode : CKEDITOR
.ENTER_P
) ],
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.
343 // Check elements that have been marked as possibly invalid.
344 while ( ( check
= toBeChecked
.pop() ) ) {
346 // Element has been already removed.
347 if ( !element
.parent
)
350 // Handle custom elements as inline elements (http://dev.ckeditor.com/ticket/12683).
351 parentDtd
= DTD
[ element
.parent
.name
] || DTD
.span
;
353 switch ( check
.check
) {
354 // Check if element itself is correct.
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
);
364 // Check if element is in correct context. If not - remove element.
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
);
371 // Check if element is in correct context. If not - remove parent.
373 if ( element
.parent
.type
!= CKEDITOR
.NODE_DOCUMENT_FRAGMENT
&& !parentDtd
[ element
.name
] )
374 removeElement( element
.parent
, enterTag
, toBeChecked
);
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.
389 * @param {CKEDITOR.feature} feature The feature to be tested.
390 * @returns {Boolean} Whether this feature can be enabled.
392 checkFeature: function( feature
) {
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
);
404 return !feature
.requiredContent
|| this.check( feature
.requiredContent
);
408 * Disables Advanced Content Filter.
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.
414 * In other cases the filter can be disabled by setting
415 * {@link CKEDITOR.config#allowedContent} to `true`.
417 disable: function() {
418 this.disabled
= true;
422 * Adds disallowed content rules to the filter.
424 * Read about rules formats in the [Allowed Content Rules guide](#!/guide/dev_allowed_content_rules).
426 * // Disallow all styles on the image elements.
427 * editor.filter.disallow( 'img{*}' );
429 * // Disallow all span and div elements.
430 * editor.filter.disallow( 'span div' );
433 * @param {CKEDITOR.filter.disallowedContentRules} newRules Rule(s) to be added.
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 ) )
442 if ( typeof newRules
== 'string' )
443 newRules
= parseRulesString( newRules
);
445 addAndOptimizeRules( this, newRules
, null, this.disallowedContent
, this._
.disallowedRules
);
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.
454 * editor.filter.allow( 'i; span{!font-style}' );
455 * editor.filter.addContentForms( [
458 * [ 'span', function( el ) {
459 * return el.styles[ 'font-style' ] == 'italic';
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).
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}.
471 * @param {Array} forms The content forms of a feature.
473 addContentForms: function( forms
) {
484 // First, find preferred form - this is, first allowed.
485 for ( i
= 0; i
< forms
.length
&& !preferredForm
; ++i
) {
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
;
493 // This feature doesn't have preferredForm, so ignore it.
494 if ( !preferredForm
)
497 for ( i
= 0; i
< forms
.length
; ++i
)
498 transfGroups
.push( getContentFormTransformationGroup( forms
[ i
], preferredForm
) );
500 this.addTransformations( transfGroups
);
504 * Adds a callback which will be executed on every element
505 * that the filter reaches when filtering, before the element is filtered.
507 * By returning {@link CKEDITOR#FILTER_SKIP_TREE} it is possible to
508 * skip filtering of the current element and all its ancestors.
510 * editor.filter.addElementCallback( function( el ) {
511 * if ( el.hasClass( 'protected' ) )
512 * return CKEDITOR.FILTER_SKIP_TREE;
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.
521 * @param {Function} callback The callback to be executed.
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
= [];
528 this.elementCallbacks
.push( callback
);
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.
538 * @param {CKEDITOR.feature} feature
539 * @returns {Boolean} Whether this feature can be enabled.
541 addFeature: function( feature
) {
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
);
553 // If default configuration (will be checked inside #allow()),
554 // then add allowed content rules.
555 this.allow( feature
.allowedContent
, feature
.name
);
557 this.addTransformations( feature
.contentTransformations
);
558 this.addContentForms( feature
.contentForms
);
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
);
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.
572 * A single transformation rule is an object with four properties:
574 * * `check` (optional) – 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
578 * * `element` (optional) – 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) – 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` – 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.
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.
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.
600 * editor.filter.addTransformations( [
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'
611 * // This rule will add the foo="1" attribute to all images that
615 * left: function( el ) {
616 * return !el.attributes.foo;
618 * right: function( el, tools ) {
619 * el.attributes.foo = '1';
626 * // config.allowedContent = 'table{height,width}; tr td'.
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>'
632 * // config.allowedContent = 'table[height,width]; tr td'.
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>'
638 * // config.allowedContent = 'table{width,height}[height,width]; tr td'.
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>'
643 * // Note: Both forms are allowed (size set by style and by attributes), but only
644 * // the first transformation is applied — the size is always transformed to a style.
645 * // This is because only the first transformation matching allowed content rules is applied.
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}.
650 * @param {Array} transformations
652 addTransformations: function( transformations
) {
656 if ( !transformations
)
659 var optimized
= this._
.transformations
,
662 for ( i
= 0; i
< transformations
.length
; ++i
) {
663 group
= optimizeTransformationsGroup( transformations
[ i
] );
665 if ( !optimized
[ group
.name
] )
666 optimized
[ group
.name
] = [];
668 optimized
[ group
.name
].push( group
.rules
);
673 * Checks whether the content defined in the `test` argument is allowed
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.
683 * // Rule: 'img[!src,alt]'.
684 * filter.check( 'img[alt]' ); // -> true
685 * filter.check( 'img[alt]', true, true ); // -> false
687 * Second `check()` call returned `false` because `src` is required.
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.
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.
699 check: function( test
, applyTransformations
, strictCheck
) {
703 // If rules are an array, expand it and return the logical OR value of
705 if ( CKEDITOR
.tools
.isArray( test
) ) {
706 for ( var i
= test
.length
; i
-- ; ) {
707 if ( this.check( test
[ i
], applyTransformations
, strictCheck
) )
713 var element
, result
, cacheKey
;
715 if ( typeof test
== 'string' ) {
716 cacheKey
= test
+ '<' + ( applyTransformations
=== false ? '0' : '1' ) + ( strictCheck
? '1' : '0' ) + '>';
718 // Check if result of this check hasn't been already cached.
719 if ( cacheKey
in this._
.cachedChecks
)
720 return this._
.cachedChecks
[ cacheKey
];
722 // Create test element from string.
723 element
= mockElementFromString( test
);
725 // Create test element from CKEDITOR.style.
726 element
= mockElementFromStyle( test
);
730 var clone
= CKEDITOR
.tools
.clone( element
),
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
] );
740 // Transformations could modify styles or classes, so they need to be copied
741 // to attributes object.
742 updateAttributes( element
);
745 // Filter clone of mocked element.
746 processElement( this, clone
, toBeRemoved
, {
748 doTransform: applyTransformations
!== false,
749 skipRequired: !strictCheck
,
750 skipFinalValidation: !strictCheck
753 // Element has been marked for removal.
754 if ( toBeRemoved
.length
> 0 ) {
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 ) ) {
763 // Cache result of this test - we can build cache only for string tests.
764 if ( typeof test
== 'string' )
765 this._
.cachedChecks
[ cacheKey
] = result
;
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}.
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.
779 getAllowedEnterMode: ( function() {
780 var tagsToCheck
= [ 'p', 'div', 'br' ],
783 div: CKEDITOR
.ENTER_DIV
,
784 br: CKEDITOR
.ENTER_BR
787 return function( defaultMode
, reverse
) {
788 // Clone the array first.
789 var tags
= tagsToCheck
.slice(),
792 // Check the default mode first.
793 if ( this.check( enterModeTags
[ defaultMode
] ) )
796 // If not reverse order, reverse array so we can pop() from it.
798 tags
= tags
.reverse();
800 while ( ( tag
= tags
.pop() ) ) {
801 if ( this.check( tag
) )
802 return enterModes
[ tag
];
805 return CKEDITOR
.ENTER_BR
;
810 * Returns a clone of this filter instance.
813 * @returns {CKEDITOR.filter}
816 var ret
= new CKEDITOR
.filter(),
817 clone
= CKEDITOR
.tools
.clone
;
819 // Cloning allowed content related things.
820 ret
.allowedContent
= clone( this.allowedContent
);
821 ret
._
.allowedRules
= clone( this._
.allowedRules
);
823 // Disallowed content rules.
824 ret
.disallowedContent
= clone( this.disallowedContent
);
825 ret
._
.disallowedRules
= clone( this._
.disallowedRules
);
827 ret
._
.transformations
= clone( this._
.transformations
);
829 ret
.disabled
= this.disabled
;
830 ret
.editor
= this.editor
;
836 * Destroys the filter instance and removes it from the global {@link CKEDITOR.filter#instances} object.
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.
845 delete this.allowedContent
;
846 delete this.disallowedContent
;
850 function addAndOptimizeRules( that
, newRules
, featureName
, standardizedRules
, optimizedRules
) {
852 rulesToOptimize
= [];
854 for ( groupName
in newRules
) {
855 rule
= newRules
[ groupName
];
857 // { 'p h1': true } => { 'p h1': {} }.
858 if ( typeof rule
== 'boolean' )
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.
867 // If this is not an unnamed rule ({ '$1' => { ... } })
868 // move elements list to property.
869 if ( groupName
.charAt( 0 ) != '$' )
870 rule
.elements
= groupName
;
873 rule
.featureName
= featureName
.toLowerCase();
875 standardizeRule( rule
);
877 // Save rule and remember to optimize it.
878 standardizedRules
.push( rule
);
879 rulesToOptimize
.push( rule
);
882 optimizeRules( optimizedRules
, rulesToOptimize
);
885 // Apply ACR to an 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
) )
895 // If element doesn't have all required styles/attrs/classes
896 // this rule doesn't match it.
897 if ( !skipRequired
&& !hasAllRequired( rule
, element
) )
900 // If this rule doesn't validate properties only mark element as valid.
901 if ( !rule
.propertiesOnly
)
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
);
908 if ( !status
.allStyles
)
909 status
.allStyles
= applyAllowedRuleToHash( rule
.styles
, element
.styles
, status
.validStyles
);
911 if ( !status
.allClasses
)
912 status
.allClasses
= applyAllowedRuleToArray( rule
.classes
, element
.classes
, status
.validClasses
);
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
) {
922 // True means that all elements of array are accepted (the asterix was used for classes).
923 if ( itemsRule
=== true )
926 for ( var i
= 0, l
= items
.length
, item
; i
< l
; ++i
) {
928 if ( !validItems
[ item
] )
929 validItems
[ item
] = itemsRule( item
);
935 function applyAllowedRuleToHash( itemsRule
, items
, validItems
) {
939 if ( itemsRule
=== true )
942 for ( var name
in items
) {
943 if ( !validItems
[ name
] )
944 validItems
[ name
] = itemsRule( name
);
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
) )
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
)
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
;
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
) {
973 var hadInvalid
= false,
974 allDisallowed
= itemsRule
=== true;
976 for ( var i
= items
.length
; i
--; ) {
977 if ( allDisallowed
|| itemsRule( items
[ i
] ) ) {
978 items
.splice( i
, 1 );
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
) {
992 var hadInvalid
= false,
993 allDisallowed
= itemsRule
=== true;
995 for ( var name
in items
) {
996 if ( allDisallowed
|| itemsRule( name
) ) {
997 delete items
[ name
];
1005 function beforeAddingRule( that
, newRules
, overrideCustom
) {
1006 if ( that
.disabled
)
1009 // Don't override custom user's configuration if not explicitly requested.
1010 if ( that
.customConfig
&& !overrideCustom
)
1016 // Clear cache, because new rules could change results of checks.
1017 that
._
.cachedChecks
= {};
1022 // Convert CKEDITOR.style to filter's rule.
1023 function convertStyleToRules( style
) {
1024 var styleDef
= style
.getDefinition(),
1027 attrs
= styleDef
.attributes
;
1029 rules
[ styleDef
.element
] = rule
= {
1030 styles: styleDef
.styles
,
1031 requiredStyles: styleDef
.styles
&& CKEDITOR
.tools
.objectKeys( styleDef
.styles
)
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
);
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
) {
1054 if ( validator
=== true )
1057 if ( typeof validator
== 'string' ) {
1058 validator
= trim( validator
);
1059 if ( validator
== '*' )
1062 return CKEDITOR
.tools
.convertArrayToObject( validator
.split( delimiter
) );
1064 else if ( CKEDITOR
.tools
.isArray( validator
) ) {
1065 if ( validator
.length
)
1066 return CKEDITOR
.tools
.convertArrayToObject( validator
);
1075 for ( var i
in validator
) {
1076 obj
[ i
] = validator
[ i
];
1080 return len
? obj : false;
1084 function executeElementCallbacks( element
, callbacks
) {
1085 for ( var i
= 0, l
= callbacks
.length
, retVal
; i
< l
; ++i
) {
1086 if ( ( retVal
= callbacks
[ i
]( element
) ) )
1091 // Extract required properties from "required" validator and "all" properties.
1092 // Remove exclamation marks from "all" properties.
1095 // requiredClasses = { cl1: true }
1096 // (all) classes = { cl1: true, cl2: true, '!cl3': true }
1099 // returned = { cl1: true, cl3: true }
1100 // all = { cl1: true, cl2: true, cl3: true }
1102 // This function returns false if nothing is required.
1103 function extractRequired( required
, all
) {
1114 if ( i
.charAt( 0 ) == '!' ) {
1117 required
[ i
] = true;
1122 while ( ( i
= unbang
.pop() ) ) {
1123 all
[ i
] = all
[ '!' + i
];
1124 delete all
[ '!' + i
];
1127 return empty
? false : required
;
1130 // Does the actual filtering by appling allowed content rules
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
,
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
,
1145 // Whether any of rules accepted element.
1146 // If not - it will be stripped.
1148 // Objects containing accepted attributes, classes and styles.
1149 validAttributes: {},
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,
1158 // Whether element had (before applying DACRs) at least one invalid attribute/class/style.
1159 hadInvalidAttribute: false,
1160 hadInvalidClass: false,
1161 hadInvalidStyle: false
1165 // Early return - if there are no rules for this element (specific or generic), remove it.
1166 if ( !allowedRules
&& !genericAllowedRules
)
1169 // Could not be done yet if there were no transformations and if this
1170 // is real (not mocked) object.
1171 populateProperties( element
);
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 )
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
);
1189 if ( allowedRules
) {
1190 for ( i
= 0, l
= allowedRules
.length
; i
< l
; ++i
)
1191 applyAllowedRule( allowedRules
[ i
], element
, status
, skipRequired
);
1194 if ( genericAllowedRules
) {
1195 for ( i
= 0, l
= genericAllowedRules
.length
; i
< l
; ++i
)
1196 applyAllowedRule( genericAllowedRules
[ i
], element
, status
, skipRequired
);
1202 // Check whether element has all properties (styles,classes,attrs) required by a rule.
1203 function hasAllRequired( rule
, element
) {
1204 if ( rule
.nothingRequired
)
1207 var i
, req
, reqs
, existing
;
1209 if ( ( reqs
= rule
.requiredClasses
) ) {
1210 existing
= element
.classes
;
1211 for ( i
= 0; i
< reqs
.length
; ++i
) {
1213 if ( typeof req
== 'string' ) {
1214 if ( CKEDITOR
.tools
.indexOf( existing
, req
) == -1 )
1217 // This means regexp.
1219 if ( !CKEDITOR
.tools
.checkIfAnyArrayItemMatches( existing
, req
) )
1225 return hasAllRequiredInHash( element
.styles
, rule
.requiredStyles
) &&
1226 hasAllRequiredInHash( element
.attributes
, rule
.requiredAttributes
);
1229 // Check whether all items in required (array) exist in existing (object).
1230 function hasAllRequiredInHash( existing
, required
) {
1234 for ( var i
= 0, req
; i
< required
.length
; ++i
) {
1235 req
= required
[ i
];
1236 if ( typeof req
== 'string' ) {
1237 if ( !( req
in existing
) )
1240 // This means regexp.
1242 if ( !CKEDITOR
.tools
.checkIfAnyObjectPropertyMatches( existing
, req
) )
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
;
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
= [];
1263 if ( classes
.length
)
1264 element
.attributes
[ 'class' ] = classes
.join( ' ' );
1266 element
.attributes
.style
= CKEDITOR
.tools
.writeCssText( element
.styles
);
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
|| {};
1278 if ( styles
&& !CKEDITOR
.tools
.isEmpty( styles
) ) {
1279 styles
= copy( styles
);
1280 attrs
.style
= CKEDITOR
.tools
.writeCssText( styles
, true );
1286 name: styleDef
.element
,
1288 classes: attrs
[ 'class' ] ? attrs
[ 'class' ].split( /\s+/ ) : [],
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.
1302 var keys
= str
.split( /\s*,\s*/ ).sort(),
1305 while ( keys
.length
)
1306 obj
[ keys
.shift() ] = TEST_VALUE
;
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.
1316 // @returns An array of strings and regexps.
1317 function optimizeRequiredProperties( requiredProperties
) {
1319 for ( var propertyName
in requiredProperties
) {
1320 if ( propertyName
.indexOf( '*' ) > -1 )
1321 arr
.push( new RegExp( '^' + propertyName
.replace( /\*/g, '.*' ) + '$' ) );
1323 arr
.push( propertyName
);
1328 var validators
= { styles: 1, attributes: 1, classes: 1 },
1329 validatorsRequired
= {
1330 styles: 'requiredStyles',
1331 attributes: 'requiredAttributes',
1332 classes: 'requiredClasses'
1335 // Optimize a rule by replacing validators with functions
1336 // and rewriting requiredXXX validators to arrays.
1337 function optimizeRule( rule
) {
1342 for ( validatorName
in validators
)
1343 rule
[ validatorName
] = validatorFunction( rule
[ validatorName
] );
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;
1357 rule
.nothingRequired
= nothingRequired
;
1358 rule
.noProperties
= !( rule
.attributes
|| rule
.classes
|| rule
.styles
);
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
;
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
);
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
);
1380 // If elements list was explicitly defined,
1381 // add this rule for every defined element.
1383 // We don't need elements validator for this kind of rule.
1384 var elements
= rule
.elements
;
1385 delete rule
.elements
;
1387 for ( element
in elements
) {
1388 if ( !elementsRules
[ element
] )
1389 elementsRules
[ element
] = [ rule
];
1391 elementsRules
[ element
][ priority
? 'unshift' : 'push' ]( rule
);
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,
1400 styles: /{([^}]+)}/,
1401 attrs: /\[([^\]]+)\]/,
1402 classes: /\(([^\)]+)\)/
1405 function parseRulesString( input
) {
1407 props
, styles
, attrs
, classes
,
1411 input
= trim( input
);
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' );
1419 styles
= attrs
= classes
= null;
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 ],
1431 // Move to the next group.
1432 input
= input
.slice( match
[ 0 ].length
);
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;
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' ];
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+/ ) : [];
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\}/, '' ) ),
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.
1479 protectedFrag
= CKEDITOR
.htmlParser
.fragment
.fromHtml( source
);
1481 if ( protectedFrag
.children
.length
== 1 && ( node
= protectedFrag
.children
[ 0 ] ).type
== CKEDITOR
.NODE_ELEMENT
)
1482 processElement( that
, node
, toBeRemoved
, filterOpts
);
1484 // If protected element has been marked to be removed, return 'false' - comment was rejected.
1485 return !toBeRemoved
.length
;
1488 var unprotectElementsNamesRegexp
= /^cke:(object|embed|param)$/,
1489 protectElementsNamesRegexp
= /^(object|embed|param)$/;
1491 // The actual function which filters, transforms and does other funny things with an element.
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
) {
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).
1514 element
.name
= element
.name
.replace( unprotectElementsNamesRegexp
, '$1' );
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
;
1523 // If transformations are set apply all groups.
1524 if ( opts
.doTransform
)
1525 transformElement( that
, element
);
1527 if ( opts
.doFilter
) {
1528 // Apply all filters.
1529 status
= filterElement( that
, element
, opts
);
1531 // Handle early return from filterElement.
1533 toBeRemoved
.push( element
);
1534 return FILTER_ELEMENT_MODIFIED
;
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
;
1543 // Update element's attributes based on status of filtering.
1544 if ( updateElement( element
, status
) )
1545 retVal
= FILTER_ELEMENT_MODIFIED
;
1547 if ( !opts
.skipFinalValidation
&& !validateElement( element
) ) {
1548 toBeRemoved
.push( element
);
1549 return FILTER_ELEMENT_MODIFIED
;
1553 // Protect previously unprotected elements.
1555 element
.name
= element
.name
.replace( protectElementsNamesRegexp
, 'cke:$1' );
1560 // Returns a regexp object which can be used to test if a property
1561 // matches one of wildcard validators.
1562 function regexifyPropertiesWithWildcards( validators
) {
1566 for ( i
in validators
) {
1567 if ( i
.indexOf( '*' ) > -1 )
1568 patterns
.push( i
.replace( /\*/g, '.*' ) );
1571 if ( patterns
.length
)
1572 return new RegExp( '^(?:' + patterns
.join( '|' ) + ')$' );
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 );
1582 var delim
= /\s*,\s*/,
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;
1591 rule
.match
= rule
.match
|| null;
1594 // Does the element transformation by applying registered
1595 // transformation rules.
1596 function transformElement( that
, element
) {
1597 var transformations
= that
._
.transformations
[ element
.name
],
1600 if ( !transformations
)
1603 populateProperties( element
);
1605 for ( i
= 0; i
< transformations
.length
; ++i
)
1606 applyTransformationsGroup( that
, element
, transformations
[ i
] );
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
);
1614 // Copy element's styles and classes back to attributes array.
1615 function updateAttributes( element
) {
1616 var attrs
= element
.attributes
,
1619 // Will be recreated later if any of styles/classes exists.
1621 delete attrs
[ 'class' ];
1623 if ( ( styles
= CKEDITOR
.tools
.writeCssText( element
.styles
, true ) ) )
1624 attrs
.style
= styles
;
1626 if ( element
.classes
.length
)
1627 attrs
[ 'class' ] = element
.classes
.sort().join( ' ' );
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
,
1644 internalAttr
= /^data-cke-/,
1647 // Will be recreated later if any of styles/classes were passed.
1649 delete attrs
[ 'class' ];
1651 delete element
.classBackup
;
1652 delete element
.styleBackup
;
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
]
1664 delete attrs
[ name
];
1668 delete attrs
[ name
];
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
] );
1687 if ( stylesArr
.length
)
1688 attrs
.style
= stylesArr
.sort().join( '; ' );
1690 else if ( origStyles
) {
1691 attrs
.style
= origStyles
;
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
] );
1700 if ( classesArr
.length
)
1701 attrs
[ 'class' ] = classesArr
.sort().join( ' ' );
1703 if ( origClasses
&& classesArr
.length
< origClasses
.split( /\s+/ ).length
)
1706 else if ( origClasses
) {
1707 attrs
[ 'class' ] = origClasses
;
1713 function validateElement( element
) {
1714 switch ( element
.name
) {
1716 // Code borrowed from htmlDataProcessor, so ACF does the same clean up.
1717 if ( !( element
.children
.length
|| element
.attributes
.name
|| element
.attributes
.id
) )
1721 if ( !element
.attributes
.src
)
1729 function validatorFunction( validator
) {
1732 if ( validator
=== true )
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
);
1740 return function( value
) {
1741 return value
in validator
|| ( regexp
&& value
.match( regexp
) );
1746 // REMOVE ELEMENT ---------------------------------------------------------
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
];
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
] )
1764 function createBr() {
1765 return new CKEDITOR
.htmlParser
.element( 'br' );
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
];
1774 function isBrOrBlock( node
) {
1775 return node
.type
== CKEDITOR
.NODE_ELEMENT
&&
1776 ( node
.name
== 'br' || DTD
.$block
[ node
.name
] );
1779 // Try to remove element in the best possible way.
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
;
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() );
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
} );
1801 } else if ( DTD
.$block
[ name
] || name
== 'tr' ) {
1802 if ( enterTag
== 'br' )
1803 stripBlockBr( element
, toBeChecked
);
1805 stripBlock( element
, enterTag
, toBeChecked
);
1807 // Special case - elements that may contain CDATA should be removed completely.
1808 else if ( name
in { style: 1, script: 1 } )
1810 // The rest of inline elements. May also be the last resort
1811 // for some special elements.
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();
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
;
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
} );
1837 var parent
= element
.parent
,
1838 shouldAutoP
= parent
.type
== CKEDITOR
.NODE_DOCUMENT_FRAGMENT
|| parent
.name
== 'body',
1839 i
, child
, p
, parentDtd
;
1841 for ( i
= children
.length
; i
> 0; ) {
1842 child
= children
[ --i
];
1844 // If parent requires auto paragraphing and child is inline node,
1845 // insert this child into newly created paragraph.
1846 if ( shouldAutoP
&& inlineNode( child
) ) {
1848 p
= new CKEDITOR
.htmlParser
.element( enterTag
);
1849 p
.insertAfter( element
);
1851 // Check if this p/div was put in correct context.
1852 // If not - strip parent.
1853 toBeChecked
.push( { check: 'parent-down', el: p
} );
1857 // Child which doesn't need to be auto paragraphed.
1860 parentDtd
= DTD
[ parent
.name
] || DTD
.span
;
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
]
1869 toBeChecked
.push( { check: 'el-up', el: child
} );
1873 // All children have been moved to element's parent, so remove it.
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
) {
1885 if ( element
.previous
&& !isBrOrBlock( element
.previous
) ) {
1887 br
.insertBefore( element
);
1890 if ( element
.next
&& !isBrOrBlock( element
.next
) ) {
1892 br
.insertAfter( element
);
1895 element
.replaceWithChildren();
1899 // TRANSFORMATIONS --------------------------------------------------------
1901 var transformationsTools
;
1903 // Apply given transformations group to the element.
1904 function applyTransformationsGroup( filter
, element
, group
) {
1907 for ( i
= 0; i
< group
.length
; ++i
) {
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.
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
;
1931 if ( element
.name
!= def
.element
)
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 )
1943 if ( element
.attributes
[ attrName
] != defAttrs
[ attrName
] )
1948 for ( styleName
in defStyles
) {
1949 if ( element
.styles
[ styleName
] != defStyles
[ styleName
] )
1956 // Return transformation group for content form.
1957 // One content form makes one transformation rule in one group.
1958 function getContentFormTransformationGroup( form
, preferredForm
) {
1961 if ( typeof form
== 'string' )
1963 else if ( form
instanceof CKEDITOR
.style
)
1966 element
= form
[ 0 ];
1973 right: function( el
, tools
) {
1974 tools
.transform( el
, preferredForm
);
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
) {
1983 return rule
.element
;
1985 return check
.match( /^([a-z0-9]+)/i )[ 0 ];
1986 return rule
.left
.getDefinition().element
;
1989 function getMatchStyleFn( style
) {
1990 return function( el
) {
1991 return elementMatchesStyle( el
, style
);
1995 function getTransformationFn( toolName
) {
1996 return function( el
, tools
) {
1997 tools
[ toolName
]( el
);
2001 function optimizeTransformationsGroup( rules
) {
2002 var groupName
, i
, rule
,
2004 optimizedRules
= [];
2006 for ( i
= 0; i
< rules
.length
; ++i
) {
2009 if ( typeof rule
== 'string' ) {
2010 rule
= rule
.split( /\s*:\s*/ );
2020 // Extract element name.
2022 groupName
= getElementNameForTransformation( rule
, check
);
2024 if ( left
instanceof CKEDITOR
.style
)
2025 left
= getMatchStyleFn( left
);
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
,
2033 // Handle shorthand format. E.g.: 'table[width]:sizeToAttribute'.
2034 right: typeof right
== 'string' ? getTransformationFn( right
) : right
2040 rules: optimizedRules
2045 * Singleton containing tools useful for transformation rules.
2047 * @class CKEDITOR.filter.transformationsTools
2050 transformationsTools
= CKEDITOR
.filter
.transformationsTools
= {
2052 * Converts `width` and `height` attributes to styles.
2054 * @param {CKEDITOR.htmlParser.element} element
2056 sizeToStyle: function( element
) {
2057 this.lengthToStyle( element
, 'width' );
2058 this.lengthToStyle( element
, 'height' );
2062 * Converts `width` and `height` styles to attributes.
2064 * @param {CKEDITOR.htmlParser.element} element
2066 sizeToAttribute: function( element
) {
2067 this.lengthToAttribute( element
, 'width' );
2068 this.lengthToAttribute( element
, 'height' );
2072 * Converts length in the `attrName` attribute to a valid CSS length (like `width` or `height`).
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.
2078 lengthToStyle: function( element
, attrName
, styleName
) {
2079 styleName
= styleName
|| attrName
;
2081 if ( !( styleName
in element
.styles
) ) {
2082 var value
= element
.attributes
[ attrName
];
2085 if ( ( /^\d+$/ ).test( value
) )
2088 element
.styles
[ styleName
] = value
;
2092 delete element
.attributes
[ attrName
];
2096 * Converts length in the `styleName` style to a valid length attribute (like `width` or `height`).
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.
2102 lengthToAttribute: function( element
, styleName
, attrName
) {
2103 attrName
= attrName
|| styleName
;
2105 if ( !( attrName
in element
.attributes
) ) {
2106 var value
= element
.styles
[ styleName
],
2107 match
= value
&& value
.match( /^(\d+)(?:\.\d*)?px$/ );
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
;
2116 delete element
.styles
[ styleName
];
2120 * Converts the `align` attribute to the `float` style if not set. The attribute
2121 * is always removed.
2123 * @param {CKEDITOR.htmlParser.element} element
2125 alignmentToStyle: function( element
) {
2126 if ( !( 'float' in element
.styles
) ) {
2127 var value
= element
.attributes
.align
;
2129 if ( value
== 'left' || value
== 'right' )
2130 element
.styles
[ 'float' ] = value
; // Uh... GCC doesn't like the 'float' prop name.
2133 delete element
.attributes
.align
;
2137 * Converts the `float` style to the `align` attribute if not set.
2138 * The style is always removed.
2140 * @param {CKEDITOR.htmlParser.element} element
2142 alignmentToAttribute: function( element
) {
2143 if ( !( 'align' in element
.attributes
) ) {
2144 var value
= element
.styles
[ 'float' ];
2146 if ( value
== 'left' || value
== 'right' )
2147 element
.attributes
.align
= value
;
2150 delete element
.styles
[ 'float' ]; // Uh... GCC doesn't like the 'float' prop name.
2154 * Converts the shorthand form of the `border` style to seperate styles.
2156 * @param {CKEDITOR.htmlParser.element} element
2158 splitBorderShorthand: function( element
) {
2159 if ( !element
.styles
.border
) {
2163 var widths
= element
.styles
.border
.match( /([\.\d]+\w+)/g ) || [ '0px' ];
2164 switch ( widths
.length
) {
2166 element
.styles
[ 'border-width' ] = widths
[0];
2169 mapStyles( [ 0, 1, 0, 1 ] );
2172 mapStyles( [ 0, 1, 2, 1 ] );
2175 mapStyles( [ 0, 1, 2, 3 ] );
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' ];
2184 delete element
.styles
.border
;
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] ];
2194 listTypeToStyle: function( element
) {
2195 if ( element
.attributes
.type
) {
2196 switch ( element
.attributes
.type
) {
2198 element
.styles
[ 'list-style-type' ] = 'lower-alpha';
2201 element
.styles
[ 'list-style-type' ] = 'upper-alpha';
2204 element
.styles
[ 'list-style-type' ] = 'lower-roman';
2207 element
.styles
[ 'list-style-type' ] = 'upper-roman';
2210 element
.styles
[ 'list-style-type' ] = 'decimal';
2213 element
.styles
[ 'list-style-type' ] = element
.attributes
.type
;
2219 * Converts the shorthand form of the `margin` style to seperate styles.
2221 * @param {CKEDITOR.htmlParser.element} element
2223 splitMarginShorthand: function( element
) {
2224 if ( !element
.styles
.margin
) {
2228 var widths
= element
.styles
.margin
.match( /(\-?[\.\d]+\w+)/g ) || [ '0px' ];
2229 switch ( widths
.length
) {
2231 mapStyles( [ 0, 0, 0, 0 ] );
2234 mapStyles( [ 0, 1, 0, 1 ] );
2237 mapStyles( [ 0, 1, 2, 1 ] );
2240 mapStyles( [ 0, 1, 2, 3 ] );
2244 delete element
.styles
.margin
;
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] ];
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.
2259 * @param {CKEDITOR.htmlParser.element} element
2260 * @param {CKEDITOR.style} style
2262 matchesStyle: elementMatchesStyle
,
2265 * Transforms an element to a given form.
2269 * * {@link CKEDITOR.style},
2270 * * string – the new name of the element.
2272 * @param {CKEDITOR.htmlParser.element} el
2273 * @param {CKEDITOR.style/String} form
2275 transform: function( el
, form
) {
2276 if ( typeof form
== 'string' )
2278 // Form is an instance of CKEDITOR.style.
2280 var def
= form
.getDefinition(),
2281 defStyles
= def
.styles
,
2282 defAttrs
= def
.attributes
,
2283 attrName
, styleName
,
2284 existingClassesPattern
, defClasses
, cl
;
2286 el
.name
= def
.element
;
2288 for ( attrName
in defAttrs
) {
2289 if ( attrName
== 'class' ) {
2290 existingClassesPattern
= el
.classes
.join( '|' );
2291 defClasses
= defAttrs
[ attrName
].split( /\s+/ );
2293 while ( ( cl
= defClasses
.pop() ) ) {
2294 if ( existingClassesPattern
.indexOf( cl
) == -1 )
2295 el
.classes
.push( cl
);
2298 el
.attributes
[ attrName
] = defAttrs
[ attrName
];
2303 for ( styleName
in defStyles
) {
2304 el
.styles
[ styleName
] = defStyles
[ styleName
];
2313 * Allowed content rules. This setting is used when
2314 * instantiating {@link CKEDITOR.editor#filter}.
2316 * The following values are accepted:
2318 * * {@link CKEDITOR.filter.allowedContentRules} – defined rules will be added
2319 * to the {@link CKEDITOR.editor#filter}.
2320 * * `true` – 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 – the filter will be configured by loaded features
2323 * (toolbar items, commands, etc.).
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.
2330 * CKEDITOR.replace( 'textarea_id', {
2331 * allowedContent: 'p b i; a[!href]',
2333 * instanceReady: function( evt ) {
2334 * var editor = evt.editor;
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>'
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.
2348 * Read more in the [documentation](#!/guide/dev_acf)
2349 * and see the [SDK sample](http://sdk.ckeditor.com/samples/acf.html).
2352 * @cfg {CKEDITOR.filter.allowedContentRules/Boolean} [allowedContent=null]
2353 * @member CKEDITOR.config
2357 * This option makes it possible to set additional allowed
2358 * content rules for {@link CKEDITOR.editor#filter}.
2360 * It is especially useful in combination with the default
2361 * {@link CKEDITOR.config#allowedContent} value:
2363 * CKEDITOR.replace( 'textarea_id', {
2364 * plugins: 'wysiwygarea,toolbar,format',
2365 * extraAllowedContent: 'b i',
2367 * instanceReady: function( evt ) {
2368 * var editor = evt.editor;
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>'
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.
2384 * @cfg {Object/String} extraAllowedContent
2385 * @member CKEDITOR.config
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).
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}.
2397 * @cfg {CKEDITOR.filter.disallowedContentRules} disallowedContent
2398 * @member CKEDITOR.config
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).
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).
2412 * @event dataFiltered
2413 * @member CKEDITOR.editor
2414 * @param {CKEDITOR.editor} editor This editor instance.
2418 * Virtual class which is the [Allowed Content Rules](#!/guide/dev_allowed_content_rules) formats type.
2420 * Possible formats are:
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 – used mainly for integrating plugins with Advanced Content Filter,
2425 * * an array of the above formats.
2428 * @class CKEDITOR.filter.allowedContentRules
2433 * Virtual class representing the {@link CKEDITOR.filter#disallow} argument and a type of
2434 * the {@link CKEDITOR.config#disallowedContent} option.
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.
2440 * Read more in the [Disallowed Content guide](#!/guide/dev_disallowed_content).
2443 * @class CKEDITOR.filter.disallowedContentRules
2448 * Virtual class representing {@link CKEDITOR.filter#check} argument.
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.
2457 * 'img[src,alt](foo)' // Correct rule.
2458 * 'ol, ul(!foo)' // Incorrect rule. Multiple elements and required
2459 * // properties are not supported.
2462 * @class CKEDITOR.filter.contentRule
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.
2473 * * "Bold" command, button, and keystroke – it does not mean exactly
2474 * `<strong>` or `<b>` but just the ability to create bold text.
2475 * * "Format" drop-down list – it also does not imply any HTML tag.
2476 * * "Link" command, button, and keystroke.
2477 * * "Image" command, button, and dialog window.
2479 * Thus most often a feature is an instance of one of the following classes:
2481 * * {@link CKEDITOR.command}
2482 * * {@link CKEDITOR.ui.button}
2483 * * {@link CKEDITOR.ui.richCombo}
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}.
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}).
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}.
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.
2500 * @class CKEDITOR.feature
2505 * HTML code that can be generated by this feature.
2507 * For example a basic image feature (image button displaying the image dialog window)
2508 * may allow `'img[!src,alt,width,height]'`.
2510 * During the feature activation this value is passed to {@link CKEDITOR.filter#allow}.
2512 * @property {CKEDITOR.filter.allowedContentRules} [allowedContent=null]
2516 * Minimal HTML code that this feature must be allowed to
2517 * generate in order to work.
2519 * For example a basic image feature (image button displaying the image dialog window)
2520 * needs `'img[src,alt]'` in order to be activated.
2522 * During the feature validation this value is passed to {@link CKEDITOR.filter#check}.
2524 * If this value is not provided, a feature will be always activated.
2526 * @property {CKEDITOR.filter.contentRule} [requiredContent=null]
2530 * The name of the feature.
2532 * It is used for example to identify which {@link CKEDITOR.filter#allowedContent}
2533 * rule was added for which feature.
2535 * @property {String} name
2539 * Feature content forms to be registered in the {@link CKEDITOR.editor#filter}
2540 * during the feature activation.
2542 * See {@link CKEDITOR.filter#addContentForms} for more details.
2544 * @property [contentForms=null]
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.
2551 * See {@link CKEDITOR.filter#addTransformations} for more details.
2553 * @property [contentTransformations=null]
2557 * Returns a feature that this feature needs to register.
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}.
2563 * This method is executed when a feature is passed to the {@link CKEDITOR.editor#addFeature}.
2566 * @returns {CKEDITOR.feature}