text: ﻿/**
   * @license Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved.
   * For licensing, see LICENSE.md or http://ckeditor.com/license
   */
  
  'use strict';
  
  ( function() {
  
  	var template = '<img alt="" src="" />',
  		templateBlock = new CKEDITOR.template(
  			'<figure class="{captionedClass}">' +
  				template +
  				'<figcaption>{captionPlaceholder}</figcaption>' +
  			'</figure>' ),
  		alignmentsObj = { left: 0, center: 1, right: 2 },
  		regexPercent = /^\s*(\d+\%)\s*$/i;
  
  	CKEDITOR.plugins.add( 'image2', {
  		// jscs:disable maximumLineLength
  		lang: 'af,ar,az,bg,bn,bs,ca,cs,cy,da,de,de-ch,el,en,en-au,en-ca,en-gb,eo,es,es-mx,et,eu,fa,fi,fo,fr,fr-ca,gl,gu,he,hi,hr,hu,id,is,it,ja,ka,km,ko,ku,lt,lv,mk,mn,ms,nb,nl,no,oc,pl,pt,pt-br,ro,ru,si,sk,sl,sq,sr,sr-latn,sv,th,tr,tt,ug,uk,vi,zh,zh-cn', // %REMOVE_LINE_CORE%
  		// jscs:enable maximumLineLength
  		requires: 'widget,dialog',
  		icons: 'image',
  		hidpi: true,
  
  		onLoad: function() {
  			CKEDITOR.addCss(
  			'.cke_image_nocaption{' +
  				// This is to remove unwanted space so resize
  				// wrapper is displayed property.
  				'line-height:0' +
  			'}' +
  			'.cke_editable.cke_image_sw, .cke_editable.cke_image_sw *{cursor:sw-resize !important}' +
  			'.cke_editable.cke_image_se, .cke_editable.cke_image_se *{cursor:se-resize !important}' +
  			'.cke_image_resizer{' +
  				'display:none;' +
  				'position:absolute;' +
  				'width:10px;' +
  				'height:10px;' +
  				'bottom:-5px;' +
  				'right:-5px;' +
  				'background:#000;' +
  				'outline:1px solid #fff;' +
  				// Prevent drag handler from being misplaced (http://dev.ckeditor.com/ticket/11207).
  				'line-height:0;' +
  				'cursor:se-resize;' +
  			'}' +
  			'.cke_image_resizer_wrapper{' +
  				'position:relative;' +
  				'display:inline-block;' +
  				'line-height:0;' +
  			'}' +
  			// Bottom-left corner style of the resizer.
  			'.cke_image_resizer.cke_image_resizer_left{' +
  				'right:auto;' +
  				'left:-5px;' +
  				'cursor:sw-resize;' +
  			'}' +
  			'.cke_widget_wrapper:hover .cke_image_resizer,' +
  			'.cke_image_resizer.cke_image_resizing{' +
  				'display:block' +
  			'}' +
  			// Expand widget wrapper when linked inline image.
  			'.cke_widget_wrapper>a{' +
  				'display:inline-block' +
  			'}' );
  		},
  
  		init: function( editor ) {
  			// Adapts configuration from original image plugin. Should be removed
  			// when we'll rename image2 to image.
  			var config = editor.config,
  				lang = editor.lang.image2,
  				image = widgetDef( editor );
  
  			// Since filebrowser plugin discovers config properties by dialog (plugin?)
  			// names (sic!), this hack will be necessary as long as Image2 is not named
  			// Image. And since Image2 will never be Image, for sure some filebrowser logic
  			// got to be refined.
  			config.filebrowserImage2BrowseUrl = config.filebrowserImageBrowseUrl;
  			config.filebrowserImage2UploadUrl = config.filebrowserImageUploadUrl;
  
  			// Add custom elementspath names to widget definition.
  			image.pathName = lang.pathName;
  			image.editables.caption.pathName = lang.pathNameCaption;
  
  			// Register the widget.
  			editor.widgets.add( 'image', image );
  
  			// Add toolbar button for this plugin.
  			editor.ui.addButton && editor.ui.addButton( 'Image', {
  				label: editor.lang.common.image,
  				command: 'image',
  				toolbar: 'insert,10'
  			} );
  
  			// Register context menu option for editing widget.
  			if ( editor.contextMenu ) {
  				editor.addMenuGroup( 'image', 10 );
  
  				editor.addMenuItem( 'image', {
  					label: lang.menu,
  					command: 'image',
  					group: 'image'
  				} );
  			}
  
  			CKEDITOR.dialog.add( 'image2', this.path + 'dialogs/image2.js' );
  		},
  
  		afterInit: function( editor ) {
  			// Integrate with align commands (justify plugin).
  			var align = { left: 1, right: 1, center: 1, block: 1 },
  				integrate = alignCommandIntegrator( editor );
  
  			for ( var value in align )
  				integrate( value );
  
  			// Integrate with link commands (link plugin).
  			linkCommandIntegrator( editor );
  		}
  	} );
  
  	// Wiget states (forms) depending on alignment and configuration.
  	//
  	// Non-captioned widget (inline styles)
  	// 		┌──────┬───────────────────────────────┬─────────────────────────────┐
  	// 		│Align │Internal form                  │Data                         │
  	// 		├──────┼───────────────────────────────┼─────────────────────────────┤
  	// 		│none  │<wrapper>                      │<img />                      │
  	// 		│      │ <img />                       │                             │
  	// 		│      │</wrapper>                     │                             │
  	// 		├──────┼───────────────────────────────┼─────────────────────────────┤
  	// 		│left  │<wrapper style=”float:left”>   │<img style=”float:left” />   │
  	// 		│      │ <img />                       │                             │
  	// 		│      │</wrapper>                     │                             │
  	// 		├──────┼───────────────────────────────┼─────────────────────────────┤
  	// 		│center│<wrapper>                      │<p style=”text-align:center”>│
  	// 		│      │ <p style=”text-align:center”> │  <img />                    │
  	// 		│      │   <img />                     │</p>                         │
  	// 		│      │ </p>                          │                             │
  	// 		│      │</wrapper>                     │                             │
  	// 		├──────┼───────────────────────────────┼─────────────────────────────┤
  	// 		│right │<wrapper style=”float:right”>  │<img style=”float:right” />  │
  	// 		│      │ <img />                       │                             │
  	// 		│      │</wrapper>                     │                             │
  	// 		└──────┴───────────────────────────────┴─────────────────────────────┘
  	//
  	// Non-captioned widget (config.image2_alignClasses defined)
  	// 		┌──────┬───────────────────────────────┬─────────────────────────────┐
  	// 		│Align │Internal form                  │Data                         │
  	// 		├──────┼───────────────────────────────┼─────────────────────────────┤
  	// 		│none  │<wrapper>                      │<img />                      │
  	// 		│      │ <img />                       │                             │
  	// 		│      │</wrapper>                     │                             │
  	// 		├──────┼───────────────────────────────┼─────────────────────────────┤
  	// 		│left  │<wrapper class=”left”>         │<img class=”left” />         │
  	// 		│      │ <img />                       │                             │
  	// 		│      │</wrapper>                     │                             │
  	// 		├──────┼───────────────────────────────┼─────────────────────────────┤
  	// 		│center│<wrapper>                      │<p class=”center”>           │
  	// 		│      │ <p class=”center”>            │ <img />                     │
  	// 		│      │   <img />                     │</p>                         │
  	// 		│      │ </p>                          │                             │
  	// 		│      │</wrapper>                     │                             │
  	// 		├──────┼───────────────────────────────┼─────────────────────────────┤
  	// 		│right │<wrapper class=”right”>        │<img class=”right” />        │
  	// 		│      │ <img />                       │                             │
  	// 		│      │</wrapper>                     │                             │
  	// 		└──────┴───────────────────────────────┴─────────────────────────────┘
  	//
  	// Captioned widget (inline styles)
  	// 		┌──────┬────────────────────────────────────────┬────────────────────────────────────────┐
  	// 		│Align │Internal form                           │Data                                    │
  	// 		├──────┼────────────────────────────────────────┼────────────────────────────────────────┤
  	// 		│none  │<wrapper>                               │<figure />                              │
  	// 		│      │ <figure />                             │                                        │
  	// 		│      │</wrapper>                              │                                        │
  	// 		├──────┼────────────────────────────────────────┼────────────────────────────────────────┤
  	// 		│left  │<wrapper style=”float:left”>            │<figure style=”float:left” />           │
  	// 		│      │ <figure />                             │                                        │
  	// 		│      │</wrapper>                              │                                        │
  	// 		├──────┼────────────────────────────────────────┼────────────────────────────────────────┤
  	// 		│center│<wrapper style=”text-align:center”>     │<div style=”text-align:center”>         │
  	// 		│      │ <figure style=”display:inline-block” />│ <figure style=”display:inline-block” />│
  	// 		│      │</wrapper>                              │</p>                                    │
  	// 		├──────┼────────────────────────────────────────┼────────────────────────────────────────┤
  	// 		│right │<wrapper style=”float:right”>           │<figure style=”float:right” />          │
  	// 		│      │ <figure />                             │                                        │
  	// 		│      │</wrapper>                              │                                        │
  	// 		└──────┴────────────────────────────────────────┴────────────────────────────────────────┘
  	//
  	// Captioned widget (config.image2_alignClasses defined)
  	// 		┌──────┬────────────────────────────────────────┬────────────────────────────────────────┐
  	// 		│Align │Internal form                           │Data                                    │
  	// 		├──────┼────────────────────────────────────────┼────────────────────────────────────────┤
  	// 		│none  │<wrapper>                               │<figure />                              │
  	// 		│      │ <figure />                             │                                        │
  	// 		│      │</wrapper>                              │                                        │
  	// 		├──────┼────────────────────────────────────────┼────────────────────────────────────────┤
  	// 		│left  │<wrapper class=”left”>                  │<figure class=”left” />                 │
  	// 		│      │ <figure />                             │                                        │
  	// 		│      │</wrapper>                              │                                        │
  	// 		├──────┼────────────────────────────────────────┼────────────────────────────────────────┤
  	// 		│center│<wrapper class=”center”>                │<div class=”center”>                    │
  	// 		│      │ <figure />                             │ <figure />                             │
  	// 		│      │</wrapper>                              │</p>                                    │
  	// 		├──────┼────────────────────────────────────────┼────────────────────────────────────────┤
  	// 		│right │<wrapper class=”right”>                 │<figure class=”right” />                │
  	// 		│      │ <figure />                             │                                        │
  	// 		│      │</wrapper>                              │                                        │
  	// 		└──────┴────────────────────────────────────────┴────────────────────────────────────────┘
  	//
  	// @param {CKEDITOR.editor}
  	// @returns {Object}
  	function widgetDef( editor ) {
  		var alignClasses = editor.config.image2_alignClasses,
  			captionedClass = editor.config.image2_captionedClass;
  
  		function deflate() {
  			if ( this.deflated )
  				return;
  
  			// Remember whether widget was focused before destroyed.
  			if ( editor.widgets.focused == this.widget )
  				this.focused = true;
  
  			editor.widgets.destroy( this.widget );
  
  			// Mark widget was destroyed.
  			this.deflated = true;
  		}
  
  		function inflate() {
  			var editable = editor.editable(),
  			doc = editor.document;
  
  			// Create a new widget. This widget will be either captioned
  			// non-captioned, block or inline according to what is the
  			// new state of the widget.
  			if ( this.deflated ) {
  				this.widget = editor.widgets.initOn( this.element, 'image', this.widget.data );
  
  				// Once widget was re-created, it may become an inline element without
  				// block wrapper (i.e. when unaligned, end not captioned). Let's do some
  				// sort of autoparagraphing here (http://dev.ckeditor.com/ticket/10853).
  				if ( this.widget.inline && !( new CKEDITOR.dom.elementPath( this.widget.wrapper, editable ).block ) ) {
  					var block = doc.createElement( editor.activeEnterMode == CKEDITOR.ENTER_P ? 'p' : 'div' );
  					block.replace( this.widget.wrapper );
  					this.widget.wrapper.move( block );
  				}
  
  				// The focus must be transferred from the old one (destroyed)
  				// to the new one (just created).
  				if ( this.focused ) {
  					this.widget.focus();
  					delete this.focused;
  				}
  
  				delete this.deflated;
  			}
  
  			// If now widget was destroyed just update wrapper's alignment.
  			// According to the new state.
  			else {
  				setWrapperAlign( this.widget, alignClasses );
  			}
  		}
  
  		return {
  			allowedContent: getWidgetAllowedContent( editor ),
  
  			requiredContent: 'img[src,alt]',
  
  			features: getWidgetFeatures( editor ),
  
  			styleableElements: 'img figure',
  
  			// This widget converts style-driven dimensions to attributes.
  			contentTransformations: [
  				[ 'img[width]: sizeToAttribute' ]
  			],
  
  			// This widget has an editable caption.
  			editables: {
  				caption: {
  					selector: 'figcaption',
  					allowedContent: 'br em strong sub sup u s; a[!href,target]'
  				}
  			},
  
  			parts: {
  				image: 'img',
  				caption: 'figcaption'
  				// parts#link defined in widget#init
  			},
  
  			// The name of this widget's dialog.
  			dialog: 'image2',
  
  			// Template of the widget: plain image.
  			template: template,
  
  			data: function() {
  				var features = this.features;
  
  				// Image can't be captioned when figcaption is disallowed (http://dev.ckeditor.com/ticket/11004).
  				if ( this.data.hasCaption && !editor.filter.checkFeature( features.caption ) )
  					this.data.hasCaption = false;
  
  				// Image can't be aligned when floating is disallowed (http://dev.ckeditor.com/ticket/11004).
  				if ( this.data.align != 'none' && !editor.filter.checkFeature( features.align ) )
  					this.data.align = 'none';
  
  				// Convert the internal form of the widget from the old state to the new one.
  				this.shiftState( {
  					widget: this,
  					element: this.element,
  					oldData: this.oldData,
  					newData: this.data,
  					deflate: deflate,
  					inflate: inflate
  				} );
  
  				// Update widget.parts.link since it will not auto-update unless widget
  				// is destroyed and re-inited.
  				if ( !this.data.link ) {
  					if ( this.parts.link )
  						delete this.parts.link;
  				} else {
  					if ( !this.parts.link )
  						this.parts.link = this.parts.image.getParent();
  				}
  
  				this.parts.image.setAttributes( {
  					src: this.data.src,
  
  					// This internal is required by the editor.
  					'data-cke-saved-src': this.data.src,
  
  					alt: this.data.alt
  				} );
  
  				// If shifting non-captioned -> captioned, remove classes
  				// related to styles from <img/>.
  				if ( this.oldData && !this.oldData.hasCaption && this.data.hasCaption ) {
  					for ( var c in this.data.classes )
  						this.parts.image.removeClass( c );
  				}
  
  				// Set dimensions of the image according to gathered data.
  				// Do it only when the attributes are allowed (http://dev.ckeditor.com/ticket/11004).
  				if ( editor.filter.checkFeature( features.dimension ) )
  					setDimensions( this );
  
  				// Cache current data.
  				this.oldData = CKEDITOR.tools.extend( {}, this.data );
  			},
  
  			init: function() {
  				var helpers = CKEDITOR.plugins.image2,
  					image = this.parts.image,
  					data = {
  						hasCaption: !!this.parts.caption,
  						src: image.getAttribute( 'src' ),
  						alt: image.getAttribute( 'alt' ) || '',
  						width: image.getAttribute( 'width' ) || '',
  						height: image.getAttribute( 'height' ) || '',
  
  						// Lock ratio is on by default (http://dev.ckeditor.com/ticket/10833).
  						lock: this.ready ? helpers.checkHasNaturalRatio( image ) : true
  					};
  
  				// If we used 'a' in widget#parts definition, it could happen that
  				// selected element is a child of widget.parts#caption. Since there's no clever
  				// way to solve it with CSS selectors, it's done like that. (http://dev.ckeditor.com/ticket/11783).
  				var link = image.getAscendant( 'a' );
  
  				if ( link && this.wrapper.contains( link ) )
  					this.parts.link = link;
  
  				// Depending on configuration, read style/class from element and
  				// then remove it. Removed style/class will be set on wrapper in #data listener.
  				// Note: Center alignment is detected during upcast, so only left/right cases
  				// are checked below.
  				if ( !data.align ) {
  					var alignElement = data.hasCaption ? this.element : image;
  
  					// Read the initial left/right alignment from the class set on element.
  					if ( alignClasses ) {
  						if ( alignElement.hasClass( alignClasses[ 0 ] ) ) {
  							data.align = 'left';
  						} else if ( alignElement.hasClass( alignClasses[ 2 ] ) ) {
  							data.align = 'right';
  						}
  
  						if ( data.align ) {
  							alignElement.removeClass( alignClasses[ alignmentsObj[ data.align ] ] );
  						} else {
  							data.align = 'none';
  						}
  					}
  					// Read initial float style from figure/image and then remove it.
  					else {
  						data.align = alignElement.getStyle( 'float' ) || 'none';
  						alignElement.removeStyle( 'float' );
  					}
  				}
  
  				// Update data.link object with attributes if the link has been discovered.
  				if ( editor.plugins.link && this.parts.link ) {
  					data.link = helpers.getLinkAttributesParser()( editor, this.parts.link );
  
  					// Get rid of cke_widget_* classes in data. Otherwise
  					// they might appear in link dialog.
  					var advanced = data.link.advanced;
  					if ( advanced && advanced.advCSSClasses ) {
  						advanced.advCSSClasses = CKEDITOR.tools.trim( advanced.advCSSClasses.replace( /cke_\S+/, '' ) );
  					}
  				}
  
  				// Get rid of extra vertical space when there's no caption.
  				// It will improve the look of the resizer.
  				this.wrapper[ ( data.hasCaption ? 'remove' : 'add' ) + 'Class' ]( 'cke_image_nocaption' );
  
  				this.setData( data );
  
  				// Setup dynamic image resizing with mouse.
  				// Don't initialize resizer when dimensions are disallowed (http://dev.ckeditor.com/ticket/11004).
  				if ( editor.filter.checkFeature( this.features.dimension ) && editor.config.image2_disableResizer !== true )
  					setupResizer( this );
  
  				this.shiftState = helpers.stateShifter( this.editor );
  
  				// Add widget editing option to its context menu.
  				this.on( 'contextMenu', function( evt ) {
  					evt.data.image = CKEDITOR.TRISTATE_OFF;
  
  					// Integrate context menu items for link.
  					// Note that widget may be wrapped in a link, which
  					// does not belong to that widget (http://dev.ckeditor.com/ticket/11814).
  					if ( this.parts.link || this.wrapper.getAscendant( 'a' ) )
  						evt.data.link = evt.data.unlink = CKEDITOR.TRISTATE_OFF;
  				} );
  
  				// Pass the reference to this widget to the dialog.
  				this.on( 'dialog', function( evt ) {
  					evt.data.widget = this;
  				}, this );
  			},
  
  			// Overrides default method to handle internal mutability of Image2.
  			// @see CKEDITOR.plugins.widget#addClass
  			addClass: function( className ) {
  				getStyleableElement( this ).addClass( className );
  			},
  
  			// Overrides default method to handle internal mutability of Image2.
  			// @see CKEDITOR.plugins.widget#hasClass
  			hasClass: function( className ) {
  				return getStyleableElement( this ).hasClass( className );
  			},
  
  			// Overrides default method to handle internal mutability of Image2.
  			// @see CKEDITOR.plugins.widget#removeClass
  			removeClass: function( className ) {
  				getStyleableElement( this ).removeClass( className );
  			},
  
  			// Overrides default method to handle internal mutability of Image2.
  			// @see CKEDITOR.plugins.widget#getClasses
  			getClasses: ( function() {
  				var classRegex = new RegExp( '^(' + [].concat( captionedClass, alignClasses ).join( '|' ) + ')$' );
  
  				return function() {
  					var classes = this.repository.parseElementClasses( getStyleableElement( this ).getAttribute( 'class' ) );
  
  					// Neither config.image2_captionedClass nor config.image2_alignClasses
  					// do not belong to style classes.
  					for ( var c in classes ) {
  						if ( classRegex.test( c ) )
  							delete classes[ c ];
  					}
  
  					return classes;
  				};
  			} )(),
  
  			upcast: upcastWidgetElement( editor ),
  			downcast: downcastWidgetElement( editor ),
  
  			getLabel: function() {
  				var label = ( this.data.alt || '' ) + ' ' + this.pathName;
  
  				return this.editor.lang.widget.label.replace( /%1/, label );
  			}
  		};
  	}
  
  	/**
  	 * A set of Enhanced Image (image2) plugin helpers.
  	 *
  	 * @class
  	 * @singleton
  	 */
  	CKEDITOR.plugins.image2 = {
  		stateShifter: function( editor ) {
  			// Tag name used for centering non-captioned widgets.
  			var doc = editor.document,
  				alignClasses = editor.config.image2_alignClasses,
  				captionedClass = editor.config.image2_captionedClass,
  				editable = editor.editable(),
  
  				// The order that stateActions get executed. It matters!
  				shiftables = [ 'hasCaption', 'align', 'link' ];
  
  			// Atomic procedures, one per state variable.
  			var stateActions = {
  				align: function( shift, oldValue, newValue ) {
  					var el = shift.element;
  
  					// Alignment changed.
  					if ( shift.changed.align ) {
  						// No caption in the new state.
  						if ( !shift.newData.hasCaption ) {
  							// Changed to "center" (non-captioned).
  							if ( newValue == 'center' ) {
  								shift.deflate();
  								shift.element = wrapInCentering( editor, el );
  							}
  
  							// Changed to "non-center" from "center" while caption removed.
  							if ( !shift.changed.hasCaption && oldValue == 'center' && newValue != 'center' ) {
  								shift.deflate();
  								shift.element = unwrapFromCentering( el );
  							}
  						}
  					}
  
  					// Alignment remains and "center" removed caption.
  					else if ( newValue == 'center' && shift.changed.hasCaption && !shift.newData.hasCaption ) {
  						shift.deflate();
  						shift.element = wrapInCentering( editor, el );
  					}
  
  					// Finally set display for figure.
  					if ( !alignClasses && el.is( 'figure' ) ) {
  						if ( newValue == 'center' )
  							el.setStyle( 'display', 'inline-block' );
  						else
  							el.removeStyle( 'display' );
  					}
  				},
  
  				hasCaption:	function( shift, oldValue, newValue ) {
  					// This action is for real state change only.
  					if ( !shift.changed.hasCaption )
  						return;
  
  					// Get <img/> or <a><img/></a> from widget. Note that widget element might itself
  					// be what we're looking for. Also element can be <p style="text-align:center"><a>...</a></p>.
  					var imageOrLink;
  					if ( shift.element.is( { img: 1, a: 1 } ) )
  						imageOrLink = shift.element;
  					else
  						imageOrLink =  shift.element.findOne( 'a,img' );
  
  					// Switching hasCaption always destroys the widget.
  					shift.deflate();
  
  					// There was no caption, but the caption is to be added.
  					if ( newValue ) {
  						// Create new <figure> from widget template.
  						var figure = CKEDITOR.dom.element.createFromHtml( templateBlock.output( {
  							captionedClass: captionedClass,
  							captionPlaceholder: editor.lang.image2.captionPlaceholder
  						} ), doc );
  
  						// Replace element with <figure>.
  						replaceSafely( figure, shift.element );
  
  						// Use old <img/> or <a><img/></a> instead of the one from the template,
  						// so we won't lose additional attributes.
  						imageOrLink.replace( figure.findOne( 'img' ) );
  
  						// Update widget's element.
  						shift.element = figure;
  					}
  
  					// The caption was present, but now it's to be removed.
  					else {
  						// Unwrap <img/> or <a><img/></a> from figure.
  						imageOrLink.replace( shift.element );
  
  						// Update widget's element.
  						shift.element = imageOrLink;
  					}
  				},
  
  				link: function( shift, oldValue, newValue ) {
  					if ( shift.changed.link ) {
  						var img = shift.element.is( 'img' ) ?
  								shift.element : shift.element.findOne( 'img' ),
  							link = shift.element.is( 'a' ) ?
  								shift.element : shift.element.findOne( 'a' ),
  							// Why deflate:
  							// If element is <img/>, it will be wrapped into <a>,
  							// which becomes a new widget.element.
  							// If element is <a><img/></a>, it will be unlinked
  							// so <img/> becomes a new widget.element.
  							needsDeflate = ( shift.element.is( 'a' ) && !newValue ) || ( shift.element.is( 'img' ) && newValue ),
  							newEl;
  
  						if ( needsDeflate )
  							shift.deflate();
  
  						// If unlinked the image, returned element is <img>.
  						if ( !newValue )
  							newEl = unwrapFromLink( link );
  						else {
  							// If linked the image, returned element is <a>.
  							if ( !oldValue )
  								newEl = wrapInLink( img, shift.newData.link );
  
  							// Set and remove all attributes associated with this state.
  							var attributes = CKEDITOR.plugins.image2.getLinkAttributesGetter()( editor, newValue );
  
  							if ( !CKEDITOR.tools.isEmpty( attributes.set ) )
  								( newEl || link ).setAttributes( attributes.set );
  
  							if ( attributes.removed.length )
  								( newEl || link ).removeAttributes( attributes.removed );
  						}
  
  						if ( needsDeflate )
  							shift.element = newEl;
  					}
  				}
  			};
  
  			function wrapInCentering( editor, element ) {
  				var attribsAndStyles = {};
  
  				if ( alignClasses )
  					attribsAndStyles.attributes = { 'class': alignClasses[ 1 ] };
  				else
  					attribsAndStyles.styles = { 'text-align': 'center' };
  
  				// There's no gentle way to center inline element with CSS, so create p/div
  				// that wraps widget contents and does the trick either with style or class.
  				var center = doc.createElement(
  					editor.activeEnterMode == CKEDITOR.ENTER_P ? 'p' : 'div', attribsAndStyles );
  
  				// Replace element with centering wrapper.
  				replaceSafely( center, element );
  				element.move( center );
  
  				return center;
  			}
  
  			function unwrapFromCentering( element ) {
  				var imageOrLink = element.findOne( 'a,img' );
  
  				imageOrLink.replace( element );
  
  				return imageOrLink;
  			}
  
  			// Wraps <img/> -> <a><img/></a>.
  			// Returns reference to <a>.
  			//
  			// @param {CKEDITOR.dom.element} img
  			// @param {Object} linkData
  			// @returns {CKEDITOR.dom.element}
  			function wrapInLink( img, linkData ) {
  				var link = doc.createElement( 'a', {
  					attributes: {
  						href: linkData.url
  					}
  				} );
  
  				link.replace( img );
  				img.move( link );
  
  				return link;
  			}
  
  			// De-wraps <a><img/></a> -> <img/>.
  			// Returns the reference to <img/>
  			//
  			// @param {CKEDITOR.dom.element} link
  			// @returns {CKEDITOR.dom.element}
  			function unwrapFromLink( link ) {
  				var img = link.findOne( 'img' );
  
  				img.replace( link );
  
  				return img;
  			}
  
  			function replaceSafely( replacing, replaced ) {
  				if ( replaced.getParent() ) {
  					var range = editor.createRange();
  
  					range.moveToPosition( replaced, CKEDITOR.POSITION_BEFORE_START );
  
  					// Remove old element. Do it before insertion to avoid a case when
  					// element is moved from 'replaced' element before it, what creates
  					// a tricky case which insertElementIntorRange does not handle.
  					replaced.remove();
  
  					editable.insertElementIntoRange( replacing, range );
  				}
  				else {
  					replacing.replace( replaced );
  				}
  			}
  
  			return function( shift ) {
  				var name, i;
  
  				shift.changed = {};
  
  				for ( i = 0; i < shiftables.length; i++ ) {
  					name = shiftables[ i ];
  
  					shift.changed[ name ] = shift.oldData ?
  						shift.oldData[ name ] !== shift.newData[ name ] : false;
  				}
  
  				// Iterate over possible state variables.
  				for ( i = 0; i < shiftables.length; i++ ) {
  					name = shiftables[ i ];
  
  					stateActions[ name ]( shift,
  						shift.oldData ? shift.oldData[ name ] : null,
  						shift.newData[ name ] );
  				}
  
  				shift.inflate();
  			};
  		},
  
  		/**
  		 * Checks whether the current image ratio matches the natural one
  		 * by comparing dimensions.
  		 *
  		 * @param {CKEDITOR.dom.element} image
  		 * @returns {Boolean}
  		 */
  		checkHasNaturalRatio: function( image ) {
  			var $ = image.$,
  				natural = this.getNatural( image );
  
  			// The reason for two alternative comparisons is that the rounding can come from
  			// both dimensions, e.g. there are two cases:
  			// 	1. height is computed as a rounded relation of the real height and the value of width,
  			//	2. width is computed as a rounded relation of the real width and the value of heigh.
  			return Math.round( $.clientWidth / natural.width * natural.height ) == $.clientHeight ||
  				Math.round( $.clientHeight / natural.height * natural.width ) == $.clientWidth;
  		},
  
  		/**
  		 * Returns natural dimensions of the image. For modern browsers
  		 * it uses natural(Width|Height). For old ones (IE8) it creates
  		 * a new image and reads the dimensions.
  		 *
  		 * @param {CKEDITOR.dom.element} image
  		 * @returns {Object}
  		 */
  		getNatural: function( image ) {
  			var dimensions;
  
  			if ( image.$.naturalWidth ) {
  				dimensions = {
  					width: image.$.naturalWidth,
  					height: image.$.naturalHeight
  				};
  			} else {
  				var img = new Image();
  				img.src = image.getAttribute( 'src' );
  
  				dimensions = {
  					width: img.width,
  					height: img.height
  				};
  			}
  
  			return dimensions;
  		},
  
  		/**
  		 * Returns an attribute getter function. Default getter comes from the Link plugin
  		 * and is documented by {@link CKEDITOR.plugins.link#getLinkAttributes}.
  		 *
  		 * **Note:** It is possible to override this method and use a custom getter e.g.
  		 * in the absence of the Link plugin.
  		 *
  		 * **Note:** If a custom getter is used, a data model format it produces
  		 * must be compatible with {@link CKEDITOR.plugins.link#getLinkAttributes}.
  		 *
  		 * **Note:** A custom getter must understand the data model format produced by
  		 * {@link #getLinkAttributesParser} to work correctly.
  		 *
  		 * @returns {Function} A function that gets (composes) link attributes.
  		 * @since 4.5.5
  		 */
  		getLinkAttributesGetter: function() {
  			// http://dev.ckeditor.com/ticket/13885
  			return CKEDITOR.plugins.link.getLinkAttributes;
  		},
  
  		/**
  		 * Returns an attribute parser function. Default parser comes from the Link plugin
  		 * and is documented by {@link CKEDITOR.plugins.link#parseLinkAttributes}.
  		 *
  		 * **Note:** It is possible to override this method and use a custom parser e.g.
  		 * in the absence of the Link plugin.
  		 *
  		 * **Note:** If a custom parser is used, a data model format produced by the parser
  		 * must be compatible with {@link #getLinkAttributesGetter}.
  		 *
  		 * **Note:** If a custom parser is used, it should be compatible with the
  		 * {@link CKEDITOR.plugins.link#parseLinkAttributes} data model format. Otherwise the
  		 * Link plugin dialog may not be populated correctly with parsed data. However
  		 * as long as Enhanced Image is **not** used with the Link plugin dialog, any custom data model
  		 * will work, being stored as an internal property of Enhanced Image widget's data only.
  		 *
  		 * @returns {Function} A function that parses attributes.
  		 * @since 4.5.5
  		 */
  		getLinkAttributesParser: function() {
  			// http://dev.ckeditor.com/ticket/13885
  			return CKEDITOR.plugins.link.parseLinkAttributes;
  		}
  	};
  
  	function setWrapperAlign( widget, alignClasses ) {
  		var wrapper = widget.wrapper,
  			align = widget.data.align,
  			hasCaption = widget.data.hasCaption;
  
  		if ( alignClasses ) {
  			// Remove all align classes first.
  			for ( var i = 3; i--; )
  				wrapper.removeClass( alignClasses[ i ] );
  
  			if ( align == 'center' ) {
  				// Avoid touching non-captioned, centered widgets because
  				// they have the class set on the element instead of wrapper:
  				//
  				// 	<div class="cke_widget_wrapper">
  				// 		<p class="center-class">
  				// 			<img />
  				// 		</p>
  				// 	</div>
  				if ( hasCaption ) {
  					wrapper.addClass( alignClasses[ 1 ] );
  				}
  			} else if ( align != 'none' ) {
  				wrapper.addClass( alignClasses[ alignmentsObj[ align ] ] );
  			}
  		} else {
  			if ( align == 'center' ) {
  				if ( hasCaption )
  					wrapper.setStyle( 'text-align', 'center' );
  				else
  					wrapper.removeStyle( 'text-align' );
  
  				wrapper.removeStyle( 'float' );
  			}
  			else {
  				if ( align == 'none' )
  					wrapper.removeStyle( 'float' );
  				else
  					wrapper.setStyle( 'float', align );
  
  				wrapper.removeStyle( 'text-align' );
  			}
  		}
  	}
  
  	// Returns a function that creates widgets from all <img> and
  	// <figure class="{config.image2_captionedClass}"> elements.
  	//
  	// @param {CKEDITOR.editor} editor
  	// @returns {Function}
  	function upcastWidgetElement( editor ) {
  		var isCenterWrapper = centerWrapperChecker( editor ),
  			captionedClass = editor.config.image2_captionedClass;
  
  		// @param {CKEDITOR.htmlParser.element} el
  		// @param {Object} data
  		return function( el, data ) {
  			var dimensions = { width: 1, height: 1 },
  				name = el.name,
  				image;
  
  			// http://dev.ckeditor.com/ticket/11110 Don't initialize on pasted fake objects.
  			if ( el.attributes[ 'data-cke-realelement' ] )
  				return;
  
  			// If a center wrapper is found, there are 3 possible cases:
  			//
  			// 1. <div style="text-align:center"><figure>...</figure></div>.
  			//    In this case centering is done with a class set on widget.wrapper.
  			//    Simply replace centering wrapper with figure (it's no longer necessary).
  			//
  			// 2. <p style="text-align:center"><img/></p>.
  			//    Nothing to do here: <p> remains for styling purposes.
  			//
  			// 3. <div style="text-align:center"><img/></div>.
  			//    Nothing to do here (2.) but that case is only possible in enterMode different
  			//    than ENTER_P.
  			if ( isCenterWrapper( el ) ) {
  				if ( name == 'div' ) {
  					var figure = el.getFirst( 'figure' );
  
  					// Case #1.
  					if ( figure ) {
  						el.replaceWith( figure );
  						el = figure;
  					}
  				}
  				// Cases #2 and #3 (handled transparently)
  
  				// If there's a centering wrapper, save it in data.
  				data.align = 'center';
  
  				// Image can be wrapped in link <a><img/></a>.
  				image = el.getFirst( 'img' ) || el.getFirst( 'a' ).getFirst( 'img' );
  			}
  
  			// No center wrapper has been found.
  			else if ( name == 'figure' && el.hasClass( captionedClass ) ) {
  				image = el.getFirst( 'img' ) || el.getFirst( 'a' ).getFirst( 'img' );
  
  				// Upcast linked image like <a><img/></a>.
  			} else if ( isLinkedOrStandaloneImage( el ) ) {
  				image = el.name == 'a' ? el.children[ 0 ] : el;
  			}
  
  			if ( !image )
  				return;
  
  			// If there's an image, then cool, we got a widget.
  			// Now just remove dimension attributes expressed with %.
  			for ( var d in dimensions ) {
  				var dimension = image.attributes[ d ];
  
  				if ( dimension && dimension.match( regexPercent ) )
  					delete image.attributes[ d ];
  			}
  
  			return el;
  		};
  	}
  
  	// Returns a function which transforms the widget to the external format
  	// according to the current configuration.
  	//
  	// @param {CKEDITOR.editor}
  	function downcastWidgetElement( editor ) {
  		var alignClasses = editor.config.image2_alignClasses;
  
  		// @param {CKEDITOR.htmlParser.element} el
  		return function( el ) {
  			// In case of <a><img/></a>, <img/> is the element to hold
  			// inline styles or classes (image2_alignClasses).
  			var attrsHolder = el.name == 'a' ? el.getFirst() : el,
  				attrs = attrsHolder.attributes,
  				align = this.data.align;
  
  			// De-wrap the image from resize handle wrapper.
  			// Only block widgets have one.
  			if ( !this.inline ) {
  				var resizeWrapper = el.getFirst( 'span' );
  
  				if ( resizeWrapper )
  					resizeWrapper.replaceWith( resizeWrapper.getFirst( { img: 1, a: 1 } ) );
  			}
  
  			if ( align && align != 'none' ) {
  				var styles = CKEDITOR.tools.parseCssText( attrs.style || '' );
  
  				// When the widget is captioned (<figure>) and internally centering is done
  				// with widget's wrapper style/class, in the external data representation,
  				// <figure> must be wrapped with an element holding an style/class:
  				//
  				// 	<div style="text-align:center">
  				// 		<figure class="image" style="display:inline-block">...</figure>
  				// 	</div>
  				// or
  				// 	<div class="some-center-class">
  				// 		<figure class="image">...</figure>
  				// 	</div>
  				//
  				if ( align == 'center' && el.name == 'figure' ) {
  					el = el.wrapWith( new CKEDITOR.htmlParser.element( 'div',
  						alignClasses ? { 'class': alignClasses[ 1 ] } : { style: 'text-align:center' } ) );
  				}
  
  				// If left/right, add float style to the downcasted element.
  				else if ( align in { left: 1, right: 1 } ) {
  					if ( alignClasses )
  						attrsHolder.addClass( alignClasses[ alignmentsObj[ align ] ] );
  					else
  						styles[ 'float' ] = align;
  				}
  
  				// Update element styles.
  				if ( !alignClasses && !CKEDITOR.tools.isEmpty( styles ) )
  					attrs.style = CKEDITOR.tools.writeCssText( styles );
  			}
  
  			return el;
  		};
  	}
  
  	// Returns a function that checks if an element is a centering wrapper.
  	//
  	// @param {CKEDITOR.editor} editor
  	// @returns {Function}
  	function centerWrapperChecker( editor ) {
  		var captionedClass = editor.config.image2_captionedClass,
  			alignClasses = editor.config.image2_alignClasses,
  			validChildren = { figure: 1, a: 1, img: 1 };
  
  		return function( el ) {
  			// Wrapper must be either <div> or <p>.
  			if ( !( el.name in { div: 1, p: 1 } ) )
  				return false;
  
  			var children = el.children;
  
  			// Centering wrapper can have only one child.
  			if ( children.length !== 1 )
  				return false;
  
  			var child = children[ 0 ];
  
  			// Only <figure> or <img /> can be first (only) child of centering wrapper,
  			// regardless of its type.
  			if ( !( child.name in validChildren ) )
  				return false;
  
  			// If centering wrapper is <p>, only <img /> can be the child.
  			//   <p style="text-align:center"><img /></p>
  			if ( el.name == 'p' ) {
  				if ( !isLinkedOrStandaloneImage( child ) )
  					return false;
  			}
  			// Centering <div> can hold <img/> or <figure>, depending on enterMode.
  			else {
  				// If a <figure> is the first (only) child, it must have a class.
  				//   <div style="text-align:center"><figure>...</figure><div>
  				if ( child.name == 'figure' ) {
  					if ( !child.hasClass( captionedClass ) )
  						return false;
  				} else {
  					// Centering <div> can hold <img/> or <a><img/></a> only when enterMode
  					// is ENTER_(BR|DIV).
  					//   <div style="text-align:center"><img /></div>
  					//   <div style="text-align:center"><a><img /></a></div>
  					if ( editor.enterMode == CKEDITOR.ENTER_P )
  						return false;
  
  					// Regardless of enterMode, a child which is not <figure> must be
  					// either <img/> or <a><img/></a>.
  					if ( !isLinkedOrStandaloneImage( child ) )
  						return false;
  				}
  			}
  
  			// Centering wrapper got to be... centering. If image2_alignClasses are defined,
  			// check for centering class. Otherwise, check the style.
  			if ( alignClasses ? el.hasClass( alignClasses[ 1 ] ) :
  					CKEDITOR.tools.parseCssText( el.attributes.style || '', true )[ 'text-align' ] == 'center' )
  				return true;
  
  			return false;
  		};
  	}
  
  	// Checks whether element is <img/> or <a><img/></a>.
  	//
  	// @param {CKEDITOR.htmlParser.element}
  	function isLinkedOrStandaloneImage( el ) {
  		if ( el.name == 'img' )
  			return true;
  		else if ( el.name == 'a' )
  			return el.children.length == 1 && el.getFirst( 'img' );
  
  		return false;
  	}
  
  	// Sets width and height of the widget image according to current widget data.
  	//
  	// @param {CKEDITOR.plugins.widget} widget
  	function setDimensions( widget ) {
  		var data = widget.data,
  			dimensions = { width: data.width, height: data.height },
  			image = widget.parts.image;
  
  		for ( var d in dimensions ) {
  			if ( dimensions[ d ] )
  				image.setAttribute( d, dimensions[ d ] );
  			else
  				image.removeAttribute( d );
  		}
  	}
  
  	// Defines all features related to drag-driven image resizing.
  	//
  	// @param {CKEDITOR.plugins.widget} widget
  	function setupResizer( widget ) {
  		var editor = widget.editor,
  			editable = editor.editable(),
  			doc = editor.document,
  
  			// Store the resizer in a widget for testing (http://dev.ckeditor.com/ticket/11004).
  			resizer = widget.resizer = doc.createElement( 'span' );
  
  		resizer.addClass( 'cke_image_resizer' );
  		resizer.setAttribute( 'title', editor.lang.image2.resizer );
  		resizer.append( new CKEDITOR.dom.text( '\u200b', doc ) );
  
  		// Inline widgets don't need a resizer wrapper as an image spans the entire widget.
  		if ( !widget.inline ) {
  			var imageOrLink = widget.parts.link || widget.parts.image,
  				oldResizeWrapper = imageOrLink.getParent(),
  				resizeWrapper = doc.createElement( 'span' );
  
  			resizeWrapper.addClass( 'cke_image_resizer_wrapper' );
  			resizeWrapper.append( imageOrLink );
  			resizeWrapper.append( resizer );
  			widget.element.append( resizeWrapper, true );
  
  			// Remove the old wrapper which could came from e.g. pasted HTML
  			// and which could be corrupted (e.g. resizer span has been lost).
  			if ( oldResizeWrapper.is( 'span' ) )
  				oldResizeWrapper.remove();
  		} else {
  			widget.wrapper.append( resizer );
  		}
  
  		// Calculate values of size variables and mouse offsets.
  		resizer.on( 'mousedown', function( evt ) {
  			var image = widget.parts.image,
  
  				// "factor" can be either 1 or -1. I.e.: For right-aligned images, we need to
  				// subtract the difference to get proper width, etc. Without "factor",
  				// resizer starts working the opposite way.
  				factor = widget.data.align == 'right' ? -1 : 1,
  
  				// The x-coordinate of the mouse relative to the screen
  				// when button gets pressed.
  				startX = evt.data.$.screenX,
  				startY = evt.data.$.screenY,
  
  				// The initial dimensions and aspect ratio of the image.
  				startWidth = image.$.clientWidth,
  				startHeight = image.$.clientHeight,
  				ratio = startWidth / startHeight,
  
  				listeners = [],
  
  				// A class applied to editable during resizing.
  				cursorClass = 'cke_image_s' + ( !~factor ? 'w' : 'e' ),
  
  				nativeEvt, newWidth, newHeight, updateData,
  				moveDiffX, moveDiffY, moveRatio;
  
  			// Save the undo snapshot first: before resizing.
  			editor.fire( 'saveSnapshot' );
  
  			// Mousemove listeners are removed on mouseup.
  			attachToDocuments( 'mousemove', onMouseMove, listeners );
  
  			// Clean up the mousemove listener. Update widget data if valid.
  			attachToDocuments( 'mouseup', onMouseUp, listeners );
  
  			// The entire editable will have the special cursor while resizing goes on.
  			editable.addClass( cursorClass );
  
  			// This is to always keep the resizer element visible while resizing.
  			resizer.addClass( 'cke_image_resizing' );
  
  			// Attaches an event to a global document if inline editor.
  			// Additionally, if classic (`iframe`-based) editor, also attaches the same event to `iframe`'s document.
  			function attachToDocuments( name, callback, collection ) {
  				var globalDoc = CKEDITOR.document,
  					listeners = [];
  
  				if ( !doc.equals( globalDoc ) )
  					listeners.push( globalDoc.on( name, callback ) );
  
  				listeners.push( doc.on( name, callback ) );
  
  				if ( collection ) {
  					for ( var i = listeners.length; i--; )
  						collection.push( listeners.pop() );
  				}
  			}
  
  			// Calculate with first, and then adjust height, preserving ratio.
  			function adjustToX() {
  				newWidth = startWidth + factor * moveDiffX;
  				newHeight = Math.round( newWidth / ratio );
  			}
  
  			// Calculate height first, and then adjust width, preserving ratio.
  			function adjustToY() {
  				newHeight = startHeight - moveDiffY;
  				newWidth = Math.round( newHeight * ratio );
  			}
  
  			// This is how variables refer to the geometry.
  			// Note: x corresponds to moveOffset, this is the position of mouse
  			// Note: o corresponds to [startX, startY].
  			//
  			// 	+--------------+--------------+
  			// 	|              |              |
  			// 	|      I       |      II      |
  			// 	|              |              |
  			// 	+------------- o -------------+ _ _ _
  			// 	|              |              |      ^
  			// 	|      VI      |     III      |      | moveDiffY
  			// 	|              |         x _ _ _ _ _ v
  			// 	+--------------+---------|----+
  			// 	               |         |
  			// 	                <------->
  			// 	                moveDiffX
  			function onMouseMove( evt ) {
  				nativeEvt = evt.data.$;
  
  				// This is how far the mouse is from the point the button was pressed.
  				moveDiffX = nativeEvt.screenX - startX;
  				moveDiffY = startY - nativeEvt.screenY;
  
  				// This is the aspect ratio of the move difference.
  				moveRatio = Math.abs( moveDiffX / moveDiffY );
  
  				// Left, center or none-aligned widget.
  				if ( factor == 1 ) {
  					if ( moveDiffX <= 0 ) {
  						// Case: IV.
  						if ( moveDiffY <= 0 )
  							adjustToX();
  
  						// Case: I.
  						else {
  							if ( moveRatio >= ratio )
  								adjustToX();
  							else
  								adjustToY();
  						}
  					} else {
  						// Case: III.
  						if ( moveDiffY <= 0 ) {
  							if ( moveRatio >= ratio )
  								adjustToY();
  							else
  								adjustToX();
  						}
  
  						// Case: II.
  						else {
  							adjustToY();
  						}
  					}
  				}
  
  				// Right-aligned widget. It mirrors behaviours, so I becomes II,
  				// IV becomes III and vice-versa.
  				else {
  					if ( moveDiffX <= 0 ) {
  						// Case: IV.
  						if ( moveDiffY <= 0 ) {
  							if ( moveRatio >= ratio )
  								adjustToY();
  							else
  								adjustToX();
  						}
  
  						// Case: I.
  						else {
  							adjustToY();
  						}
  					} else {
  						// Case: III.
  						if ( moveDiffY <= 0 )
  							adjustToX();
  
  						// Case: II.
  						else {
  							if ( moveRatio >= ratio ) {
  								adjustToX();
  							} else {
  								adjustToY();
  							}
  						}
  					}
  				}
  
  				// Don't update attributes if less than 10.
  				// This is to prevent images to visually disappear.
  				if ( newWidth >= 15 && newHeight >= 15 ) {
  					image.setAttributes( { width: newWidth, height: newHeight } );
  					updateData = true;
  				} else {
  					updateData = false;
  				}
  			}
  
  			function onMouseUp() {
  				var l;
  
  				while ( ( l = listeners.pop() ) )
  					l.removeListener();
  
  				// Restore default cursor by removing special class.
  				editable.removeClass( cursorClass );
  
  				// This is to bring back the regular behaviour of the resizer.
  				resizer.removeClass( 'cke_image_resizing' );
  
  				if ( updateData ) {
  					widget.setData( { width: newWidth, height: newHeight } );
  
  					// Save another undo snapshot: after resizing.
  					editor.fire( 'saveSnapshot' );
  				}
  
  				// Don't update data twice or more.
  				updateData = false;
  			}
  		} );
  
  		// Change the position of the widget resizer when data changes.
  		widget.on( 'data', function() {
  			resizer[ widget.data.align == 'right' ? 'addClass' : 'removeClass' ]( 'cke_image_resizer_left' );
  		} );
  	}
  
  	// Integrates widget alignment setting with justify
  	// plugin's commands (execution and refreshment).
  	// @param {CKEDITOR.editor} editor
  	// @param {String} value 'left', 'right', 'center' or 'block'
  	function alignCommandIntegrator( editor ) {
  		var execCallbacks = [],
  			enabled;
  
  		return function( value ) {
  			var command = editor.getCommand( 'justify' + value );
  
  			// Most likely, the justify plugin isn't loaded.
  			if ( !command )
  				return;
  
  			// This command will be manually refreshed along with
  			// other commands after exec.
  			execCallbacks.push( function() {
  				command.refresh( editor, editor.elementPath() );
  			} );
  
  			if ( value in { right: 1, left: 1, center: 1 } ) {
  				command.on( 'exec', function( evt ) {
  					var widget = getFocusedWidget( editor );
  
  					if ( widget ) {
  						widget.setData( 'align', value );
  
  						// Once the widget changed its align, all the align commands
  						// must be refreshed: the event is to be cancelled.
  						for ( var i = execCallbacks.length; i--; )
  							execCallbacks[ i ]();
  
  						evt.cancel();
  					}
  				} );
  			}
  
  			command.on( 'refresh', function( evt ) {
  				var widget = getFocusedWidget( editor ),
  					allowed = { right: 1, left: 1, center: 1 };
  
  				if ( !widget )
  					return;
  
  				// Cache "enabled" on first use. This is because filter#checkFeature may
  				// not be available during plugin's afterInit in the future — a moment when
  				// alignCommandIntegrator is called.
  				if ( enabled === undefined )
  					enabled = editor.filter.checkFeature( editor.widgets.registered.image.features.align );
  
  				// Don't allow justify commands when widget alignment is disabled (http://dev.ckeditor.com/ticket/11004).
  				if ( !enabled )
  					this.setState( CKEDITOR.TRISTATE_DISABLED );
  				else {
  					this.setState(
  						( widget.data.align == value ) ? (
  							CKEDITOR.TRISTATE_ON
  						) : (
  							( value in allowed ) ? CKEDITOR.TRISTATE_OFF : CKEDITOR.TRISTATE_DISABLED
  						)
  					);
  				}
  
  				evt.cancel();
  			} );
  		};
  	}
  
  	function linkCommandIntegrator( editor ) {
  		// Nothing to integrate with if link is not loaded.
  		if ( !editor.plugins.link )
  			return;
  
  		CKEDITOR.on( 'dialogDefinition', function( evt ) {
  			var dialog = evt.data;
  
  			if ( dialog.name == 'link' ) {
  				var def = dialog.definition;
  
  				var onShow = def.onShow,
  					onOk = def.onOk;
  
  				def.onShow = function() {
  					var widget = getFocusedWidget( editor ),
  						displayTextField = this.getContentElement( 'info', 'linkDisplayText' ).getElement().getParent().getParent();
  
  					// Widget cannot be enclosed in a link, i.e.
  					//		<a>foo<inline widget/>bar</a>
  					if ( widget && ( widget.inline ? !widget.wrapper.getAscendant( 'a' ) : 1 ) ) {
  						this.setupContent( widget.data.link || {} );
  
  						// Hide the display text in case of linking image2 widget.
  						displayTextField.hide();
  					} else {
  						// Make sure that display text is visible, as it might be hidden by image2 integration
  						// before.
  						displayTextField.show();
  						onShow.apply( this, arguments );
  					}
  				};
  
  				// Set widget data if linking the widget using
  				// link dialog (instead of default action).
  				// State shifter handles data change and takes
  				// care of internal DOM structure of linked widget.
  				def.onOk = function() {
  					var widget = getFocusedWidget( editor );
  
  					// Widget cannot be enclosed in a link, i.e.
  					//		<a>foo<inline widget/>bar</a>
  					if ( widget && ( widget.inline ? !widget.wrapper.getAscendant( 'a' ) : 1 ) ) {
  						var data = {};
  
  						// Collect data from fields.
  						this.commitContent( data );
  
  						// Set collected data to widget.
  						widget.setData( 'link', data );
  					} else {
  						onOk.apply( this, arguments );
  					}
  				};
  			}
  		} );
  
  		// Overwrite default behaviour of unlink command.
  		editor.getCommand( 'unlink' ).on( 'exec', function( evt ) {
  			var widget = getFocusedWidget( editor );
  
  			// Override unlink only when link truly belongs to the widget.
  			// If wrapped inline widget in a link, let default unlink work (http://dev.ckeditor.com/ticket/11814).
  			if ( !widget || !widget.parts.link )
  				return;
  
  			widget.setData( 'link', null );
  
  			// Selection (which is fake) may not change if unlinked image in focused widget,
  			// i.e. if captioned image. Let's refresh command state manually here.
  			this.refresh( editor, editor.elementPath() );
  
  			evt.cancel();
  		} );
  
  		// Overwrite default refresh of unlink command.
  		editor.getCommand( 'unlink' ).on( 'refresh', function( evt ) {
  			var widget = getFocusedWidget( editor );
  
  			if ( !widget )
  				return;
  
  			// Note that widget may be wrapped in a link, which
  			// does not belong to that widget (http://dev.ckeditor.com/ticket/11814).
  			this.setState( widget.data.link || widget.wrapper.getAscendant( 'a' ) ?
  				CKEDITOR.TRISTATE_OFF : CKEDITOR.TRISTATE_DISABLED );
  
  			evt.cancel();
  		} );
  	}
  
  	// Returns the focused widget, if of the type specific for this plugin.
  	// If no widget is focused, `null` is returned.
  	//
  	// @param {CKEDITOR.editor}
  	// @returns {CKEDITOR.plugins.widget}
  	function getFocusedWidget( editor ) {
  		var widget = editor.widgets.focused;
  
  		if ( widget && widget.name == 'image' )
  			return widget;
  
  		return null;
  	}
  
  	// Returns a set of widget allowedContent rules, depending
  	// on configurations like config#image2_alignClasses or
  	// config#image2_captionedClass.
  	//
  	// @param {CKEDITOR.editor}
  	// @returns {Object}
  	function getWidgetAllowedContent( editor ) {
  		var alignClasses = editor.config.image2_alignClasses,
  			rules = {
  				// Widget may need <div> or <p> centering wrapper.
  				div: {
  					match: centerWrapperChecker( editor )
  				},
  				p: {
  					match: centerWrapperChecker( editor )
  				},
  				img: {
  					attributes: '!src,alt,width,height'
  				},
  				figure: {
  					classes: '!' + editor.config.image2_captionedClass
  				},
  				figcaption: true
  			};
  
  		if ( alignClasses ) {
  			// Centering class from the config.
  			rules.div.classes = alignClasses[ 1 ];
  			rules.p.classes = rules.div.classes;
  
  			// Left/right classes from the config.
  			rules.img.classes = alignClasses[ 0 ] + ',' + alignClasses[ 2 ];
  			rules.figure.classes += ',' + rules.img.classes;
  		} else {
  			// Centering with text-align.
  			rules.div.styles = 'text-align';
  			rules.p.styles = 'text-align';
  
  			rules.img.styles = 'float';
  			rules.figure.styles = 'float,display';
  		}
  
  		return rules;
  	}
  
  	// Returns a set of widget feature rules, depending
  	// on editor configuration. Note that the following may not cover
  	// all the possible cases since requiredContent supports a single
  	// tag only.
  	//
  	// @param {CKEDITOR.editor}
  	// @returns {Object}
  	function getWidgetFeatures( editor ) {
  		var alignClasses = editor.config.image2_alignClasses,
  			features = {
  				dimension: {
  					requiredContent: 'img[width,height]'
  				},
  				align: {
  					requiredContent: 'img' +
  						( alignClasses ? '(' + alignClasses[ 0 ] + ')' : '{float}' )
  				},
  				caption: {
  					requiredContent: 'figcaption'
  				}
  			};
  
  		return features;
  	}
  
  	// Returns element which is styled, considering current
  	// state of the widget.
  	//
  	// @see CKEDITOR.plugins.widget#applyStyle
  	// @param {CKEDITOR.plugins.widget} widget
  	// @returns {CKEDITOR.dom.element}
  	function getStyleableElement( widget ) {
  		return widget.data.hasCaption ? widget.element : widget.parts.image;
  	}
  } )();
  
  /**
   * A CSS class applied to the `<figure>` element of a captioned image.
   *
   * Read more in the [documentation](#!/guide/dev_captionedimage) and see the
   * [SDK sample](http://sdk.ckeditor.com/samples/captionedimage.html).
   *
   *		// Changes the class to "captionedImage".
   *		config.image2_captionedClass = 'captionedImage';
   *
   * @cfg {String} [image2_captionedClass='image']
   * @member CKEDITOR.config
   */
  CKEDITOR.config.image2_captionedClass = 'image';
  
  /**
   * Determines whether dimension inputs should be automatically filled when the image URL changes in the Enhanced Image
   * plugin dialog window.
   *
   * Read more in the [documentation](#!/guide/dev_captionedimage) and see the
   * [SDK sample](http://sdk.ckeditor.com/samples/captionedimage.html).
   *
   *		config.image2_prefillDimensions = false;
   *
   * @since 4.5
   * @cfg {Boolean} [image2_prefillDimensions=true]
   * @member CKEDITOR.config
   */
  
  /**
   * Disables the image resizer. By default the resizer is enabled.
   *
   * Read more in the [documentation](#!/guide/dev_captionedimage) and see the
   * [SDK sample](http://sdk.ckeditor.com/samples/captionedimage.html).
   *
   *		config.image2_disableResizer = true;
   *
   * @since 4.5
   * @cfg {Boolean} [image2_disableResizer=false]
   * @member CKEDITOR.config
   */
  
  /**
   * CSS classes applied to aligned images. Useful to take control over the way
   * the images are aligned, i.e. to customize output HTML and integrate external stylesheets.
   *
   * Classes should be defined in an array of three elements, containing left, center, and right
   * alignment classes, respectively. For example:
   *
   *		config.image2_alignClasses = [ 'align-left', 'align-center', 'align-right' ];
   *
   * **Note**: Once this configuration option is set, the plugin will no longer produce inline
   * styles for alignment. It means that e.g. the following HTML will be produced:
   *
   *		<img alt="My image" class="custom-center-class" src="foo.png" />
   *
   * instead of:
   *
   *		<img alt="My image" style="float:left" src="foo.png" />
   *
   * **Note**: Once this configuration option is set, corresponding style definitions
   * must be supplied to the editor:
   *
   * * For [classic editor](#!/guide/dev_framed) it can be done by defining additional
   * styles in the {@link CKEDITOR.config#contentsCss stylesheets loaded by the editor}. The same
   * styles must be provided on the target page where the content will be loaded.
   * * For [inline editor](#!/guide/dev_inline) the styles can be defined directly
   * with `<style> ... <style>` or `<link href="..." rel="stylesheet">`, i.e. within the `<head>`
   * of the page.
   *
   * For example, considering the following configuration:
   *
   *		config.image2_alignClasses = [ 'align-left', 'align-center', 'align-right' ];
   *
   * CSS rules can be defined as follows:
   *
   *		.align-left {
   *			float: left;
   *		}
   *
   *		.align-right {
   *			float: right;
   *		}
   *
   *		.align-center {
   *			text-align: center;
   *		}
   *
   *		.align-center > figure {
   *			display: inline-block;
   *		}
   *
   * Read more in the [documentation](#!/guide/dev_captionedimage) and see the
   * [SDK sample](http://sdk.ckeditor.com/samples/captionedimage.html).
   *
   * @since 4.4
   * @cfg {String[]} [image2_alignClasses=null]
   * @member CKEDITOR.config
   */
  
  /**
   * Determines whether alternative text is required for the captioned image.
   *
   *		config.image2_altRequired = true;
   *
   * Read more in the [documentation](#!/guide/dev_captionedimage) and see the
   * [SDK sample](http://sdk.ckeditor.com/samples/captionedimage.html).
   *
   * @since 4.6.0
   * @cfg {Boolean} [image2_altRequired=false]
   * @member CKEDITOR.config
   */
  