1618e3f69b0aa0e5bff728c197b87a0fe5105882
2 * the base Upload Interface for uploading.
4 * this base uploader is optionally extended by firefogg
7 "mwe-upload-transcode-in-progress" : "Transcode and upload in progress (do not close this window)",
8 "mwe-upload-in-progress" : "Upload in progress (do not close this window)",
9 "mwe-upload-transcoded-status" : "Transcoded",
10 "mwe-uploaded-status" : "Uploaded",
11 "mwe-upload-stats-fileprogres" : "$1 of $2",
12 "mwe-upload_completed" : "Your upload is complete",
13 "mwe-upload_done" : "<a href=\"$1\">Your upload <i>should be<\/i> accessible<\/a>.",
14 "mwe-upload-unknown-size" : "Unknown size",
15 "mwe-cancel-confim" : "Are you sure you want to cancel?",
16 "mwe-successfulupload" : "Upload successful",
17 "mwe-uploaderror" : "Upload error",
18 "mwe-uploadwarning" : "Upload warning",
19 "mwe-unknown-error" : "Unknown error:",
20 "mwe-return-to-form" : "Return to form",
21 "mwe-file-exists-duplicate" : "This file is a duplicate of the following file:",
22 "mwe-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.",
23 "mwe-fileexists-thumb" : "<center><b>Existing file<\/b><\/center>",
24 "mwe-ignorewarning" : "Ignore warning and save file anyway",
25 "mwe-file-thumbnail-no" : "The filename begins with <b><tt>$1<\/tt><\/b>",
26 "mwe-go-to-resource" : "Go to resource page",
27 "mwe-upload-misc-error" : "Unknown upload error",
28 "mwe-wgfogg_warning_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>).",
29 "mwe-cancel-button" : "Cancel",
30 "mwe-ok-button" : "OK"
33 var default_bui_options
= {
35 'parent_uploader':null,
37 'done_upload_cb': null,
38 'target_edit_from':null,
40 //upload_mode can be 'post', 'api' or 'autodetect'. (autodetect issues an api call)
41 'upload_mode': 'autodetect'
44 var mvBaseUploadInterface = function( iObj
){
45 return this.init( iObj
);
47 mvBaseUploadInterface
.prototype = {
48 parent_uploader
:false,
49 formData
:{}, //the form to be submitted
50 warnings_sessionkey
:null,
51 chunks_supported
:false,
52 form_post_override
:false,
56 init: function( iObj
){
59 //inherit iObj properties:
60 for(var i
in default_bui_options
){
64 this[i
] = default_bui_options
[i
];
70 //set up the local pointer to the edit form:
71 _this
.editForm
= _this
.getEditForm();
73 //set up the org_onsubmit if not set:
74 if( typeof( _this
.org_onsubmit
) == 'undefined' && _this
.editForm
.onsubmit
)
75 _this
.org_onsubmit
= _this
.editForm
.onsubmit
;
77 //set up the submit action:
78 $j( _this
.editForm
).submit( function(){
80 //run the original onsubmit (if not run yet set flag to avoid excessive chaining )
81 if( typeof( _this
.org_onsubmit
) == 'function' ){
82 if( ! _this
.org_onsubmit() ){
83 //error in org submit return false;
87 //check for post action override:
88 if( _this
.form_post_override
){
89 js_log('form_post_override is true do form proccessing:');
92 //get the input form data in flat json:
93 var tmpAryData
= $j( _this
.editForm
).serializeArray();
94 for(var i
=0; i
< tmpAryData
.length
; i
++){
95 if( tmpAryData
[i
]['name'] )
96 _this
.formData
[ tmpAryData
[i
]['name'] ] = tmpAryData
[i
]['value'];
98 //put into a try catch so we are sure to return false:
100 //get a clean loader:
101 _this
.dispProgressOverlay();
103 //for some unknown reason we have to drop down the #p-search z-index:
104 $j('#p-search').css('z-index', 1);
106 //select upload mode:
107 _this
.detectUploadMode();
112 //don't submit the form we will do the post in ajax
118 detectUploadMode:function( callback
){
120 //check the upload mode:
121 if( _this
.upload_mode
== 'autodetect' ){
122 js_log('detectUploadMode::' + _this
.upload_mode
+ ' api:' + _this
.api_url
);
123 if( ! _this
.api_url
)
124 return js_error( 'Error: can\'t autodetect mode without api url' );
126 'data':{ 'action':'paraminfo','modules':'upload' },
129 if( typeof data
.paraminfo
== 'undefined' || typeof data
.paraminfo
.modules
== 'undefined' )
130 return js_error( 'Error: bad api results' );
131 if( typeof data
.paraminfo
.modules
[0].classname
== 'undefined'){
132 js_log( 'Autodetect Upload Mode: \'post\' ');
133 _this
.upload_mode
= 'post';
135 js_log( 'Autodetect Upload Mode: api ' );
136 _this
.upload_mode
= 'api';
137 //check to see if chunks are supported:
138 for( var i
in data
.paraminfo
.modules
[0].parameters
){
139 var pname
= data
.paraminfo
.modules
[0].parameters
[i
].name
;
140 if( pname
== 'enablechunks' ){
141 js_log( 'this.chunks_supported = true' );
142 _this
.chunks_supported
= true;
147 js_log("do call: doUploadSwitch");
148 _this
.doUploadSwitch();
151 _this
.doUploadSwitch();
154 doUploadSwitch:function(){
156 js_log('mvUPload:doUploadSwitch():' + _this
.upload_mode
);
157 //issue a normal post request
158 if( _this
.upload_mode
== 'post' ) {
159 //we don't support the upload api
160 //trick the browser into thinking the wpUpload button was pressed (there might be a cleaner way to do this)
161 $j(_this
.editForm
).append(
162 '<input type="hidden" name="wpUpload" value="' + $j('input[name=\'wpUpload\']').val() + '"/>'
165 _this
.form_post_override
= true;
167 _this
.editForm
.submit();
169 _this
.upload_mode
=='api' &&
170 ( $j('#wpSourceTypeFile').length
== 0 || $j('#wpSourceTypeFile').get(0).checked
)
172 //@@TODO check for sendAsBinnary to support firefox 3.5 progress
174 //set the form target to iframe target:
175 _this
.iframeId
= 'f_' + ($j('iframe').length
+ 1);
176 $j(_this
.editForm
).attr('target', _this
.iframeId
);
179 $j("body").append('<iframe src="javascript:false;" id="' + _this
.iframeId
+ '" ' +
180 'name="' + _this
.iframeId
+ '" style="display:none;" ></iframe>');
182 //set up the done binding
183 $j('#' + _this
.iframeId
).load(function(){
184 _this
.proccessIframeResult( $j(this).get(0) );
187 //set the editForm iframe target
188 //$j(_this.editForm).attr('target', id);
190 //set the action to the api url:
191 $j(_this
.editForm
).attr('action', _this
.api_url
);
193 if( $j(_this
.editForm
).find("[name='action']").length
== 0)
194 $j(_this
.editForm
).append('<input type="hidden" name="action" value="upload">');
197 if( $j(_this
.editForm
).find("[name='format']").length
== 0)
198 $j(_this
.editForm
).append('<input type="hidden" name="format" value="jsonfm">');
200 //map the form vars to api vars:
201 $j(_this
.editForm
).find("[name='wpUploadFile']").attr('name', 'file');
202 $j(_this
.editForm
).find("[name='wpDestFile']").attr('name', 'filename');
203 $j(_this
.editForm
).find("[name='wpUploadDescription']").attr('name', 'comment');
204 $j(_this
.editForm
).find("[name='wpEditToken']").attr('name', 'token');
205 $j(_this
.editForm
).find("[name='wpIgnoreWarning']").attr('name', 'ignorewarnings');
206 $j(_this
.editForm
).find("[name='wpWatchthis']").attr('name', 'watch');
208 //update the status to 100% progress bar (no status in iframe submit)
209 $j('#up-progressbar' ).progressbar('value', parseInt( 100 ) );
210 $j('#up-status-container').html( gM('mwe-upload-in-progress') );
212 js_log('do iframe form submit to: ' + $j(_this
.editForm
).attr('target'));
215 _this
.form_post_override
= true;
216 //reset the done with action flag:
217 _this
.action_done
= false;
219 _this
.editForm
.submit();
222 }else if( _this
.upload_mode
== 'api' && $j('#wpSourceTypeURL').get(0).checked
){
223 js_log('doHttpUpload (no form submit) ');
224 //if the api is supported.. && source type is http do upload with http status updates
226 'url' : $j('#wpUploadFileURL').val(),
227 'filename' : $j('#wpDestFile').val(),
228 'comment' : $j('#wpUploadDescription').val(),
229 'watch' : ($j('#wpWatchthis').is(':checked'))?'true':'false'
231 //set up ignore warnings and watch arguments:
232 if( $j('#wpIgnoreWarning').is(':checked') ){
233 httpUpConf
[ 'ignorewarnings'] = 'true';
235 if( $j('#wpWatchthis').is(':checked') ){
236 httpUpConf
[ 'watch'] = 'true';
238 //check for editToken
239 _this
.etoken
= $j("#wpEditToken").val();
240 _this
.doHttpUpload( httpUpConf
);
242 js_error( 'Error: unrecongized upload mode: ' + _this
.upload_mode
);
246 proccessIframeResult:function(iframe
){
248 var doc
= iframe
.contentDocument
? iframe
.contentDocument
: frames
[iframe
.id
].document
;
250 if (doc
.readyState
&& doc
.readyState
!= 'complete'){
254 if (doc
.body
&& doc
.body
.innerHTML
== "false"){
258 if (doc
.XMLDocument
){
259 // response is a xml document IE property
260 response
= doc
.XMLDocument
;
261 } else if (doc
.body
){
263 json_str
= $j(doc
.body
).find('pre').html();
266 response
= window
["eval"]("(" +json_str
+ ")");
271 // response is a xml document
274 //do proccess the api result
275 _this
.processApiResult( response
);
277 doHttpUpload:function( opt
){
279 //set the http box to loading (in case we don't get an update for some time)
280 $j('#dlbox-centered').html( '<h5>' + _this
.getProgressTitle() + '</h5>' +
281 mv_get_loading_img( 'left:40%;top:20%')
286 'asyncdownload' : true //do a s
288 //set config from options:
293 //else try and get a token:
294 if(!_this
.etoken
&& _this
.api_url
){
295 js_log('Error:doHttpUpload: missing token');
297 req
['token'] =_this
.etoken
;
299 //reset the done with action flag:
300 _this
.action_done
= false;
304 'url' : _this
.api_url
306 _this
.processApiResult( data
);
309 doAjaxWarningIgnore:function(){
311 if( !_this
.upload_session_key
)
312 return js_error('missing upload_session_key (can\'t ignore warnigns');
313 //do the ignore warnings submit to the api:
315 'ignorewarnings' : 'true',
316 'sessionkey' :!_this
.upload_session_key
318 //add token if present:
320 req
['token'] = this.etoken
;
326 _this
.processApiResult(data
);
329 doAjaxUploadStatus:function() {
332 //set up the progress display for status updates:
333 _this
.dispProgressOverlay();
336 'httpstatus' : 'true',
337 'sessionkey' : _this
.upload_session_key
339 //add token if present:
341 req
['token'] = this.etoken
;
343 var uploadStatus = function(){
344 //do the api request:
347 'url' : _this
.api_url
349 //@@check if we are done
350 if( data
.upload
['apiUploadResult'] ){
351 //update status to 100%
352 _this
.updateProgress( 1 );
353 if(typeof JSON
== 'undefined'){
354 //we need to load the jQuery json parser: (older browsers don't have JSON.parse
358 var apiResult
= $j
.secureEvalJSON( data
.upload
['apiUploadResult'] );
359 _this
.processApiResult( apiResult
);
364 apiResult
= JSON
.parse ( data
.upload
['apiUploadResult'] ) ;
366 //could not parse api result
367 js_log('errro: could not parse apiUploadResult ')
369 _this
.processApiResult( apiResult
);
374 //@@ else update status:
375 if( data
.upload
['content_length'] && data
.upload
['loaded'] ){
376 //we have content length we can show percentage done:
377 var perc
= data
.upload
['loaded'] / data
.upload
['content_length'];
379 _this
.updateProgress( perc
);
380 //special case update the file progress where we have data size:
381 $j('#up-status-container').html(
382 gM('mwe-upload-stats-fileprogres', [
383 formatSize( data
.upload
['loaded'] ),
384 formatSize( data
.upload
['content_length'] )
388 }else if( data
.upload
['loaded'] ){
389 _this
.updateProgress( 1 );
390 js_log('just have loaded (no cotent length: ' + data
.upload
['loaded']);
391 //for lack of content-length requests:
392 $j('#up-status-container').html(
393 gM('mwe-upload-stats-fileprogres', [
394 formatSize( data
.upload
['loaded'] ),
395 gM('mwe-upload-unknown-size')
400 //(we got a result) set it to 100ms + your server update interval (in our case 2s)
401 setTimeout(uploadStatus
, 2100);
406 apiUpdateErrorCheck:function( apiRes
){
408 if( apiRes
.error
|| ( apiRes
.upload
&& apiRes
.upload
.result
== "Failure" ) ){
409 //gennerate the error button:
411 bObj
[ gM('mwe-return-to-form') ] = function(){
412 _this
.form_post_override
= false;
413 $j(this).dialog('close');
416 //@@TODO should be refactored to more specialUpload page type error handling
418 //check a few places for the error code:
420 var errorReplaceArg
='';
421 if( apiRes
.error
&& apiRes
.error
.code
){
422 error_code
= apiRes
.error
.code
;
423 }else if( apiRes
.upload
.code
){
424 if(typeof apiRes
.upload
.code
== 'object'){
425 if(apiRes
.upload
.code
[0]){
426 error_code
= apiRes
.upload
.code
[0];
428 if(apiRes
.upload
.code
['status']){
429 error_code
= apiRes
.upload
.code
['status'];
430 if(apiRes
.upload
.code
['filtered'])
431 errorReplaceArg
=apiRes
.upload
.code
['filtered'];
439 if(typeof apiRes
.error
== 'string')
440 error_msg
= apiRes
.error
;
441 //error space is too large so we don't front load it
442 //this upload error space replicates code in: SpecialUpload.php::processUpload()
443 //would be nice if we refactored that to the upload api.(problem is some need special actions)
444 var error_msg_key
= {
445 '2' : 'largefileserver',
448 '5' : 'illegalfilename'
451 //@@todo: need to write conditionals that mirror SpecialUpload for handling these error types:
452 var error_onlykey
= {
453 '1': 'BEFORE_PROCESSING',
454 '6': 'PROTECTED_PAGE',
455 '7': 'OVERWRITE_EXISTING_FILE',
456 '8': 'FILETYPE_MISSING',
457 '9': 'FILETYPE_BADTYPE',
458 '10': 'VERIFICATION_ERROR',
459 '11': 'UPLOAD_VERIFICATION_ERROR',
460 '12': 'UPLOAD_WARNING',
461 '13': 'INTERNAL_ERROR',
462 '14': 'MIN_LENGHT_PARTNAME'
465 //do a remote call to get the error msg:
466 if(!error_code
|| error_code
== 'unknown-error'){
467 if(typeof JSON
!= 'undefined'){
468 js_log('Error: apiRes: ' + JSON
.stringify( apiRes
) );
470 if( apiRes
.upload
.error
== 'internal-error'){
471 errorKey
= apiRes
.upload
.details
[0];
472 gMsgLoadRemote(errorKey
, function(){
473 _this
.updateProgressWin( gM( 'mwe-uploaderror' ), gM( errorKey
), bObj
);
479 _this
.updateProgressWin( gM('mwe-uploaderror'), gM('mwe-unknown-error') + '<br>' + error_msg
, bObj
);
482 if(apiRes
.error
&& apiRes
.error
.info
){
483 _this
.updateProgressWin( gM('mwe-uploaderror'), apiRes
.error
.info
,bObj
);
486 if(typeof error_code
== 'number' && typeof error_msg_key
[error_code
] == 'undefined' ){
487 if(apiRes
.upload
.code
.finalExt
){
488 _this
.updateProgressWin( gM('mwe-uploaderror'), gM('mwe-wgfogg_warning_bad_extension', apiRes
.upload
.code
.finalExt
) , bObj
);
490 _this
.updateProgressWin( gM('mwe-uploaderror'), gM('mwe-unknown-error') + ' : ' + error_code
, bObj
);
493 js_log('get key: ' + error_msg_key
[ error_code
])
494 gMsgLoadRemote( error_msg_key
[ error_code
], function(){
495 _this
.updateProgressWin( gM('mwe-uploaderror'), gM( error_msg_key
[ error_code
], errorReplaceArg
), bObj
);
503 //check for upload.error type erros.
504 if( apiRes
.upload
&& apiRes
.upload
.error
){
505 js_log(' apiRes.upload.error: ' + apiRes
.upload
.error
);
506 _this
.updateProgressWin( gM('mwe-uploaderror'), gM('mwe-unknown-error') + '<br>', bObj
);
509 //check for known warnings:
510 if(apiRes
.upload
&& apiRes
.upload
.warnings
){
513 for(var wtype
in apiRes
.upload
.warnings
){
514 var winfo
= apiRes
.upload
.warnings
[wtype
]
519 if(winfo
[1] && winfo
[1].title
&& winfo
[1].title
.mTextform
){
520 wmsg
+= gM('mwe-file-exists-duplicate') +' '+
521 '<b>' + winfo
[1].title
.mTextform
+ '</b>';
523 //misc error (weird that winfo[1] not present
524 wmsg
+= gM('mwe-upload-misc-error') + ' ' + wtype
;
527 case 'file-thumbnail-no':
528 wmsg
+= gM('mwe-file-thumbnail-no', winfo
);
531 wmsg
+= gM('mwe-upload-misc-error') + ' ' + wtype
;
537 if( apiRes
.upload
.warnings
.sessionkey
)
538 _this
.warnings_sessionkey
= apiRes
.upload
.warnings
.sessionkey
;
540 bObj
[ gM('mwe-ignorewarning') ] = function() {
541 //re-inciate the upload proccess
542 $j('#wpIgnoreWarning').attr('checked', true);
543 $j( '#mw-upload-form' ).submit();
545 bObj
[ gM('mwe-return-to-form') ] = function(){
546 $j(this).dialog('close');
547 _this
.form_post_override
= false;
549 _this
.updateProgressWin( gM('mwe-uploadwarning'), '<h3>' + gM('mwe-uploadwarning') + '</h3>' +wmsg
+ '<p>',bObj
);
555 processApiResult: function( apiRes
){
557 js_log('processApiResult::');
558 //check for upload api error:
559 // {"upload":{"result":"Failure","error":"unknown-error","code":{"status":5,"filtered":"NGC2207%2BIC2163.jpg"}}}
560 if( _this
.apiUpdateErrorCheck(apiRes
) === false){
561 //error returned false (updated and
564 //check for upload_session key for async upload:
565 if( apiRes
.upload
&& apiRes
.upload
.upload_session_key
){
566 //set the session key
567 _this
.upload_session_key
= apiRes
.upload
.upload_session_key
;
569 //do ajax upload status:
570 _this
.doAjaxUploadStatus();
571 js_log("set upload_session_key: " + _this
.upload_session_key
);
575 if( apiRes
.upload
.imageinfo
&& apiRes
.upload
.imageinfo
.descriptionurl
){
576 var url
= apiRes
.upload
.imageinfo
.descriptionurl
;
578 if( _this
.done_upload_cb
&& typeof _this
.done_upload_cb
== 'function'){
580 $j('#upProgressDialog').dialog('close');
582 _this
.done_upload_cb( url
);
585 bObj
[ gM('mwe-return-to-form')] = function(){
586 $j(this).dialog('close');
587 _this
.form_post_override
= false;
589 bObj
[ gM('mwe-go-to-resource') ] = function(){
590 window
.location
= url
;
592 _this
.action_done
= true;
593 _this
.updateProgressWin( gM('mwe-successfulupload'), gM( 'mwe-upload_done', url
), bObj
);
594 js_log('apiRes.upload.imageinfo::'+url
);
600 updateProgressWin:function(title_txt
, msg
, buttons
){
603 title_txt
= _this
.getProgressTitle();
605 msg
= mv_get_loading_img( 'left:40%;top:40px;');
606 $j( '#upProgressDialog' ).dialog('option', 'title', title_txt
);
607 $j( '#upProgressDialog' ).html( msg
);
609 $j('#upProgressDialog').dialog('option','buttons', buttons
);
611 //@@todo should convice the jquery ui people to not use object keys as user msg's
613 bObj
[ gM('mwe-ok-button') ] = function(){
614 $j(this).dialog('close');
616 $j('#upProgressDialog').dialog('option','buttons', bObj
);
619 getProgressTitle:function(){
620 return gM('mwe-upload-in-progress');
622 getEditForm:function(){
623 if( this.target_edit_from
&& $j( this.target_edit_from
).length
!= 0){
624 return $j( this.target_edit_from
).get(0);
626 //just return the first form fond on the page.
627 return $j('form :first').get(0);
629 updateProgress:function( perc
){
630 //js_log('update progress: ' + perc);
631 $j( '#up-progressbar' ).progressbar('value', parseInt( perc
* 100 ) );
632 $j( '#up-pstatus' ).html( parseInt( perc
* 100 ) + '% - ' );
634 /*update to jQuery.ui progress display type */
635 dispProgressOverlay:function(){
637 //remove old instance:
638 if($j('#upProgressDialog').length
!=0){
639 $j('#upProgressDialog').dialog( 'destroy' ).remove();
642 $j('body').append('<div id="upProgressDialog" ></div>');
644 $j('#upProgressDialog').dialog({
645 title
:_this
.getProgressTitle(),
650 beforeclose: function(event
, ui
) {
651 if( event
.button
==0 && _this
.action_done
=== false){
652 return _this
.cancel_action();
654 //click on button (dont do close action);
658 buttons
: _this
.cancel_button()
660 $j('#upProgressDialog').html(
661 //set initial content:
662 '<div id="up-pbar-container" style="width:90%;height:15px;" >' +
663 '<div id="up-progressbar" style="height:15px;"></div>' +
664 '<div id="up-status-container">'+
665 '<span id="up-pstatus">0% - </span> ' +
666 '<span id="up-status-state">' + gM('mwe-uploaded-status') + '</span> ' +
670 //just display an empty progress window
671 $j('#upProgressDialog').dialog('open');
673 //setup progress bar:
674 $j('#up-progressbar').progressbar({
679 cancel_button:function(){
681 var cancelBtn
= new Array();
682 cancelBtn
[ gM('mwe-cancel-button') ] = function(){
683 return _this
.cancel_action(this)
687 cancel_action : function( dlElm
){
689 if( confirm( gM('mwe-cancel-confim') )){
690 //@@todo (cancel the encode / upload)
691 $j(this).dialog('close');
699 //add some jquery binding helpers
702 * doDestCheck checks the destination
704 $.fn
.doDestCheck = function( opt
){
706 var destFile
= this.selector
;
707 //set up option defaults;
709 opt
.warn_target
= '#wpDestFile-warning';
712 $j(opt
.warn_target
).empty();
715 $j(destFile
).after('<img id = "mw-spinner-wpDestFile" src ="'+ stylepath
+ '/common/images/spinner.gif" />');
716 //try and get a thumb of the current file (check its destination)
719 'titles': 'File:' + $j(destFile
).val(),//@@todo we may need a more clever way to get a the filename
721 'iiprop':'url|mime|size',
726 $j('#mw-spinner-wpDestFile').remove();
727 if(data
&& data
.query
&& data
.query
.pages
){
728 if( data
.query
.pages
[-1] ){
729 //all good no file there
731 for(var page_id
in data
.query
.pages
){
732 var ntitle
= ( data
.query
.normalized
)? data
.query
.normalized
[0].to
: data
.query
.pages
[ page_id
].title
733 var img
= data
.query
.pages
[ page_id
].imageinfo
[0];
734 $j('#wpDestFile-warning').html(
737 gM('mwe-fileexists', ntitle
) +
739 '<div class="thumb tright">' +
740 '<div style="width: ' + ( parseInt(img
.thumbwidth
)+2 ) + 'px;" class="thumbinner">' +
741 '<a title="' + ntitle
+ '" class="image" href="' + img
.descriptionurl
+ '">' +
742 '<img width="' + img
.thumbwidth
+ '" height="' + img
.thumbheight
+ '" border="0" class="thumbimage" ' +
743 'src="' + img
.thumburl
+ '"' +
744 ' alt="' + ntitle
+ '"/>' +
746 '<div class="thumbcaption">' +
747 '<div class="magnify">' +
748 '<a title="' + gM('thumbnail-more') + '" class="internal" ' +
749 'href="' + img
.descriptionurl
+'"><img width="15" height="11" alt="" ' +
750 'src="' + stylepath
+ "/common/images/magnify-clip.png\" />" +
753 gM('mwe-fileexists-thumb') +