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