]> scripts.mit.edu Git - autoinstallsdev/mediawiki.git/blob - includes/specials/SpecialUpload.php
MediaWiki 1.14.0-scripts
[autoinstallsdev/mediawiki.git] / includes / specials / SpecialUpload.php
1 <?php
2 /**
3  * @file
4  * @ingroup SpecialPage
5  */
6
7
8 /**
9  * Entry point
10  */
11 function wfSpecialUpload() {
12         global $wgRequest;
13         $form = new UploadForm( $wgRequest );
14         $form->execute();
15 }
16
17 /**
18  * implements Special:Upload
19  * @ingroup SpecialPage
20  */
21 class UploadForm {
22         const SUCCESS = 0;
23         const BEFORE_PROCESSING = 1;
24         const LARGE_FILE_SERVER = 2;
25         const EMPTY_FILE = 3;
26         const MIN_LENGTH_PARTNAME = 4;
27         const ILLEGAL_FILENAME = 5;
28         const PROTECTED_PAGE = 6;
29         const OVERWRITE_EXISTING_FILE = 7;
30         const FILETYPE_MISSING = 8;
31         const FILETYPE_BADTYPE = 9;
32         const VERIFICATION_ERROR = 10;
33         const UPLOAD_VERIFICATION_ERROR = 11;
34         const UPLOAD_WARNING = 12;
35         const INTERNAL_ERROR = 13;
36
37         /**#@+
38          * @access private
39          */
40         var $mComment, $mLicense, $mIgnoreWarning, $mCurlError;
41         var $mDestName, $mTempPath, $mFileSize, $mFileProps;
42         var $mCopyrightStatus, $mCopyrightSource, $mReUpload, $mAction, $mUploadClicked;
43         var $mSrcName, $mSessionKey, $mStashed, $mDesiredDestName, $mRemoveTempFile, $mSourceType;
44         var $mDestWarningAck, $mCurlDestHandle;
45         var $mLocalFile;
46
47         # Placeholders for text injection by hooks (must be HTML)
48         # extensions should take care to _append_ to the present value
49         var $uploadFormTextTop;
50         var $uploadFormTextAfterSummary;
51
52         const SESSION_VERSION = 1;
53         /**#@-*/
54
55         /**
56          * Constructor : initialise object
57          * Get data POSTed through the form and assign them to the object
58          * @param $request Data posted.
59          */
60         function UploadForm( &$request ) {
61                 global $wgAllowCopyUploads;
62                 $this->mDesiredDestName   = $request->getText( 'wpDestFile' );
63                 $this->mIgnoreWarning     = $request->getCheck( 'wpIgnoreWarning' );
64                 $this->mComment           = $request->getText( 'wpUploadDescription' );
65
66                 if( !$request->wasPosted() ) {
67                         # GET requests just give the main form; no data except destination
68                         # filename and description
69                         return;
70                 }
71
72                 # Placeholders for text injection by hooks (empty per default)
73                 $this->uploadFormTextTop = "";
74                 $this->uploadFormTextAfterSummary = "";
75
76                 $this->mReUpload          = $request->getCheck( 'wpReUpload' );
77                 $this->mUploadClicked     = $request->getCheck( 'wpUpload' );
78
79                 $this->mLicense           = $request->getText( 'wpLicense' );
80                 $this->mCopyrightStatus   = $request->getText( 'wpUploadCopyStatus' );
81                 $this->mCopyrightSource   = $request->getText( 'wpUploadSource' );
82                 $this->mWatchthis         = $request->getBool( 'wpWatchthis' );
83                 $this->mSourceType        = $request->getText( 'wpSourceType' );
84                 $this->mDestWarningAck    = $request->getText( 'wpDestFileWarningAck' );
85
86                 $this->mAction            = $request->getVal( 'action' );
87
88                 $this->mSessionKey        = $request->getInt( 'wpSessionKey' );
89                 if( !empty( $this->mSessionKey ) &&
90                         isset( $_SESSION['wsUploadData'][$this->mSessionKey]['version'] ) &&
91                         $_SESSION['wsUploadData'][$this->mSessionKey]['version'] == self::SESSION_VERSION ) {
92                         /**
93                          * Confirming a temporarily stashed upload.
94                          * We don't want path names to be forged, so we keep
95                          * them in the session on the server and just give
96                          * an opaque key to the user agent.
97                          */
98                         $data = $_SESSION['wsUploadData'][$this->mSessionKey];
99                         $this->mTempPath         = $data['mTempPath'];
100                         $this->mFileSize         = $data['mFileSize'];
101                         $this->mSrcName          = $data['mSrcName'];
102                         $this->mFileProps        = $data['mFileProps'];
103                         $this->mCurlError        = 0/*UPLOAD_ERR_OK*/;
104                         $this->mStashed          = true;
105                         $this->mRemoveTempFile   = false;
106                 } else {
107                         /**
108                          *Check for a newly uploaded file.
109                          */
110                         if( $wgAllowCopyUploads && $this->mSourceType == 'web' ) {
111                                 $this->initializeFromUrl( $request );
112                         } else {
113                                 $this->initializeFromUpload( $request );
114                         }
115                 }
116         }
117
118         /**
119          * Initialize the uploaded file from PHP data
120          * @access private
121          */
122         function initializeFromUpload( $request ) {
123                 $this->mTempPath       = $request->getFileTempName( 'wpUploadFile' );
124                 $this->mFileSize       = $request->getFileSize( 'wpUploadFile' );
125                 $this->mSrcName        = $request->getFileName( 'wpUploadFile' );
126                 $this->mCurlError      = $request->getUploadError( 'wpUploadFile' );
127                 $this->mSessionKey     = false;
128                 $this->mStashed        = false;
129                 $this->mRemoveTempFile = false; // PHP will handle this
130         }
131
132         /**
133          * Copy a web file to a temporary file
134          * @access private
135          */
136         function initializeFromUrl( $request ) {
137                 global $wgTmpDirectory;
138                 $url = $request->getText( 'wpUploadFileURL' );
139                 $local_file = tempnam( $wgTmpDirectory, 'WEBUPLOAD' );
140
141                 $this->mTempPath       = $local_file;
142                 $this->mFileSize       = 0; # Will be set by curlCopy
143                 $this->mCurlError      = $this->curlCopy( $url, $local_file );
144                 $pathParts             = explode( '/', $url );
145                 $this->mSrcName        = array_pop( $pathParts );
146                 $this->mSessionKey     = false;
147                 $this->mStashed        = false;
148
149                 // PHP won't auto-cleanup the file
150                 $this->mRemoveTempFile = file_exists( $local_file );
151         }
152
153         /**
154          * Safe copy from URL
155          * Returns true if there was an error, false otherwise
156          */
157         private function curlCopy( $url, $dest ) {
158                 global $wgUser, $wgOut;
159
160                 if( !$wgUser->isAllowed( 'upload_by_url' ) ) {
161                         $wgOut->permissionRequired( 'upload_by_url' );
162                         return true;
163                 }
164
165                 # Maybe remove some pasting blanks :-)
166                 $url =  trim( $url );
167                 if( stripos($url, 'http://') !== 0 && stripos($url, 'ftp://') !== 0 ) {
168                         # Only HTTP or FTP URLs
169                         $wgOut->showErrorPage( 'upload-proto-error', 'upload-proto-error-text' );
170                         return true;
171                 }
172
173                 # Open temporary file
174                 $this->mCurlDestHandle = @fopen( $this->mTempPath, "wb" );
175                 if( $this->mCurlDestHandle === false ) {
176                         # Could not open temporary file to write in
177                         $wgOut->showErrorPage( 'upload-file-error', 'upload-file-error-text');
178                         return true;
179                 }
180
181                 $ch = curl_init();
182                 curl_setopt( $ch, CURLOPT_HTTP_VERSION, 1.0); # Probably not needed, but apparently can work around some bug
183                 curl_setopt( $ch, CURLOPT_TIMEOUT, 10); # 10 seconds timeout
184                 curl_setopt( $ch, CURLOPT_LOW_SPEED_LIMIT, 512); # 0.5KB per second minimum transfer speed
185                 curl_setopt( $ch, CURLOPT_URL, $url);
186                 curl_setopt( $ch, CURLOPT_WRITEFUNCTION, array( $this, 'uploadCurlCallback' ) );
187                 curl_exec( $ch );
188                 $error = curl_errno( $ch ) ? true : false;
189                 $errornum =  curl_errno( $ch );
190                 // if ( $error ) print curl_error ( $ch ) ; # Debugging output
191                 curl_close( $ch );
192
193                 fclose( $this->mCurlDestHandle );
194                 unset( $this->mCurlDestHandle );
195                 if( $error ) {
196                         unlink( $dest );
197                         if( wfEmptyMsg( "upload-curl-error$errornum", wfMsg("upload-curl-error$errornum") ) )
198                                 $wgOut->showErrorPage( 'upload-misc-error', 'upload-misc-error-text' );
199                         else
200                                 $wgOut->showErrorPage( "upload-curl-error$errornum", "upload-curl-error$errornum-text" );
201                 }
202
203                 return $error;
204         }
205
206         /**
207          * Callback function for CURL-based web transfer
208          * Write data to file unless we've passed the length limit;
209          * if so, abort immediately.
210          * @access private
211          */
212         function uploadCurlCallback( $ch, $data ) {
213                 global $wgMaxUploadSize;
214                 $length = strlen( $data );
215                 $this->mFileSize += $length;
216                 if( $this->mFileSize > $wgMaxUploadSize ) {
217                         return 0;
218                 }
219                 fwrite( $this->mCurlDestHandle, $data );
220                 return $length;
221         }
222
223         /**
224          * Start doing stuff
225          * @access public
226          */
227         function execute() {
228                 global $wgUser, $wgOut;
229                 global $wgEnableUploads;
230
231                 # Check uploading enabled
232                 if( !$wgEnableUploads ) {
233                         $wgOut->showErrorPage( 'uploaddisabled', 'uploaddisabledtext', array( $this->mDesiredDestName ) );
234                         return;
235                 }
236
237                 # Check permissions
238                 if( !$wgUser->isAllowed( 'upload' ) ) {
239                         if( !$wgUser->isLoggedIn() ) {
240                                 $wgOut->showErrorPage( 'uploadnologin', 'uploadnologintext' );
241                         } else {
242                                 $wgOut->permissionRequired( 'upload' );
243                         }
244                         return;
245                 }
246
247                 # Check blocks
248                 if( $wgUser->isBlocked() ) {
249                         $wgOut->blockedPage();
250                         return;
251                 }
252
253                 if( wfReadOnly() ) {
254                         $wgOut->readOnlyPage();
255                         return;
256                 }
257
258                 if( $this->mReUpload ) {
259                         if( !$this->unsaveUploadedFile() ) {
260                                 return;
261                         }
262                         # Because it is probably checked and shouldn't be
263                         $this->mIgnoreWarning = false;
264                         
265                         $this->mainUploadForm();
266                 } else if( 'submit' == $this->mAction || $this->mUploadClicked ) {
267                         $this->processUpload();
268                 } else {
269                         $this->mainUploadForm();
270                 }
271
272                 $this->cleanupTempFile();
273         }
274
275         /**
276          * Do the upload
277          * Checks are made in SpecialUpload::execute()
278          *
279          * @access private
280          */
281         function processUpload(){
282                 global $wgUser, $wgOut, $wgFileExtensions, $wgLang;
283                 $details = null;
284                 $value = null;
285                 $value = $this->internalProcessUpload( $details );
286
287                 switch($value) {
288                         case self::SUCCESS:
289                                 $wgOut->redirect( $this->mLocalFile->getTitle()->getFullURL() );
290                                 break;
291
292                         case self::BEFORE_PROCESSING:
293                                 break;
294
295                         case self::LARGE_FILE_SERVER:
296                                 $this->mainUploadForm( wfMsgHtml( 'largefileserver' ) );
297                                 break;
298
299                         case self::EMPTY_FILE:
300                                 $this->mainUploadForm( wfMsgHtml( 'emptyfile' ) );
301                                 break;
302
303                         case self::MIN_LENGTH_PARTNAME:
304                                 $this->mainUploadForm( wfMsgHtml( 'minlength1' ) );
305                                 break;
306
307                         case self::ILLEGAL_FILENAME:
308                                 $filtered = $details['filtered'];
309                                 $this->uploadError( wfMsgWikiHtml( 'illegalfilename', htmlspecialchars( $filtered ) ) );
310                                 break;
311
312                         case self::PROTECTED_PAGE:
313                                 $wgOut->showPermissionsErrorPage( $details['permissionserrors'] );
314                                 break;
315
316                         case self::OVERWRITE_EXISTING_FILE:
317                                 $errorText = $details['overwrite'];
318                                 $this->uploadError( $wgOut->parse( $errorText ) );
319                                 break;
320
321                         case self::FILETYPE_MISSING:
322                                 $this->uploadError( wfMsgExt( 'filetype-missing', array ( 'parseinline' ) ) );
323                                 break;
324
325                         case self::FILETYPE_BADTYPE:
326                                 $finalExt = $details['finalExt'];
327                                 $this->uploadError(
328                                         wfMsgExt( 'filetype-banned-type',
329                                                 array( 'parseinline' ),
330                                                 htmlspecialchars( $finalExt ),
331                                                 $wgLang->commaList( $wgFileExtensions ),
332                                                 $wgLang->formatNum( count($wgFileExtensions) )
333                                         )
334                                 );
335                                 break;
336
337                         case self::VERIFICATION_ERROR:
338                                 $veri = $details['veri'];
339                                 $this->uploadError( $veri->toString() );
340                                 break;
341
342                         case self::UPLOAD_VERIFICATION_ERROR:
343                                 $error = $details['error'];
344                                 $this->uploadError( $error );
345                                 break;
346
347                         case self::UPLOAD_WARNING:
348                                 $warning = $details['warning'];
349                                 $this->uploadWarning( $warning );
350                                 break;
351
352                         case self::INTERNAL_ERROR:
353                                 $internal = $details['internal'];
354                                 $this->showError( $internal );
355                                 break;
356
357                         default:
358                                 throw new MWException( __METHOD__ . ": Unknown value `{$value}`" );
359                 }
360         }
361
362         /**
363          * Really do the upload
364          * Checks are made in SpecialUpload::execute()
365          *
366          * @param array $resultDetails contains result-specific dict of additional values
367          *
368          * @access private
369          */
370         function internalProcessUpload( &$resultDetails ) {
371                 global $wgUser;
372
373                 if( !wfRunHooks( 'UploadForm:BeforeProcessing', array( &$this ) ) )
374                 {
375                         wfDebug( "Hook 'UploadForm:BeforeProcessing' broke processing the file." );
376                         return self::BEFORE_PROCESSING;
377                 }
378
379                 /**
380                  * If there was no filename or a zero size given, give up quick.
381                  */
382                 if( trim( $this->mSrcName ) == '' || empty( $this->mFileSize ) ) {
383                         return self::EMPTY_FILE;
384                 }
385
386                 /* Check for curl error */
387                 if( $this->mCurlError ) {
388                         return self::BEFORE_PROCESSING;
389                 }
390
391                 /**
392                  * Chop off any directories in the given filename. Then
393                  * filter out illegal characters, and try to make a legible name
394                  * out of it. We'll strip some silently that Title would die on.
395                  */
396                 if( $this->mDesiredDestName ) {
397                         $basename = $this->mDesiredDestName;
398                 } else {
399                         $basename = $this->mSrcName;
400                 }
401                 $filtered = wfStripIllegalFilenameChars( $basename );
402                 
403                 /* Normalize to title form before we do any further processing */
404                 $nt = Title::makeTitleSafe( NS_FILE, $filtered );
405                 if( is_null( $nt ) ) {
406                         $resultDetails = array( 'filtered' => $filtered );
407                         return self::ILLEGAL_FILENAME;
408                 }
409                 $filtered = $nt->getDBkey();
410                 
411                 /**
412                  * We'll want to blacklist against *any* 'extension', and use
413                  * only the final one for the whitelist.
414                  */
415                 list( $partname, $ext ) = $this->splitExtensions( $filtered );
416
417                 if( count( $ext ) ) {
418                         $finalExt = $ext[count( $ext ) - 1];
419                 } else {
420                         $finalExt = '';
421                 }
422
423                 # If there was more than one "extension", reassemble the base
424                 # filename to prevent bogus complaints about length
425                 if( count( $ext ) > 1 ) {
426                         for( $i = 0; $i < count( $ext ) - 1; $i++ )
427                                 $partname .= '.' . $ext[$i];
428                 }
429
430                 if( strlen( $partname ) < 1 ) {
431                         return self::MIN_LENGTH_PARTNAME;
432                 }
433
434                 $this->mLocalFile = wfLocalFile( $nt );
435                 $this->mDestName = $this->mLocalFile->getName();
436
437                 /**
438                  * If the image is protected, non-sysop users won't be able
439                  * to modify it by uploading a new revision.
440                  */
441                 $permErrors = $nt->getUserPermissionsErrors( 'edit', $wgUser );
442                 $permErrorsUpload = $nt->getUserPermissionsErrors( 'upload', $wgUser );
443                 $permErrorsCreate = ( $nt->exists() ? array() : $nt->getUserPermissionsErrors( 'create', $wgUser ) );
444
445                 if( $permErrors || $permErrorsUpload || $permErrorsCreate ) {
446                         // merge all the problems into one list, avoiding duplicates
447                         $permErrors = array_merge( $permErrors, wfArrayDiff2( $permErrorsUpload, $permErrors ) );
448                         $permErrors = array_merge( $permErrors, wfArrayDiff2( $permErrorsCreate, $permErrors ) );
449                         $resultDetails = array( 'permissionserrors' => $permErrors );
450                         return self::PROTECTED_PAGE;
451                 }
452
453                 /**
454                  * In some cases we may forbid overwriting of existing files.
455                  */
456                 $overwrite = $this->checkOverwrite( $this->mDestName );
457                 if( $overwrite !== true ) {
458                         $resultDetails = array( 'overwrite' => $overwrite );
459                         return self::OVERWRITE_EXISTING_FILE;
460                 }
461
462                 /* Don't allow users to override the blacklist (check file extension) */
463                 global $wgCheckFileExtensions, $wgStrictFileExtensions;
464                 global $wgFileExtensions, $wgFileBlacklist;
465                 if ($finalExt == '') {
466                         return self::FILETYPE_MISSING;
467                 } elseif ( $this->checkFileExtensionList( $ext, $wgFileBlacklist ) ||
468                                 ($wgCheckFileExtensions && $wgStrictFileExtensions &&
469                                         !$this->checkFileExtension( $finalExt, $wgFileExtensions ) ) ) {
470                         $resultDetails = array( 'finalExt' => $finalExt );
471                         return self::FILETYPE_BADTYPE;
472                 }
473
474                 /**
475                  * Look at the contents of the file; if we can recognize the
476                  * type but it's corrupt or data of the wrong type, we should
477                  * probably not accept it.
478                  */
479                 if( !$this->mStashed ) {
480                         $this->mFileProps = File::getPropsFromPath( $this->mTempPath, $finalExt );
481                         $this->checkMacBinary();
482                         $veri = $this->verify( $this->mTempPath, $finalExt );
483
484                         if( $veri !== true ) { //it's a wiki error...
485                                 $resultDetails = array( 'veri' => $veri );
486                                 return self::VERIFICATION_ERROR;
487                         }
488
489                         /**
490                          * Provide an opportunity for extensions to add further checks
491                          */
492                         $error = '';
493                         if( !wfRunHooks( 'UploadVerification',
494                                         array( $this->mDestName, $this->mTempPath, &$error ) ) ) {
495                                 $resultDetails = array( 'error' => $error );
496                                 return self::UPLOAD_VERIFICATION_ERROR;
497                         }
498                 }
499
500
501                 /**
502                  * Check for non-fatal conditions
503                  */
504                 if ( ! $this->mIgnoreWarning ) {
505                         $warning = '';
506
507                         global $wgCapitalLinks;
508                         if( $wgCapitalLinks ) {
509                                 $filtered = ucfirst( $filtered );
510                         }
511                         if( $basename != $filtered ) {
512                                 $warning .=  '<li>'.wfMsgHtml( 'badfilename', htmlspecialchars( $this->mDestName ) ).'</li>';
513                         }
514
515                         global $wgCheckFileExtensions;
516                         if ( $wgCheckFileExtensions ) {
517                                 if ( !$this->checkFileExtension( $finalExt, $wgFileExtensions ) ) {
518                                         global $wgLang;
519                                         $warning .= '<li>' .
520                                         wfMsgExt( 'filetype-unwanted-type',
521                                                 array( 'parseinline' ),
522                                                 htmlspecialchars( $finalExt ),
523                                                 $wgLang->commaList( $wgFileExtensions ),
524                                                 $wgLang->formatNum( count($wgFileExtensions) )
525                                         ) . '</li>';
526                                 }
527                         }
528
529                         global $wgUploadSizeWarning;
530                         if ( $wgUploadSizeWarning && ( $this->mFileSize > $wgUploadSizeWarning ) ) {
531                                 $skin = $wgUser->getSkin();
532                                 $wsize = $skin->formatSize( $wgUploadSizeWarning );
533                                 $asize = $skin->formatSize( $this->mFileSize );
534                                 $warning .= '<li>' . wfMsgHtml( 'large-file', $wsize, $asize ) . '</li>';
535                         }
536                         if ( $this->mFileSize == 0 ) {
537                                 $warning .= '<li>'.wfMsgHtml( 'emptyfile' ).'</li>';
538                         }
539
540                         if ( !$this->mDestWarningAck ) {
541                                 $warning .= self::getExistsWarning( $this->mLocalFile );
542                         }
543                         
544                         $warning .= $this->getDupeWarning( $this->mTempPath, $finalExt );
545                         
546                         if( $warning != '' ) {
547                                 /**
548                                  * Stash the file in a temporary location; the user can choose
549                                  * to let it through and we'll complete the upload then.
550                                  */
551                                 $resultDetails = array( 'warning' => $warning );
552                                 return self::UPLOAD_WARNING;
553                         }
554                 }
555
556                 /**
557                  * Try actually saving the thing...
558                  * It will show an error form on failure.
559                  */
560                 $pageText = self::getInitialPageText( $this->mComment, $this->mLicense,
561                         $this->mCopyrightStatus, $this->mCopyrightSource );
562
563                 $status = $this->mLocalFile->upload( $this->mTempPath, $this->mComment, $pageText,
564                         File::DELETE_SOURCE, $this->mFileProps );
565                 if ( !$status->isGood() ) {
566                         $resultDetails = array( 'internal' => $status->getWikiText() );
567                         return self::INTERNAL_ERROR;
568                 } else {
569                         if ( $this->mWatchthis ) {
570                                 global $wgUser;
571                                 $wgUser->addWatch( $this->mLocalFile->getTitle() );
572                         }
573                         // Success, redirect to description page
574                         $img = null; // @todo: added to avoid passing a ref to null - should this be defined somewhere?
575                         wfRunHooks( 'UploadComplete', array( &$this ) );
576                         return self::SUCCESS;
577                 }
578         }
579
580         /**
581          * Do existence checks on a file and produce a warning
582          * This check is static and can be done pre-upload via AJAX
583          * Returns an HTML fragment consisting of one or more LI elements if there is a warning
584          * Returns an empty string if there is no warning
585          */
586         static function getExistsWarning( $file ) {
587                 global $wgUser, $wgContLang;
588                 // Check for uppercase extension. We allow these filenames but check if an image
589                 // with lowercase extension exists already
590                 $warning = '';
591                 $align = $wgContLang->isRtl() ? 'left' : 'right';
592
593                 if( strpos( $file->getName(), '.' ) == false ) {
594                         $partname = $file->getName();
595                         $rawExtension = '';
596                 } else {
597                         $n = strrpos( $file->getName(), '.' );
598                         $rawExtension = substr( $file->getName(), $n + 1 );
599                         $partname = substr( $file->getName(), 0, $n );
600                 }
601
602                 $sk = $wgUser->getSkin();
603
604                 if ( $rawExtension != $file->getExtension() ) {
605                         // We're not using the normalized form of the extension.
606                         // Normal form is lowercase, using most common of alternate
607                         // extensions (eg 'jpg' rather than 'JPEG').
608                         //
609                         // Check for another file using the normalized form...
610                         $nt_lc = Title::makeTitle( NS_FILE, $partname . '.' . $file->getExtension() );
611                         $file_lc = wfLocalFile( $nt_lc );
612                 } else {
613                         $file_lc = false;
614                 }
615
616                 if( $file->exists() ) {
617                         $dlink = $sk->makeKnownLinkObj( $file->getTitle() );
618                         if ( $file->allowInlineDisplay() ) {
619                                 $dlink2 = $sk->makeImageLinkObj( $file->getTitle(), wfMsgExt( 'fileexists-thumb', 'parseinline' ),
620                                         $file->getName(), $align, array(), false, true );
621                         } elseif ( !$file->allowInlineDisplay() && $file->isSafeFile() ) {
622                                 $icon = $file->iconThumb();
623                                 $dlink2 = '<div style="float:' . $align . '" id="mw-media-icon">' .
624                                         $icon->toHtml( array( 'desc-link' => true ) ) . '<br />' . $dlink . '</div>';
625                         } else {
626                                 $dlink2 = '';
627                         }
628
629                         $warning .= '<li>' . wfMsgExt( 'fileexists', array('parseinline','replaceafter'), $dlink ) . '</li>' . $dlink2;
630
631                 } elseif( $file->getTitle()->getArticleID() ) {
632                         $lnk = $sk->makeKnownLinkObj( $file->getTitle(), '', 'redirect=no' );
633                         $warning .= '<li>' . wfMsgExt( 'filepageexists', array( 'parseinline', 'replaceafter' ), $lnk ) . '</li>';
634                 } elseif ( $file_lc && $file_lc->exists() ) {
635                         # Check if image with lowercase extension exists.
636                         # It's not forbidden but in 99% it makes no sense to upload the same filename with uppercase extension
637                         $dlink = $sk->makeKnownLinkObj( $nt_lc );
638                         if ( $file_lc->allowInlineDisplay() ) {
639                                 $dlink2 = $sk->makeImageLinkObj( $nt_lc, wfMsgExt( 'fileexists-thumb', 'parseinline' ),
640                                         $nt_lc->getText(), $align, array(), false, true );
641                         } elseif ( !$file_lc->allowInlineDisplay() && $file_lc->isSafeFile() ) {
642                                 $icon = $file_lc->iconThumb();
643                                 $dlink2 = '<div style="float:' . $align . '" id="mw-media-icon">' .
644                                         $icon->toHtml( array( 'desc-link' => true ) ) . '<br />' . $dlink . '</div>';
645                         } else {
646                                 $dlink2 = '';
647                         }
648
649                         $warning .= '<li>' .
650                                 wfMsgExt( 'fileexists-extension', 'parsemag',
651                                         $file->getTitle()->getPrefixedText(), $dlink ) .
652                                 '</li>' . $dlink2;
653
654                 } elseif ( ( substr( $partname , 3, 3 ) == 'px-' || substr( $partname , 2, 3 ) == 'px-' )
655                         && ereg( "[0-9]{2}" , substr( $partname , 0, 2) ) )
656                 {
657                         # Check for filenames like 50px- or 180px-, these are mostly thumbnails
658                         $nt_thb = Title::newFromText( substr( $partname , strpos( $partname , '-' ) +1 ) . '.' . $rawExtension );
659                         $file_thb = wfLocalFile( $nt_thb );
660                         if ($file_thb->exists() ) {
661                                 # Check if an image without leading '180px-' (or similiar) exists
662                                 $dlink = $sk->makeKnownLinkObj( $nt_thb);
663                                 if ( $file_thb->allowInlineDisplay() ) {
664                                         $dlink2 = $sk->makeImageLinkObj( $nt_thb,
665                                                 wfMsgExt( 'fileexists-thumb', 'parseinline' ),
666                                                 $nt_thb->getText(), $align, array(), false, true );
667                                 } elseif ( !$file_thb->allowInlineDisplay() && $file_thb->isSafeFile() ) {
668                                         $icon = $file_thb->iconThumb();
669                                         $dlink2 = '<div style="float:' . $align . '" id="mw-media-icon">' .
670                                                 $icon->toHtml( array( 'desc-link' => true ) ) . '<br />' .
671                                                 $dlink . '</div>';
672                                 } else {
673                                         $dlink2 = '';
674                                 }
675
676                                 $warning .= '<li>' . wfMsgExt( 'fileexists-thumbnail-yes', 'parsemag', $dlink ) .
677                                         '</li>' . $dlink2;
678                         } else {
679                                 # Image w/o '180px-' does not exists, but we do not like these filenames
680                                 $warning .= '<li>' . wfMsgExt( 'file-thumbnail-no', 'parseinline' ,
681                                         substr( $partname , 0, strpos( $partname , '-' ) +1 ) ) . '</li>';
682                         }
683                 }
684
685                 $filenamePrefixBlacklist = self::getFilenamePrefixBlacklist();
686                 # Do the match
687                 foreach( $filenamePrefixBlacklist as $prefix ) {
688                         if ( substr( $partname, 0, strlen( $prefix ) ) == $prefix ) {
689                                 $warning .= '<li>' . wfMsgExt( 'filename-bad-prefix', 'parseinline', $prefix ) . '</li>';
690                                 break;
691                         }
692                 }
693
694                 if ( $file->wasDeleted() && !$file->exists() ) {
695                         # If the file existed before and was deleted, warn the user of this
696                         # Don't bother doing so if the file exists now, however
697                         $ltitle = SpecialPage::getTitleFor( 'Log' );
698                         $llink = $sk->makeKnownLinkObj( $ltitle, wfMsgHtml( 'deletionlog' ),
699                                 'type=delete&page=' . $file->getTitle()->getPrefixedUrl() );
700                         $warning .= '<li>' . wfMsgWikiHtml( 'filewasdeleted', $llink ) . '</li>';
701                 }
702                 return $warning;
703         }
704
705         /**
706          * Get a list of warnings
707          *
708          * @param string local filename, e.g. 'file exists', 'non-descriptive filename'
709          * @return array list of warning messages
710          */
711         static function ajaxGetExistsWarning( $filename ) {
712                 $file = wfFindFile( $filename );
713                 if( !$file ) {
714                         // Force local file so we have an object to do further checks against
715                         // if there isn't an exact match...
716                         $file = wfLocalFile( $filename );
717                 }
718                 $s = '&nbsp;';
719                 if ( $file ) {
720                         $warning = self::getExistsWarning( $file );
721                         if ( $warning !== '' ) {
722                                 $s = "<ul>$warning</ul>";
723                         }
724                 }
725                 return $s;
726         }
727
728         /**
729          * Render a preview of a given license for the AJAX preview on upload
730          *
731          * @param string $license
732          * @return string
733          */
734         public static function ajaxGetLicensePreview( $license ) {
735                 global $wgParser, $wgUser;
736                 $text = '{{' . $license . '}}';
737                 $title = Title::makeTitle( NS_FILE, 'Sample.jpg' );
738                 $options = ParserOptions::newFromUser( $wgUser );
739
740                 // Expand subst: first, then live templates...
741                 $text = $wgParser->preSaveTransform( $text, $title, $wgUser, $options );
742                 $output = $wgParser->parse( $text, $title, $options );
743
744                 return $output->getText();
745         }
746         
747         /**
748          * Check for duplicate files and throw up a warning before the upload
749          * completes.
750          */
751         function getDupeWarning( $tempfile, $extension ) {
752                 $hash = File::sha1Base36( $tempfile );
753                 $dupes = RepoGroup::singleton()->findBySha1( $hash );
754                 $archivedImage = new ArchivedFile( null, 0, $hash.".$extension" );
755                 if( $dupes ) {
756                         global $wgOut;
757                         $msg = "<gallery>";
758                         foreach( $dupes as $file ) {
759                                 $title = $file->getTitle();
760                                 $msg .= $title->getPrefixedText() .
761                                         "|" . $title->getText() . "\n";
762                         }
763                         $msg .= "</gallery>";
764                         return "<li>" .
765                                 wfMsgExt( "file-exists-duplicate", array( "parse" ), count( $dupes ) ) .
766                                 $wgOut->parse( $msg ) .
767                                 "</li>\n";
768                 } elseif ( $archivedImage->getID() > 0 ) {
769                         global $wgOut;
770                         $name = Title::makeTitle( NS_FILE, $archivedImage->getName() )->getPrefixedText();
771                         return Xml::tags( 'li', null, wfMsgExt( 'file-deleted-duplicate', array( 'parseinline' ), array( $name ) ) );
772                 } else {
773                         return '';
774                 }
775         }
776
777         /**
778          * Get a list of blacklisted filename prefixes from [[MediaWiki:filename-prefix-blacklist]]
779          *
780          * @return array list of prefixes
781          */
782         public static function getFilenamePrefixBlacklist() {
783                 $blacklist = array();
784                 $message = wfMsgForContent( 'filename-prefix-blacklist' );
785                 if( $message && !( wfEmptyMsg( 'filename-prefix-blacklist', $message ) || $message == '-' ) ) {
786                         $lines = explode( "\n", $message );
787                         foreach( $lines as $line ) {
788                                 // Remove comment lines
789                                 $comment = substr( trim( $line ), 0, 1 );
790                                 if ( $comment == '#' || $comment == '' ) {
791                                         continue;
792                                 }
793                                 // Remove additional comments after a prefix
794                                 $comment = strpos( $line, '#' );
795                                 if ( $comment > 0 ) {
796                                         $line = substr( $line, 0, $comment-1 );
797                                 }
798                                 $blacklist[] = trim( $line );
799                         }
800                 }
801                 return $blacklist;
802         }
803
804         /**
805          * Stash a file in a temporary directory for later processing
806          * after the user has confirmed it.
807          *
808          * If the user doesn't explicitly cancel or accept, these files
809          * can accumulate in the temp directory.
810          *
811          * @param string $saveName - the destination filename
812          * @param string $tempName - the source temporary file to save
813          * @return string - full path the stashed file, or false on failure
814          * @access private
815          */
816         function saveTempUploadedFile( $saveName, $tempName ) {
817                 global $wgOut;
818                 $repo = RepoGroup::singleton()->getLocalRepo();
819                 $status = $repo->storeTemp( $saveName, $tempName );
820                 if ( !$status->isGood() ) {
821                         $this->showError( $status->getWikiText() );
822                         return false;
823                 } else {
824                         return $status->value;
825                 }
826         }
827
828         /**
829          * Stash a file in a temporary directory for later processing,
830          * and save the necessary descriptive info into the session.
831          * Returns a key value which will be passed through a form
832          * to pick up the path info on a later invocation.
833          *
834          * @return int
835          * @access private
836          */
837         function stashSession() {
838                 $stash = $this->saveTempUploadedFile( $this->mDestName, $this->mTempPath );
839
840                 if( !$stash ) {
841                         # Couldn't save the file.
842                         return false;
843                 }
844
845                 $key = mt_rand( 0, 0x7fffffff );
846                 $_SESSION['wsUploadData'][$key] = array(
847                         'mTempPath'       => $stash,
848                         'mFileSize'       => $this->mFileSize,
849                         'mSrcName'        => $this->mSrcName,
850                         'mFileProps'      => $this->mFileProps,
851                         'version'         => self::SESSION_VERSION,
852                 );
853                 return $key;
854         }
855
856         /**
857          * Remove a temporarily kept file stashed by saveTempUploadedFile().
858          * @access private
859          * @return success
860          */
861         function unsaveUploadedFile() {
862                 global $wgOut;
863                 $repo = RepoGroup::singleton()->getLocalRepo();
864                 $success = $repo->freeTemp( $this->mTempPath );
865                 if ( ! $success ) {
866                         $wgOut->showFileDeleteError( $this->mTempPath );
867                         return false;
868                 } else {
869                         return true;
870                 }
871         }
872
873         /* -------------------------------------------------------------- */
874
875         /**
876          * @param string $error as HTML
877          * @access private
878          */
879         function uploadError( $error ) {
880                 global $wgOut;
881                 $wgOut->addHTML( '<h2>' . wfMsgHtml( 'uploadwarning' ) . "</h2>\n" );
882                 $wgOut->addHTML( '<span class="error">' . $error . '</span>' );
883         }
884
885         /**
886          * There's something wrong with this file, not enough to reject it
887          * totally but we require manual intervention to save it for real.
888          * Stash it away, then present a form asking to confirm or cancel.
889          *
890          * @param string $warning as HTML
891          * @access private
892          */
893         function uploadWarning( $warning ) {
894                 global $wgOut;
895                 global $wgUseCopyrightUpload;
896
897                 $this->mSessionKey = $this->stashSession();
898                 if( !$this->mSessionKey ) {
899                         # Couldn't save file; an error has been displayed so let's go.
900                         return;
901                 }
902
903                 $wgOut->addHTML( '<h2>' . wfMsgHtml( 'uploadwarning' ) . "</h2>\n" );
904                 $wgOut->addHTML( '<ul class="warning">' . $warning . "</ul>\n" );
905
906                 $titleObj = SpecialPage::getTitleFor( 'Upload' );
907
908                 if ( $wgUseCopyrightUpload ) {
909                         $copyright = Xml::hidden( 'wpUploadCopyStatus', $this->mCopyrightStatus ) . "\n" .
910                                         Xml::hidden( 'wpUploadSource', $this->mCopyrightSource ) . "\n";
911                 } else {
912                         $copyright = '';
913                 }
914
915                 $wgOut->addHTML(
916                         Xml::openElement( 'form', array( 'method' => 'post', 'action' => $titleObj->getLocalURL( 'action=submit' ),
917                                  'enctype' => 'multipart/form-data', 'id' => 'uploadwarning' ) ) . "\n" .
918                         Xml::hidden( 'wpIgnoreWarning', '1' ) . "\n" .
919                         Xml::hidden( 'wpSessionKey', $this->mSessionKey ) . "\n" .
920                         Xml::hidden( 'wpUploadDescription', $this->mComment ) . "\n" .
921                         Xml::hidden( 'wpLicense', $this->mLicense ) . "\n" .
922                         Xml::hidden( 'wpDestFile', $this->mDesiredDestName ) . "\n" .
923                         Xml::hidden( 'wpWatchthis', $this->mWatchthis ) . "\n" .
924                         "{$copyright}<br />" .
925                         Xml::submitButton( wfMsg( 'ignorewarning' ), array ( 'name' => 'wpUpload', 'id' => 'wpUpload', 'checked' => 'checked' ) ) . ' ' .
926                         Xml::submitButton( wfMsg( 'reuploaddesc' ), array ( 'name' => 'wpReUpload', 'id' => 'wpReUpload' ) ) .
927                         Xml::closeElement( 'form' ) . "\n"
928                 );
929         }
930
931         /**
932          * Displays the main upload form, optionally with a highlighted
933          * error message up at the top.
934          *
935          * @param string $msg as HTML
936          * @access private
937          */
938         function mainUploadForm( $msg='' ) {
939                 global $wgOut, $wgUser, $wgLang, $wgMaxUploadSize;
940                 global $wgUseCopyrightUpload, $wgUseAjax, $wgAjaxUploadDestCheck, $wgAjaxLicensePreview;
941                 global $wgRequest, $wgAllowCopyUploads;
942                 global $wgStylePath, $wgStyleVersion;
943
944                 $useAjaxDestCheck = $wgUseAjax && $wgAjaxUploadDestCheck;
945                 $useAjaxLicensePreview = $wgUseAjax && $wgAjaxLicensePreview;
946
947                 $adc = wfBoolToStr( $useAjaxDestCheck );
948                 $alp = wfBoolToStr( $useAjaxLicensePreview );
949                 $autofill = wfBoolToStr( $this->mDesiredDestName == '' );
950
951                 $wgOut->addScript( "<script type=\"text/javascript\">
952 wgAjaxUploadDestCheck = {$adc};
953 wgAjaxLicensePreview = {$alp};
954 wgUploadAutoFill = {$autofill};
955 </script>" );
956                 $wgOut->addScriptFile( 'upload.js' );
957                 $wgOut->addScriptFile( 'edit.js' ); // For <charinsert> support
958
959                 if( !wfRunHooks( 'UploadForm:initial', array( &$this ) ) )
960                 {
961                         wfDebug( "Hook 'UploadForm:initial' broke output of the upload form" );
962                         return false;
963                 }
964
965                 if( $this->mDesiredDestName ) {
966                         $title = Title::makeTitleSafe( NS_FILE, $this->mDesiredDestName );
967                         // Show a subtitle link to deleted revisions (to sysops et al only)
968                         if( $title instanceof Title && ( $count = $title->isDeleted() ) > 0 && $wgUser->isAllowed( 'deletedhistory' ) ) {
969                                 $link = wfMsgExt(
970                                         $wgUser->isAllowed( 'delete' ) ? 'thisisdeleted' : 'viewdeleted',
971                                         array( 'parse', 'replaceafter' ),
972                                         $wgUser->getSkin()->makeKnownLinkObj(
973                                                 SpecialPage::getTitleFor( 'Undelete', $title->getPrefixedText() ),
974                                                 wfMsgExt( 'restorelink', array( 'parsemag', 'escape' ), $count )
975                                         )
976                                 );
977                                 $wgOut->addHTML( "<div id=\"contentSub2\">{$link}</div>" );
978                         }
979
980                         // Show the relevant lines from deletion log (for still deleted files only)
981                         if( $title instanceof Title && $title->isDeleted() > 0 && !$title->exists() ) {
982                                 $this->showDeletionLog( $wgOut, $title->getPrefixedText() );
983                         }
984                 }
985
986                 $cols = intval($wgUser->getOption( 'cols' ));
987
988                 if( $wgUser->getOption( 'editwidth' ) ) {
989                         $width = " style=\"width:100%\"";
990                 } else {
991                         $width = '';
992                 }
993
994                 if ( '' != $msg ) {
995                         $sub = wfMsgHtml( 'uploaderror' );
996                         $wgOut->addHTML( "<h2>{$sub}</h2>\n" .
997                           "<span class='error'>{$msg}</span>\n" );
998                 }
999                 $wgOut->addHTML( '<div id="uploadtext">' );
1000                 $wgOut->addWikiMsg( 'uploadtext', $this->mDesiredDestName );
1001                 $wgOut->addHTML( "</div>\n" );
1002
1003                 # Print a list of allowed file extensions, if so configured.  We ignore
1004                 # MIME type here, it's incomprehensible to most people and too long.
1005                 global $wgCheckFileExtensions, $wgStrictFileExtensions,
1006                 $wgFileExtensions, $wgFileBlacklist;
1007
1008                 $allowedExtensions = '';
1009                 if( $wgCheckFileExtensions ) {
1010                         if( $wgStrictFileExtensions ) {
1011                                 # Everything not permitted is banned
1012                                 $extensionsList =
1013                                         '<div id="mw-upload-permitted">' .
1014                                         wfMsgWikiHtml( 'upload-permitted', $wgLang->commaList( $wgFileExtensions ) ) .
1015                                         "</div>\n";
1016                         } else {
1017                                 # We have to list both preferred and prohibited
1018                                 $extensionsList =
1019                                         '<div id="mw-upload-preferred">' .
1020                                         wfMsgWikiHtml( 'upload-preferred', $wgLang->commaList( $wgFileExtensions ) ) .
1021                                         "</div>\n" .
1022                                         '<div id="mw-upload-prohibited">' .
1023                                         wfMsgWikiHtml( 'upload-prohibited', $wgLang->commaList( $wgFileBlacklist ) ) .
1024                                         "</div>\n";
1025                         }
1026                 } else {
1027                         # Everything is permitted.
1028                         $extensionsList = '';
1029                 }
1030
1031                 # Get the maximum file size from php.ini as $wgMaxUploadSize works for uploads from URL via CURL only
1032                 # See http://www.php.net/manual/en/ini.core.php#ini.upload-max-filesize for possible values of upload_max_filesize
1033                 $val = trim( ini_get( 'upload_max_filesize' ) );
1034                 $last = strtoupper( ( substr( $val, -1 ) ) );
1035                 switch( $last ) {
1036                         case 'G':
1037                                 $val2 = substr( $val, 0, -1 ) * 1024 * 1024 * 1024;
1038                                 break;
1039                         case 'M':
1040                                 $val2 = substr( $val, 0, -1 ) * 1024 * 1024;
1041                                 break;
1042                         case 'K':
1043                                 $val2 = substr( $val, 0, -1 ) * 1024;
1044                                 break;
1045                         default:
1046                                 $val2 = $val;
1047                 }
1048                 $val2 = $wgAllowCopyUploads ? min( $wgMaxUploadSize, $val2 ) : $val2;
1049                 $maxUploadSize = '<div id="mw-upload-maxfilesize">' . 
1050                         wfMsgExt( 'upload-maxfilesize', array( 'parseinline', 'escapenoentities' ), 
1051                                 $wgLang->formatSize( $val2 ) ) .
1052                                 "</div>\n";
1053
1054                 $sourcefilename = wfMsgExt( 'sourcefilename', array( 'parseinline', 'escapenoentities' ) );
1055         $destfilename = wfMsgExt( 'destfilename', array( 'parseinline', 'escapenoentities' ) ); 
1056                 
1057                 $summary = wfMsgExt( 'fileuploadsummary', 'parseinline' );
1058
1059                 $licenses = new Licenses();
1060                 $license = wfMsgExt( 'license', array( 'parseinline' ) );
1061                 $nolicense = wfMsgHtml( 'nolicense' );
1062                 $licenseshtml = $licenses->getHtml();
1063
1064                 $ulb = wfMsgHtml( 'uploadbtn' );
1065
1066
1067                 $titleObj = SpecialPage::getTitleFor( 'Upload' );
1068
1069                 $encDestName = htmlspecialchars( $this->mDesiredDestName );
1070
1071                 $watchChecked = $this->watchCheck()
1072                         ? 'checked="checked"'
1073                         : '';
1074                 $warningChecked = $this->mIgnoreWarning ? 'checked' : '';
1075
1076                 // Prepare form for upload or upload/copy
1077                 if( $wgAllowCopyUploads && $wgUser->isAllowed( 'upload_by_url' ) ) {
1078                         $filename_form =
1079                                 "<input type='radio' id='wpSourceTypeFile' name='wpSourceType' value='file' " .
1080                                    "onchange='toggle_element_activation(\"wpUploadFileURL\",\"wpUploadFile\")' checked='checked' />" .
1081                                  "<input tabindex='1' type='file' name='wpUploadFile' id='wpUploadFile' " .
1082                                    "onfocus='" .
1083                                      "toggle_element_activation(\"wpUploadFileURL\",\"wpUploadFile\");" .
1084                                      "toggle_element_check(\"wpSourceTypeFile\",\"wpSourceTypeURL\")' " .
1085                                      "onchange='fillDestFilename(\"wpUploadFile\")' size='60' />" .
1086                                 wfMsgHTML( 'upload_source_file' ) . "<br/>" .
1087                                 "<input type='radio' id='wpSourceTypeURL' name='wpSourceType' value='web' " .
1088                                   "onchange='toggle_element_activation(\"wpUploadFile\",\"wpUploadFileURL\")' />" .
1089                                 "<input tabindex='1' type='text' name='wpUploadFileURL' id='wpUploadFileURL' " .
1090                                   "onfocus='" .
1091                                     "toggle_element_activation(\"wpUploadFile\",\"wpUploadFileURL\");" .
1092                                     "toggle_element_check(\"wpSourceTypeURL\",\"wpSourceTypeFile\")' " .
1093                                     "onchange='fillDestFilename(\"wpUploadFileURL\")' size='60' disabled='disabled' />" .
1094                                 wfMsgHtml( 'upload_source_url' ) ;
1095                 } else {
1096                         $filename_form =
1097                                 "<input tabindex='1' type='file' name='wpUploadFile' id='wpUploadFile' " .
1098                                 ($this->mDesiredDestName?"":"onchange='fillDestFilename(\"wpUploadFile\")' ") .
1099                                 "size='60' />" .
1100                                 "<input type='hidden' name='wpSourceType' value='file' />" ;
1101                 }
1102                 if ( $useAjaxDestCheck ) {
1103                         $warningRow = "<tr><td colspan='2' id='wpDestFile-warning'>&nbsp;</td></tr>";
1104                         $destOnkeyup = 'onkeyup="wgUploadWarningObj.keypress();"';
1105                 } else {
1106                         $warningRow = '';
1107                         $destOnkeyup = '';
1108                 }
1109
1110                 $encComment = htmlspecialchars( $this->mComment );
1111
1112                 $wgOut->addHTML(
1113                          Xml::openElement( 'form', array( 'method' => 'post', 'action' => $titleObj->getLocalURL(),
1114                                  'enctype' => 'multipart/form-data', 'id' => 'mw-upload-form' ) ) .
1115                          Xml::openElement( 'fieldset' ) .
1116                          Xml::element( 'legend', null, wfMsg( 'upload' ) ) .
1117                          Xml::openElement( 'table', array( 'border' => '0', 'id' => 'mw-upload-table' ) ) .
1118                          "<tr>
1119                                 {$this->uploadFormTextTop}
1120                                 <td class='mw-label'>
1121                                         <label for='wpUploadFile'>{$sourcefilename}</label>
1122                                 </td>
1123                                 <td class='mw-input'>
1124                                         {$filename_form}
1125                                 </td>
1126                         </tr>
1127                         <tr>
1128                                 <td></td>
1129                                 <td>
1130                                         {$maxUploadSize}
1131                                         {$extensionsList}
1132                                 </td>
1133                         </tr>
1134                         <tr>
1135                                 <td class='mw-label'>
1136                                         <label for='wpDestFile'>{$destfilename}</label>
1137                                 </td>
1138                                 <td class='mw-input'>
1139                                         <input tabindex='2' type='text' name='wpDestFile' id='wpDestFile' size='60'
1140                                                 value=\"{$encDestName}\" onchange='toggleFilenameFiller()' $destOnkeyup />
1141                                 </td>
1142                         </tr>
1143                         <tr>
1144                                 <td class='mw-label'>
1145                                         <label for='wpUploadDescription'>{$summary}</label>
1146                                 </td>
1147                                 <td class='mw-input'>
1148                                         <textarea tabindex='3' name='wpUploadDescription' id='wpUploadDescription' rows='6'
1149                                                 cols='{$cols}'{$width}>$encComment</textarea>
1150                                         {$this->uploadFormTextAfterSummary}
1151                                 </td>
1152                         </tr>
1153                         <tr>"
1154                 );
1155
1156                 if ( $licenseshtml != '' ) {
1157                         global $wgStylePath;
1158                         $wgOut->addHTML( "
1159                                         <td class='mw-label'>
1160                                                 <label for='wpLicense'>$license</label>
1161                                         </td>
1162                                         <td class='mw-input'>
1163                                                 <select name='wpLicense' id='wpLicense' tabindex='4'
1164                                                         onchange='licenseSelectorCheck()'>
1165                                                         <option value=''>$nolicense</option>
1166                                                         $licenseshtml
1167                                                 </select>
1168                                         </td>
1169                                 </tr>
1170                                 <tr>"
1171                         );
1172                         if( $useAjaxLicensePreview ) {
1173                                 $wgOut->addHTML( "
1174                                                 <td></td>
1175                                                 <td id=\"mw-license-preview\"></td>
1176                                         </tr>
1177                                         <tr>"
1178                                 );
1179                         }
1180                 }
1181
1182                 if ( $wgUseCopyrightUpload ) {
1183                         $filestatus = wfMsgExt( 'filestatus', 'escapenoentities' );
1184                         $copystatus =  htmlspecialchars( $this->mCopyrightStatus );
1185                         $filesource = wfMsgExt( 'filesource', 'escapenoentities' );
1186                         $uploadsource = htmlspecialchars( $this->mCopyrightSource );
1187
1188                         $wgOut->addHTML( "
1189                                         <td class='mw-label' style='white-space: nowrap;'>
1190                                                 <label for='wpUploadCopyStatus'>$filestatus</label></td>
1191                                         <td class='mw-input'>
1192                                                 <input tabindex='5' type='text' name='wpUploadCopyStatus' id='wpUploadCopyStatus'
1193                                                         value=\"$copystatus\" size='60' />
1194                                         </td>
1195                                 </tr>
1196                                 <tr>
1197                                         <td class='mw-label'>
1198                                                 <label for='wpUploadCopyStatus'>$filesource</label>
1199                                         </td>
1200                                         <td class='mw-input'>
1201                                                 <input tabindex='6' type='text' name='wpUploadSource' id='wpUploadCopyStatus'
1202                                                         value=\"$uploadsource\" size='60' />
1203                                         </td>
1204                                 </tr>
1205                                 <tr>"
1206                         );
1207                 }
1208
1209                 $wgOut->addHTML( "
1210                                 <td></td>
1211                                 <td>
1212                                         <input tabindex='7' type='checkbox' name='wpWatchthis' id='wpWatchthis' $watchChecked value='true' />
1213                                         <label for='wpWatchthis'>" . wfMsgHtml( 'watchthisupload' ) . "</label>
1214                                         <input tabindex='8' type='checkbox' name='wpIgnoreWarning' id='wpIgnoreWarning' value='true' $warningChecked/>
1215                                         <label for='wpIgnoreWarning'>" . wfMsgHtml( 'ignorewarnings' ) . "</label>
1216                                 </td>
1217                         </tr>
1218                         $warningRow
1219                         <tr>
1220                                 <td></td>
1221                                         <td class='mw-input'>
1222                                                 <input tabindex='9' type='submit' name='wpUpload' value=\"{$ulb}\"" . $wgUser->getSkin()->tooltipAndAccesskey( 'upload' ) . " />
1223                                         </td>
1224                         </tr>
1225                         <tr>
1226                                 <td></td>
1227                                 <td class='mw-input'>"
1228                 );
1229                 $wgOut->addWikiText( wfMsgForContent( 'edittools' ) );
1230                 $wgOut->addHTML( "
1231                                 </td>
1232                         </tr>" .
1233                         Xml::closeElement( 'table' ) .
1234                         Xml::hidden( 'wpDestFileWarningAck', '', array( 'id' => 'wpDestFileWarningAck' ) ) .
1235                         Xml::closeElement( 'fieldset' ) .
1236                         Xml::closeElement( 'form' )
1237                 );
1238                 $uploadfooter = wfMsgNoTrans( 'uploadfooter' );
1239                 if( $uploadfooter != '-' && !wfEmptyMsg( 'uploadfooter', $uploadfooter ) ){
1240                         $wgOut->addWikiText( '<div id="mw-upload-footer-message">' . $uploadfooter . '</div>' );
1241                 }
1242         }
1243
1244         /* -------------------------------------------------------------- */
1245         
1246         /**
1247          * See if we should check the 'watch this page' checkbox on the form
1248          * based on the user's preferences and whether we're being asked
1249          * to create a new file or update an existing one.
1250          *
1251          * In the case where 'watch edits' is off but 'watch creations' is on,
1252          * we'll leave the box unchecked.
1253          *
1254          * Note that the page target can be changed *on the form*, so our check
1255          * state can get out of sync.
1256          */
1257         function watchCheck() {
1258                 global $wgUser;
1259                 if( $wgUser->getOption( 'watchdefault' ) ) {
1260                         // Watch all edits!
1261                         return true;
1262                 }
1263                 
1264                 $local = wfLocalFile( $this->mDesiredDestName );
1265                 if( $local && $local->exists() ) {
1266                         // We're uploading a new version of an existing file.
1267                         // No creation, so don't watch it if we're not already.
1268                         return $local->getTitle()->userIsWatching();
1269                 } else {
1270                         // New page should get watched if that's our option.
1271                         return $wgUser->getOption( 'watchcreations' );
1272                 }
1273         }
1274
1275         /**
1276          * Split a file into a base name and all dot-delimited 'extensions'
1277          * on the end. Some web server configurations will fall back to
1278          * earlier pseudo-'extensions' to determine type and execute
1279          * scripts, so the blacklist needs to check them all.
1280          *
1281          * @return array
1282          */
1283         public function splitExtensions( $filename ) {
1284                 $bits = explode( '.', $filename );
1285                 $basename = array_shift( $bits );
1286                 return array( $basename, $bits );
1287         }
1288
1289         /**
1290          * Perform case-insensitive match against a list of file extensions.
1291          * Returns true if the extension is in the list.
1292          *
1293          * @param string $ext
1294          * @param array $list
1295          * @return bool
1296          */
1297         function checkFileExtension( $ext, $list ) {
1298                 return in_array( strtolower( $ext ), $list );
1299         }
1300
1301         /**
1302          * Perform case-insensitive match against a list of file extensions.
1303          * Returns true if any of the extensions are in the list.
1304          *
1305          * @param array $ext
1306          * @param array $list
1307          * @return bool
1308          */
1309         public function checkFileExtensionList( $ext, $list ) {
1310                 foreach( $ext as $e ) {
1311                         if( in_array( strtolower( $e ), $list ) ) {
1312                                 return true;
1313                         }
1314                 }
1315                 return false;
1316         }
1317
1318         /**
1319          * Verifies that it's ok to include the uploaded file
1320          *
1321          * @param string $tmpfile the full path of the temporary file to verify
1322          * @param string $extension The filename extension that the file is to be served with
1323          * @return mixed true of the file is verified, a WikiError object otherwise.
1324          */
1325         function verify( $tmpfile, $extension ) {
1326                 #magically determine mime type
1327                 $magic = MimeMagic::singleton();
1328                 $mime = $magic->guessMimeType($tmpfile,false);
1329
1330
1331                 #check mime type, if desired
1332                 global $wgVerifyMimeType;
1333                 if ($wgVerifyMimeType) {
1334                         wfDebug ( "\n\nmime: <$mime> extension: <$extension>\n\n");
1335                         #check mime type against file extension
1336                         if( !self::verifyExtension( $mime, $extension ) ) {
1337                                 return new WikiErrorMsg( 'uploadcorrupt' );
1338                         }
1339
1340                         #check mime type blacklist
1341                         global $wgMimeTypeBlacklist;
1342                         if( isset($wgMimeTypeBlacklist) && !is_null($wgMimeTypeBlacklist) ) {
1343                                 if ( $this->checkFileExtension( $mime, $wgMimeTypeBlacklist ) ) {
1344                                         return new WikiErrorMsg( 'filetype-badmime', htmlspecialchars( $mime ) );
1345                                 }
1346
1347                                 # Check IE type
1348                                 $fp = fopen( $tmpfile, 'rb' );
1349                                 $chunk = fread( $fp, 256 );
1350                                 fclose( $fp );
1351                                 $extMime = $magic->guessTypesForExtension( $extension );
1352                                 $ieTypes = $magic->getIEMimeTypes( $tmpfile, $chunk, $extMime );
1353                                 foreach ( $ieTypes as $ieType ) {
1354                                         if ( $this->checkFileExtension( $ieType, $wgMimeTypeBlacklist ) ) {
1355                                                 return new WikiErrorMsg( 'filetype-bad-ie-mime', $ieType );
1356                                         }
1357                                 }
1358                         }
1359                 }
1360
1361                 #check for htmlish code and javascript
1362                 if( $this->detectScript ( $tmpfile, $mime, $extension ) ) {
1363                         return new WikiErrorMsg( 'uploadscripted' );
1364                 }
1365                 if( $extension == 'svg' || $mime == 'image/svg+xml' ) {
1366                         if( $this->detectScriptInSvg( $tmpfile ) ) {
1367                                 return new WikiErrorMsg( 'uploadscripted' );
1368                         }
1369                 }
1370
1371                 /**
1372                 * Scan the uploaded file for viruses
1373                 */
1374                 $virus= $this->detectVirus($tmpfile);
1375                 if ( $virus ) {
1376                         return new WikiErrorMsg( 'uploadvirus', htmlspecialchars($virus) );
1377                 }
1378
1379                 wfDebug( __METHOD__.": all clear; passing.\n" );
1380                 return true;
1381         }
1382
1383         /**
1384          * Checks if the mime type of the uploaded file matches the file extension.
1385          *
1386          * @param string $mime the mime type of the uploaded file
1387          * @param string $extension The filename extension that the file is to be served with
1388          * @return bool
1389          */
1390         static function verifyExtension( $mime, $extension ) {
1391                 $magic = MimeMagic::singleton();
1392
1393                 if ( ! $mime || $mime == 'unknown' || $mime == 'unknown/unknown' )
1394                         if ( ! $magic->isRecognizableExtension( $extension ) ) {
1395                                 wfDebug( __METHOD__.": passing file with unknown detected mime type; " .
1396                                         "unrecognized extension '$extension', can't verify\n" );
1397                                 return true;
1398                         } else {
1399                                 wfDebug( __METHOD__.": rejecting file with unknown detected mime type; ".
1400                                         "recognized extension '$extension', so probably invalid file\n" );
1401                                 return false;
1402                         }
1403
1404                 $match= $magic->isMatchingExtension($extension,$mime);
1405
1406                 if ($match===NULL) {
1407                         wfDebug( __METHOD__.": no file extension known for mime type $mime, passing file\n" );
1408                         return true;
1409                 } elseif ($match===true) {
1410                         wfDebug( __METHOD__.": mime type $mime matches extension $extension, passing file\n" );
1411
1412                         #TODO: if it's a bitmap, make sure PHP or ImageMagic resp. can handle it!
1413                         return true;
1414
1415                 } else {
1416                         wfDebug( __METHOD__.": mime type $mime mismatches file extension $extension, rejecting file\n" );
1417                         return false;
1418                 }
1419         }
1420
1421
1422         /**
1423          * Heuristic for detecting files that *could* contain JavaScript instructions or
1424          * things that may look like HTML to a browser and are thus
1425          * potentially harmful. The present implementation will produce false positives in some situations.
1426          *
1427          * @param string $file Pathname to the temporary upload file
1428          * @param string $mime The mime type of the file
1429          * @param string $extension The extension of the file
1430          * @return bool true if the file contains something looking like embedded scripts
1431          */
1432         function detectScript($file, $mime, $extension) {
1433                 global $wgAllowTitlesInSVG;
1434
1435                 #ugly hack: for text files, always look at the entire file.
1436                 #For binarie field, just check the first K.
1437
1438                 if (strpos($mime,'text/')===0) $chunk = file_get_contents( $file );
1439                 else {
1440                         $fp = fopen( $file, 'rb' );
1441                         $chunk = fread( $fp, 1024 );
1442                         fclose( $fp );
1443                 }
1444
1445                 $chunk= strtolower( $chunk );
1446
1447                 if (!$chunk) return false;
1448
1449                 #decode from UTF-16 if needed (could be used for obfuscation).
1450                 if (substr($chunk,0,2)=="\xfe\xff") $enc= "UTF-16BE";
1451                 elseif (substr($chunk,0,2)=="\xff\xfe") $enc= "UTF-16LE";
1452                 else $enc= NULL;
1453
1454                 if ($enc) $chunk= iconv($enc,"ASCII//IGNORE",$chunk);
1455
1456                 $chunk= trim($chunk);
1457
1458                 #FIXME: convert from UTF-16 if necessarry!
1459
1460                 wfDebug("SpecialUpload::detectScript: checking for embedded scripts and HTML stuff\n");
1461
1462                 #check for HTML doctype
1463                 if (eregi("<!DOCTYPE *X?HTML",$chunk)) return true;
1464
1465                 /**
1466                 * Internet Explorer for Windows performs some really stupid file type
1467                 * autodetection which can cause it to interpret valid image files as HTML
1468                 * and potentially execute JavaScript, creating a cross-site scripting
1469                 * attack vectors.
1470                 *
1471                 * Apple's Safari browser also performs some unsafe file type autodetection
1472                 * which can cause legitimate files to be interpreted as HTML if the
1473                 * web server is not correctly configured to send the right content-type
1474                 * (or if you're really uploading plain text and octet streams!)
1475                 *
1476                 * Returns true if IE is likely to mistake the given file for HTML.
1477                 * Also returns true if Safari would mistake the given file for HTML
1478                 * when served with a generic content-type.
1479                 */
1480
1481                 $tags = array(
1482                         '<a href',
1483                         '<body',
1484                         '<head',
1485                         '<html',   #also in safari
1486                         '<img',
1487                         '<pre',
1488                         '<script', #also in safari
1489                         '<table'
1490                         );
1491                 if( ! $wgAllowTitlesInSVG && $extension !== 'svg' && $mime !== 'image/svg' ) {
1492                         $tags[] = '<title';
1493                 }
1494
1495                 foreach( $tags as $tag ) {
1496                         if( false !== strpos( $chunk, $tag ) ) {
1497                                 return true;
1498                         }
1499                 }
1500
1501                 /*
1502                 * look for javascript
1503                 */
1504
1505                 #resolve entity-refs to look at attributes. may be harsh on big files... cache result?
1506                 $chunk = Sanitizer::decodeCharReferences( $chunk );
1507
1508                 #look for script-types
1509                 if (preg_match('!type\s*=\s*[\'"]?\s*(?:\w*/)?(?:ecma|java)!sim',$chunk)) return true;
1510
1511                 #look for html-style script-urls
1512                 if (preg_match('!(?:href|src|data)\s*=\s*[\'"]?\s*(?:ecma|java)script:!sim',$chunk)) return true;
1513
1514                 #look for css-style script-urls
1515                 if (preg_match('!url\s*\(\s*[\'"]?\s*(?:ecma|java)script:!sim',$chunk)) return true;
1516
1517                 wfDebug("SpecialUpload::detectScript: no scripts found\n");
1518                 return false;
1519         }
1520
1521         function detectScriptInSvg( $filename ) {
1522                 $check = new XmlTypeCheck( $filename, array( $this, 'checkSvgScriptCallback' ) );
1523                 return $check->filterMatch;
1524         }
1525         
1526         /**
1527          * @todo Replace this with a whitelist filter!
1528          */
1529         function checkSvgScriptCallback( $element, $attribs ) {
1530                 $stripped = $this->stripXmlNamespace( $element );
1531                 
1532                 if( $stripped == 'script' ) {
1533                         wfDebug( __METHOD__ . ": Found script element '$element' in uploaded file.\n" );
1534                         return true;
1535                 }
1536                 
1537                 foreach( $attribs as $attrib => $value ) {
1538                         $stripped = $this->stripXmlNamespace( $attrib );
1539                         if( substr( $stripped, 0, 2 ) == 'on' ) {
1540                                 wfDebug( __METHOD__ . ": Found script attribute '$attrib'='value' in uploaded file.\n" );
1541                                 return true;
1542                         }
1543                         if( $stripped == 'href' && strpos( strtolower( $value ), 'javascript:' ) !== false ) {
1544                                 wfDebug( __METHOD__ . ": Found script href attribute '$attrib'='$value' in uploaded file.\n" );
1545                                 return true;
1546                         }
1547                 }
1548         }
1549         
1550         private function stripXmlNamespace( $name ) {
1551                 // 'http://www.w3.org/2000/svg:script' -> 'script'
1552                 $parts = explode( ':', strtolower( $name ) );
1553                 return array_pop( $parts );
1554         }
1555         
1556         /**
1557          * Generic wrapper function for a virus scanner program.
1558          * This relies on the $wgAntivirus and $wgAntivirusSetup variables.
1559          * $wgAntivirusRequired may be used to deny upload if the scan fails.
1560          *
1561          * @param string $file Pathname to the temporary upload file
1562          * @return mixed false if not virus is found, NULL if the scan fails or is disabled,
1563          *         or a string containing feedback from the virus scanner if a virus was found.
1564          *         If textual feedback is missing but a virus was found, this function returns true.
1565          */
1566         function detectVirus($file) {
1567                 global $wgAntivirus, $wgAntivirusSetup, $wgAntivirusRequired, $wgOut;
1568
1569                 if ( !$wgAntivirus ) {
1570                         wfDebug( __METHOD__.": virus scanner disabled\n");
1571                         return NULL;
1572                 }
1573
1574                 if ( !$wgAntivirusSetup[$wgAntivirus] ) {
1575                         wfDebug( __METHOD__.": unknown virus scanner: $wgAntivirus\n" );
1576                         $wgOut->wrapWikiMsg( '<div class="error">$1</div>', array( 'virus-badscanner', $wgAntivirus ) );
1577                         return wfMsg('virus-unknownscanner') . " $wgAntivirus";
1578                 }
1579
1580                 # look up scanner configuration
1581                 $command = $wgAntivirusSetup[$wgAntivirus]["command"];
1582                 $exitCodeMap = $wgAntivirusSetup[$wgAntivirus]["codemap"];
1583                 $msgPattern = isset( $wgAntivirusSetup[$wgAntivirus]["messagepattern"] ) ?
1584                         $wgAntivirusSetup[$wgAntivirus]["messagepattern"] : null;
1585
1586                 if ( strpos( $command,"%f" ) === false ) {
1587                         # simple pattern: append file to scan
1588                         $command .= " " . wfEscapeShellArg( $file );
1589                 } else {
1590                         # complex pattern: replace "%f" with file to scan
1591                         $command = str_replace( "%f", wfEscapeShellArg( $file ), $command );
1592                 }
1593
1594                 wfDebug( __METHOD__.": running virus scan: $command \n" );
1595
1596                 # execute virus scanner
1597                 $exitCode = false;
1598
1599                 #NOTE: there's a 50 line workaround to make stderr redirection work on windows, too.
1600                 #      that does not seem to be worth the pain.
1601                 #      Ask me (Duesentrieb) about it if it's ever needed.
1602                 $output = array();
1603                 if ( wfIsWindows() ) {
1604                         exec( "$command", $output, $exitCode );
1605                 } else {
1606                         exec( "$command 2>&1", $output, $exitCode );
1607                 }
1608
1609                 # map exit code to AV_xxx constants.
1610                 $mappedCode = $exitCode;
1611                 if ( $exitCodeMap ) {
1612                         if ( isset( $exitCodeMap[$exitCode] ) ) {
1613                                 $mappedCode = $exitCodeMap[$exitCode];
1614                         } elseif ( isset( $exitCodeMap["*"] ) ) {
1615                                 $mappedCode = $exitCodeMap["*"];
1616                         }
1617                 }
1618
1619                 if ( $mappedCode === AV_SCAN_FAILED ) {
1620                         # scan failed (code was mapped to false by $exitCodeMap)
1621                         wfDebug( __METHOD__.": failed to scan $file (code $exitCode).\n" );
1622
1623                         if ( $wgAntivirusRequired ) {
1624                                 return wfMsg('virus-scanfailed', array( $exitCode ) );
1625                         } else {
1626                                 return NULL;
1627                         }
1628                 } else if ( $mappedCode === AV_SCAN_ABORTED ) {
1629                         # scan failed because filetype is unknown (probably imune)
1630                         wfDebug( __METHOD__.": unsupported file type $file (code $exitCode).\n" );
1631                         return NULL;
1632                 } else if ( $mappedCode === AV_NO_VIRUS ) {
1633                         # no virus found
1634                         wfDebug( __METHOD__.": file passed virus scan.\n" );
1635                         return false;
1636                 } else {
1637                         $output = join( "\n", $output );
1638                         $output = trim( $output );
1639
1640                         if ( !$output ) {
1641                                 $output = true; #if there's no output, return true
1642                         } elseif ( $msgPattern ) {
1643                                 $groups = array();
1644                                 if ( preg_match( $msgPattern, $output, $groups ) ) {
1645                                         if ( $groups[1] ) {
1646                                                 $output = $groups[1];
1647                                         }
1648                                 }
1649                         }
1650
1651                         wfDebug( __METHOD__.": FOUND VIRUS! scanner feedback: $output" );
1652                         return $output;
1653                 }
1654         }
1655
1656         /**
1657          * Check if the temporary file is MacBinary-encoded, as some uploads
1658          * from Internet Explorer on Mac OS Classic and Mac OS X will be.
1659          * If so, the data fork will be extracted to a second temporary file,
1660          * which will then be checked for validity and either kept or discarded.
1661          *
1662          * @access private
1663          */
1664         function checkMacBinary() {
1665                 $macbin = new MacBinary( $this->mTempPath );
1666                 if( $macbin->isValid() ) {
1667                         $dataFile = tempnam( wfTempDir(), "WikiMacBinary" );
1668                         $dataHandle = fopen( $dataFile, 'wb' );
1669
1670                         wfDebug( "SpecialUpload::checkMacBinary: Extracting MacBinary data fork to $dataFile\n" );
1671                         $macbin->extractData( $dataHandle );
1672
1673                         $this->mTempPath = $dataFile;
1674                         $this->mFileSize = $macbin->dataForkLength();
1675
1676                         // We'll have to manually remove the new file if it's not kept.
1677                         $this->mRemoveTempFile = true;
1678                 }
1679                 $macbin->close();
1680         }
1681
1682         /**
1683          * If we've modified the upload file we need to manually remove it
1684          * on exit to clean up.
1685          * @access private
1686          */
1687         function cleanupTempFile() {
1688                 if ( $this->mRemoveTempFile && file_exists( $this->mTempPath ) ) {
1689                         wfDebug( "SpecialUpload::cleanupTempFile: Removing temporary file {$this->mTempPath}\n" );
1690                         unlink( $this->mTempPath );
1691                 }
1692         }
1693
1694         /**
1695          * Check if there's an overwrite conflict and, if so, if restrictions
1696          * forbid this user from performing the upload.
1697          *
1698          * @return mixed true on success, WikiError on failure
1699          * @access private
1700          */
1701         function checkOverwrite( $name ) {
1702                 $img = wfFindFile( $name );
1703
1704                 $error = '';
1705                 if( $img ) {
1706                         global $wgUser, $wgOut;
1707                         if( $img->isLocal() ) {
1708                                 if( !self::userCanReUpload( $wgUser, $img->name ) ) {
1709                                         $error = 'fileexists-forbidden';
1710                                 }
1711                         } else {
1712                                 if( !$wgUser->isAllowed( 'reupload' ) ||
1713                                     !$wgUser->isAllowed( 'reupload-shared' ) ) {
1714                                         $error = "fileexists-shared-forbidden";
1715                                 }
1716                         }
1717                 }
1718
1719                 if( $error ) {
1720                         $errorText = wfMsg( $error, wfEscapeWikiText( $img->getName() ) );
1721                         return $errorText;
1722                 }
1723
1724                 // Rockin', go ahead and upload
1725                 return true;
1726         }
1727
1728          /**
1729          * Check if a user is the last uploader
1730          *
1731          * @param User $user
1732          * @param string $img, image name
1733          * @return bool
1734          */
1735         public static function userCanReUpload( User $user, $img ) {
1736                 if( $user->isAllowed( 'reupload' ) )
1737                         return true; // non-conditional
1738                 if( !$user->isAllowed( 'reupload-own' ) )
1739                         return false;
1740
1741                 $dbr = wfGetDB( DB_SLAVE );
1742                 $row = $dbr->selectRow('image',
1743                 /* SELECT */ 'img_user',
1744                 /* WHERE */ array( 'img_name' => $img )
1745                 );
1746                 if ( !$row )
1747                         return false;
1748
1749                 return $user->getId() == $row->img_user;
1750         }
1751
1752         /**
1753          * Display an error with a wikitext description
1754          */
1755         function showError( $description ) {
1756                 global $wgOut;
1757                 $wgOut->setPageTitle( wfMsg( "internalerror" ) );
1758                 $wgOut->setRobotPolicy( "noindex,nofollow" );
1759                 $wgOut->setArticleRelated( false );
1760                 $wgOut->enableClientCache( false );
1761                 $wgOut->addWikiText( $description );
1762         }
1763
1764         /**
1765          * Get the initial image page text based on a comment and optional file status information
1766          */
1767         static function getInitialPageText( $comment, $license, $copyStatus, $source ) {
1768                 global $wgUseCopyrightUpload;
1769                 if ( $wgUseCopyrightUpload ) {
1770                         if ( $license != '' ) {
1771                                 $licensetxt = '== ' . wfMsgForContent( 'license' ) . " ==\n" . '{{' . $license . '}}' . "\n";
1772                         }
1773                         $pageText = '== ' . wfMsg ( 'filedesc' ) . " ==\n" . $comment . "\n" .
1774                           '== ' . wfMsgForContent ( 'filestatus' ) . " ==\n" . $copyStatus . "\n" .
1775                           "$licensetxt" .
1776                           '== ' . wfMsgForContent ( 'filesource' ) . " ==\n" . $source ;
1777                 } else {
1778                         if ( $license != '' ) {
1779                                 $filedesc = $comment == '' ? '' : '== ' . wfMsg ( 'filedesc' ) . " ==\n" . $comment . "\n";
1780                                  $pageText = $filedesc .
1781                                          '== ' . wfMsgForContent ( 'license' ) . " ==\n" . '{{' . $license . '}}' . "\n";
1782                         } else {
1783                                 $pageText = $comment;
1784                         }
1785                 }
1786                 return $pageText;
1787         }
1788
1789         /**
1790          * If there are rows in the deletion log for this file, show them,
1791          * along with a nice little note for the user
1792          *
1793          * @param OutputPage $out
1794          * @param string filename
1795          */
1796         private function showDeletionLog( $out, $filename ) {
1797                 global $wgUser;
1798                 $loglist = new LogEventsList( $wgUser->getSkin(), $out );
1799                 $pager = new LogPager( $loglist, 'delete', false, $filename );
1800                 if( $pager->getNumRows() > 0 ) {
1801                         $out->addHTML( '<div class="mw-warning-with-logexcerpt">' );
1802                         $out->addWikiMsg( 'upload-wasdeleted' );
1803                         $out->addHTML(
1804                                 $loglist->beginLogEventsList() .
1805                                 $pager->getBody() .
1806                                 $loglist->endLogEventsList()
1807                         );
1808                         $out->addHTML( '</div>' );
1809                 }
1810         }
1811 }