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