2
* @license Copyright (c) 2003-2014, CKSource - Frederico Knabben. All rights reserved.
3
* For licensing, see LICENSE.md or http://ckeditor.com/license
9
var DTD = CKEDITOR.dtd,
10
copy = CKEDITOR.tools.copy,
11
trim = CKEDITOR.tools.trim,
12
TEST_VALUE = 'cke-test',
13
enterModeTags = [ '', 'p', 'br', 'div' ];
16
* Highly configurable class which implements input data filtering mechanisms
17
* and core functions used for the activation of editor features.
19
* A filter instance is always available under the {@link CKEDITOR.editor#filter}
20
* property and is used by the editor in its core features like filtering input data,
21
* applying data transformations, validating whether a feature may be enabled for
22
* the current setup. It may be configured in two ways:
24
* * By the user, with the {@link CKEDITOR.config#allowedContent} setting.
25
* * Automatically, by loaded features (toolbar items, commands, etc.).
27
* In both cases additional allowed content rules may be added by
28
* setting the {@link CKEDITOR.config#extraAllowedContent}
29
* configuration option.
31
* **Note**: Filter rules will be extended with the following elements
32
* depending on the {@link CKEDITOR.config#enterMode} and
33
* {@link CKEDITOR.config#shiftEnterMode} settings:
35
* * `'p'` – for {@link CKEDITOR#ENTER_P},
36
* * `'div'` – for {@link CKEDITOR#ENTER_DIV},
37
* * `'br'` – for {@link CKEDITOR#ENTER_BR}.
39
* **Read more** about the Advanced Content Filter in [guides](#!/guide/dev_advanced_content_filter).
41
* Filter may also be used as a standalone instance by passing
42
* {@link CKEDITOR.filter.allowedContentRules} instead of {@link CKEDITOR.editor}
45
* var filter = new CKEDITOR.filter( 'b' );
47
* filter.check( 'b' ); // -> true
48
* filter.check( 'i' ); // -> false
49
* filter.allow( 'i' );
50
* filter.check( 'i' ); // -> true
54
* @constructor Creates a filter class instance.
55
* @param {CKEDITOR.editor/CKEDITOR.filter.allowedContentRules} editorOrRules
57
CKEDITOR.filter = function( editorOrRules ) {
59
* Whether custom {@link CKEDITOR.config#allowedContent} was set.
61
* This property does not apply to the standalone filter.
64
* @property {Boolean} customConfig
68
* Array of rules added by the {@link #allow} method (including those
69
* loaded from {@link CKEDITOR.config#allowedContent} and
70
* {@link CKEDITOR.config#extraAllowedContent}).
72
* Rules in this array are in unified allowed content rules format.
74
* This property is useful for debugging issues with rules string parsing
75
* or for checking which rules were automatically added by editor features.
79
this.allowedContent = [];
82
* Whether the filter is disabled.
84
* To disable the filter, set {@link CKEDITOR.config#allowedContent} to `true`
85
* or use the {@link #disable} method.
89
this.disabled = false;
92
* Editor instance if not a standalone filter.
95
* @property {CKEDITOR.editor} [=null]
100
* Filter's unique id. It can be used to find filter instance in
101
* {@link CKEDITOR.filter#instances CKEDITOR.filter.instance} object.
105
* @property {Number} id
107
this.id = CKEDITOR.tools.getNextNumber();
110
// Optimized allowed content rules.
112
// Object: element name => array of transformations groups.
117
// Register filter instance.
118
CKEDITOR.filter.instances[ this.id ] = this;
120
if ( editorOrRules instanceof CKEDITOR.editor ) {
121
var editor = this.editor = editorOrRules;
122
this.customConfig = true;
124
var allowedContent = editor.config.allowedContent;
126
// Disable filter completely by setting config.allowedContent = true.
127
if ( allowedContent === true ) {
128
this.disabled = true;
132
if ( !allowedContent )
133
this.customConfig = false;
135
this.allow( allowedContent, 'config', 1 );
136
this.allow( editor.config.extraAllowedContent, 'extra', 1 );
138
// Enter modes should extend filter rules (ENTER_P adds 'p' rule, etc.).
139
this.allow( enterModeTags[ editor.enterMode ] + ' ' + enterModeTags[ editor.shiftEnterMode ], 'default', 1 );
141
// Rules object passed in editorOrRules argument - initialize standalone filter.
143
this.customConfig = false;
144
this.allow( editorOrRules, 'default', 1 );
149
* Object containing all filter instances stored under their
150
* {@link #id} properties.
152
* var filter = new CKEDITOR.filter( 'p' );
153
* filter === CKEDITOR.filter.instances[ filter.id ];
157
* @property instances
159
CKEDITOR.filter.instances = {};
161
CKEDITOR.filter.prototype = {
163
* Adds allowed content rules to the filter.
165
* Read about rules formats in [Allowed Content Rules guide](#!/guide/dev_allowed_content_rules).
167
* // Add a basic rule for custom image feature (e.g. 'MyImage' button).
168
* editor.filter.allow( 'img[!src,alt]', 'MyImage' );
170
* // Add rules for two header styles allowed by 'HeadersCombo'.
171
* var header1Style = new CKEDITOR.style( { element: 'h1' } ),
172
* header2Style = new CKEDITOR.style( { element: 'h2' } );
173
* editor.filter.allow( [ header1Style, header2Style ], 'HeadersCombo' );
175
* @param {CKEDITOR.filter.allowedContentRules} newRules Rule(s) to be added.
176
* @param {String} [featureName] Name of a feature that allows this content (most often plugin/button/command name).
177
* @param {Boolean} [overrideCustom] By default this method will reject any rules
178
* if {@link CKEDITOR.config#allowedContent} is defined to avoid overriding it.
179
* Pass `true` to force rules addition.
180
* @returns {Boolean} Whether the rules were accepted.
182
allow: function( newRules, featureName, overrideCustom ) {
186
// Don't override custom user's configuration if not explicitly requested.
187
if ( this.customConfig && !overrideCustom )
193
// Clear cache, because new rules could change results of checks.
194
this._.cachedChecks = {};
198
if ( typeof newRules == 'string' )
199
newRules = parseRulesString( newRules );
200
else if ( newRules instanceof CKEDITOR.style )
201
newRules = convertStyleToRules( newRules );
202
else if ( CKEDITOR.tools.isArray( newRules ) ) {
203
for ( i = 0; i < newRules.length; ++i )
204
ret = this.allow( newRules[ i ], featureName, overrideCustom );
205
return ret; // Return last status.
209
rulesToOptimize = [];
211
for ( groupName in newRules ) {
212
rule = newRules[ groupName ];
214
// { 'p h1': true } => { 'p h1': {} }.
215
if ( typeof rule == 'boolean' )
217
// { 'p h1': func } => { 'p h1': { match: func } }.
218
else if ( typeof rule == 'function' )
219
rule = { match: rule };
220
// Clone (shallow) rule, because we'll modify it later.
224
// If this is not an unnamed rule ({ '$1' => { ... } })
225
// move elements list to property.
226
if ( groupName.charAt( 0 ) != '$' )
227
rule.elements = groupName;
230
rule.featureName = featureName.toLowerCase();
232
standardizeRule( rule );
234
// Save rule and remember to optimize it.
235
this.allowedContent.push( rule );
236
rulesToOptimize.push( rule );
239
optimizeRules( this._.rules, rulesToOptimize );
245
* Applies this filter to passed {@link CKEDITOR.htmlParser.fragment} or {@link CKEDITOR.htmlParser.element}.
246
* The result of filtering is a DOM tree without disallowed content.
248
* // Create standalone filter passing 'p' and 'b' elements.
249
* var filter = new CKEDITOR.filter( 'p b' ),
250
* // Parse HTML string to pseudo DOM structure.
251
* fragment = CKEDITOR.htmlParser.fragment.fromHtml( '<p><b>foo</b> <i>bar</i></p>' ),
252
* writer = new CKEDITOR.htmlParser.basicWriter();
254
* filter.applyTo( fragment );
255
* fragment.writeHtml( writer );
256
* writer.getHtml(); // -> '<p><b>foo</b> bar</p>'
258
* @param {CKEDITOR.htmlParser.fragment/CKEDITOR.htmlParser.element} fragment Node to be filtered.
259
* @param {Boolean} [toHtml] Set to `true` if the filter is used together with {@link CKEDITOR.htmlDataProcessor#toHtml}.
260
* @param {Boolean} [transformOnly] If set to `true` only transformations will be applied. Content
261
* will not be filtered with allowed content rules.
262
* @param {Number} [enterMode] Enter mode used by the filter when deciding how to strip disallowed element.
263
* Defaults to {@link CKEDITOR.editor#activeEnterMode} for a editor's filter or to {@link CKEDITOR#ENTER_P} for standalone filter.
264
* @returns {Boolean} Whether some part of the `fragment` was removed by the filter.
266
applyTo: function( fragment, toHtml, transformOnly, enterMode ) {
270
var toBeRemoved = [],
271
rules = !transformOnly && this._.rules,
272
transformations = this._.transformations,
273
filterFn = getFilterFunction( this ),
274
protectedRegexs = this.editor && this.editor.config.protectedSource,
277
// Filter all children, skip root (fragment or editable-like wrapper used by data processor).
278
fragment.forEach( function( el ) {
279
if ( el.type == CKEDITOR.NODE_ELEMENT ) {
280
// Do not filter element with data-cke-filter="off" and all their descendants.
281
if ( el.attributes[ 'data-cke-filter' ] == 'off' )
284
// (#10260) Don't touch elements like spans with data-cke-* attribute since they're
285
// responsible e.g. for placing markers, bookmarks, odds and stuff.
286
// We love 'em and we don't wanna lose anything during the filtering.
287
// '|' is to avoid tricky joints like data-="foo" + cke-="bar". Yes, they're possible.
289
// NOTE: data-cke-* assigned elements are preserved only when filter is used with
290
// htmlDataProcessor.toHtml because we don't want to protect them when outputting data
292
if ( toHtml && el.name == 'span' && ~CKEDITOR.tools.objectKeys( el.attributes ).join( '|' ).indexOf( 'data-cke-' ) )
295
if ( filterFn( el, rules, transformations, toBeRemoved, toHtml ) )
298
else if ( el.type == CKEDITOR.NODE_COMMENT && el.value.match( /^\{cke_protected\}(?!\{C\})/ ) ) {
299
if ( !filterProtectedElement( el, protectedRegexs, filterFn, rules, transformations, toHtml ) )
300
toBeRemoved.push( el );
304
if ( toBeRemoved.length )
307
var node, element, check,
309
enterTag = enterModeTags[ enterMode || ( this.editor ? this.editor.enterMode : CKEDITOR.ENTER_P ) ];
311
// Remove elements in reverse order - from leaves to root, to avoid conflicts.
312
while ( ( node = toBeRemoved.pop() ) ) {
313
if ( node.type == CKEDITOR.NODE_ELEMENT )
314
removeElement( node, enterTag, toBeChecked );
315
// This is a comment securing rejected element - remove it completely.
320
// Check elements that have been marked as possibly invalid.
321
while ( ( check = toBeChecked.pop() ) ) {
323
// Element has been already removed.
324
if ( !element.parent )
327
switch ( check.check ) {
328
// Check if element itself is correct.
330
// Check if element included in $removeEmpty has no children.
331
if ( DTD.$removeEmpty[ element.name ] && !element.children.length )
332
removeElement( element, enterTag, toBeChecked );
333
// Check if that is invalid element.
334
else if ( !validateElement( element ) )
335
removeElement( element, enterTag, toBeChecked );
338
// Check if element is in correct context. If not - remove element.
340
// Check if e.g. li is a child of body after ul has been removed.
341
if ( element.parent.type != CKEDITOR.NODE_DOCUMENT_FRAGMENT &&
342
!DTD[ element.parent.name ][ element.name ]
344
removeElement( element, enterTag, toBeChecked );
347
// Check if element is in correct context. If not - remove parent.
349
if ( element.parent.type != CKEDITOR.NODE_DOCUMENT_FRAGMENT &&
350
!DTD[ element.parent.name ][ element.name ]
352
removeElement( element.parent, enterTag, toBeChecked );
361
* Checks whether a {@link CKEDITOR.feature} can be enabled. Unlike {@link #addFeature},
362
* this method always checks the feature, even when the default configuration
363
* for {@link CKEDITOR.config#allowedContent} is used.
367
* @param {CKEDITOR.feature} feature The feature to be tested.
368
* @returns {Boolean} Whether this feature can be enabled.
370
checkFeature: function( feature ) {
377
// Some features may want to register other features.
378
// E.g. a button may return a command bound to it.
379
if ( feature.toFeature )
380
feature = feature.toFeature( this.editor );
382
return !feature.requiredContent || this.check( feature.requiredContent );
386
* Disables Advanced Content Filter.
388
* This method is meant to be used by plugins which are not
389
* compatible with the filter and in other cases in which the filter
390
* has to be disabled during the initialization phase or runtime.
392
* In other cases the filter can be disabled by setting
393
* {@link CKEDITOR.config#allowedContent} to `true`.
395
disable: function() {
396
this.disabled = true;
400
* Adds an array of {@link CKEDITOR.feature} content forms. All forms
401
* will then be transformed to the first form which is allowed by the filter.
403
* editor.filter.allow( 'i; span{!font-style}' );
404
* editor.filter.addContentForms( [
407
* [ 'span', function( el ) {
408
* return el.styles[ 'font-style' ] == 'italic';
411
* // Now <em> and <span style="font-style:italic"> will be replaced with <i>
412
* // because this is the first allowed form.
413
* // <span> is allowed too, but it is the last form and
414
* // additionaly, the editor cannot transform an element based on
415
* // the array+function form).
417
* This method is used by the editor to register {@link CKEDITOR.feature#contentForms}
418
* when adding a feature with {@link #addFeature} or {@link CKEDITOR.editor#addFeature}.
420
* @param {Array} forms The content forms of a feature.
422
addContentForms: function( forms ) {
433
// First, find preferred form - this is, first allowed.
434
for ( i = 0; i < forms.length && !preferredForm; ++i ) {
437
// Check only strings and styles - array format isn't supported by #check().
438
if ( ( typeof form == 'string' || form instanceof CKEDITOR.style ) && this.check( form ) )
439
preferredForm = form;
442
// This feature doesn't have preferredForm, so ignore it.
443
if ( !preferredForm )
446
for ( i = 0; i < forms.length; ++i )
447
transfGroups.push( getContentFormTransformationGroup( forms[ i ], preferredForm ) );
449
this.addTransformations( transfGroups );
453
* Checks whether a feature can be enabled for the HTML restrictions in place
454
* for the current CKEditor instance, based on the HTML code the feature might
455
* generate and the minimal HTML code the feature needs to be able to generate.
459
* @param {CKEDITOR.feature} feature
460
* @returns {Boolean} Whether this feature can be enabled.
462
addFeature: function( feature ) {
469
// Some features may want to register other features.
470
// E.g. a button may return a command bound to it.
471
if ( feature.toFeature )
472
feature = feature.toFeature( this.editor );
474
// If default configuration (will be checked inside #allow()),
475
// then add allowed content rules.
476
this.allow( feature.allowedContent, feature.name );
478
this.addTransformations( feature.contentTransformations );
479
this.addContentForms( feature.contentForms );
481
// If custom configuration, then check if required content is allowed.
482
if ( this.customConfig && feature.requiredContent )
483
return this.check( feature.requiredContent );
489
* Adds an array of content transformation groups. One group
490
* may contain many transformation rules, but only the first
491
* matching rule in a group is executed.
493
* A single transformation rule is an object with four properties:
495
* * `check` (optional) – if set and {@link CKEDITOR.filter} does
496
* not accept this {@link CKEDITOR.filter.contentRule}, this transformation rule
497
* will not be executed (it does not *match*). This value is passed
499
* * `element` (optional) – this string property tells the filter on which
500
* element this transformation can be run. It is optional, because
501
* the element name can be obtained from `check` (if it is a String format)
502
* or `left` (if it is a {@link CKEDITOR.style} instance).
503
* * `left` (optional) – a function accepting an element or a {@link CKEDITOR.style}
504
* instance verifying whether the transformation should be
505
* executed on this specific element. If it returns `false` or if an element
506
* does not match this style, this transformation rule does not *match*.
507
* * `right` – a function accepting an element and {@link CKEDITOR.filter.transformationsTools}
508
* or a string containing the name of the {@link CKEDITOR.filter.transformationsTools} method
509
* that should be called on an element.
511
* A shorthand format is also available. A transformation rule can be defined by
512
* a single string `'check:right'`. The string before `':'` will be used as
513
* the `check` property and the second part as the `right` property.
515
* Transformation rules can be grouped. The filter will try to apply
516
* the first rule in a group. If it *matches*, the filter will ignore subsequent rules and
517
* will move to the next group. If it does not *match*, the next rule will be checked.
521
* editor.filter.addTransformations( [
524
* // First rule. If table{width} is allowed, it
525
* // executes {@link CKEDITOR.filter.transformationsTools#sizeToStyle} on a table element.
526
* 'table{width}: sizeToStyle',
527
* // Second rule should not be executed if the first was.
528
* 'table[width]: sizeToAttribute'
532
* // This rule will add the foo="1" attribute to all images that
536
* left: function( el ) {
537
* return !el.attributes.foo;
539
* right: function( el, tools ) {
540
* el.attributes.foo = '1';
547
* // config.allowedContent = 'table{height,width}; tr td'.
549
* // '<table style="height:100px; width:200px">...</table>' -> '<table style="height:100px; width:200px">...</table>'
550
* // '<table height="100" width="200">...</table>' -> '<table style="height:100px; width:200px">...</table>'
553
* // config.allowedContent = 'table[height,width]; tr td'.
555
* // '<table style="height:100px; width:200px">...</table>' -> '<table height="100" width="200">...</table>'
556
* // '<table height="100" width="200">...</table>' -> '<table height="100" width="200"">...</table>'
559
* // config.allowedContent = 'table{width,height}[height,width]; tr td'.
561
* // '<table style="height:100px; width:200px">...</table>' -> '<table style="height:100px; width:200px">...</table>'
562
* // '<table height="100" width="200">...</table>' -> '<table style="height:100px; width:200px">...</table>'
564
* // Note: Both forms are allowed (size set by style and by attributes), but only
565
* // the first transformation is applied — the size is always transformed to a style.
566
* // This is because only the first transformation matching allowed content rules is applied.
568
* This method is used by the editor to add {@link CKEDITOR.feature#contentTransformations}
569
* when adding a feature by {@link #addFeature} or {@link CKEDITOR.editor#addFeature}.
571
* @param {Array} transformations
573
addTransformations: function( transformations ) {
577
if ( !transformations )
580
var optimized = this._.transformations,
583
for ( i = 0; i < transformations.length; ++i ) {
584
group = optimizeTransformationsGroup( transformations[ i ] );
586
if ( !optimized[ group.name ] )
587
optimized[ group.name ] = [];
589
optimized[ group.name ].push( group.rules );
594
* Checks whether the content defined in the `test` argument is allowed
597
* If `strictCheck` is set to `false` (default value), this method checks
598
* if all parts of the `test` (styles, attributes, and classes) are
599
* accepted by the filter. If `strictCheck` is set to `true`, the test
600
* must also contain the required attributes, styles, and classes.
604
* // Rule: 'img[!src,alt]'.
605
* filter.check( 'img[alt]' ); // -> true
606
* filter.check( 'img[alt]', true, true ); // -> false
608
* Second `check()` call returned `false` because `src` is required.
610
* **Note:** The `test` argument is of {@link CKEDITOR.filter.contentRule} type, which is
611
* a limited version of {@link CKEDITOR.filter.allowedContentRules}. Read more about it
612
* in the {@link CKEDITOR.filter.contentRule}'s documentation.
614
* @param {CKEDITOR.filter.contentRule} test
615
* @param {Boolean} [applyTransformations=true] Whether to use registered transformations.
616
* @param {Boolean} [strictCheck] Whether the filter should check if an element with exactly
617
* these properties is allowed.
618
* @returns {Boolean} Returns `true` if the content is allowed.
620
check: function( test, applyTransformations, strictCheck ) {
624
// If rules are an array, expand it and return the logical OR value of
626
if ( CKEDITOR.tools.isArray( test ) ) {
627
for ( var i = test.length ; i-- ; ) {
628
if ( this.check( test[ i ], applyTransformations, strictCheck ) )
634
var element, result, cacheKey;
636
if ( typeof test == 'string' ) {
637
cacheKey = test + '<' + ( applyTransformations === false ? '0' : '1' ) + ( strictCheck ? '1' : '0' ) + '>';
639
// Check if result of this check hasn't been already cached.
640
if ( cacheKey in this._.cachedChecks )
641
return this._.cachedChecks[ cacheKey ];
643
// Create test element from string.
644
element = mockElementFromString( test );
646
// Create test element from CKEDITOR.style.
647
element = mockElementFromStyle( test );
650
var clone = CKEDITOR.tools.clone( element ),
654
// Apply transformations to original element.
655
// Transformations will be applied to clone by the filter function.
656
if ( applyTransformations !== false && ( transformations = this._.transformations[ element.name ] ) ) {
657
for ( i = 0; i < transformations.length; ++i )
658
applyTransformationsGroup( this, element, transformations[ i ] );
660
// Transformations could modify styles or classes, so they need to be copied
661
// to attributes object.
662
updateAttributes( element );
665
// Filter clone of mocked element.
666
// Do not run transformations.
667
getFilterFunction( this )( clone, this._.rules, applyTransformations === false ? false : this._.transformations, toBeRemoved, false, !strictCheck, !strictCheck );
669
// Element has been marked for removal.
670
if ( toBeRemoved.length > 0 )
672
// Compare only left to right, because clone may be only trimmed version of original element.
673
else if ( !CKEDITOR.tools.objectCompare( element.attributes, clone.attributes, true ) )
678
// Cache result of this test - we can build cache only for string tests.
679
if ( typeof test == 'string' )
680
this._.cachedChecks[ cacheKey ] = result;
686
* Returns first enter mode allowed by this filter rules. Modes are checked in `p`, `div`, `br` order.
687
* If none of tags is allowed this method will return {@link CKEDITOR#ENTER_BR}.
690
* @param {Number} defaultMode The default mode which will be checked as the first one.
691
* @param {Boolean} [reverse] Whether to check modes in reverse order (used for shift enter mode).
692
* @returns {Number} Allowed enter mode.
694
getAllowedEnterMode: ( function() {
695
var tagsToCheck = [ 'p', 'div', 'br' ],
698
div: CKEDITOR.ENTER_DIV,
699
br: CKEDITOR.ENTER_BR
702
return function( defaultMode, reverse ) {
703
// Clone the array first.
704
var tags = tagsToCheck.slice(),
707
// Check the default mode first.
708
if ( this.check( enterModeTags[ defaultMode ] ) )
711
// If not reverse order, reverse array so we can pop() from it.
713
tags = tags.reverse();
715
while ( ( tag = tags.pop() ) ) {
716
if ( this.check( tag ) )
717
return enterModes[ tag ];
720
return CKEDITOR.ENTER_BR;
725
// Apply ACR to an element
728
// @param status Object containing status of element's filtering.
729
// @param {Boolean} isSpecific True if this is specific element's rule, false if generic.
730
// @param {Boolean} skipRequired If true don't check if element has all required properties.
731
function applyRule( rule, element, status, isSpecific, skipRequired ) {
732
var name = element.name;
734
// This generic rule doesn't apply to this element - skip it.
735
if ( !isSpecific && typeof rule.elements == 'function' && !rule.elements( name ) )
738
// This rule doesn't match this element - skip it.
740
if ( !rule.match( element ) )
744
// If element doesn't have all required styles/attrs/classes
745
// this rule doesn't match it.
746
if ( !skipRequired && !hasAllRequired( rule, element ) )
749
// If this rule doesn't validate properties only mark element as valid.
750
if ( !rule.propertiesOnly )
753
// Apply rule only when all attrs/styles/classes haven't been marked as valid.
754
if ( !status.allAttributes )
755
status.allAttributes = applyRuleToHash( rule.attributes, element.attributes, status.validAttributes );
757
if ( !status.allStyles )
758
status.allStyles = applyRuleToHash( rule.styles, element.styles, status.validStyles );
760
if ( !status.allClasses )
761
status.allClasses = applyRuleToArray( rule.classes, element.classes, status.validClasses );
764
// Apply itemsRule to items (only classes are kept in array).
765
// Push accepted items to validItems array.
766
// Return true when all items are valid.
767
function applyRuleToArray( itemsRule, items, validItems ) {
771
// True means that all elements of array are accepted (the asterix was used for classes).
772
if ( itemsRule === true )
775
for ( var i = 0, l = items.length, item; i < l; ++i ) {
777
if ( !validItems[ item ] )
778
validItems[ item ] = itemsRule( item );
784
function applyRuleToHash( itemsRule, items, validItems ) {
788
if ( itemsRule === true )
791
for ( var name in items ) {
792
if ( !validItems[ name ] )
793
validItems[ name ] = itemsRule( name, items[ name ] );
799
// Convert CKEDITOR.style to filter's rule.
800
function convertStyleToRules( style ) {
801
var styleDef = style.getDefinition(),
804
attrs = styleDef.attributes;
806
rules[ styleDef.element ] = rule = {
807
styles: styleDef.styles,
808
requiredStyles: styleDef.styles && CKEDITOR.tools.objectKeys( styleDef.styles )
812
attrs = copy( attrs );
813
rule.classes = attrs[ 'class' ] ? attrs[ 'class' ].split( /\s+/ ) : null;
814
rule.requiredClasses = rule.classes;
815
delete attrs[ 'class' ];
816
rule.attributes = attrs;
817
rule.requiredAttributes = attrs && CKEDITOR.tools.objectKeys( attrs );
823
// Convert all validator formats (string, array, object, boolean) to hash or boolean:
824
// * true is returned for '*'/true validator,
825
// * false is returned for empty validator (no validator at all (false/null) or e.g. empty array),
826
// * object is returned in other cases.
827
function convertValidatorToHash( validator, delimiter ) {
831
if ( validator === true )
834
if ( typeof validator == 'string' ) {
835
validator = trim( validator );
836
if ( validator == '*' )
839
return CKEDITOR.tools.convertArrayToObject( validator.split( delimiter ) );
841
else if ( CKEDITOR.tools.isArray( validator ) ) {
842
if ( validator.length )
843
return CKEDITOR.tools.convertArrayToObject( validator );
852
for ( var i in validator ) {
853
obj[ i ] = validator[ i ];
857
return len ? obj : false;
861
// Extract required properties from "required" validator and "all" properties.
862
// Remove exclamation marks from "all" properties.
865
// requiredClasses = { cl1: true }
866
// (all) classes = { cl1: true, cl2: true, '!cl3': true }
869
// returned = { cl1: true, cl3: true }
870
// all = { cl1: true, cl2: true, cl3: true }
872
// This function returns false if nothing is required.
873
function extractRequired( required, all ) {
884
if ( i.charAt( 0 ) == '!' ) {
887
required[ i ] = true;
892
while ( ( i = unbang.pop() ) ) {
893
all[ i ] = all[ '!' + i ];
894
delete all[ '!' + i ];
897
return empty ? false : required;
900
// Filter element protected with a comment.
901
// Returns true if protected content is ok, false otherwise.
902
function filterProtectedElement( comment, protectedRegexs, filterFn, rules, transformations, toHtml ) {
903
var source = decodeURIComponent( comment.value.replace( /^\{cke_protected\}/, '' ) ),
908
// Protected element's and protected source's comments look exactly the same.
909
// Check if what we have isn't a protected source instead of protected script/noscript.
910
if ( protectedRegexs ) {
911
for ( i = 0; i < protectedRegexs.length; ++i ) {
912
if ( ( match = source.match( protectedRegexs[ i ] ) ) &&
913
match[ 0 ].length == source.length // Check whether this pattern matches entire source
914
// to avoid '<script>alert("<? 1 ?>")</script>' matching
915
// the PHP's protectedSource regexp.
921
protectedFrag = CKEDITOR.htmlParser.fragment.fromHtml( source );
923
if ( protectedFrag.children.length == 1 && ( node = protectedFrag.children[ 0 ] ).type == CKEDITOR.NODE_ELEMENT )
924
filterFn( node, rules, transformations, toBeRemoved, toHtml );
926
// If protected element has been marked to be removed, return 'false' - comment was rejected.
927
return !toBeRemoved.length;
930
// Returns function that accepts {@link CKEDITOR.htmlParser.element}
931
// and filters it basing on allowed content rules registered by
932
// {@link #allow} method.
934
// @param {CKEDITOR.filter} that
935
function getFilterFunction( that ) {
936
// Return cached function.
937
if ( that._.filterFunction )
938
return that._.filterFunction;
940
var unprotectElementsNamesRegexp = /^cke:(object|embed|param)$/,
941
protectElementsNamesRegexp = /^(object|embed|param)$/;
943
// Return and cache created function.
944
// @param {CKEDITOR.htmlParser.element}
945
// @param [optimizedRules] Rules to be used.
946
// @param [transformations] Transformations to be applied.
947
// @param {Array} toBeRemoved Array into which elements rejected by the filter will be pushed.
948
// @param {Boolean} [toHtml] Set to true if filter used together with htmlDP#toHtml
949
// @param {Boolean} [skipRequired] Whether element's required properties shouldn't be verified.
950
// @param {Boolean} [skipFinalValidation] Whether to not perform final element validation (a,img).
951
// @returns {Boolean} Whether content has been modified.
952
return that._.filterFunction = function( element, optimizedRules, transformations, toBeRemoved, toHtml, skipRequired, skipFinalValidation ) {
953
var name = element.name,
957
// Unprotect elements names previously protected by htmlDataProcessor
958
// (see protectElementNames and protectSelfClosingElements functions).
959
// Note: body, title, etc. are not protected by htmlDataP (or are protected and then unprotected).
961
element.name = name = name.replace( unprotectElementsNamesRegexp, '$1' );
963
// If transformations are set apply all groups.
964
if ( ( transformations = transformations && transformations[ name ] ) ) {
965
populateProperties( element );
967
for ( i = 0; i < transformations.length; ++i )
968
applyTransformationsGroup( that, element, transformations[ i ] );
970
// Do not count on updateElement(), because it:
971
// * may not be called,
972
// * may skip some properties when all are marked as valid.
973
updateAttributes( element );
976
if ( optimizedRules ) {
977
// Name could be changed by transformations.
980
var rules = optimizedRules.elements[ name ],
981
genericRules = optimizedRules.generic,
983
// Whether any of rules accepted element.
984
// If not - it will be stripped.
986
// Objects containing accepted attributes, classes and styles.
990
// Whether all are valid.
991
// If we know that all element's attrs/classes/styles are valid
992
// we can skip their validation, to improve performance.
993
allAttributes: false,
998
// Early return - if there are no rules for this element (specific or generic), remove it.
999
if ( !rules && !genericRules ) {
1000
toBeRemoved.push( element );
1004
// Could not be done yet if there were no transformations and if this
1005
// is real (not mocked) object.
1006
populateProperties( element );
1009
for ( i = 0, l = rules.length; i < l; ++i )
1010
applyRule( rules[ i ], element, status, true, skipRequired );
1013
if ( genericRules ) {
1014
for ( i = 0, l = genericRules.length; i < l; ++i )
1015
applyRule( genericRules[ i ], element, status, false, skipRequired );
1018
// Finally, if after running all filter rules it still hasn't been allowed - remove it.
1019
if ( !status.valid ) {
1020
toBeRemoved.push( element );
1024
// Update element's attributes based on status of filtering.
1025
if ( updateElement( element, status ) )
1028
if ( !skipFinalValidation && !validateElement( element ) ) {
1029
toBeRemoved.push( element );
1034
// Protect previously unprotected elements.
1036
element.name = element.name.replace( protectElementsNamesRegexp, 'cke:$1' );
1042
// Check whether element has all properties (styles,classes,attrs) required by a rule.
1043
function hasAllRequired( rule, element ) {
1044
if ( rule.nothingRequired )
1047
var i, reqs, existing;
1049
if ( ( reqs = rule.requiredClasses ) ) {
1050
existing = element.classes;
1051
for ( i = 0; i < reqs.length; ++i ) {
1052
if ( CKEDITOR.tools.indexOf( existing, reqs[ i ] ) == -1 )
1057
return hasAllRequiredInHash( element.styles, rule.requiredStyles ) &&
1058
hasAllRequiredInHash( element.attributes, rule.requiredAttributes );
1061
// Check whether all items in required (array) exist in existing (object).
1062
function hasAllRequiredInHash( existing, required ) {
1066
for ( var i = 0; i < required.length; ++i ) {
1067
if ( !( required[ i ] in existing ) )
1074
// Create pseudo element that will be passed through filter
1075
// to check if tested string is allowed.
1076
function mockElementFromString( str ) {
1077
var element = parseRulesString( str )[ '$1' ],
1078
styles = element.styles,
1079
classes = element.classes;
1081
element.name = element.elements;
1082
element.classes = classes = ( classes ? classes.split( /\s*,\s*/ ) : [] );
1083
element.styles = mockHash( styles );
1084
element.attributes = mockHash( element.attributes );
1085
element.children = [];
1087
if ( classes.length )
1088
element.attributes[ 'class' ] = classes.join( ' ' );
1090
element.attributes.style = CKEDITOR.tools.writeCssText( element.styles );
1095
// Create pseudo element that will be passed through filter
1096
// to check if tested style is allowed.
1097
function mockElementFromStyle( style ) {
1098
var styleDef = style.getDefinition(),
1099
styles = styleDef.styles,
1100
attrs = styleDef.attributes || {};
1103
styles = copy( styles );
1104
attrs.style = CKEDITOR.tools.writeCssText( styles, true );
1109
name: styleDef.element,
1111
classes: attrs[ 'class' ] ? attrs[ 'class' ].split( /\s+/ ) : [],
1119
// Mock hash based on string.
1120
// 'a,b,c' => { a: 'cke-test', b: 'cke-test', c: 'cke-test' }
1121
// Used to mock styles and attributes objects.
1122
function mockHash( str ) {
1123
// It may be a null or empty string.
1127
var keys = str.split( /\s*,\s*/ ).sort(),
1130
while ( keys.length )
1131
obj[ keys.shift() ] = TEST_VALUE;
1136
var validators = { styles: 1, attributes: 1, classes: 1 },
1137
validatorsRequired = {
1138
styles: 'requiredStyles',
1139
attributes: 'requiredAttributes',
1140
classes: 'requiredClasses'
1143
// Optimize a rule by replacing validators with functions
1144
// and rewriting requiredXXX validators to arrays.
1145
function optimizeRule( rule ) {
1147
for ( i in validators )
1148
rule[ i ] = validatorFunction( rule[ i ] );
1150
var nothingRequired = true;
1151
for ( i in validatorsRequired ) {
1152
i = validatorsRequired[ i ];
1153
rule[ i ] = CKEDITOR.tools.objectKeys( rule[ i ] );
1155
nothingRequired = false;
1158
rule.nothingRequired = nothingRequired;
1161
// Add optimized version of rule to optimizedRules object.
1162
function optimizeRules( optimizedRules, rules ) {
1163
var elementsRules = optimizedRules.elements || {},
1164
genericRules = optimizedRules.generic || [],
1165
i, l, j, rule, element, priority;
1167
for ( i = 0, l = rules.length; i < l; ++i ) {
1168
// Shallow copy. Do not modify original rule.
1169
rule = copy( rules[ i ] );
1170
priority = rule.classes === true || rule.styles === true || rule.attributes === true;
1171
optimizeRule( rule );
1173
// E.g. "*(xxx)[xxx]" - it's a generic rule that
1174
// validates properties only.
1175
// Or '$1': { match: function() {...} }
1176
if ( rule.elements === true || rule.elements === null ) {
1177
rule.elements = validatorFunction( rule.elements );
1178
// Add priority rules at the beginning.
1179
genericRules[ priority ? 'unshift' : 'push' ]( rule );
1181
// If elements list was explicitly defined,
1182
// add this rule for every defined element.
1184
// We don't need elements validator for this kind of rule.
1185
var elements = rule.elements;
1186
delete rule.elements;
1188
for ( element in elements ) {
1189
if ( !elementsRules[ element ] )
1190
elementsRules[ element ] = [ rule ];
1192
elementsRules[ element ][ priority ? 'unshift' : 'push' ]( rule );
1197
optimizedRules.elements = elementsRules;
1198
optimizedRules.generic = genericRules.length ? genericRules : null;
1201
// < elements >< styles, attributes and classes >< separator >
1202
var rulePattern = /^([a-z0-9*\s]+)((?:\s*\{[!\w\-,\s\*]+\}\s*|\s*\[[!\w\-,\s\*]+\]\s*|\s*\([!\w\-,\s\*]+\)\s*){0,3})(?:;\s*|$)/i,
1204
styles: /{([^}]+)}/,
1205
attrs: /\[([^\]]+)\]/,
1206
classes: /\(([^\)]+)\)/
1209
function parseRulesString( input ) {
1211
props, styles, attrs, classes,
1215
input = trim( input );
1217
while ( ( match = input.match( rulePattern ) ) ) {
1218
if ( ( props = match[ 2 ] ) ) {
1219
styles = parseProperties( props, 'styles' );
1220
attrs = parseProperties( props, 'attrs' );
1221
classes = parseProperties( props, 'classes' );
1223
styles = attrs = classes = null;
1225
// Add as an unnamed rule, because there can be two rules
1226
// for one elements set defined in string format.
1227
rules[ '$' + groupNum++ ] = {
1228
elements: match[ 1 ],
1234
// Move to the next group.
1235
input = input.slice( match[ 0 ].length );
1241
// Extract specified properties group (styles, attrs, classes) from
1242
// what stands after the elements list in string format of allowedContent.
1243
function parseProperties( properties, groupName ) {
1244
var group = properties.match( groupsPatterns[ groupName ] );
1245
return group ? trim( group[ 1 ] ) : null;
1248
function populateProperties( element ) {
1249
// Parse classes and styles if that hasn't been done before.
1250
if ( !element.styles )
1251
element.styles = CKEDITOR.tools.parseCssText( element.attributes.style || '', 1 );
1252
if ( !element.classes )
1253
element.classes = element.attributes[ 'class' ] ? element.attributes[ 'class' ].split( /\s+/ ) : [];
1256
// Standardize a rule by converting all validators to hashes.
1257
function standardizeRule( rule ) {
1258
rule.elements = convertValidatorToHash( rule.elements, /\s+/ ) || null;
1259
rule.propertiesOnly = rule.propertiesOnly || ( rule.elements === true );
1261
var delim = /\s*,\s*/,
1264
for ( i in validators ) {
1265
rule[ i ] = convertValidatorToHash( rule[ i ], delim ) || null;
1266
rule[ validatorsRequired[ i ] ] = extractRequired( convertValidatorToHash(
1267
rule[ validatorsRequired[ i ] ], delim ), rule[ i ] ) || null;
1270
rule.match = rule.match || null;
1273
// Copy element's styles and classes back to attributes array.
1274
function updateAttributes( element ) {
1275
var attrs = element.attributes,
1279
// Will be recreated later if any of styles/classes exists.
1281
delete attrs[ 'class' ];
1283
if ( ( styles = CKEDITOR.tools.writeCssText( element.styles, true ) ) )
1284
attrs.style = styles;
1286
if ( element.classes.length )
1287
attrs[ 'class' ] = element.classes.sort().join( ' ' );
1290
// Update element object based on status of filtering.
1291
// @returns Whether element was modified.
1292
function updateElement( element, status ) {
1293
var validAttrs = status.validAttributes,
1294
validStyles = status.validStyles,
1295
validClasses = status.validClasses,
1296
attrs = element.attributes,
1297
styles = element.styles,
1298
origClasses = attrs[ 'class' ],
1299
origStyles = attrs.style,
1303
internalAttr = /^data-cke-/,
1306
// Will be recreated later if any of styles/classes were passed.
1308
delete attrs[ 'class' ];
1310
if ( !status.allAttributes ) {
1311
for ( name in attrs ) {
1312
// If not valid and not internal attribute delete it.
1313
if ( !validAttrs[ name ] ) {
1314
// Allow all internal attibutes...
1315
if ( internalAttr.test( name ) ) {
1316
// ... unless this is a saved attribute and the original one isn't allowed.
1317
if ( name != ( origName = name.replace( /^data-cke-saved-/, '' ) ) &&
1318
!validAttrs[ origName ]
1320
delete attrs[ name ];
1324
delete attrs[ name ];
1332
if ( !status.allStyles ) {
1333
for ( name in styles ) {
1334
if ( validStyles[ name ] )
1335
stylesArr.push( name + ':' + styles[ name ] );
1339
if ( stylesArr.length )
1340
attrs.style = stylesArr.sort().join( '; ' );
1342
else if ( origStyles )
1343
attrs.style = origStyles;
1345
if ( !status.allClasses ) {
1346
for ( name in validClasses ) {
1347
if ( validClasses[ name ] )
1348
classesArr.push( name );
1350
if ( classesArr.length )
1351
attrs[ 'class' ] = classesArr.sort().join( ' ' );
1353
if ( origClasses && classesArr.length < origClasses.split( /\s+/ ).length )
1356
else if ( origClasses )
1357
attrs[ 'class' ] = origClasses;
1362
function validateElement( element ) {
1365
switch ( element.name ) {
1367
// Code borrowed from htmlDataProcessor, so ACF does the same clean up.
1368
if ( !( element.children.length || element.attributes.name ) )
1372
if ( !element.attributes.src )
1380
function validatorFunction( validator ) {
1383
if ( validator === true )
1386
return function( value ) {
1387
return value in validator;
1392
// REMOVE ELEMENT ---------------------------------------------------------
1395
// Checks whether node is allowed by DTD.
1396
function allowedIn( node, parentDtd ) {
1397
if ( node.type == CKEDITOR.NODE_ELEMENT )
1398
return parentDtd[ node.name ];
1399
if ( node.type == CKEDITOR.NODE_TEXT )
1400
return parentDtd[ '#' ];
1404
// Check whether all children will be valid in new context.
1405
// Note: it doesn't verify if text node is valid, because
1406
// new parent should accept them.
1407
function checkChildren( children, newParentName ) {
1408
var allowed = DTD[ newParentName ];
1410
for ( var i = 0, l = children.length, child; i < l; ++i ) {
1411
child = children[ i ];
1412
if ( child.type == CKEDITOR.NODE_ELEMENT && !allowed[ child.name ] )
1419
function createBr() {
1420
return new CKEDITOR.htmlParser.element( 'br' );
1423
// Whether this is an inline element or text.
1424
function inlineNode( node ) {
1425
return node.type == CKEDITOR.NODE_TEXT ||
1426
node.type == CKEDITOR.NODE_ELEMENT && DTD.$inline[ node.name ];
1429
function isBrOrBlock( node ) {
1430
return node.type == CKEDITOR.NODE_ELEMENT &&
1431
( node.name == 'br' || DTD.$block[ node.name ] );
1434
// Try to remove element in the best possible way.
1436
// @param {Array} toBeChecked After executing this function
1437
// this array will contain elements that should be checked
1438
// because they were marked as potentially:
1439
// * in wrong context (e.g. li in body),
1440
// * empty elements from $removeEmpty,
1441
// * incorrect img/a/other element validated by validateElement().
1442
function removeElement( element, enterTag, toBeChecked ) {
1443
var name = element.name;
1445
if ( DTD.$empty[ name ] || !element.children.length ) {
1446
// Special case - hr in br mode should be replaced with br, not removed.
1447
if ( name == 'hr' && enterTag == 'br' )
1448
element.replaceWith( createBr() );
1450
// Parent might become an empty inline specified in $removeEmpty or empty a[href].
1451
if ( element.parent )
1452
toBeChecked.push( { check: 'it', el: element.parent } );
1456
} else if ( DTD.$block[ name ] || name == 'tr' ) {
1457
if ( enterTag == 'br' )
1458
stripBlockBr( element, toBeChecked );
1460
stripBlock( element, enterTag, toBeChecked );
1462
// Special case - elements that may contain CDATA
1463
// should be removed completely. <script> is handled
1464
// by filterProtectedElement().
1465
else if ( name == 'style' )
1467
// The rest of inline elements. May also be the last resort
1468
// for some special elements.
1470
// Parent might become an empty inline specified in $removeEmpty or empty a[href].
1471
if ( element.parent )
1472
toBeChecked.push( { check: 'it', el: element.parent } );
1473
element.replaceWithChildren();
1477
// Strip element block, but leave its content.
1478
// Works in 'div' and 'p' enter modes.
1479
function stripBlock( element, enterTag, toBeChecked ) {
1480
var children = element.children;
1482
// First, check if element's children may be wrapped with <p/div>.
1483
// Ignore that <p/div> may not be allowed in element.parent.
1484
// This will be fixed when removing parent or by toBeChecked rule.
1485
if ( checkChildren( children, enterTag ) ) {
1486
element.name = enterTag;
1487
element.attributes = {};
1488
// Check if this p/div was put in correct context.
1489
// If not - strip parent.
1490
toBeChecked.push( { check: 'parent-down', el: element } );
1494
var parent = element.parent,
1495
shouldAutoP = parent.type == CKEDITOR.NODE_DOCUMENT_FRAGMENT || parent.name == 'body',
1496
i, j, child, p, node,
1499
for ( i = children.length; i > 0; ) {
1500
child = children[ --i ];
1502
// If parent requires auto paragraphing and child is inline node,
1503
// insert this child into newly created paragraph.
1504
if ( shouldAutoP && inlineNode( child ) ) {
1506
p = new CKEDITOR.htmlParser.element( enterTag );
1507
p.insertAfter( element );
1509
// Check if this p/div was put in correct context.
1510
// If not - strip parent.
1511
toBeChecked.push( { check: 'parent-down', el: p } );
1515
// Child which doesn't need to be auto paragraphed.
1518
child.insertAfter( element );
1519
// If inserted into invalid context, mark it and check
1520
// after removing all elements.
1521
if ( parent.type != CKEDITOR.NODE_DOCUMENT_FRAGMENT &&
1522
child.type == CKEDITOR.NODE_ELEMENT &&
1523
!DTD[ parent.name ][ child.name ]
1525
toBeChecked.push( { check: 'el-up', el: child } );
1529
// All children have been moved to element's parent, so remove it.
1533
// Prepend/append block with <br> if isn't
1534
// already prepended/appended with <br> or block and
1535
// isn't first/last child of its parent.
1536
// Then replace element with its children.
1537
// <p>a</p><p>b</p> => <p>a</p><br>b => a<br>b
1538
function stripBlockBr( element, toBeChecked ) {
1541
if ( element.previous && !isBrOrBlock( element.previous ) ) {
1543
br.insertBefore( element );
1546
if ( element.next && !isBrOrBlock( element.next ) ) {
1548
br.insertAfter( element );
1551
element.replaceWithChildren();
1555
// TRANSFORMATIONS --------------------------------------------------------
1558
// Apply given transformations group to the element.
1559
function applyTransformationsGroup( filter, element, group ) {
1562
for ( i = 0; i < group.length; ++i ) {
1565
// Test with #check or #left only if it's set.
1566
// Do not apply transformations because that creates infinite loop.
1567
if ( ( !rule.check || filter.check( rule.check, false ) ) &&
1568
( !rule.left || rule.left( element ) ) ) {
1569
rule.right( element, transformationsTools );
1570
return; // Only first matching rule in a group is executed.
1575
// Check whether element matches CKEDITOR.style.
1576
// The element can be a "superset" of style,
1577
// e.g. it may have more classes, but need to have
1578
// at least those defined in style.
1579
function elementMatchesStyle( element, style ) {
1580
var def = style.getDefinition(),
1581
defAttrs = def.attributes,
1582
defStyles = def.styles,
1583
attrName, styleName,
1584
classes, classPattern, cl;
1586
if ( element.name != def.element )
1589
for ( attrName in defAttrs ) {
1590
if ( attrName == 'class' ) {
1591
classes = defAttrs[ attrName ].split( /\s+/ );
1592
classPattern = element.classes.join( '|' );
1593
while ( ( cl = classes.pop() ) ) {
1594
if ( classPattern.indexOf( cl ) == -1 )
1598
if ( element.attributes[ attrName ] != defAttrs[ attrName ] )
1603
for ( styleName in defStyles ) {
1604
if ( element.styles[ styleName ] != defStyles[ styleName ] )
1611
// Return transformation group for content form.
1612
// One content form makes one transformation rule in one group.
1613
function getContentFormTransformationGroup( form, preferredForm ) {
1616
if ( typeof form == 'string' )
1618
else if ( form instanceof CKEDITOR.style )
1621
element = form[ 0 ];
1628
right: function( el, tools ) {
1629
tools.transform( el, preferredForm );
1634
// Obtain element's name from transformation rule.
1635
// It will be defined by #element, or #check or #left (styleDef.element).
1636
function getElementNameForTransformation( rule, check ) {
1638
return rule.element;
1640
return check.match( /^([a-z0-9]+)/i )[ 0 ];
1641
return rule.left.getDefinition().element;
1644
function getMatchStyleFn( style ) {
1645
return function( el ) {
1646
return elementMatchesStyle( el, style );
1650
function getTransformationFn( toolName ) {
1651
return function( el, tools ) {
1652
tools[ toolName ]( el );
1656
function optimizeTransformationsGroup( rules ) {
1657
var groupName, i, rule,
1659
optimizedRules = [];
1661
for ( i = 0; i < rules.length; ++i ) {
1664
if ( typeof rule == 'string' ) {
1665
rule = rule.split( /\s*:\s*/ );
1675
// Extract element name.
1677
groupName = getElementNameForTransformation( rule, check );
1679
if ( left instanceof CKEDITOR.style )
1680
left = getMatchStyleFn( left );
1682
optimizedRules.push( {
1683
// It doesn't make sense to test against name rule (e.g. 'table'), so don't save it.
1684
check: check == groupName ? null : check,
1688
// Handle shorthand format. E.g.: 'table[width]:sizeToAttribute'.
1689
right: typeof right == 'string' ? getTransformationFn( right ) : right
1695
rules: optimizedRules
1700
* Singleton containing tools useful for transformation rules.
1702
* @class CKEDITOR.filter.transformationsTools
1705
var transformationsTools = CKEDITOR.filter.transformationsTools = {
1707
* Converts `width` and `height` attributes to styles.
1709
* @param {CKEDITOR.htmlParser.element} element
1711
sizeToStyle: function( element ) {
1712
this.lengthToStyle( element, 'width' );
1713
this.lengthToStyle( element, 'height' );
1717
* Converts `width` and `height` styles to attributes.
1719
* @param {CKEDITOR.htmlParser.element} element
1721
sizeToAttribute: function( element ) {
1722
this.lengthToAttribute( element, 'width' );
1723
this.lengthToAttribute( element, 'height' );
1727
* Converts length in the `attrName` attribute to a valid CSS length (like `width` or `height`).
1729
* @param {CKEDITOR.htmlParser.element} element
1730
* @param {String} attrName Name of the attribute that will be converted.
1731
* @param {String} [styleName=attrName] Name of the style into which the attribute will be converted.
1733
lengthToStyle: function( element, attrName, styleName ) {
1734
styleName = styleName || attrName;
1736
if ( !( styleName in element.styles ) ) {
1737
var value = element.attributes[ attrName ];
1740
if ( ( /^\d+$/ ).test( value ) )
1743
element.styles[ styleName ] = value;
1747
delete element.attributes[ attrName ];
1751
* Converts length in the `styleName` style to a valid length attribute (like `width` or `height`).
1753
* @param {CKEDITOR.htmlParser.element} element
1754
* @param {String} styleName Name of the style that will be converted.
1755
* @param {String} [attrName=styleName] Name of the attribute into which the style will be converted.
1757
lengthToAttribute: function( element, styleName, attrName ) {
1758
attrName = attrName || styleName;
1760
if ( !( attrName in element.attributes ) ) {
1761
var value = element.styles[ styleName ],
1762
match = value && value.match( /^(\d+)(?:\.\d*)?px$/ );
1765
element.attributes[ attrName ] = match[ 1 ];
1766
// Pass the TEST_VALUE used by filter#check when mocking element.
1767
else if ( value == TEST_VALUE )
1768
element.attributes[ attrName ] = TEST_VALUE;
1771
delete element.styles[ styleName ];
1775
* Converts the `align` attribute to the `float` style if not set. Attribute
1776
* is always removed.
1778
* @param {CKEDITOR.htmlParser.element} element
1780
alignmentToStyle: function( element ) {
1781
if ( !( 'float' in element.styles ) ) {
1782
var value = element.attributes.align;
1784
if ( value == 'left' || value == 'right' )
1785
element.styles[ 'float' ] = value; // Uh... GCC doesn't like the 'float' prop name.
1788
delete element.attributes.align;
1792
* Converts the `float` style to the `align` attribute if not set.
1793
* Style is always removed.
1795
* @param {CKEDITOR.htmlParser.element} element
1797
alignmentToAttribute: function( element ) {
1798
if ( !( 'align' in element.attributes ) ) {
1799
var value = element.styles[ 'float' ];
1801
if ( value == 'left' || value == 'right' )
1802
element.attributes.align = value;
1805
delete element.styles[ 'float' ]; // Uh... GCC doesn't like the 'float' prop name.
1809
* Checks whether an element matches a given {@link CKEDITOR.style}.
1810
* The element can be a "superset" of a style, e.g. it may have
1811
* more classes, but needs to have at least those defined in the style.
1813
* @param {CKEDITOR.htmlParser.element} element
1814
* @param {CKEDITOR.style} style
1816
matchesStyle: elementMatchesStyle,
1819
* Transforms element to given form.
1823
* * {@link CKEDITOR.style},
1824
* * string – the new name of an element.
1826
* @param {CKEDITOR.htmlParser.element} el
1827
* @param {CKEDITOR.style/String} form
1829
transform: function( el, form ) {
1830
if ( typeof form == 'string' )
1832
// Form is an instance of CKEDITOR.style.
1834
var def = form.getDefinition(),
1835
defStyles = def.styles,
1836
defAttrs = def.attributes,
1837
attrName, styleName,
1838
existingClassesPattern, defClasses, cl;
1840
el.name = def.element;
1842
for ( attrName in defAttrs ) {
1843
if ( attrName == 'class' ) {
1844
existingClassesPattern = el.classes.join( '|' );
1845
defClasses = defAttrs[ attrName ].split( /\s+/ );
1847
while ( ( cl = defClasses.pop() ) ) {
1848
if ( existingClassesPattern.indexOf( cl ) == -1 )
1849
el.classes.push( cl );
1852
el.attributes[ attrName ] = defAttrs[ attrName ];
1856
for ( styleName in defStyles ) {
1857
el.styles[ styleName ] = defStyles[ styleName ];
1866
* Allowed content rules. This setting is used when
1867
* instantiating {@link CKEDITOR.editor#filter}.
1869
* The following values are accepted:
1871
* * {@link CKEDITOR.filter.allowedContentRules} – defined rules will be added
1872
* to the {@link CKEDITOR.editor#filter}.
1873
* * `true` – will disable the filter (data will not be filtered,
1874
* all features will be activated).
1875
* * default – the filter will be configured by loaded features
1876
* (toolbar items, commands, etc.).
1878
* In all cases filter configuration may be extended by
1879
* {@link CKEDITOR.config#extraAllowedContent}. This option may be especially
1880
* useful when you want to use the default `allowedContent` value
1881
* along with some additional rules.
1883
* CKEDITOR.replace( 'textarea_id', {
1884
* allowedContent: 'p b i; a[!href]',
1886
* instanceReady: function( evt ) {
1887
* var editor = evt.editor;
1889
* editor.filter.check( 'h1' ); // -> false
1890
* editor.setData( '<h1><i>Foo</i></h1><p class="left"><span>Bar</span> <a href="http://foo.bar">foo</a></p>' );
1891
* // Editor contents will be:
1892
* '<p><i>Foo</i></p><p>Bar <a href="http://foo.bar">foo</a></p>'
1898
* @cfg {CKEDITOR.filter.allowedContentRules/Boolean} [allowedContent=null]
1899
* @member CKEDITOR.config
1903
* This option makes it possible to set additional allowed
1904
* content rules for {@link CKEDITOR.editor#filter}.
1906
* It is especially useful in combination with the default
1907
* {@link CKEDITOR.config#allowedContent} value:
1909
* CKEDITOR.replace( 'textarea_id', {
1910
* plugins: 'wysiwygarea,toolbar,format',
1911
* extraAllowedContent: 'b i',
1913
* instanceReady: function( evt ) {
1914
* var editor = evt.editor;
1916
* editor.filter.check( 'h1' ); // -> true (thanks to Format combo)
1917
* editor.filter.check( 'b' ); // -> true (thanks to extraAllowedContent)
1918
* editor.setData( '<h1><i>Foo</i></h1><p class="left"><b>Bar</b> <a href="http://foo.bar">foo</a></p>' );
1919
* // Editor contents will be:
1920
* '<h1><i>Foo</i></h1><p><b>Bar</b> foo</p>'
1925
* See {@link CKEDITOR.config#allowedContent} for more details.
1928
* @cfg {Object/String} extraAllowedContent
1929
* @member CKEDITOR.config
1933
* This event is fired when {@link CKEDITOR.filter} has stripped some
1934
* content from the data that was loaded (e.g. by {@link CKEDITOR.editor#method-setData}
1935
* method or in the source mode) or inserted (e.g. when pasting or using the
1936
* {@link CKEDITOR.editor#method-insertHtml} method).
1938
* This event is useful when testing whether the {@link CKEDITOR.config#allowedContent}
1939
* setting is sufficient and correct for a system that is migrating to CKEditor 4.1
1940
* (where the [Advanced Content Filter](#!/guide/dev_advanced_content_filter) was introduced).
1943
* @event dataFiltered
1944
* @member CKEDITOR.editor
1945
* @param {CKEDITOR.editor} editor This editor instance.
1949
* Virtual class which is the [Allowed Content Rules](#!/guide/dev_allowed_content_rules) formats type.
1951
* Possible formats are:
1953
* * the [string format](#!/guide/dev_allowed_content_rules-section-2),
1954
* * the [object format](#!/guide/dev_allowed_content_rules-section-3),
1955
* * a {@link CKEDITOR.style} instance – used mainly for integrating plugins with Advanced Content Filter,
1956
* * an array of the above formats.
1959
* @class CKEDITOR.filter.allowedContentRules
1964
* Virtual class representing {@link CKEDITOR.filter#check} argument.
1966
* This is a simplified version of the {@link CKEDITOR.filter.allowedContentRules} type.
1967
* It may contain only one element and its styles, classes, and attributes. Only the
1968
* string format and a {@link CKEDITOR.style} instances are accepted. Required properties
1969
* are not allowed in this format.
1973
* 'img[src,alt](foo)' // Correct rule.
1974
* 'ol, ul(!foo)' // Incorrect rule. Multiple elements and required
1975
* // properties are not supported.
1978
* @class CKEDITOR.filter.contentRule
1983
* Interface that may be automatically implemented by any
1984
* instance of any class which has at least the `name` property and
1985
* can be meant as an editor feature.
1989
* * "Bold" command, button, and keystroke – it does not mean exactly
1990
* `<strong>` or `<b>` but just the ability to create bold text.
1991
* * "Format" drop-down list – it also does not imply any HTML tag.
1992
* * "Link" command, button, and keystroke.
1993
* * "Image" command, button, and dialog window.
1995
* Thus most often a feature is an instance of one of the following classes:
1997
* * {@link CKEDITOR.command}
1998
* * {@link CKEDITOR.ui.button}
1999
* * {@link CKEDITOR.ui.richCombo}
2001
* None of them have a `name` property explicitly defined, but
2002
* it is set by {@link CKEDITOR.editor#addCommand} and {@link CKEDITOR.ui#add}.
2004
* During editor initialization all features that the editor should activate
2005
* should be passed to {@link CKEDITOR.editor#addFeature} (shorthand for {@link CKEDITOR.filter#addFeature}).
2007
* This method checks if a feature can be activated (see {@link #requiredContent}) and if yes,
2008
* then it registers allowed content rules required by this feature (see {@link #allowedContent}) along
2009
* with two kinds of transformations: {@link #contentForms} and {@link #contentTransformations}.
2011
* By default all buttons that are included in [toolbar layout configuration](#!/guide/dev_toolbar)
2012
* are checked and registered with {@link CKEDITOR.editor#addFeature}, all styles available in the
2013
* 'Format' and 'Styles' drop-down lists are checked and registered too and so on.
2016
* @class CKEDITOR.feature
2021
* HTML code that can be generated by this feature.
2023
* For example a basic image feature (image button displaying the image dialog window)
2024
* may allow `'img[!src,alt,width,height]'`.
2026
* During the feature activation this value is passed to {@link CKEDITOR.filter#allow}.
2028
* @property {CKEDITOR.filter.allowedContentRules} [allowedContent=null]
2032
* Minimal HTML code that this feature must be allowed to
2033
* generate in order to work.
2035
* For example a basic image feature (image button displaying the image dialog window)
2036
* needs `'img[src,alt]'` in order to be activated.
2038
* During the feature validation this value is passed to {@link CKEDITOR.filter#check}.
2040
* If this value is not provided, a feature will be always activated.
2042
* @property {CKEDITOR.filter.contentRule} [requiredContent=null]
2046
* The name of the feature.
2048
* It is used for example to identify which {@link CKEDITOR.filter#allowedContent}
2049
* rule was added for which feature.
2051
* @property {String} name
2055
* Feature content forms to be registered in the {@link CKEDITOR.editor#filter}
2056
* during the feature activation.
2058
* See {@link CKEDITOR.filter#addContentForms} for more details.
2060
* @property [contentForms=null]
2064
* Transformations (usually for content generated by this feature, but not necessarily)
2065
* that will be registered in the {@link CKEDITOR.editor#filter} during the feature activation.
2067
* See {@link CKEDITOR.filter#addTransformations} for more details.
2069
* @property [contentTransformations=null]
2073
* Returns a feature that this feature needs to register.
2075
* In some cases, during activation, one feature may need to register
2076
* another feature. For example a {@link CKEDITOR.ui.button} often registers
2077
* a related command. See {@link CKEDITOR.ui.button#toFeature}.
2079
* This method is executed when a feature is passed to the {@link CKEDITOR.editor#addFeature}.
2082
* @returns {CKEDITOR.feature}