|
7 | 7 | 'use strict';
|
8 | 8 |
|
9 | 9 | var DTD = CKEDITOR.dtd,
|
| 10 | + // processElement flag - means that element has been somehow modified. |
| 11 | + FILTER_ELEMENT_MODIFIED = 1, |
| 12 | + // processElement flag - meaning explained in CKEDITOR.FILTER_SKIP_TREE doc. |
| 13 | + FILTER_SKIP_TREE = 2, |
10 | 14 | copy = CKEDITOR.tools.copy,
|
11 | 15 | trim = CKEDITOR.tools.trim,
|
12 | 16 | TEST_VALUE = 'cke-test',
|
13 | 17 | enterModeTags = [ '', 'p', 'br', 'div' ];
|
14 | 18 |
|
| 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 | + |
15 | 32 | /**
|
16 | 33 | * Highly configurable class which implements input data filtering mechanisms
|
17 | 34 | * and core functions used for the activation of editor features.
|
|
83 | 100 | */
|
84 | 101 | this.disallowedContent = [];
|
85 | 102 |
|
| 103 | + /** |
| 104 | + * Array of element callbacks. See {@link #addElementCallback}. |
| 105 | + * |
| 106 | + * @readonly |
| 107 | + * @property {Function[]} [=null] |
| 108 | + */ |
| 109 | + this.elementCallbacks = null; |
| 110 | + |
86 | 111 | /**
|
87 | 112 | * Whether the filter is disabled.
|
88 | 113 | *
|
|
245 | 270 | var that = this,
|
246 | 271 | toBeRemoved = [],
|
247 | 272 | protectedRegexs = this.editor && this.editor.config.protectedSource,
|
| 273 | + processRetVal, |
248 | 274 | isModified = false,
|
249 | 275 | filterOpts = {
|
250 | 276 | doFilter: !transformOnly,
|
251 | 277 | doTransform: true,
|
| 278 | + doCallbacks: true, |
252 | 279 | toHtml: toHtml
|
253 | 280 | };
|
254 | 281 |
|
|
270 | 297 | if ( toHtml && el.name == 'span' && ~CKEDITOR.tools.objectKeys( el.attributes ).join( '|' ).indexOf( 'data-cke-' ) )
|
271 | 298 | return;
|
272 | 299 |
|
273 |
| - if ( processElement( that, el, toBeRemoved, filterOpts ) ) |
| 300 | + processRetVal = processElement( that, el, toBeRemoved, filterOpts ); |
| 301 | + if ( processRetVal & FILTER_ELEMENT_MODIFIED ) |
274 | 302 | isModified = true;
|
| 303 | + else if ( processRetVal & FILTER_SKIP_TREE ) |
| 304 | + return false; |
275 | 305 | }
|
276 | 306 | else if ( el.type == CKEDITOR.NODE_COMMENT && el.value.match( /^\{cke_protected\}(?!\{C\})/ ) ) {
|
277 | 307 | if ( !processProtectedElement( that, el, protectedRegexs, filterOpts ) )
|
|
442 | 472 | this.addTransformations( transfGroups );
|
443 | 473 | },
|
444 | 474 |
|
| 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 | + |
445 | 503 | /**
|
446 | 504 | * Checks whether a feature can be enabled for the HTML restrictions in place
|
447 | 505 | * for the current CKEditor instance, based on the HTML code the feature might
|
|
953 | 1011 | }
|
954 | 1012 | }
|
955 | 1013 |
|
| 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 | + |
956 | 1021 | // Extract required properties from "required" validator and "all" properties.
|
957 | 1022 | // Remove exclamation marks from "all" properties.
|
958 | 1023 | //
|
|
1360 | 1425 | // @param {Array} toBeRemoved Array into which elements rejected by the filter will be pushed.
|
1361 | 1426 | // @param {Boolean} [opts.doFilter] Whether element should be filtered.
|
1362 | 1427 | // @param {Boolean} [opts.doTransform] Whether transformations should be applied.
|
| 1428 | + // @param {Boolean} [opts.doCallbacks] Whether to execute element callbacks. |
1363 | 1429 | // @param {Boolean} [opts.toHtml] Set to true if filter used together with htmlDP#toHtml
|
1364 | 1430 | // @param {Boolean} [opts.skipRequired] Whether element's required properties shouldn't be verified.
|
1365 | 1431 | // @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. |
1367 | 1435 | function processElement( that, element, toBeRemoved, opts ) {
|
1368 | 1436 | var status,
|
1369 |
| - isModified = false; |
| 1437 | + retVal = 0, |
| 1438 | + callbacksRetVal; |
1370 | 1439 |
|
1371 | 1440 | // Unprotect elements names previously protected by htmlDataProcessor
|
1372 | 1441 | // (see protectElementNames and protectSelfClosingElements functions).
|
1373 | 1442 | // Note: body, title, etc. are not protected by htmlDataP (or are protected and then unprotected).
|
1374 | 1443 | if ( opts.toHtml )
|
1375 | 1444 | element.name = element.name.replace( unprotectElementsNamesRegexp, '$1' );
|
1376 | 1445 |
|
| 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 | + |
1377 | 1453 | // If transformations are set apply all groups.
|
1378 | 1454 | if ( opts.doTransform )
|
1379 | 1455 | transformElement( that, element );
|
|
1385 | 1461 | // Handle early return from filterElement.
|
1386 | 1462 | if ( !status ) {
|
1387 | 1463 | toBeRemoved.push( element );
|
1388 |
| - return true; |
| 1464 | + return FILTER_ELEMENT_MODIFIED; |
1389 | 1465 | }
|
1390 | 1466 |
|
1391 | 1467 | // Finally, if after running all filter rules it still hasn't been allowed - remove it.
|
1392 | 1468 | if ( !status.valid ) {
|
1393 | 1469 | toBeRemoved.push( element );
|
1394 |
| - return true; |
| 1470 | + return FILTER_ELEMENT_MODIFIED; |
1395 | 1471 | }
|
1396 | 1472 |
|
1397 | 1473 | // Update element's attributes based on status of filtering.
|
1398 | 1474 | if ( updateElement( element, status ) )
|
1399 |
| - isModified = true; |
| 1475 | + retVal = FILTER_ELEMENT_MODIFIED; |
1400 | 1476 |
|
1401 | 1477 | if ( !opts.skipFinalValidation && !validateElement( element ) ) {
|
1402 | 1478 | toBeRemoved.push( element );
|
1403 |
| - return true; |
| 1479 | + return FILTER_ELEMENT_MODIFIED; |
1404 | 1480 | }
|
1405 | 1481 | }
|
1406 | 1482 |
|
1407 | 1483 | // Protect previously unprotected elements.
|
1408 | 1484 | if ( opts.toHtml )
|
1409 | 1485 | element.name = element.name.replace( protectElementsNamesRegexp, 'cke:$1' );
|
1410 | 1486 |
|
1411 |
| - return isModified; |
| 1487 | + return retVal; |
1412 | 1488 | }
|
1413 | 1489 |
|
1414 | 1490 | // Returns a regexp object which can be used to test if a property
|
|
0 commit comments