1 /*
  2 Copyright (c) 2003-2012, CKSource - Frederico Knabben. All rights reserved.
  3 For licensing, see LICENSE.html or http://ckeditor.com/license
  4 */
  5 
  6 CKEDITOR.dialog.add( 'link', function( editor )
  7 {
  8 	var plugin = CKEDITOR.plugins.link;
  9 	// Handles the event when the "Target" selection box is changed.
 10 	var targetChanged = function()
 11 	{
 12 		var dialog = this.getDialog(),
 13 			popupFeatures = dialog.getContentElement( 'target', 'popupFeatures' ),
 14 			targetName = dialog.getContentElement( 'target', 'linkTargetName' ),
 15 			value = this.getValue();
 16 
 17 		if ( !popupFeatures || !targetName )
 18 			return;
 19 
 20 		popupFeatures = popupFeatures.getElement();
 21 		popupFeatures.hide();
 22 		targetName.setValue( '' );
 23 
 24 		switch ( value )
 25  		{
 26 			case 'frame' :
 27 				targetName.setLabel( editor.lang.link.targetFrameName );
 28 				targetName.getElement().show();
 29 				break;
 30 			case 'popup' :
 31 				popupFeatures.show();
 32 				targetName.setLabel( editor.lang.link.targetPopupName );
 33 				targetName.getElement().show();
 34 				break;
 35 			default :
 36 				targetName.setValue( value );
 37 				targetName.getElement().hide();
 38 				break;
 39  		}
 40 
 41 	};
 42 
 43 	// Handles the event when the "Type" selection box is changed.
 44 	var linkTypeChanged = function()
 45 	{
 46 		var dialog = this.getDialog(),
 47 			partIds = [ 'urlOptions', 'anchorOptions', 'emailOptions' ],
 48 			typeValue = this.getValue(),
 49 			uploadTab = dialog.definition.getContents( 'upload' ),
 50 			uploadInitiallyHidden = uploadTab && uploadTab.hidden;
 51 
 52 		if ( typeValue == 'url' )
 53 		{
 54 			if ( editor.config.linkShowTargetTab )
 55 				dialog.showPage( 'target' );
 56 			if ( !uploadInitiallyHidden )
 57 				dialog.showPage( 'upload' );
 58 		}
 59 		else
 60 		{
 61 			dialog.hidePage( 'target' );
 62 			if ( !uploadInitiallyHidden )
 63 				dialog.hidePage( 'upload' );
 64 		}
 65 
 66 		for ( var i = 0 ; i < partIds.length ; i++ )
 67 		{
 68 			var element = dialog.getContentElement( 'info', partIds[i] );
 69 			if ( !element )
 70 				continue;
 71 
 72 			element = element.getElement().getParent().getParent();
 73 			if ( partIds[i] == typeValue + 'Options' )
 74 				element.show();
 75 			else
 76 				element.hide();
 77 		}
 78 
 79 		dialog.layout();
 80 	};
 81 
 82 	// Loads the parameters in a selected link to the link dialog fields.
 83 	var javascriptProtocolRegex = /^javascript:/,
 84 		emailRegex = /^mailto:([^?]+)(?:\?(.+))?$/,
 85 		emailSubjectRegex = /subject=([^;?:@&=$,\/]*)/,
 86 		emailBodyRegex = /body=([^;?:@&=$,\/]*)/,
 87 		anchorRegex = /^#(.*)$/,
 88 		urlRegex = /^((?:http|https|ftp|news):\/\/)?(.*)$/,
 89 		selectableTargets = /^(_(?:self|top|parent|blank))$/,
 90 		encodedEmailLinkRegex = /^javascript:void\(location\.href='mailto:'\+String\.fromCharCode\(([^)]+)\)(?:\+'(.*)')?\)$/,
 91 		functionCallProtectedEmailLinkRegex = /^javascript:([^(]+)\(([^)]+)\)$/;
 92 
 93 	var popupRegex =
 94 		/\s*window.open\(\s*this\.href\s*,\s*(?:'([^']*)'|null)\s*,\s*'([^']*)'\s*\)\s*;\s*return\s*false;*\s*/;
 95 	var popupFeaturesRegex = /(?:^|,)([^=]+)=(\d+|yes|no)/gi;
 96 
 97 	var parseLink = function( editor, element )
 98 	{
 99 		var href = ( element  && ( element.data( 'cke-saved-href' ) || element.getAttribute( 'href' ) ) ) || '',
100 		 	javascriptMatch,
101 			emailMatch,
102 			anchorMatch,
103 			urlMatch,
104 			retval = {};
105 
106 		if ( ( javascriptMatch = href.match( javascriptProtocolRegex ) ) )
107 		{
108 			if ( emailProtection == 'encode' )
109 			{
110 				href = href.replace( encodedEmailLinkRegex,
111 						function ( match, protectedAddress, rest )
112 						{
113 							return 'mailto:' +
114 							       String.fromCharCode.apply( String, protectedAddress.split( ',' ) ) +
115 							       ( rest && unescapeSingleQuote( rest ) );
116 						});
117 			}
118 			// Protected email link as function call.
119 			else if ( emailProtection )
120 			{
121 				href.replace( functionCallProtectedEmailLinkRegex, function( match, funcName, funcArgs )
122 				{
123 					if ( funcName == compiledProtectionFunction.name )
124 					{
125 						retval.type = 'email';
126 						var email = retval.email = {};
127 
128 						var paramRegex = /[^,\s]+/g,
129 							paramQuoteRegex = /(^')|('$)/g,
130 							paramsMatch = funcArgs.match( paramRegex ),
131 							paramsMatchLength = paramsMatch.length,
132 							paramName,
133 							paramVal;
134 
135 						for ( var i = 0; i < paramsMatchLength; i++ )
136 						{
137 							paramVal = decodeURIComponent( unescapeSingleQuote( paramsMatch[ i ].replace( paramQuoteRegex, '' ) ) );
138 							paramName = compiledProtectionFunction.params[ i ].toLowerCase();
139 							email[ paramName ] = paramVal;
140 						}
141 						email.address = [ email.name, email.domain ].join( '@' );
142 					}
143 				} );
144 			}
145 		}
146 
147 		if ( !retval.type )
148 		{
149 			if ( ( anchorMatch = href.match( anchorRegex ) ) )
150 			{
151 				retval.type = 'anchor';
152 				retval.anchor = {};
153 				retval.anchor.name = retval.anchor.id = anchorMatch[1];
154 			}
155 			// Protected email link as encoded string.
156 			else if ( ( emailMatch = href.match( emailRegex ) ) )
157 			{
158 				var subjectMatch = href.match( emailSubjectRegex ),
159 					bodyMatch = href.match( emailBodyRegex );
160 
161 				retval.type = 'email';
162 				var email = ( retval.email = {} );
163 				email.address = emailMatch[ 1 ];
164 				subjectMatch && ( email.subject = decodeURIComponent( subjectMatch[ 1 ] ) );
165 				bodyMatch && ( email.body = decodeURIComponent( bodyMatch[ 1 ] ) );
166 			}
167 			// urlRegex matches empty strings, so need to check for href as well.
168 			else if (  href && ( urlMatch = href.match( urlRegex ) ) )
169 			{
170 				retval.type = 'url';
171 				retval.url = {};
172 				retval.url.protocol = urlMatch[1];
173 				retval.url.url = urlMatch[2];
174 			}
175 			else
176 				retval.type = 'url';
177 		}
178 
179 		// Load target and popup settings.
180 		if ( element )
181 		{
182 			var target = element.getAttribute( 'target' );
183 			retval.target = {};
184 			retval.adv = {};
185 
186 			// IE BUG: target attribute is an empty string instead of null in IE if it's not set.
187 			if ( !target )
188 			{
189 				var onclick = element.data( 'cke-pa-onclick' ) || element.getAttribute( 'onclick' ),
190 					onclickMatch = onclick && onclick.match( popupRegex );
191 				if ( onclickMatch )
192 				{
193 					retval.target.type = 'popup';
194 					retval.target.name = onclickMatch[1];
195 
196 					var featureMatch;
197 					while ( ( featureMatch = popupFeaturesRegex.exec( onclickMatch[2] ) ) )
198 					{
199 						// Some values should remain numbers (#7300)
200 						if ( ( featureMatch[2] == 'yes' || featureMatch[2] == '1' ) && !( featureMatch[1] in { height:1, width:1, top:1, left:1 } ) )
201 							retval.target[ featureMatch[1] ] = true;
202 						else if ( isFinite( featureMatch[2] ) )
203 							retval.target[ featureMatch[1] ] = featureMatch[2];
204 					}
205 				}
206 			}
207 			else
208 			{
209 				var targetMatch = target.match( selectableTargets );
210 				if ( targetMatch )
211 					retval.target.type = retval.target.name = target;
212 				else
213 				{
214 					retval.target.type = 'frame';
215 					retval.target.name = target;
216 				}
217 			}
218 
219 			var me = this;
220 			var advAttr = function( inputName, attrName )
221 			{
222 				var value = element.getAttribute( attrName );
223 				if ( value !== null )
224 					retval.adv[ inputName ] = value || '';
225 			};
226 			advAttr( 'advId', 'id' );
227 			advAttr( 'advLangDir', 'dir' );
228 			advAttr( 'advAccessKey', 'accessKey' );
229 
230 			retval.adv.advName =
231 				element.data( 'cke-saved-name' )
232 				|| element.getAttribute( 'name' )
233 				|| '';
234 			advAttr( 'advLangCode', 'lang' );
235 			advAttr( 'advTabIndex', 'tabindex' );
236 			advAttr( 'advTitle', 'title' );
237 			advAttr( 'advContentType', 'type' );
238 			CKEDITOR.plugins.link.synAnchorSelector ?
239 				retval.adv.advCSSClasses = getLinkClass( element )
240 				: advAttr( 'advCSSClasses', 'class' );
241 			advAttr( 'advCharset', 'charset' );
242 			advAttr( 'advStyles', 'style' );
243 			advAttr( 'advRel', 'rel' );
244 		}
245 
246 		// Find out whether we have any anchors in the editor.
247 		var anchors = retval.anchors = [],
248 			i, count, item;
249 
250 		// For some browsers we set contenteditable="false" on anchors, making document.anchors not to include them, so we must traverse the links manually (#7893).
251 		if ( CKEDITOR.plugins.link.emptyAnchorFix )
252 		{
253 			var links = editor.document.getElementsByTag( 'a' );
254 			for ( i = 0, count = links.count(); i < count; i++ )
255 			{
256 				item = links.getItem( i );
257 				if ( item.data( 'cke-saved-name' ) || item.hasAttribute( 'name' ) )
258 					anchors.push( { name : item.data( 'cke-saved-name' ) || item.getAttribute( 'name' ), id : item.getAttribute( 'id' ) } );
259 			}
260 		}
261 		else
262 		{
263 			var anchorList = new CKEDITOR.dom.nodeList( editor.document.$.anchors );
264 			for ( i = 0, count = anchorList.count(); i < count; i++ )
265 			{
266 				item = anchorList.getItem( i );
267 				anchors[ i ] = { name : item.getAttribute( 'name' ), id : item.getAttribute( 'id' ) };
268 			}
269 		}
270 
271 		if ( CKEDITOR.plugins.link.fakeAnchor )
272 		{
273 			var imgs = editor.document.getElementsByTag( 'img' );
274 			for ( i = 0, count = imgs.count(); i < count; i++ )
275 			{
276 				if ( ( item = CKEDITOR.plugins.link.tryRestoreFakeAnchor( editor, imgs.getItem( i ) ) ) )
277 					anchors.push( { name : item.getAttribute( 'name' ), id : item.getAttribute( 'id' ) } );
278 			}
279 		}
280 
281 		// Record down the selected element in the dialog.
282 		this._.selectedElement = element;
283 		return retval;
284 	};
285 
286 	var setupParams = function( page, data )
287 	{
288 		if ( data[page] )
289 			this.setValue( data[page][this.id] || '' );
290 	};
291 
292 	var setupPopupParams = function( data )
293 	{
294 		return setupParams.call( this, 'target', data );
295 	};
296 
297 	var setupAdvParams = function( data )
298 	{
299 		return setupParams.call( this, 'adv', data );
300 	};
301 
302 	var commitParams = function( page, data )
303 	{
304 		if ( !data[page] )
305 			data[page] = {};
306 
307 		data[page][this.id] = this.getValue() || '';
308 	};
309 
310 	var commitPopupParams = function( data )
311 	{
312 		return commitParams.call( this, 'target', data );
313 	};
314 
315 	var commitAdvParams = function( data )
316 	{
317 		return commitParams.call( this, 'adv', data );
318 	};
319 
320 	function unescapeSingleQuote( str )
321 	{
322 		return str.replace( /\\'/g, '\'' );
323 	}
324 
325 	function escapeSingleQuote( str )
326 	{
327 		return str.replace( /'/g, '\\$&' );
328 	}
329 
330 	var emailProtection = editor.config.emailProtection || '';
331 
332 	// Compile the protection function pattern.
333 	if ( emailProtection && emailProtection != 'encode' )
334 	{
335 		var compiledProtectionFunction = {};
336 
337 		emailProtection.replace( /^([^(]+)\(([^)]+)\)$/, function( match, funcName, params )
338 		{
339 			compiledProtectionFunction.name = funcName;
340 			compiledProtectionFunction.params = [];
341 			params.replace( /[^,\s]+/g, function( param )
342 			{
343 				compiledProtectionFunction.params.push( param );
344 			} );
345 		} );
346 	}
347 
348 	function protectEmailLinkAsFunction( email )
349 	{
350 		var retval,
351 			name = compiledProtectionFunction.name,
352 			params = compiledProtectionFunction.params,
353 			paramName,
354 			paramValue;
355 
356 		retval = [ name, '(' ];
357 		for ( var i = 0; i < params.length; i++ )
358 		{
359 			paramName = params[ i ].toLowerCase();
360 			paramValue = email[ paramName ];
361 
362 			i > 0 && retval.push( ',' );
363 			retval.push( '\'',
364 						 paramValue ?
365 						 escapeSingleQuote( encodeURIComponent( email[ paramName ] ) )
366 						 : '',
367 						 '\'');
368 		}
369 		retval.push( ')' );
370 		return retval.join( '' );
371 	}
372 
373 	function protectEmailAddressAsEncodedString( address )
374 	{
375 		var charCode,
376 			length = address.length,
377 			encodedChars = [];
378 		for ( var i = 0; i < length; i++ )
379 		{
380 			charCode = address.charCodeAt( i );
381 			encodedChars.push( charCode );
382 		}
383 		return 'String.fromCharCode(' + encodedChars.join( ',' ) + ')';
384 	}
385 
386 	function getLinkClass( ele )
387 	{
388 		var className = ele.getAttribute( 'class' );
389 		return className ? className.replace( /\s*(?:cke_anchor_empty|cke_anchor)(?:\s*$)?/g, '' ) : '';
390 	}
391 
392 	var commonLang = editor.lang.common,
393 		linkLang = editor.lang.link;
394 
395 	return {
396 		title : linkLang.title,
397 		minWidth : 350,
398 		minHeight : 230,
399 		contents : [
400 			{
401 				id : 'info',
402 				label : linkLang.info,
403 				title : linkLang.info,
404 				elements :
405 				[
406 					{
407 						id : 'linkType',
408 						type : 'select',
409 						label : linkLang.type,
410 						'default' : 'url',
411 						items :
412 						[
413 							[ linkLang.toUrl, 'url' ],
414 							[ linkLang.toAnchor, 'anchor' ],
415 							[ linkLang.toEmail, 'email' ]
416 						],
417 						onChange : linkTypeChanged,
418 						setup : function( data )
419 						{
420 							if ( data.type )
421 								this.setValue( data.type );
422 						},
423 						commit : function( data )
424 						{
425 							data.type = this.getValue();
426 						}
427 					},
428 					{
429 						type : 'vbox',
430 						id : 'urlOptions',
431 						children :
432 						[
433 							{
434 								type : 'hbox',
435 								widths : [ '25%', '75%' ],
436 								children :
437 								[
438 									{
439 										id : 'protocol',
440 										type : 'select',
441 										label : commonLang.protocol,
442 										'default' : 'http://',
443 										items :
444 										[
445 											// Force 'ltr' for protocol names in BIDI. (#5433)
446 											[ 'http://\u200E', 'http://' ],
447 											[ 'https://\u200E', 'https://' ],
448 											[ 'ftp://\u200E', 'ftp://' ],
449 											[ 'news://\u200E', 'news://' ],
450 											[ linkLang.other , '' ]
451 										],
452 										setup : function( data )
453 										{
454 											if ( data.url )
455 												this.setValue( data.url.protocol || '' );
456 										},
457 										commit : function( data )
458 										{
459 											if ( !data.url )
460 												data.url = {};
461 
462 											data.url.protocol = this.getValue();
463 										}
464 									},
465 									{
466 										type : 'text',
467 										id : 'url',
468 										label : commonLang.url,
469 										required: true,
470 										onLoad : function ()
471 										{
472 											this.allowOnChange = true;
473 										},
474 										onKeyUp : function()
475 										{
476 											this.allowOnChange = false;
477 											var	protocolCmb = this.getDialog().getContentElement( 'info', 'protocol' ),
478 												url = this.getValue(),
479 												urlOnChangeProtocol = /^(http|https|ftp|news):\/\/(?=.)/i,
480 												urlOnChangeTestOther = /^((javascript:)|[#\/\.\?])/i;
481 
482 											var protocol = urlOnChangeProtocol.exec( url );
483 											if ( protocol )
484 											{
485 												this.setValue( url.substr( protocol[ 0 ].length ) );
486 												protocolCmb.setValue( protocol[ 0 ].toLowerCase() );
487 											}
488 											else if ( urlOnChangeTestOther.test( url ) )
489 												protocolCmb.setValue( '' );
490 
491 											this.allowOnChange = true;
492 										},
493 										onChange : function()
494 										{
495 											if ( this.allowOnChange )		// Dont't call on dialog load.
496 												this.onKeyUp();
497 										},
498 										validate : function()
499 										{
500 											var dialog = this.getDialog();
501 
502 											if ( dialog.getContentElement( 'info', 'linkType' ) &&
503 													dialog.getValueOf( 'info', 'linkType' ) != 'url' )
504 												return true;
505 
506 											if ( (/javascript\:/).test( this.getValue() ) ) {
507 												alert( commonLang.invalidValue );
508 												return false;
509 											}
510 
511 											if ( this.getDialog().fakeObj )	// Edit Anchor.
512 												return true;
513 
514 											var func = CKEDITOR.dialog.validate.notEmpty( linkLang.noUrl );
515 											return func.apply( this );
516 										},
517 										setup : function( data )
518 										{
519 											this.allowOnChange = false;
520 											if ( data.url )
521 												this.setValue( data.url.url );
522 											this.allowOnChange = true;
523 
524 										},
525 										commit : function( data )
526 										{
527 											// IE will not trigger the onChange event if the mouse has been used
528 											// to carry all the operations #4724
529 											this.onChange();
530 
531 											if ( !data.url )
532 												data.url = {};
533 
534 											data.url.url = this.getValue();
535 											this.allowOnChange = false;
536 										}
537 									}
538 								],
539 								setup : function( data )
540 								{
541 									if ( !this.getDialog().getContentElement( 'info', 'linkType' ) )
542 										this.getElement().show();
543 								}
544 							},
545 							{
546 								type : 'button',
547 								id : 'browse',
548 								hidden : 'true',
549 								filebrowser : 'info:url',
550 								label : commonLang.browseServer
551 							}
552 						]
553 					},
554 					{
555 						type : 'vbox',
556 						id : 'anchorOptions',
557 						width : 260,
558 						align : 'center',
559 						padding : 0,
560 						children :
561 						[
562 							{
563 								type : 'fieldset',
564 								id : 'selectAnchorText',
565 								label : linkLang.selectAnchor,
566 								setup : function( data )
567 								{
568 									if ( data.anchors.length > 0 )
569 										this.getElement().show();
570 									else
571 										this.getElement().hide();
572 								},
573 								children :
574 								[
575 									{
576 										type : 'hbox',
577 										id : 'selectAnchor',
578 										children :
579 										[
580 											{
581 												type : 'select',
582 												id : 'anchorName',
583 												'default' : '',
584 												label : linkLang.anchorName,
585 												style : 'width: 100%;',
586 												items :
587 												[
588 													[ '' ]
589 												],
590 												setup : function( data )
591 												{
592 													this.clear();
593 													this.add( '' );
594 													for ( var i = 0 ; i < data.anchors.length ; i++ )
595 													{
596 														if ( data.anchors[i].name )
597 															this.add( data.anchors[i].name );
598 													}
599 
600 													if ( data.anchor )
601 														this.setValue( data.anchor.name );
602 
603 													var linkType = this.getDialog().getContentElement( 'info', 'linkType' );
604 													if ( linkType && linkType.getValue() == 'email' )
605 														this.focus();
606 												},
607 												commit : function( data )
608 												{
609 													if ( !data.anchor )
610 														data.anchor = {};
611 
612 													data.anchor.name = this.getValue();
613 												}
614 											},
615 											{
616 												type : 'select',
617 												id : 'anchorId',
618 												'default' : '',
619 												label : linkLang.anchorId,
620 												style : 'width: 100%;',
621 												items :
622 												[
623 													[ '' ]
624 												],
625 												setup : function( data )
626 												{
627 													this.clear();
628 													this.add( '' );
629 													for ( var i = 0 ; i < data.anchors.length ; i++ )
630 													{
631 														if ( data.anchors[i].id )
632 															this.add( data.anchors[i].id );
633 													}
634 
635 													if ( data.anchor )
636 														this.setValue( data.anchor.id );
637 												},
638 												commit : function( data )
639 												{
640 													if ( !data.anchor )
641 														data.anchor = {};
642 
643 													data.anchor.id = this.getValue();
644 												}
645 											}
646 										],
647 										setup : function( data )
648 										{
649 											if ( data.anchors.length > 0 )
650 												this.getElement().show();
651 											else
652 												this.getElement().hide();
653 										}
654 									}
655 								]
656 							},
657 							{
658 								type : 'html',
659 								id : 'noAnchors',
660 								style : 'text-align: center;',
661 								html : '<div role="note" tabIndex="-1">' + CKEDITOR.tools.htmlEncode( linkLang.noAnchors ) + '</div>',
662 								// Focus the first element defined in above html.
663 								focus : true,
664 								setup : function( data )
665 								{
666 									if ( data.anchors.length < 1 )
667 										this.getElement().show();
668 									else
669 										this.getElement().hide();
670 								}
671 							}
672 						],
673 						setup : function( data )
674 						{
675 							if ( !this.getDialog().getContentElement( 'info', 'linkType' ) )
676 								this.getElement().hide();
677 						}
678 					},
679 					{
680 						type :  'vbox',
681 						id : 'emailOptions',
682 						padding : 1,
683 						children :
684 						[
685 							{
686 								type : 'text',
687 								id : 'emailAddress',
688 								label : linkLang.emailAddress,
689 								required : true,
690 								validate : function()
691 								{
692 									var dialog = this.getDialog();
693 
694 									if ( !dialog.getContentElement( 'info', 'linkType' ) ||
695 											dialog.getValueOf( 'info', 'linkType' ) != 'email' )
696 										return true;
697 
698 									var func = CKEDITOR.dialog.validate.notEmpty( linkLang.noEmail );
699 									return func.apply( this );
700 								},
701 								setup : function( data )
702 								{
703 									if ( data.email )
704 										this.setValue( data.email.address );
705 
706 									var linkType = this.getDialog().getContentElement( 'info', 'linkType' );
707 									if ( linkType && linkType.getValue() == 'email' )
708 										this.select();
709 								},
710 								commit : function( data )
711 								{
712 									if ( !data.email )
713 										data.email = {};
714 
715 									data.email.address = this.getValue();
716 								}
717 							},
718 							{
719 								type : 'text',
720 								id : 'emailSubject',
721 								label : linkLang.emailSubject,
722 								setup : function( data )
723 								{
724 									if ( data.email )
725 										this.setValue( data.email.subject );
726 								},
727 								commit : function( data )
728 								{
729 									if ( !data.email )
730 										data.email = {};
731 
732 									data.email.subject = this.getValue();
733 								}
734 							},
735 							{
736 								type : 'textarea',
737 								id : 'emailBody',
738 								label : linkLang.emailBody,
739 								rows : 3,
740 								'default' : '',
741 								setup : function( data )
742 								{
743 									if ( data.email )
744 										this.setValue( data.email.body );
745 								},
746 								commit : function( data )
747 								{
748 									if ( !data.email )
749 										data.email = {};
750 
751 									data.email.body = this.getValue();
752 								}
753 							}
754 						],
755 						setup : function( data )
756 						{
757 							if ( !this.getDialog().getContentElement( 'info', 'linkType' ) )
758 								this.getElement().hide();
759 						}
760 					}
761 				]
762 			},
763 			{
764 				id : 'target',
765 				label : linkLang.target,
766 				title : linkLang.target,
767 				elements :
768 				[
769 					{
770 						type : 'hbox',
771 						widths : [ '50%', '50%' ],
772 						children :
773 						[
774 							{
775 								type : 'select',
776 								id : 'linkTargetType',
777 								label : commonLang.target,
778 								'default' : 'notSet',
779 								style : 'width : 100%;',
780 								'items' :
781 								[
782 									[ commonLang.notSet, 'notSet' ],
783 									[ linkLang.targetFrame, 'frame' ],
784 									[ linkLang.targetPopup, 'popup' ],
785 									[ commonLang.targetNew, '_blank' ],
786 									[ commonLang.targetTop, '_top' ],
787 									[ commonLang.targetSelf, '_self' ],
788 									[ commonLang.targetParent, '_parent' ]
789 								],
790 								onChange : targetChanged,
791 								setup : function( data )
792 								{
793 									if ( data.target )
794 										this.setValue( data.target.type || 'notSet' );
795 									targetChanged.call( this );
796 								},
797 								commit : function( data )
798 								{
799 									if ( !data.target )
800 										data.target = {};
801 
802 									data.target.type = this.getValue();
803 								}
804 							},
805 							{
806 								type : 'text',
807 								id : 'linkTargetName',
808 								label : linkLang.targetFrameName,
809 								'default' : '',
810 								setup : function( data )
811 								{
812 									if ( data.target )
813 										this.setValue( data.target.name );
814 								},
815 								commit : function( data )
816 								{
817 									if ( !data.target )
818 										data.target = {};
819 
820 									data.target.name = this.getValue().replace(/\W/gi, '');
821 								}
822 							}
823 						]
824 					},
825 					{
826 						type : 'vbox',
827 						width : '100%',
828 						align : 'center',
829 						padding : 2,
830 						id : 'popupFeatures',
831 						children :
832 						[
833 							{
834 								type : 'fieldset',
835 								label : linkLang.popupFeatures,
836 								children :
837 								[
838 									{
839 										type : 'hbox',
840 										children :
841 										[
842 											{
843 												type : 'checkbox',
844 												id : 'resizable',
845 												label : linkLang.popupResizable,
846 												setup : setupPopupParams,
847 												commit : commitPopupParams
848 											},
849 											{
850 												type : 'checkbox',
851 												id : 'status',
852 												label : linkLang.popupStatusBar,
853 												setup : setupPopupParams,
854 												commit : commitPopupParams
855 
856 											}
857 										]
858 									},
859 									{
860 										type : 'hbox',
861 										children :
862 										[
863 											{
864 												type : 'checkbox',
865 												id : 'location',
866 												label : linkLang.popupLocationBar,
867 												setup : setupPopupParams,
868 												commit : commitPopupParams
869 
870 											},
871 											{
872 												type : 'checkbox',
873 												id : 'toolbar',
874 												label : linkLang.popupToolbar,
875 												setup : setupPopupParams,
876 												commit : commitPopupParams
877 
878 											}
879 										]
880 									},
881 									{
882 										type : 'hbox',
883 										children :
884 										[
885 											{
886 												type : 'checkbox',
887 												id : 'menubar',
888 												label : linkLang.popupMenuBar,
889 												setup : setupPopupParams,
890 												commit : commitPopupParams
891 
892 											},
893 											{
894 												type : 'checkbox',
895 												id : 'fullscreen',
896 												label : linkLang.popupFullScreen,
897 												setup : setupPopupParams,
898 												commit : commitPopupParams
899 
900 											}
901 										]
902 									},
903 									{
904 										type : 'hbox',
905 										children :
906 										[
907 											{
908 												type : 'checkbox',
909 												id : 'scrollbars',
910 												label : linkLang.popupScrollBars,
911 												setup : setupPopupParams,
912 												commit : commitPopupParams
913 
914 											},
915 											{
916 												type : 'checkbox',
917 												id : 'dependent',
918 												label : linkLang.popupDependent,
919 												setup : setupPopupParams,
920 												commit : commitPopupParams
921 
922 											}
923 										]
924 									},
925 									{
926 										type : 'hbox',
927 										children :
928 										[
929 											{
930 												type :  'text',
931 												widths : [ '50%', '50%' ],
932 												labelLayout : 'horizontal',
933 												label : commonLang.width,
934 												id : 'width',
935 												setup : setupPopupParams,
936 												commit : commitPopupParams
937 
938 											},
939 											{
940 												type :  'text',
941 												labelLayout : 'horizontal',
942 												widths : [ '50%', '50%' ],
943 												label : linkLang.popupLeft,
944 												id : 'left',
945 												setup : setupPopupParams,
946 												commit : commitPopupParams
947 
948 											}
949 										]
950 									},
951 									{
952 										type : 'hbox',
953 										children :
954 										[
955 											{
956 												type :  'text',
957 												labelLayout : 'horizontal',
958 												widths : [ '50%', '50%' ],
959 												label : commonLang.height,
960 												id : 'height',
961 												setup : setupPopupParams,
962 												commit : commitPopupParams
963 
964 											},
965 											{
966 												type :  'text',
967 												labelLayout : 'horizontal',
968 												label : linkLang.popupTop,
969 												widths : [ '50%', '50%' ],
970 												id : 'top',
971 												setup : setupPopupParams,
972 												commit : commitPopupParams
973 
974 											}
975 										]
976 									}
977 								]
978 							}
979 						]
980 					}
981 				]
982 			},
983 			{
984 				id : 'upload',
985 				label : linkLang.upload,
986 				title : linkLang.upload,
987 				hidden : true,
988 				filebrowser : 'uploadButton',
989 				elements :
990 				[
991 					{
992 						type : 'file',
993 						id : 'upload',
994 						label : commonLang.upload,
995 						style: 'height:40px',
996 						size : 29
997 					},
998 					{
999 						type : 'fileButton',
1000 						id : 'uploadButton',
1001 						label : commonLang.uploadSubmit,
1002 						filebrowser : 'info:url',
1003 						'for' : [ 'upload', 'upload' ]
1004 					}
1005 				]
1006 			},
1007 			{
1008 				id : 'advanced',
1009 				label : linkLang.advanced,
1010 				title : linkLang.advanced,
1011 				elements :
1012 				[
1013 					{
1014 						type : 'vbox',
1015 						padding : 1,
1016 						children :
1017 						[
1018 							{
1019 								type : 'hbox',
1020 								widths : [ '45%', '35%', '20%' ],
1021 								children :
1022 								[
1023 									{
1024 										type : 'text',
1025 										id : 'advId',
1026 										label : linkLang.id,
1027 										setup : setupAdvParams,
1028 										commit : commitAdvParams
1029 									},
1030 									{
1031 										type : 'select',
1032 										id : 'advLangDir',
1033 										label : linkLang.langDir,
1034 										'default' : '',
1035 										style : 'width:110px',
1036 										items :
1037 										[
1038 											[ commonLang.notSet, '' ],
1039 											[ linkLang.langDirLTR, 'ltr' ],
1040 											[ linkLang.langDirRTL, 'rtl' ]
1041 										],
1042 										setup : setupAdvParams,
1043 										commit : commitAdvParams
1044 									},
1045 									{
1046 										type : 'text',
1047 										id : 'advAccessKey',
1048 										width : '80px',
1049 										label : linkLang.acccessKey,
1050 										maxLength : 1,
1051 										setup : setupAdvParams,
1052 										commit : commitAdvParams
1053 
1054 									}
1055 								]
1056 							},
1057 							{
1058 								type : 'hbox',
1059 								widths : [ '45%', '35%', '20%' ],
1060 								children :
1061 								[
1062 									{
1063 										type : 'text',
1064 										label : linkLang.name,
1065 										id : 'advName',
1066 										setup : setupAdvParams,
1067 										commit : commitAdvParams
1068 
1069 									},
1070 									{
1071 										type : 'text',
1072 										label : linkLang.langCode,
1073 										id : 'advLangCode',
1074 										width : '110px',
1075 										'default' : '',
1076 										setup : setupAdvParams,
1077 										commit : commitAdvParams
1078 
1079 									},
1080 									{
1081 										type : 'text',
1082 										label : linkLang.tabIndex,
1083 										id : 'advTabIndex',
1084 										width : '80px',
1085 										maxLength : 5,
1086 										setup : setupAdvParams,
1087 										commit : commitAdvParams
1088 
1089 									}
1090 								]
1091 							}
1092 						]
1093 					},
1094 					{
1095 						type : 'vbox',
1096 						padding : 1,
1097 						children :
1098 						[
1099 							{
1100 								type : 'hbox',
1101 								widths : [ '45%', '55%' ],
1102 								children :
1103 								[
1104 									{
1105 										type : 'text',
1106 										label : linkLang.advisoryTitle,
1107 										'default' : '',
1108 										id : 'advTitle',
1109 										setup : setupAdvParams,
1110 										commit : commitAdvParams
1111 
1112 									},
1113 									{
1114 										type : 'text',
1115 										label : linkLang.advisoryContentType,
1116 										'default' : '',
1117 										id : 'advContentType',
1118 										setup : setupAdvParams,
1119 										commit : commitAdvParams
1120 
1121 									}
1122 								]
1123 							},
1124 							{
1125 								type : 'hbox',
1126 								widths : [ '45%', '55%' ],
1127 								children :
1128 								[
1129 									{
1130 										type : 'text',
1131 										label : linkLang.cssClasses,
1132 										'default' : '',
1133 										id : 'advCSSClasses',
1134 										setup : setupAdvParams,
1135 										commit : commitAdvParams
1136 
1137 									},
1138 									{
1139 										type : 'text',
1140 										label : linkLang.charset,
1141 										'default' : '',
1142 										id : 'advCharset',
1143 										setup : setupAdvParams,
1144 										commit : commitAdvParams
1145 
1146 									}
1147 								]
1148 							},
1149 							{
1150 								type : 'hbox',
1151 								widths : [ '45%', '55%' ],
1152 								children :
1153 								[
1154 									{
1155 										type : 'text',
1156 										label : linkLang.rel,
1157 										'default' : '',
1158 										id : 'advRel',
1159 										setup : setupAdvParams,
1160 										commit : commitAdvParams
1161 									},
1162 									{
1163 										type : 'text',
1164 										label : linkLang.styles,
1165 										'default' : '',
1166 										id : 'advStyles',
1167 										validate : CKEDITOR.dialog.validate.inlineStyle( editor.lang.common.invalidInlineStyle ),
1168 										setup : setupAdvParams,
1169 										commit : commitAdvParams
1170 									}
1171 								]
1172 							}
1173 						]
1174 					}
1175 				]
1176 			}
1177 		],
1178 		onShow : function()
1179 		{
1180 			var editor = this.getParentEditor(),
1181 				selection = editor.getSelection(),
1182 				element = null;
1183 
1184 			// Fill in all the relevant fields if there's already one link selected.
1185 			if ( ( element = plugin.getSelectedLink( editor ) ) && element.hasAttribute( 'href' ) )
1186 				selection.selectElement( element );
1187 			else
1188 				element = null;
1189 
1190 			this.setupContent( parseLink.apply( this, [ editor, element ] ) );
1191 		},
1192 		onOk : function()
1193 		{
1194 			var attributes = {},
1195 				removeAttributes = [],
1196 				data = {},
1197 				me = this,
1198 				editor = this.getParentEditor();
1199 
1200 			this.commitContent( data );
1201 
1202 			// Compose the URL.
1203 			switch ( data.type || 'url' )
1204 			{
1205 				case 'url':
1206 					var protocol = ( data.url && data.url.protocol != undefined ) ? data.url.protocol : 'http://',
1207 						url = ( data.url && CKEDITOR.tools.trim( data.url.url ) ) || '';
1208 					attributes[ 'data-cke-saved-href' ] = ( url.indexOf( '/' ) === 0 ) ? url : protocol + url;
1209 					break;
1210 				case 'anchor':
1211 					var name = ( data.anchor && data.anchor.name ),
1212 						id = ( data.anchor && data.anchor.id );
1213 					attributes[ 'data-cke-saved-href' ] = '#' + ( name || id || '' );
1214 					break;
1215 				case 'email':
1216 
1217 					var linkHref,
1218 						email = data.email,
1219 						address = email.address;
1220 
1221 					switch( emailProtection )
1222 					{
1223 						case '' :
1224 						case 'encode' :
1225 						{
1226 							var subject = encodeURIComponent( email.subject || '' ),
1227 								body = encodeURIComponent( email.body || '' );
1228 
1229 							// Build the e-mail parameters first.
1230 							var argList = [];
1231 							subject && argList.push( 'subject=' + subject );
1232 							body && argList.push( 'body=' + body );
1233 							argList = argList.length ? '?' + argList.join( '&' ) : '';
1234 
1235 							if ( emailProtection == 'encode' )
1236 							{
1237 								linkHref = [ 'javascript:void(location.href=\'mailto:\'+',
1238 											 protectEmailAddressAsEncodedString( address ) ];
1239 								// parameters are optional.
1240 								argList && linkHref.push( '+\'', escapeSingleQuote( argList ), '\'' );
1241 
1242 								linkHref.push( ')' );
1243 							}
1244 							else
1245 								linkHref = [ 'mailto:', address, argList ];
1246 
1247 							break;
1248 						}
1249 						default :
1250 						{
1251 							// Separating name and domain.
1252 							var nameAndDomain = address.split( '@', 2 );
1253 							email.name = nameAndDomain[ 0 ];
1254 							email.domain = nameAndDomain[ 1 ];
1255 
1256 							linkHref = [ 'javascript:', protectEmailLinkAsFunction( email ) ];
1257 						}
1258 					}
1259 
1260 					attributes[ 'data-cke-saved-href' ] = linkHref.join( '' );
1261 					break;
1262 			}
1263 
1264 			// Popups and target.
1265 			if ( data.target )
1266 			{
1267 				if ( data.target.type == 'popup' )
1268 				{
1269 					var onclickList = [ 'window.open(this.href, \'',
1270 							data.target.name || '', '\', \'' ];
1271 					var featureList = [ 'resizable', 'status', 'location', 'toolbar', 'menubar', 'fullscreen',
1272 							'scrollbars', 'dependent' ];
1273 					var featureLength = featureList.length;
1274 					var addFeature = function( featureName )
1275 					{
1276 						if ( data.target[ featureName ] )
1277 							featureList.push( featureName + '=' + data.target[ featureName ] );
1278 					};
1279 
1280 					for ( var i = 0 ; i < featureLength ; i++ )
1281 						featureList[i] = featureList[i] + ( data.target[ featureList[i] ] ? '=yes' : '=no' ) ;
1282 					addFeature( 'width' );
1283 					addFeature( 'left' );
1284 					addFeature( 'height' );
1285 					addFeature( 'top' );
1286 
1287 					onclickList.push( featureList.join( ',' ), '\'); return false;' );
1288 					attributes[ 'data-cke-pa-onclick' ] = onclickList.join( '' );
1289 
1290 					// Add the "target" attribute. (#5074)
1291 					removeAttributes.push( 'target' );
1292 				}
1293 				else
1294 				{
1295 					if ( data.target.type != 'notSet' && data.target.name )
1296 						attributes.target = data.target.name;
1297 					else
1298 						removeAttributes.push( 'target' );
1299 
1300 					removeAttributes.push( 'data-cke-pa-onclick', 'onclick' );
1301 				}
1302 			}
1303 
1304 			// Advanced attributes.
1305 			if ( data.adv )
1306 			{
1307 				var advAttr = function( inputName, attrName )
1308 				{
1309 					var value = data.adv[ inputName ];
1310 					if ( value )
1311 						attributes[attrName] = value;
1312 					else
1313 						removeAttributes.push( attrName );
1314 				};
1315 
1316 				advAttr( 'advId', 'id' );
1317 				advAttr( 'advLangDir', 'dir' );
1318 				advAttr( 'advAccessKey', 'accessKey' );
1319 
1320 				if ( data.adv[ 'advName' ] )
1321 					attributes[ 'name' ] = attributes[ 'data-cke-saved-name' ] = data.adv[ 'advName' ];
1322 				else
1323 					removeAttributes = removeAttributes.concat( [ 'data-cke-saved-name', 'name' ] );
1324 
1325 				advAttr( 'advLangCode', 'lang' );
1326 				advAttr( 'advTabIndex', 'tabindex' );
1327 				advAttr( 'advTitle', 'title' );
1328 				advAttr( 'advContentType', 'type' );
1329 				advAttr( 'advCSSClasses', 'class' );
1330 				advAttr( 'advCharset', 'charset' );
1331 				advAttr( 'advStyles', 'style' );
1332 				advAttr( 'advRel', 'rel' );
1333 			}
1334 
1335 
1336 			var selection = editor.getSelection();
1337 
1338 			// Browser need the "href" fro copy/paste link to work. (#6641)
1339 			attributes.href = attributes[ 'data-cke-saved-href' ];
1340 
1341 			if ( !this._.selectedElement )
1342 			{
1343 				// Create element if current selection is collapsed.
1344 				var ranges = selection.getRanges( true );
1345 				if ( ranges.length == 1 && ranges[0].collapsed )
1346 				{
1347 					// Short mailto link text view (#5736).
1348 					var text = new CKEDITOR.dom.text( data.type == 'email' ?
1349 							data.email.address : attributes[ 'data-cke-saved-href' ], editor.document );
1350 					ranges[0].insertNode( text );
1351 					ranges[0].selectNodeContents( text );
1352 					selection.selectRanges( ranges );
1353 				}
1354 
1355 				// Apply style.
1356 				var style = new CKEDITOR.style( { element : 'a', attributes : attributes } );
1357 				style.type = CKEDITOR.STYLE_INLINE;		// need to override... dunno why.
1358 				style.apply( editor.document );
1359 			}
1360 			else
1361 			{
1362 				// We're only editing an existing link, so just overwrite the attributes.
1363 				var element = this._.selectedElement,
1364 					href = element.data( 'cke-saved-href' ),
1365 					textView = element.getHtml();
1366 
1367 				element.setAttributes( attributes );
1368 				element.removeAttributes( removeAttributes );
1369 
1370 				if ( data.adv && data.adv.advName && CKEDITOR.plugins.link.synAnchorSelector )
1371 					element.addClass( element.getChildCount() ? 'cke_anchor' : 'cke_anchor_empty' );
1372 
1373 				// Update text view when user changes protocol (#4612).
1374 				if ( href == textView || data.type == 'email' && textView.indexOf( '@' ) != -1 )
1375 				{
1376 					// Short mailto link text view (#5736).
1377 					element.setHtml( data.type == 'email' ?
1378 						data.email.address : attributes[ 'data-cke-saved-href' ] );
1379 				}
1380 
1381 				selection.selectElement( element );
1382 				delete this._.selectedElement;
1383 			}
1384 		},
1385 		onLoad : function()
1386 		{
1387 			if ( !editor.config.linkShowAdvancedTab )
1388 				this.hidePage( 'advanced' );		//Hide Advanded tab.
1389 
1390 			if ( !editor.config.linkShowTargetTab )
1391 				this.hidePage( 'target' );		//Hide Target tab.
1392 
1393 		},
1394 		// Inital focus on 'url' field if link is of type URL.
1395 		onFocus : function()
1396 		{
1397 			var linkType = this.getContentElement( 'info', 'linkType' ),
1398 					urlField;
1399 			if ( linkType && linkType.getValue() == 'url' )
1400 			{
1401 				urlField = this.getContentElement( 'info', 'url' );
1402 				urlField.select();
1403 			}
1404 		}
1405 	};
1406 });
1407 
1408 /**
1409  * The e-mail address anti-spam protection option. The protection will be
1410  * applied when creating or modifying e-mail links through the editor interface.<br>
1411  * Two methods of protection can be choosed:
1412  * <ol>	<li>The e-mail parts (name, domain and any other query string) are
1413  *			assembled into a function call pattern. Such function must be
1414  *			provided by the developer in the pages that will use the contents.
1415  *		<li>Only the e-mail address is obfuscated into a special string that
1416  *			has no meaning for humans or spam bots, but which is properly
1417  *			rendered and accepted by the browser.</li></ol>
1418  * Both approaches require JavaScript to be enabled.
1419  * @name CKEDITOR.config.emailProtection
1420  * @since 3.1
1421  * @type String
1422  * @default '' (empty string = disabled)
1423  * @example
1424  * // href="mailto:tester@ckeditor.com?subject=subject&body=body"
1425  * config.emailProtection = '';
1426  * @example
1427  * // href="<a href=\"javascript:void(location.href=\'mailto:\'+String.fromCharCode(116,101,115,116,101,114,64,99,107,101,100,105,116,111,114,46,99,111,109)+\'?subject=subject&body=body\')\">e-mail</a>"
1428  * config.emailProtection = 'encode';
1429  * @example
1430  * // href="javascript:mt('tester','ckeditor.com','subject','body')"
1431  * config.emailProtection = 'mt(NAME,DOMAIN,SUBJECT,BODY)';
1432  */
1433