      1 /*
      2 Copyright (c) 2003-2009, CKSource - Frederico Knabben. All rights reserved.
      3 For licensing, see LICENSE.html or http://ckeditor.com/license
      4 */
      5
      6 /**
      7  * @fileOverview The "filebrowser" plugin, it adds support for file uploads and
      8  *               browsing.
      9  *
     10  * When file is selected inside of the file browser or uploaded, its url is
     11  * inserted automatically to a field, which is described in the 'filebrowser'
     12  * attribute. To specify field that should be updated, pass the tab id and
     13  * element id, separated with a colon.
     14  *
     15  * Example 1: (Browse)
     16  *
     17  * <pre>
     18  * {
     19  * 	type : 'button',
     20  * 	id : 'browse',
     21  * 	filebrowser : 'tabId:elementId',
     22  * 	label : editor.lang.common.browseServer
     23  * }
     24  * </pre>
     25  *
     26  * If you set the 'filebrowser' attribute on any element other than
     27  * 'fileButton', the 'Browse' action will be triggered.
     28  *
     29  * Example 2: (Quick Upload)
     30  *
     31  * <pre>
     32  * {
     33  * 	type : 'fileButton',
     34  * 	id : 'uploadButton',
     35  * 	filebrowser : 'tabId:elementId',
     36  * 	label : editor.lang.common.uploadSubmit,
     37  * 	'for' : [ 'upload', 'upload' ]
     38  * }
     39  * </pre>
     40  *
     41  * If you set the 'filebrowser' attribute on a fileButton element, the
     42  * 'QuickUpload' action will be executed.
     43  *
     44  * Filebrowser plugin also supports more advanced configuration (through
     45  * javascript object).
     46  *
     47  * The following settings are supported:
     48  *
     49  * <pre>
     50  *  [action] - Browse or QuickUpload
     51  *  [target] - field to update, tabId:elementId
     52  *  [params] - additional arguments to be passed to the server connector (optional)
     53  *  [onSelect] - function to execute when file is selected/uploaded (optional)
     54  *  [url] - the URL to be called (optional)
     55  * </pre>
     56  *
     57  * Example 3: (Quick Upload)
     58  *
     59  * <pre>
     60  * {
     61  * 	type : 'fileButton',
     62  * 	label : editor.lang.common.uploadSubmit,
     63  * 	id : 'buttonId',
     64  * 	filebrowser :
     65  * 	{
     66  * 		action : 'QuickUpload', //required
     67  * 		target : 'tab1:elementId', //required
     68  * 		params : //optional
     69  * 		{
     70  * 			type : 'Files',
     71  * 			currentFolder : '/folder/'
     72  * 		},
     73  * 		onSelect : function( fileUrl, errorMessage ) //optional
     74  * 		{
     75  * 			// Do not call the built-in selectFuntion
     76  * 			// return false;
     77  * 		}
     78  * 	},
     79  * 	'for' : [ 'tab1', 'myFile' ]
     80  * }
     81  * </pre>
     82  *
     83  * Suppose we have a file element with id 'myFile', text field with id
     84  * 'elementId' and a fileButton. If filebowser.url is not specified explicitly,
     85  * form action will be set to 'filebrowser[DialogName]UploadUrl' or, if not
     86  * specified, to 'filebrowserUploadUrl'. Additional parameters from 'params'
     87  * object will be added to the query string. It is possible to create your own
     88  * uploadHandler and cancel the built-in updateTargetElement command.
     89  *
     90  * Example 4: (Browse)
     91  *
     92  * <pre>
     93  * {
     94  * 	type : 'button',
     95  * 	id : 'buttonId',
     96  * 	label : editor.lang.common.browseServer,
     97  * 	filebrowser :
     98  * 	{
     99  * 		action : 'Browse',
    100  * 		url : '/ckfinder/ckfinder.html&type=Images',
    101  * 		target : 'tab1:elementId'
    102  * 	}
    103  * }
    104  * </pre>
    105  *
    106  * In this example, after pressing a button, file browser will be opened in a
    107  * popup. If we don't specify filebrowser.url attribute,
    108  * 'filebrowser[DialogName]BrowseUrl' or 'filebrowserBrowseUrl' will be used.
    109  * After selecting a file in a file browser, an element with id 'elementId' will
    110  * be updated. Just like in the third example, a custom 'onSelect' function may be
    111  * defined.
    112  */
    113 ( function()
    114 {
    115 	/**
    116 	 * Adds (additional) arguments to given url.
    117 	 *
    118 	 * @param {String}
    119 	 *            url The url.
    120 	 * @param {Object}
    121 	 *            params Additional parameters.
    122 	 */
    123 	function addQueryString( url, params )
    124 	{
    125 		var queryString = [];
    126
    127 		if ( !params )
    128 			return url;
    129 		else
    130 		{
    131 			for ( var i in params )
    132 				queryString.push( i + "=" + encodeURIComponent( params[ i ] ) );
    133 		}
    134
    135 		return url + ( ( url.indexOf( "?" ) != -1 ) ? "&" : "?" ) + queryString.join( "&" );
    136 	}
    137
    138 	/**
    139 	 * Make a string's first character uppercase.
    140 	 *
    141 	 * @param {String}
    142 	 *            str String.
    143 	 */
    144 	function ucFirst( str )
    145 	{
    146 		str += '';
    147 		var f = str.charAt( 0 ).toUpperCase();
    148 		return f + str.substr( 1 );
    149 	}
    150
    151 	/**
    152 	 * The onlick function assigned to the 'Browse Server' button. Opens the
    153 	 * file browser and updates target field when file is selected.
    154 	 *
    155 	 * @param {CKEDITOR.event}
    156 	 *            evt The event object.
    157 	 */
    158 	function browseServer( evt )
    159 	{
    160 		var dialog = this.getDialog();
    161 		var editor = dialog.getParentEditor();
    162
    163 		editor._.filebrowserSe = this;
    164
    165 		var width = editor.config[ 'filebrowser' + ucFirst( dialog.getName() ) + 'WindowWidth' ]
    166 				|| editor.config.filebrowserWindowWidth || '80%';
    167 		var height = editor.config[ 'filebrowser' + ucFirst( dialog.getName() ) + 'WindowHeight' ]
    168 				|| editor.config.filebrowserWindowHeight || '70%';
    169
    170 		var params = this.filebrowser.params || {};
    171 		params.CKEditor = editor.name;
    172 		params.CKEditorFuncNum = editor._.filebrowserFn;
    173 		if ( !params.langCode )
    174 			params.langCode = editor.langCode;
    175
    176 		var url = addQueryString( this.filebrowser.url, params );
    177 		editor.popup( url, width, height );
    178 	}
    179
    180 	/**
    181 	 * The onlick function assigned to the 'Upload' button. Makes the final
    182 	 * decision whether form is really submitted and updates target field when
    183 	 * file is uploaded.
    184 	 *
    185 	 * @param {CKEDITOR.event}
    186 	 *            evt The event object.
    187 	 */
    188 	function uploadFile( evt )
    189 	{
    190 		var dialog = this.getDialog();
    191 		var editor = dialog.getParentEditor();
    192
    193 		editor._.filebrowserSe = this;
    194
    195 		// If user didn't select the file, stop the upload.
    196 		if ( !dialog.getContentElement( this[ 'for' ][ 0 ], this[ 'for' ][ 1 ] ).getInputElement().$.value )
    197 			return false;
    198
    199 		if ( !dialog.getContentElement( this[ 'for' ][ 0 ], this[ 'for' ][ 1 ] ).getAction() )
    200 			return false;
    201
    202 		return true;
    203 	}
    204
    205 	/**
    206 	 * Setups the file element.
    207 	 *
    208 	 * @param {CKEDITOR.ui.dialog.file}
    209 	 *            fileInput The file element used during file upload.
    210 	 * @param {Object}
    211 	 *            filebrowser Object containing filebrowser settings assigned to
    212 	 *            the fileButton associated with this file element.
    213 	 */
    214 	function setupFileElement( editor, fileInput, filebrowser )
    215 	{
    216 		var params = filebrowser.params || {};
    217 		params.CKEditor = editor.name;
    218 		params.CKEditorFuncNum = editor._.filebrowserFn;
    219 		if ( !params.langCode )
    220 			params.langCode = editor.langCode;
    221
    222 		fileInput.action = addQueryString( filebrowser.url, params );
    223 		fileInput.filebrowser = filebrowser;
    224 	}
    225
    226 	/**
    227 	 * Traverse through the content definition and attach filebrowser to
    228 	 * elements with 'filebrowser' attribute.
    229 	 *
    230 	 * @param String
    231 	 *            dialogName Dialog name.
    232 	 * @param {CKEDITOR.dialog.dialogDefinitionObject}
    233 	 *            definition Dialog definition.
    234 	 * @param {Array}
    235 	 *            elements Array of {@link CKEDITOR.dialog.contentDefinition}
    236 	 *            objects.
    237 	 */
    238 	function attachFileBrowser( editor, dialogName, definition, elements )
    239 	{
    240 		var element, fileInput;
    241
    242 		for ( var i in elements )
    243 		{
    244 			element = elements[ i ];
    245
    246 			if ( element.type == 'hbox' || element.type == 'vbox' )
    247 				attachFileBrowser( editor, dialogName, definition, element.children );
    248
    249 			if ( !element.filebrowser )
    250 				continue;
    251
    252 			if ( typeof element.filebrowser == 'string' )
    253 			{
    254 				var fb =
    255 				{
    256 					action : ( element.type == 'fileButton' ) ? 'QuickUpload' : 'Browse',
    257 					target : element.filebrowser
    258 				};
    259 				element.filebrowser = fb;
    260 			}
    261
    262 			if ( element.filebrowser.action == 'Browse' )
    263 			{
    264 				var url = element.filebrowser.url || editor.config[ 'filebrowser' + ucFirst( dialogName ) + 'BrowseUrl' ]
    265 							|| editor.config.filebrowserBrowseUrl;
    266
    267 				if ( url )
    268 				{
    269 					element.onClick = browseServer;
    270 					element.filebrowser.url = url;
    271 					element.hidden = false;
    272 				}
    273 			}
    274 			else if ( element.filebrowser.action == 'QuickUpload' && element[ 'for' ] )
    275 			{
    276 				url =  element.filebrowser.url || editor.config[ 'filebrowser' + ucFirst( dialogName ) + 'UploadUrl' ]
    277 							|| editor.config.filebrowserUploadUrl;
    278
    279 				if ( url )
    280 				{
    281 					element.onClick = uploadFile;
    282 					element.filebrowser.url = url;
    283 					element.hidden = false;
    284 					setupFileElement( editor, definition.getContents( element[ 'for' ][ 0 ] ).get( element[ 'for' ][ 1 ] ), element.filebrowser );
    285 				}
    286 			}
    287 		}
    288 	}
    289
    290 	/**
    291 	 * Updates the target element with the url of uploaded/selected file.
    292 	 *
    293 	 * @param {String}
    294 	 *            url The url of a file.
    295 	 */
    296 	function updateTargetElement( url, sourceElement )
    297 	{
    298 		var dialog = sourceElement.getDialog();
    299 		var targetElement = sourceElement.filebrowser.target || null;
    300 		url = url.replace( /#/g, '%23' );
    301
    302 		// If there is a reference to targetElement, update it.
    303 		if ( targetElement )
    304 		{
    305 			var target = targetElement.split( ':' );
    306 			var element = dialog.getContentElement( target[ 0 ], target[ 1 ] );
    307 			if ( element )
    308 			{
    309 				element.setValue( url );
    310 				dialog.selectPage( target[ 0 ] );
    311 			}
    312 		}
    313 	}
    314
    315 	/**
    316 	 * Returns true if filebrowser is configured in one of the elements.
    317 	 *
    318 	 * @param {CKEDITOR.dialog.dialogDefinitionObject}
    319 	 *            definition Dialog definition.
    320 	 * @param String
    321 	 *            tabId The tab id where element(s) can be found.
    322 	 * @param String
    323 	 *            elementId The element id (or ids, separated with a semicolon) to check.
    324 	 */
    325 	function isConfigured( definition, tabId, elementId )
    326 	{
    327 		if ( elementId.indexOf( ";" ) !== -1 )
    328 		{
    329 			var ids = elementId.split( ";" );
    330 			for ( var i = 0 ; i < ids.length ; i++ )
    331 			{
    332 				if ( isConfigured( definition, tabId, ids[i]) )
    333 					return true;
    334 			}
    335 			return false;
    336 		}
    337
    338 		return ( definition.getContents( tabId ).get( elementId ).filebrowser && definition.getContents( tabId ).get( elementId ).filebrowser.url );
    339 	}
    340
    341 	function setUrl( fileUrl, data )
    342 	{
    343 		var dialog = this._.filebrowserSe.getDialog(),
    344 			targetInput = this._.filebrowserSe[ 'for' ],
    345 			onSelect = this._.filebrowserSe.filebrowser.onSelect;
    346
    347 		if ( targetInput )
    348 			dialog.getContentElement( targetInput[ 0 ], targetInput[ 1 ] ).reset();
    349
    350 		if ( onSelect && onSelect.call( this._.filebrowserSe, fileUrl, data ) === false )
    351 			return;
    352
    353 		// The "data" argument may be used to pass the error message to the editor.
    354 		if ( typeof data == 'string' && data )
    355 			alert( data );
    356
    357 		if ( fileUrl )
    358 			updateTargetElement( fileUrl, this._.filebrowserSe );
    359 	}
    360
    361 	CKEDITOR.plugins.add( 'filebrowser',
    362 	{
    363 		init : function( editor, pluginPath )
    364 		{
    365 			editor._.filebrowserFn = CKEDITOR.tools.addFunction( setUrl, editor );
    366
    367 			CKEDITOR.on( 'dialogDefinition', function( evt )
    368 			{
    369 				// Associate filebrowser to elements with 'filebrowser' attribute.
    370 				for ( var i in evt.data.definition.contents )
    371 				{
    372 					attachFileBrowser( evt.editor, evt.data.name, evt.data.definition, evt.data.definition.contents[ i ].elements );
    373 					if ( evt.data.definition.contents[ i ].hidden && evt.data.definition.contents[ i ].filebrowser )
    374 					{
    375 						evt.data.definition.contents[ i ].hidden =
    376 							!isConfigured( evt.data.definition, evt.data.definition.contents[ i ][ 'id' ], evt.data.definition.contents[ i ].filebrowser );
    377 					}
    378 				}
    379 			} );
    380 		}
    381 	} );
    382
    383 } )();
    384