26d2541c7cbc7bbd666b8b7bc7607db470020d43
[lhc/web/wiklou.git] / js2 / mwEmbed / libAddMedia / mvBaseUploadInterface.js
1 /**
2 * the base Upload Interface for uploading.
3 *
4 * this base uploader is optionally extended by firefogg
5 */
6 loadGM({
7 "upload-transcode-in-progress":"Doing Transcode & Upload (do not close this window)",
8 "upload-in-progress": "Upload in Progress (do not close this window)",
9 "upload-transcoded-status": "Transcoded",
10 "uploaded-status": "Uploaded",
11
12 "wgfogg_wrong_version": "You have firefogg installed but its outdated, <a href=\"http://firefogg.org\">please upgrade</a> ",
13 "upload-stats-fileprogres": "$1 of $2",
14
15 "mv_upload_completed": "Your upload is complete",
16
17 "mv_upload_done" : "Your upload <i>should be</i> accessible <a href=\"$1\">here</a>",
18 "upload-unknown-size": "Unknown size",
19
20 "mv-cancel-confim" : "Are you sure you want to cancel?",
21
22 "successfulupload" : "Successful Upload",
23 "uploaderror" : "Upload error",
24 "uploadwarning": "Upload warning",
25 "unknown-error": "Unknown Error",
26 "return-to-form": "Return to form",
27
28 "file-exists-duplicate" : "This file is a duplicate of the following file",
29 "fileexists" : "A file with this name exists already, please check <b><tt>$1</tt></b> if you are not sure if you want to change it.",
30 "fileexists-thumb": "<center><b>Existing file</b></center>",
31 "ignorewarning" : "Ignore warning and save file anyway",
32 "file-thumbnail-no" : "The filename begins with <b><tt>$1</tt></b>",
33 "go-to-resource" : "Go to Resource Page",
34
35 "wgfogg_waring_bad_extension" : "You have selected a file with an unsuported extension (<a href=\"http://commons.wikimedia.org/wiki/Commons:Firefogg#Supported_File_Types\">more information</a>).",
36
37 "cancel-button" : "Cancel",
38 "ok-button" : "OK"
39
40 });
41
42
43 var default_bui_options = {
44 'api_url':null,
45 'parent_uploader':null,
46 'edit_from':null,
47 'done_upload_cb': null,
48 'target_edit_from':null,
49
50 //upload_mode can be 'post', 'chunks' or autodetect. (autodetect issues an api call)
51 'upload_mode':'autodetect'
52
53 }
54 var mvBaseUploadInterface = function( iObj ){
55 return this.init( iObj );
56 }
57 mvBaseUploadInterface.prototype = {
58 parent_uploader:false,
59 formData:{}, //the form to be submitted
60 warnings_sessionkey:null,
61 chunks_supported:false,
62 form_post_override:false,
63 action_done:false,
64 //the edit token:
65 etoken:false,
66 init: function( iObj ){
67 if(!iObj)
68 iObj = {};
69 //inherit iObj properties:
70 for(var i in default_bui_options){
71 if(iObj[i]){
72 this[i] = iObj[i];
73 }else{
74 this[i] = default_bui_options[i];
75 }
76 }
77 },
78 setupForm:function(){
79 var _this = this;
80 //set up the local pointer to the edit form:
81 _this.editForm = _this.getEditForm();
82
83 if( _this.editForm ){
84 //set up the org_onsubmit if not set:
85 if( typeof( _this.org_onsubmit ) == 'undefined' && _this.editForm.onsubmit )
86 _this.org_onsubmit = _this.editForm.onsubmit;
87
88 //have to define the onsubmit function inline or its hard to pass the "_this" instance
89 $j( '#mw-upload-form' ).submit( function(){
90 //run the original onsubmit (if not run yet set flag to avoid excessive chaining )
91 if( typeof( _this.org_onsubmit ) == 'function' ){
92 if( ! _this.org_onsubmit() ){
93 //error in org submit return false;
94 return false;
95 }
96 }
97 //check for post action override:
98 if( _this.form_post_override ){
99 //alert('will submit here');
100 return true;
101 }
102 //get the input form data in flat json:
103 var tmpAryData = $j( _this.editForm ).serializeArray();
104 for(var i=0; i < tmpAryData.length; i++){
105 if( tmpAryData[i]['name'] )
106 _this.formData[ tmpAryData[i]['name'] ] = tmpAryData[i]['value'];
107 }
108 //put into a try catch so we are sure to return false:
109 try{
110 //get a clean loader:
111 _this.dispProgressOverlay();
112
113 //for some unknown reason we have to drop down the #p-search z-index:
114 $j('#p-search').css('z-index', 1);
115
116 //select upload mode:
117 _this.detectUploadMode();
118 }catch(e){
119
120 }
121
122 //don't submit the form we will do the post in ajax
123 return false;
124 });
125 }
126
127 },
128 detectUploadMode:function( callback ){
129 var _this = this;
130 //check the upload mode:
131 if( _this.upload_mode == 'autodetect' ){
132 js_log('detectUploadMode::' + _this.upload_mode + ' api:' + _this.api_url);
133 if( ! _this.api_url )
134 return js_error( 'Error: can\'t autodetect mode without api url' );
135 do_api_req( {
136 'data':{ 'action':'paraminfo','modules':'upload' },
137 'url' :_this.api_url
138 }, function(data){
139 if( typeof data.paraminfo == 'undefined' || typeof data.paraminfo.modules == 'undefined' )
140 return js_error( 'Error: bad api results' );
141 if( typeof data.paraminfo.modules[0].classname == 'undefined'){
142 js_log( 'Autodetect Upload Mode: \'post\' ');
143 _this.upload_mode = 'post';
144 }else{
145 js_log( 'Autodetect Upload Mode: api ' );
146 _this.upload_mode = 'api';
147 //check to see if chunks are supported:
148 for( var i in data.paraminfo.modules[0].parameters ){
149 var pname = data.paraminfo.modules[0].parameters[i].name;
150 if( pname == 'enablechunks' ){
151 js_log( 'this.chunks_supported = true' );
152 _this.chunks_supported = true;
153 break;
154 }
155 }
156 }
157 js_log("do call: doUploadSwitch");
158 _this.doUploadSwitch();
159 });
160 }else{
161 _this.doUploadSwitch();
162 }
163 },
164 doUploadSwitch:function(){
165 var _this = this;
166 js_log('mvUPload:doUploadSwitch():' + _this.upload_mode);
167 //issue a normal post request
168 if( _this.upload_mode == 'post' || //we don't support the upload api
169 (_this.upload_mode=='api' && ( $j('#wpSourceTypeFile').length == 0 || $j('#wpSourceTypeFile').get(0).checked ) ) // web form uplaod even though we support api
170 ){
171 js_log('do original submit form');
172 //update the status to 100% progress bar:
173 $j( '#up-progressbar' ).progressbar('value', parseInt( 100 ) );
174
175 $j('#up-status-container').html( gM('upload-in-progress') );
176 //do normal post upload no status indicators (also since its a file I think we have to submit the form)
177 _this.form_post_override = true;
178
179 //trick the browser into thinking the wpUpload button was pressed (there might be a cleaner way to do this)
180
181 $j(_this.editForm).append(
182 '<input type="hidden" name="wpUpload" value="' + $j('input[name=\'wpUpload\']').val() + '"/>'
183 );
184
185 //@@todo support firefox 3.0 ajax file upload progress
186 //http://igstan.blogspot.com/2009/01/pure-javascript-file-upload.html
187
188 //do the submit :
189 _this.editForm.submit();
190 return true;
191 }else if( _this.upload_mode == 'api' && $j('#wpSourceTypeURL').get(0).checked){
192 js_log('doHttpUpload (no form submit) ');
193 //if the api is supported.. && source type is http do upload with http status updates
194 var httpUpConf ={
195 'url' : $j('#wpUploadFileURL').val(),
196 'filename' : $j('#wpDestFile').val(),
197 'comment' : $j('#wpUploadDescription').val(),
198 'watch' : ($j('#wpWatchthis').is(':checked'))?'true':'false'
199 }
200 if( $j('#wpIgnoreWarning').is(':checked') ){
201 httpUpConf[ 'ignorewarnings'] = 'true';
202 }
203 //check for editToken
204 _this.etoken = $j("input[name='wpEditToken']").val();
205 _this.doHttpUpload( httpUpConf );
206 }else{
207 js_error( 'Error: unrecongized upload mode: ' + _this.upload_mode );
208 }
209 return false;
210 },
211 doHttpUpload:function( opt ){
212 var _this = this;
213 //set the http box to loading (in case we don't get an update for some time)
214 $j('#dlbox-centered').html( '<h5>' + _this.getProgressTitle() + '</h5>' +
215 mv_get_loading_img( 'left:40%;top:20%')
216 );
217 //setup request:
218 var req = {
219 'action' : 'upload',
220 'asyncdownload' : true //do a s
221 };
222 //set config from options:
223 for(var i in opt){
224 req[i]= opt[i];
225 }
226
227 //else try and get a token:
228 if(!_this.etoken && _this.api_url){
229 js_log('Error:doHttpUpload: missing token');
230 }else{
231 req['token'] =_this.etoken;
232 }
233 //reset the done with action flag:
234 _this.action_done = false;
235 //do the api req
236 do_api_req({
237 'data': req,
238 'url' : _this.api_url
239 }, function( data ){
240 _this.processApiResult( data );
241 });
242 },
243 doAjaxWarningIgnore:function(){
244 var _this = this;
245 if( !_this.upload_session_key )
246 return js_error('missing upload_session_key (can\'t ignore warnigns');
247 //do the ignore warnings submit to the api:
248 var req = {
249 'ignorewarnings' : 'true',
250 'sessionkey' :!_this.upload_session_key
251 };
252 //add token if present:
253 if(this.etoken)
254 req['token'] = this.etoken;
255
256 do_api_req({
257 'data':req,
258 'url': _this.api_url
259 },function(data){
260 _this.processApiResult(data);
261 });
262 },
263 doAjaxUploadStatus:function() {
264 var _this = this;
265
266 //set up the progress display for status updates:
267 _this.dispProgressOverlay();
268 var req = {
269 'action' : 'upload',
270 'httpstatus' : 'true',
271 'sessionkey' : _this.upload_session_key
272 };
273 //add token if present:
274 if(this.etoken)
275 req['token'] = this.etoken;
276
277 var uploadStatus = function(){
278 //do the api request:
279 do_api_req({
280 'data':req,
281 'url' : _this.api_url
282 }, function( data ){
283 //@@check if we are done
284 if( data.upload['apiUploadResult'] ){
285 //update status to 100%
286 _this.updateProgress( 1 );
287 if(typeof JSON == 'undefined'){
288 //we need to load the jQuery json parser: (older browsers don't have JSON.parse
289 mvJsLoader.doLoad([
290 '$j.secureEvalJSON'
291 ],function(){
292 var apiResult = $j.secureEvalJSON( data.upload['apiUploadResult'] );
293 _this.processApiResult( apiResult );
294 });
295 }else{
296 var apiResult = {};
297 try{
298 apiResult = JSON.parse ( data.upload['apiUploadResult'] ) ;
299 }catch (e){
300 //could not parse api result
301 js_log('errro: could not parse apiUploadResult ')
302 }
303 _this.processApiResult( apiResult );
304 }
305 return ;
306 }
307
308 //@@ else update status:
309 if( data.upload['content_length'] && data.upload['loaded'] ){
310 //we have content length we can show percentage done:
311 var perc = data.upload['loaded'] / data.upload['content_length'];
312 //update the status:
313 _this.updateProgress( perc );
314 //special case update the file progress where we have data size:
315 $j('#up-status-container').html(
316 gM('upload-stats-fileprogres', [
317 formatSize( data.upload['loaded'] ),
318 formatSize( data.upload['content_length'] )
319 ]
320 )
321 );
322 }else if( data.upload['loaded'] ){
323 _this.updateProgress( 1 );
324 js_log('just have loaded (no cotent length: ' + data.upload['loaded']);
325 //for lack of content-length requests:
326 $j('#up-status-container').html(
327 gM('upload-stats-fileprogres', [
328 formatSize( data.upload['loaded'] ),
329 gM('upload-unknown-size')
330 ]
331 )
332 );
333 }
334 //(we got a result) set it to 100ms + your server update interval (in our case 2s)
335 setTimeout(uploadStatus, 2100);
336 });
337 }
338 uploadStatus();
339 },
340 apiUpdateErrorCheck:function( apiRes ){
341 var _this = this;
342 if( apiRes.error || ( apiRes.upload && apiRes.upload.result == "Failure" ) ){
343 //gennerate the error button:
344 var bObj = {};
345 bObj[ gM('return-to-form') ] = function(){
346 $j(this).dialog('close');
347 };
348
349 //@@TODO should be refactored to more specialUpload page type error handling
350
351 //check a few places for the error code:
352 var error_code=0;
353 var errorReplaceArg='';
354 if( apiRes.error && apiRes.error.code ){
355 error_code = apiRes.error.code;
356 }else if( apiRes.upload.code ){
357 if(typeof apiRes.upload.code == 'object'){
358 if(apiRes.upload.code[0]){
359 error_code = apiRes.upload.code[0];
360 }
361 if(apiRes.upload.code['status']){
362 error_code = apiRes.upload.code['status'];
363 if(apiRes.upload.code['filtered'])
364 errorReplaceArg =apiRes.upload.code['filtered'];
365 }
366 }else{
367 apiRes.upload.code;
368 }
369 }
370
371 var error_msg = '';
372 if(typeof apiRes.error == 'string')
373 error_msg = apiRes.error;
374 //error space is too large so we don't front load it
375 //this upload error space replicates code in: SpecialUpload.php::processUpload()
376 //would be nice if we refactored that to the upload api.(problem is some need special actions)
377 var error_msg_key = {
378 '2' : 'largefileserver',
379 '3' : 'emptyfile',
380 '4' : 'minlength1',
381 '5' : 'illegalfilename'
382 };
383
384 //@@todo: need to write conditionals that mirror SpecialUpload for handling these error types:
385 var error_onlykey = {
386 '1': 'BEFORE_PROCESSING',
387 '6': 'PROTECTED_PAGE',
388 '7': 'OVERWRITE_EXISTING_FILE',
389 '8': 'FILETYPE_MISSING',
390 '9': 'FILETYPE_BADTYPE',
391 '10': 'VERIFICATION_ERROR',
392 '11': 'UPLOAD_VERIFICATION_ERROR',
393 '12': 'UPLOAD_WARNING',
394 '13': 'INTERNAL_ERROR',
395 '14': 'MIN_LENGHT_PARTNAME'
396 }
397
398 //do a remote call to get the error msg:
399 if(!error_code || error_code == 'unknown-error'){
400 if(typeof JSON != 'undefined'){
401 js_log('Error: apiRes: ' + JSON.stringify( apiRes) );
402 }
403 if( apiRes.upload.error == 'internal-error'){
404 errorKey = apiRes.upload.details[0];
405 gMsgLoadRemote(errorKey, function(){
406 _this.updateProgressWin( gM( 'uploaderror' ), gM( errorKey ), bObj );
407
408 });
409 return false;
410 }
411
412 _this.updateProgressWin( gM('uploaderror'), gM('unknown-error') + '<br>' + error_msg, bObj );
413 return false;
414 }else{
415 if(apiRes.error && apiRes.error.info ){
416 _this.updateProgressWin( gM('uploaderror'), apiRes.error.info ,bObj);
417 return false;
418 }else{
419 if(typeof error_code == 'number' && typeof error_msg_key[error_code] == 'undefined' ){
420 if(apiRes.upload.code.finalExt){
421 _this.updateProgressWin( gM('uploaderror'), gM('wgfogg_waring_bad_extension', apiRes.upload.code.finalExt) , bObj);
422 }else{
423 _this.updateProgressWin( gM('uploaderror'), gM('unknown-error') + ' : ' + error_code, bObj);
424 }
425 }else{
426 js_log('get key: ' + error_msg_key[ error_code ])
427 gMsgLoadRemote( error_msg_key[ error_code ], function(){
428 _this.updateProgressWin( gM('uploaderror'), gM( error_msg_key[ error_code ], errorReplaceArg ), bObj);
429 });
430 js_log("api.erorr");
431 }
432 return false;
433 }
434 }
435 }
436 //check for upload.error type erros.
437 if( apiRes.upload && apiRes.upload.error){
438 js_log(' apiRes.upload.error: ' + apiRes.upload.error );
439 _this.updateProgressWin( gM('uploaderror'), gM('unknown-error') + '<br>', bObj);
440 return false;
441 }
442 //check for known warnings:
443 if(apiRes.upload && apiRes.upload.warnings ){
444 //debugger;
445 var wmsg = '<ul>';
446 for(var wtype in apiRes.upload.warnings){
447 var winfo = apiRes.upload.warnings[wtype]
448 wmsg+='<li>';
449 switch(wtype){
450 case 'duplicate':
451 case 'exists':
452 if(winfo[1] && winfo[1].title && winfo[1].title.mTextform){
453 wmsg += gM('file-exists-duplicate') +' '+
454 '<b>' + winfo[1].title.mTextform + '</b>';
455 }else{
456 //misc error (weird that winfo[1] not present
457 wmsg += gM('upload-misc-error') + ' ' + wtype;
458 }
459 break;
460 case 'file-thumbnail-no':
461 wmsg += gM('file-thumbnail-no', winfo);
462 break;
463 default:
464 wmsg += gM('upload-misc-error') + ' ' + wtype;
465 break;
466 }
467 wmsg+='</li>';
468 }
469 wmsg+='</ul>';
470 if( apiRes.upload.warnings.sessionkey)
471 _this.warnings_sessionkey = apiRes.upload.warnings.sessionkey;
472 var bObj = {};
473 bObj[ gM('ignorewarning') ] = function() {
474 //re-inciate the upload proccess
475 $j('#wpIgnoreWarning').attr('checked', true);
476 $j( '#mw-upload-form' ).submit();
477 };
478 bObj[ gM('return-to-form') ] = function(){
479 $j(this).dialog('close');
480 }
481 _this.updateProgressWin( gM('uploadwarning'), '<h3>' + gM('uploadwarning') + '</h3>' +wmsg + '<p>',bObj);
482 return false;
483 }
484 //should be "OK"
485 return true;
486 },
487 processApiResult: function( apiRes ){
488 var _this = this;
489 js_log('processApiResult::');
490 //check for upload api error:
491 // {"upload":{"result":"Failure","error":"unknown-error","code":{"status":5,"filtered":"NGC2207%2BIC2163.jpg"}}}
492 if( _this.apiUpdateErrorCheck(apiRes) === false){
493 //error returned false (updated and
494 return false;
495 }else{
496 //check for upload_session key for async upload:
497 if( apiRes.upload && apiRes.upload.upload_session_key ){
498 //set the session key
499 _this.upload_session_key = apiRes.upload.upload_session_key;
500
501 //do ajax upload status:
502 _this.doAjaxUploadStatus();
503 js_log("set upload_session_key: " + _this.upload_session_key);
504 return ;
505 }
506
507 if( apiRes.upload.imageinfo && apiRes.upload.imageinfo.descriptionurl ){
508 var url = apiRes.upload.imageinfo.descriptionurl;
509 //check done action:
510 if( _this.done_upload_cb && typeof _this.done_upload_cb == 'function'){
511 //close up shop:
512 $j('#upProgressDialog').dialog('close');
513 //call the callback:
514 _this.done_upload_cb( url );
515 }else{
516 var bObj = {};
517 bObj[ gM('return-to-form')] = function(){
518 $j(this).dialog('close');
519 }
520 bObj[ gM('go-to-resource') ] = function(){
521 window.location = url;
522 };
523 _this.action_done = true;
524 _this.updateProgressWin( gM('successfulupload'), gM( 'mv_upload_done', url), bObj);
525 js_log('apiRes.upload.imageinfo::'+url);
526 }
527 return ;
528 }
529 }
530 },
531 updateProgressWin:function(title_txt, msg, buttons){
532 var _this = this;
533 if(!title_txt)
534 title_txt = _this.getProgressTitle();
535 if(!msg)
536 msg = mv_get_loading_img( 'left:40%;top:40px;');
537 $j( '#upProgressDialog' ).dialog('option', 'title', title_txt );
538 $j( '#upProgressDialog' ).html( msg );
539 if(buttons){
540 $j('#upProgressDialog').dialog('option','buttons', buttons);
541 }else{
542 //@@todo should convice the jquery ui people to not use object keys as user msg's
543 var bObj = {};
544 bObj[ gM('ok-button') ] = function(){
545 $j(this).dialog('close');
546 };
547 $j('#upProgressDialog').dialog('option','buttons', bObj);
548 }
549 },
550 getProgressTitle:function(){
551 return gM('upload-in-progress');
552 },
553 getEditForm:function(){
554 if( this.target_edit_from && $j( this.target_edit_from ).length != 0){
555 return $j( this.target_edit_from ).get(0);
556 }
557 //just return the first form fond on the page.
558 return $j('form :first').get(0);
559 },
560 updateProgress:function( perc ){
561 //js_log('update progress: ' + perc);
562 $j( '#up-progressbar' ).progressbar('value', parseInt( perc * 100 ) );
563 $j( '#up-pstatus' ).html( parseInt( perc * 100 ) + '% - ' );
564 },
565 /*update to jQuery.ui progress display type */
566 dispProgressOverlay:function(){
567 var _this = this;
568 //remove old instance:
569 if($j('#upProgressDialog').length!=0){
570 $j('#upProgressDialog').dialog( 'destroy' ).remove();
571 }
572 //re add it:
573 $j('body').append('<div id="upProgressDialog" ></div>');
574
575 $j('#upProgressDialog').dialog({
576 title:_this.getProgressTitle(),
577 bgiframe: true,
578 modal: true,
579 width:400,
580 heigh:200,
581 beforeclose: function(event, ui) {
582 if( event.button==0 && _this.action_done === false){
583 return _this.cancel_action();
584 }else{
585 //click on button (dont do close action);
586 return true;
587 }
588 },
589 buttons: _this.cancel_button()
590 });
591 $j('#upProgressDialog').html(
592 //set initial content:
593 '<div id="up-pbar-container" style="width:90%;height:15px;" >' +
594 '<div id="up-progressbar" style="height:15px;"></div>' +
595 '<div id="up-status-container">'+
596 '<span id="up-pstatus">0% - </span> ' +
597 '<span id="up-status-state">' + gM('uploaded-status') + '</span> ' +
598 '</div>'+
599 '</div>'
600 )
601 //setup progress bar:
602 $j('#up-progressbar').progressbar({
603 value:0
604 });
605 //just display an empty progress window
606 $j('#upProgressDialog').dialog('open');
607
608 },
609 cancel_button:function(){
610 var _this = this;
611 var cancelBtn = new Array();
612 cancelBtn[ gM('cancel-button') ] = function(){
613 return _this.cancel_action(this)
614 };
615 return cancelBtn;
616 },
617 cancel_action:function(dlElm){
618 //confirm:
619 if( confirm( gM('mv-cancel-confim') )){
620 //@@todo (cancel the encode / upload)
621 $j(this).dialog('close');
622 return false;
623 }else{
624 return true;
625 }
626 }
627 };