~ubuntu-branches/ubuntu/utopic/ldap-account-manager/utopic-proposed

« back to all changes in this revision

Viewing changes to templates/lib/extra/ckeditor/core/filter.js

  • Committer: Package Import Robot
  • Author(s): Roland Gruber
  • Date: 2014-06-12 17:51:20 UTC
  • mfrom: (1.2.24)
  • Revision ID: package-import@ubuntu.com-20140612175120-grobhwyk369g9aod
Tags: 4.6-1
new upstream release

Show diffs side-by-side

added added

removed removed

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