Skip to content

Commit 58af051

Browse files
committed
Merge branch 't/11737' into major
2 parents a63a790 + 38b741f commit 58af051

File tree

2 files changed

+85
-8
lines changed

2 files changed

+85
-8
lines changed

CHANGES.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ New Features:
1818
* Fixed: selection not being unlocked in inline editor after setting data ([#15000](http://dev.ckeditor.com/ticket/11500)).
1919
* [#10202](http://dev.ckeditor.com/ticket/10202): Introduced wildcards support in the [Allowed Content Rules](http://docs.ckeditor.com/#!/guide/dev_allowed_content_rules) format.
2020
* [#10276](http://dev.ckeditor.com/ticket/10276): Introduced blacklisting in the [Allowed Content Filter](http://docs.ckeditor.com/#!/guide/dev_advanced_content_filter).
21+
* [#11737](http://dev.ckeditor.com/ticket/11737): Introduced an option to prevent [filtering](http://docs.ckeditor.com/#!/guide/dev_advanced_content_filter) of element which matches custom creteria (see [`filter.addElementCallback`](http://docs.ckeditor.com/#!/api/CKEDITOR.filter-method-addElementCallback)).
2122
* [#11532](http://dev.ckeditor.com/ticket/11532): Introduced the [`editor.addContentsCss()`](http://docs.ckeditor.com/#!/api/CKEDITOR.editor-method-addContentsCss) method that can be used for adding custom CSS files.
2223
* [#11536](http://dev.ckeditor.com/ticket/11536): Added the [`CKEDITOR.tools.htmlDecode`](http://docs.ckeditor.com/#!/api/CKEDITOR.tools-method-htmlDecode) method for decoding HTML entities.
2324
* [#11377](http://dev.ckeditor.com/ticket/11377): Unified internal representation of empty anchors using the [fakeobjects](http://ckeditor.com/addon/fakeobjects).

core/filter.js

Lines changed: 84 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,28 @@
77
'use strict';
88

99
var DTD = CKEDITOR.dtd,
10+
// processElement flag - means that element has been somehow modified.
11+
FILTER_ELEMENT_MODIFIED = 1,
12+
// processElement flag - meaning explained in CKEDITOR.FILTER_SKIP_TREE doc.
13+
FILTER_SKIP_TREE = 2,
1014
copy = CKEDITOR.tools.copy,
1115
trim = CKEDITOR.tools.trim,
1216
TEST_VALUE = 'cke-test',
1317
enterModeTags = [ '', 'p', 'br', 'div' ];
1418

19+
/**
20+
* Flag indicating that current element and all its ancestors
21+
* should not be filtered.
22+
*
23+
* See {@link CKEDITOR.filter#addElementCallback} for more details.
24+
*
25+
* @since 4.4
26+
* @readonly
27+
* @property {Number} [=2]
28+
* @member CKEDITOR
29+
*/
30+
CKEDITOR.FILTER_SKIP_TREE = FILTER_SKIP_TREE;
31+
1532
/**
1633
* Highly configurable class which implements input data filtering mechanisms
1734
* and core functions used for the activation of editor features.
@@ -83,6 +100,14 @@
83100
*/
84101
this.disallowedContent = [];
85102

103+
/**
104+
* Array of element callbacks. See {@link #addElementCallback}.
105+
*
106+
* @readonly
107+
* @property {Function[]} [=null]
108+
*/
109+
this.elementCallbacks = null;
110+
86111
/**
87112
* Whether the filter is disabled.
88113
*
@@ -245,10 +270,12 @@
245270
var that = this,
246271
toBeRemoved = [],
247272
protectedRegexs = this.editor && this.editor.config.protectedSource,
273+
processRetVal,
248274
isModified = false,
249275
filterOpts = {
250276
doFilter: !transformOnly,
251277
doTransform: true,
278+
doCallbacks: true,
252279
toHtml: toHtml
253280
};
254281

@@ -270,8 +297,11 @@
270297
if ( toHtml && el.name == 'span' && ~CKEDITOR.tools.objectKeys( el.attributes ).join( '|' ).indexOf( 'data-cke-' ) )
271298
return;
272299

273-
if ( processElement( that, el, toBeRemoved, filterOpts ) )
300+
processRetVal = processElement( that, el, toBeRemoved, filterOpts );
301+
if ( processRetVal & FILTER_ELEMENT_MODIFIED )
274302
isModified = true;
303+
else if ( processRetVal & FILTER_SKIP_TREE )
304+
return false;
275305
}
276306
else if ( el.type == CKEDITOR.NODE_COMMENT && el.value.match( /^\{cke_protected\}(?!\{C\})/ ) ) {
277307
if ( !processProtectedElement( that, el, protectedRegexs, filterOpts ) )
@@ -442,6 +472,34 @@
442472
this.addTransformations( transfGroups );
443473
},
444474

475+
/**
476+
* Adds a callback which will be executed on every element
477+
* which filter reaches when filtering, before the element is filtered.
478+
*
479+
* By returning {@link CKEDITOR#FILTER_SKIP_TREE} it is possible to
480+
* skip filtering of the current element and its all ancestors.
481+
*
482+
* editor.filter.addElementCallback( function( el ) {
483+
* if ( el.hasClass( 'protected' ) )
484+
* return CKEDITOR.FILTER_SKIP_TREE;
485+
* } );
486+
*
487+
* **Note:** At this stage the element passed to callback does not
488+
* contain `attributes`, `classes` and `styles` properties which are available
489+
* temporarily on later stages of filtering. Therefore you need to use the pure
490+
* {@link CKEDITOR.htmlParser.element} interface.
491+
*
492+
* @since 4.4
493+
* @param {Function} callback The callback to be executed.
494+
*/
495+
addElementCallback: function( callback ) {
496+
// We want to keep it a falsy value, to speed up finding whether there are any callbacks.
497+
if ( !this.elementCallbacks )
498+
this.elementCallbacks = [];
499+
500+
this.elementCallbacks.push( callback );
501+
},
502+
445503
/**
446504
* Checks whether a feature can be enabled for the HTML restrictions in place
447505
* for the current CKEditor instance, based on the HTML code the feature might
@@ -953,6 +1011,13 @@
9531011
}
9541012
}
9551013

1014+
function executeElementCallbacks( element, callbacks ) {
1015+
for ( var i = 0, l = callbacks.length, retVal; i < l; ++i ) {
1016+
if ( retVal = callbacks[ i ]( element ) )
1017+
return retVal;
1018+
}
1019+
}
1020+
9561021
// Extract required properties from "required" validator and "all" properties.
9571022
// Remove exclamation marks from "all" properties.
9581023
//
@@ -1360,20 +1425,31 @@
13601425
// @param {Array} toBeRemoved Array into which elements rejected by the filter will be pushed.
13611426
// @param {Boolean} [opts.doFilter] Whether element should be filtered.
13621427
// @param {Boolean} [opts.doTransform] Whether transformations should be applied.
1428+
// @param {Boolean} [opts.doCallbacks] Whether to execute element callbacks.
13631429
// @param {Boolean} [opts.toHtml] Set to true if filter used together with htmlDP#toHtml
13641430
// @param {Boolean} [opts.skipRequired] Whether element's required properties shouldn't be verified.
13651431
// @param {Boolean} [opts.skipFinalValidation] Whether to not perform final element validation (a,img).
1366-
// @returns {Boolean} Whether content has been modified.
1432+
// @returns {Number} Possible flags:
1433+
// * FILTER_ELEMENT_MODIFIED,
1434+
// * FILTER_SKIP_TREE.
13671435
function processElement( that, element, toBeRemoved, opts ) {
13681436
var status,
1369-
isModified = false;
1437+
retVal = 0,
1438+
callbacksRetVal;
13701439

13711440
// Unprotect elements names previously protected by htmlDataProcessor
13721441
// (see protectElementNames and protectSelfClosingElements functions).
13731442
// Note: body, title, etc. are not protected by htmlDataP (or are protected and then unprotected).
13741443
if ( opts.toHtml )
13751444
element.name = element.name.replace( unprotectElementsNamesRegexp, '$1' );
13761445

1446+
// Execute element callbacks and return if one of them returned any value.
1447+
if ( opts.doCallbacks && that.elementCallbacks ) {
1448+
// For now we only support here FILTER_SKIP_TREE, so we can early return if retVal is truly value.
1449+
if ( callbacksRetVal = executeElementCallbacks( element, that.elementCallbacks ) )
1450+
return callbacksRetVal;
1451+
}
1452+
13771453
// If transformations are set apply all groups.
13781454
if ( opts.doTransform )
13791455
transformElement( that, element );
@@ -1385,30 +1461,30 @@
13851461
// Handle early return from filterElement.
13861462
if ( !status ) {
13871463
toBeRemoved.push( element );
1388-
return true;
1464+
return FILTER_ELEMENT_MODIFIED;
13891465
}
13901466

13911467
// Finally, if after running all filter rules it still hasn't been allowed - remove it.
13921468
if ( !status.valid ) {
13931469
toBeRemoved.push( element );
1394-
return true;
1470+
return FILTER_ELEMENT_MODIFIED;
13951471
}
13961472

13971473
// Update element's attributes based on status of filtering.
13981474
if ( updateElement( element, status ) )
1399-
isModified = true;
1475+
retVal = FILTER_ELEMENT_MODIFIED;
14001476

14011477
if ( !opts.skipFinalValidation && !validateElement( element ) ) {
14021478
toBeRemoved.push( element );
1403-
return true;
1479+
return FILTER_ELEMENT_MODIFIED;
14041480
}
14051481
}
14061482

14071483
// Protect previously unprotected elements.
14081484
if ( opts.toHtml )
14091485
element.name = element.name.replace( protectElementsNamesRegexp, 'cke:$1' );
14101486

1411-
return isModified;
1487+
return retVal;
14121488
}
14131489

14141490
// Returns a regexp object which can be used to test if a property

0 commit comments

Comments
 (0)