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