[ Index ] |
PHP Cross Reference of MediaWiki-1.24.0 |
[Summary view] [Print] [Text view]
1 <?php 2 /** 3 * Implements Special:Upload 4 * 5 * This program is free software; you can redistribute it and/or modify 6 * it under the terms of the GNU General Public License as published by 7 * the Free Software Foundation; either version 2 of the License, or 8 * (at your option) any later version. 9 * 10 * This program is distributed in the hope that it will be useful, 11 * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 * GNU General Public License for more details. 14 * 15 * You should have received a copy of the GNU General Public License along 16 * with this program; if not, write to the Free Software Foundation, Inc., 17 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 18 * http://www.gnu.org/copyleft/gpl.html 19 * 20 * @file 21 * @ingroup SpecialPage 22 * @ingroup Upload 23 */ 24 25 /** 26 * Form for handling uploads and special page. 27 * 28 * @ingroup SpecialPage 29 * @ingroup Upload 30 */ 31 class SpecialUpload extends SpecialPage { 32 /** 33 * Constructor : initialise object 34 * Get data POSTed through the form and assign them to the object 35 * @param WebRequest $request Data posted. 36 */ 37 public function __construct( $request = null ) { 38 parent::__construct( 'Upload', 'upload' ); 39 } 40 41 /** Misc variables **/ 42 43 /** @var WebRequest|FauxRequest The request this form is supposed to handle */ 44 public $mRequest; 45 public $mSourceType; 46 47 /** @var UploadBase */ 48 public $mUpload; 49 50 /** @var LocalFile */ 51 public $mLocalFile; 52 public $mUploadClicked; 53 54 /** User input variables from the "description" section **/ 55 56 /** @var string The requested target file name */ 57 public $mDesiredDestName; 58 public $mComment; 59 public $mLicense; 60 61 /** User input variables from the root section **/ 62 63 public $mIgnoreWarning; 64 public $mWatchthis; 65 public $mCopyrightStatus; 66 public $mCopyrightSource; 67 68 /** Hidden variables **/ 69 70 public $mDestWarningAck; 71 72 /** @var bool The user followed an "overwrite this file" link */ 73 public $mForReUpload; 74 75 /** @var bool The user clicked "Cancel and return to upload form" button */ 76 public $mCancelUpload; 77 public $mTokenOk; 78 79 /** @var bool Subclasses can use this to determine whether a file was uploaded */ 80 public $mUploadSuccessful = false; 81 82 /** Text injection points for hooks not using HTMLForm **/ 83 public $uploadFormTextTop; 84 public $uploadFormTextAfterSummary; 85 86 /** 87 * Initialize instance variables from request and create an Upload handler 88 */ 89 protected function loadRequest() { 90 $this->mRequest = $request = $this->getRequest(); 91 $this->mSourceType = $request->getVal( 'wpSourceType', 'file' ); 92 $this->mUpload = UploadBase::createFromRequest( $request ); 93 $this->mUploadClicked = $request->wasPosted() 94 && ( $request->getCheck( 'wpUpload' ) 95 || $request->getCheck( 'wpUploadIgnoreWarning' ) ); 96 97 // Guess the desired name from the filename if not provided 98 $this->mDesiredDestName = $request->getText( 'wpDestFile' ); 99 if ( !$this->mDesiredDestName && $request->getFileName( 'wpUploadFile' ) !== null ) { 100 $this->mDesiredDestName = $request->getFileName( 'wpUploadFile' ); 101 } 102 $this->mLicense = $request->getText( 'wpLicense' ); 103 104 $this->mDestWarningAck = $request->getText( 'wpDestFileWarningAck' ); 105 $this->mIgnoreWarning = $request->getCheck( 'wpIgnoreWarning' ) 106 || $request->getCheck( 'wpUploadIgnoreWarning' ); 107 $this->mWatchthis = $request->getBool( 'wpWatchthis' ) && $this->getUser()->isLoggedIn(); 108 $this->mCopyrightStatus = $request->getText( 'wpUploadCopyStatus' ); 109 $this->mCopyrightSource = $request->getText( 'wpUploadSource' ); 110 111 $this->mForReUpload = $request->getBool( 'wpForReUpload' ); // updating a file 112 113 $commentDefault = ''; 114 $commentMsg = wfMessage( 'upload-default-description' )->inContentLanguage(); 115 if ( !$this->mForReUpload && !$commentMsg->isDisabled() ) { 116 $commentDefault = $commentMsg->plain(); 117 } 118 $this->mComment = $request->getText( 'wpUploadDescription', $commentDefault ); 119 120 $this->mCancelUpload = $request->getCheck( 'wpCancelUpload' ) 121 || $request->getCheck( 'wpReUpload' ); // b/w compat 122 123 // If it was posted check for the token (no remote POST'ing with user credentials) 124 $token = $request->getVal( 'wpEditToken' ); 125 $this->mTokenOk = $this->getUser()->matchEditToken( $token ); 126 127 $this->uploadFormTextTop = ''; 128 $this->uploadFormTextAfterSummary = ''; 129 } 130 131 /** 132 * This page can be shown if uploading is enabled. 133 * Handle permission checking elsewhere in order to be able to show 134 * custom error messages. 135 * 136 * @param User $user 137 * @return bool 138 */ 139 public function userCanExecute( User $user ) { 140 return UploadBase::isEnabled() && parent::userCanExecute( $user ); 141 } 142 143 /** 144 * Special page entry point 145 * @param string $par 146 */ 147 public function execute( $par ) { 148 $this->setHeaders(); 149 $this->outputHeader(); 150 151 # Check uploading enabled 152 if ( !UploadBase::isEnabled() ) { 153 throw new ErrorPageError( 'uploaddisabled', 'uploaddisabledtext' ); 154 } 155 156 # Check permissions 157 $user = $this->getUser(); 158 $permissionRequired = UploadBase::isAllowed( $user ); 159 if ( $permissionRequired !== true ) { 160 throw new PermissionsError( $permissionRequired ); 161 } 162 163 # Check blocks 164 if ( $user->isBlocked() ) { 165 throw new UserBlockedError( $user->getBlock() ); 166 } 167 168 # Check whether we actually want to allow changing stuff 169 $this->checkReadOnly(); 170 171 $this->loadRequest(); 172 173 # Unsave the temporary file in case this was a cancelled upload 174 if ( $this->mCancelUpload ) { 175 if ( !$this->unsaveUploadedFile() ) { 176 # Something went wrong, so unsaveUploadedFile showed a warning 177 return; 178 } 179 } 180 181 # Process upload or show a form 182 if ( 183 $this->mTokenOk && !$this->mCancelUpload && 184 ( $this->mUpload && $this->mUploadClicked ) 185 ) { 186 $this->processUpload(); 187 } else { 188 # Backwards compatibility hook 189 if ( !wfRunHooks( 'UploadForm:initial', array( &$this ) ) ) { 190 wfDebug( "Hook 'UploadForm:initial' broke output of the upload form\n" ); 191 192 return; 193 } 194 $this->showUploadForm( $this->getUploadForm() ); 195 } 196 197 # Cleanup 198 if ( $this->mUpload ) { 199 $this->mUpload->cleanupTempFile(); 200 } 201 } 202 203 /** 204 * Show the main upload form 205 * 206 * @param HTMLForm|string $form An HTMLForm instance or HTML string to show 207 */ 208 protected function showUploadForm( $form ) { 209 # Add links if file was previously deleted 210 if ( $this->mDesiredDestName ) { 211 $this->showViewDeletedLinks(); 212 } 213 214 if ( $form instanceof HTMLForm ) { 215 $form->show(); 216 } else { 217 $this->getOutput()->addHTML( $form ); 218 } 219 } 220 221 /** 222 * Get an UploadForm instance with title and text properly set. 223 * 224 * @param string $message HTML string to add to the form 225 * @param string $sessionKey Session key in case this is a stashed upload 226 * @param bool $hideIgnoreWarning Whether to hide "ignore warning" check box 227 * @return UploadForm 228 */ 229 protected function getUploadForm( $message = '', $sessionKey = '', $hideIgnoreWarning = false ) { 230 # Initialize form 231 $context = new DerivativeContext( $this->getContext() ); 232 $context->setTitle( $this->getPageTitle() ); // Remove subpage 233 $form = new UploadForm( array( 234 'watch' => $this->getWatchCheck(), 235 'forreupload' => $this->mForReUpload, 236 'sessionkey' => $sessionKey, 237 'hideignorewarning' => $hideIgnoreWarning, 238 'destwarningack' => (bool)$this->mDestWarningAck, 239 240 'description' => $this->mComment, 241 'texttop' => $this->uploadFormTextTop, 242 'textaftersummary' => $this->uploadFormTextAfterSummary, 243 'destfile' => $this->mDesiredDestName, 244 ), $context ); 245 246 # Check the token, but only if necessary 247 if ( 248 !$this->mTokenOk && !$this->mCancelUpload && 249 ( $this->mUpload && $this->mUploadClicked ) 250 ) { 251 $form->addPreText( $this->msg( 'session_fail_preview' )->parse() ); 252 } 253 254 # Give a notice if the user is uploading a file that has been deleted or moved 255 # Note that this is independent from the message 'filewasdeleted' that requires JS 256 $desiredTitleObj = Title::makeTitleSafe( NS_FILE, $this->mDesiredDestName ); 257 $delNotice = ''; // empty by default 258 if ( $desiredTitleObj instanceof Title && !$desiredTitleObj->exists() ) { 259 LogEventsList::showLogExtract( $delNotice, array( 'delete', 'move' ), 260 $desiredTitleObj, 261 '', array( 'lim' => 10, 262 'conds' => array( "log_action != 'revision'" ), 263 'showIfEmpty' => false, 264 'msgKey' => array( 'upload-recreate-warning' ) ) 265 ); 266 } 267 $form->addPreText( $delNotice ); 268 269 # Add text to form 270 $form->addPreText( '<div id="uploadtext">' . 271 $this->msg( 'uploadtext', array( $this->mDesiredDestName ) )->parseAsBlock() . 272 '</div>' ); 273 # Add upload error message 274 $form->addPreText( $message ); 275 276 # Add footer to form 277 $uploadFooter = $this->msg( 'uploadfooter' ); 278 if ( !$uploadFooter->isDisabled() ) { 279 $form->addPostText( '<div id="mw-upload-footer-message">' 280 . $uploadFooter->parseAsBlock() . "</div>\n" ); 281 } 282 283 return $form; 284 } 285 286 /** 287 * Shows the "view X deleted revivions link"" 288 */ 289 protected function showViewDeletedLinks() { 290 $title = Title::makeTitleSafe( NS_FILE, $this->mDesiredDestName ); 291 $user = $this->getUser(); 292 // Show a subtitle link to deleted revisions (to sysops et al only) 293 if ( $title instanceof Title ) { 294 $count = $title->isDeleted(); 295 if ( $count > 0 && $user->isAllowed( 'deletedhistory' ) ) { 296 $restorelink = Linker::linkKnown( 297 SpecialPage::getTitleFor( 'Undelete', $title->getPrefixedText() ), 298 $this->msg( 'restorelink' )->numParams( $count )->escaped() 299 ); 300 $link = $this->msg( $user->isAllowed( 'delete' ) ? 'thisisdeleted' : 'viewdeleted' ) 301 ->rawParams( $restorelink )->parseAsBlock(); 302 $this->getOutput()->addHTML( "<div id=\"contentSub2\">{$link}</div>" ); 303 } 304 } 305 } 306 307 /** 308 * Stashes the upload and shows the main upload form. 309 * 310 * Note: only errors that can be handled by changing the name or 311 * description should be redirected here. It should be assumed that the 312 * file itself is sane and has passed UploadBase::verifyFile. This 313 * essentially means that UploadBase::VERIFICATION_ERROR and 314 * UploadBase::EMPTY_FILE should not be passed here. 315 * 316 * @param string $message HTML message to be passed to mainUploadForm 317 */ 318 protected function showRecoverableUploadError( $message ) { 319 $sessionKey = $this->mUpload->stashSession(); 320 $message = '<h2>' . $this->msg( 'uploaderror' )->escaped() . "</h2>\n" . 321 '<div class="error">' . $message . "</div>\n"; 322 323 $form = $this->getUploadForm( $message, $sessionKey ); 324 $form->setSubmitText( $this->msg( 'upload-tryagain' )->escaped() ); 325 $this->showUploadForm( $form ); 326 } 327 328 /** 329 * Stashes the upload, shows the main form, but adds a "continue anyway button". 330 * Also checks whether there are actually warnings to display. 331 * 332 * @param array $warnings 333 * @return bool True if warnings were displayed, false if there are no 334 * warnings and it should continue processing 335 */ 336 protected function showUploadWarning( $warnings ) { 337 # If there are no warnings, or warnings we can ignore, return early. 338 # mDestWarningAck is set when some javascript has shown the warning 339 # to the user. mForReUpload is set when the user clicks the "upload a 340 # new version" link. 341 if ( !$warnings || ( count( $warnings ) == 1 342 && isset( $warnings['exists'] ) 343 && ( $this->mDestWarningAck || $this->mForReUpload ) ) 344 ) { 345 return false; 346 } 347 348 $sessionKey = $this->mUpload->stashSession(); 349 350 $warningHtml = '<h2>' . $this->msg( 'uploadwarning' )->escaped() . "</h2>\n" 351 . '<ul class="warning">'; 352 foreach ( $warnings as $warning => $args ) { 353 if ( $warning == 'badfilename' ) { 354 $this->mDesiredDestName = Title::makeTitle( NS_FILE, $args )->getText(); 355 } 356 if ( $warning == 'exists' ) { 357 $msg = "\t<li>" . self::getExistsWarning( $args ) . "</li>\n"; 358 } elseif ( $warning == 'duplicate' ) { 359 $msg = $this->getDupeWarning( $args ); 360 } elseif ( $warning == 'duplicate-archive' ) { 361 if ( $args === '' ) { 362 $msg = "\t<li>" . $this->msg( 'file-deleted-duplicate-notitle' )->parse() 363 . "</li>\n"; 364 } else { 365 $msg = "\t<li>" . $this->msg( 'file-deleted-duplicate', 366 Title::makeTitle( NS_FILE, $args )->getPrefixedText() )->parse() 367 . "</li>\n"; 368 } 369 } else { 370 if ( $args === true ) { 371 $args = array(); 372 } elseif ( !is_array( $args ) ) { 373 $args = array( $args ); 374 } 375 $msg = "\t<li>" . $this->msg( $warning, $args )->parse() . "</li>\n"; 376 } 377 $warningHtml .= $msg; 378 } 379 $warningHtml .= "</ul>\n"; 380 $warningHtml .= $this->msg( 'uploadwarning-text' )->parseAsBlock(); 381 382 $form = $this->getUploadForm( $warningHtml, $sessionKey, /* $hideIgnoreWarning */ true ); 383 $form->setSubmitText( $this->msg( 'upload-tryagain' )->text() ); 384 $form->addButton( 'wpUploadIgnoreWarning', $this->msg( 'ignorewarning' )->text() ); 385 $form->addButton( 'wpCancelUpload', $this->msg( 'reuploaddesc' )->text() ); 386 387 $this->showUploadForm( $form ); 388 389 # Indicate that we showed a form 390 return true; 391 } 392 393 /** 394 * Show the upload form with error message, but do not stash the file. 395 * 396 * @param string $message HTML string 397 */ 398 protected function showUploadError( $message ) { 399 $message = '<h2>' . $this->msg( 'uploadwarning' )->escaped() . "</h2>\n" . 400 '<div class="error">' . $message . "</div>\n"; 401 $this->showUploadForm( $this->getUploadForm( $message ) ); 402 } 403 404 /** 405 * Do the upload. 406 * Checks are made in SpecialUpload::execute() 407 */ 408 protected function processUpload() { 409 // Fetch the file if required 410 $status = $this->mUpload->fetchFile(); 411 if ( !$status->isOK() ) { 412 $this->showUploadError( $this->getOutput()->parse( $status->getWikiText() ) ); 413 414 return; 415 } 416 417 if ( !wfRunHooks( 'UploadForm:BeforeProcessing', array( &$this ) ) ) { 418 wfDebug( "Hook 'UploadForm:BeforeProcessing' broke processing the file.\n" ); 419 // This code path is deprecated. If you want to break upload processing 420 // do so by hooking into the appropriate hooks in UploadBase::verifyUpload 421 // and UploadBase::verifyFile. 422 // If you use this hook to break uploading, the user will be returned 423 // an empty form with no error message whatsoever. 424 return; 425 } 426 427 // Upload verification 428 $details = $this->mUpload->verifyUpload(); 429 if ( $details['status'] != UploadBase::OK ) { 430 $this->processVerificationError( $details ); 431 432 return; 433 } 434 435 // Verify permissions for this title 436 $permErrors = $this->mUpload->verifyTitlePermissions( $this->getUser() ); 437 if ( $permErrors !== true ) { 438 $code = array_shift( $permErrors[0] ); 439 $this->showRecoverableUploadError( $this->msg( $code, $permErrors[0] )->parse() ); 440 441 return; 442 } 443 444 $this->mLocalFile = $this->mUpload->getLocalFile(); 445 446 // Check warnings if necessary 447 if ( !$this->mIgnoreWarning ) { 448 $warnings = $this->mUpload->checkWarnings(); 449 if ( $this->showUploadWarning( $warnings ) ) { 450 return; 451 } 452 } 453 454 // Get the page text if this is not a reupload 455 if ( !$this->mForReUpload ) { 456 $pageText = self::getInitialPageText( $this->mComment, $this->mLicense, 457 $this->mCopyrightStatus, $this->mCopyrightSource ); 458 } else { 459 $pageText = false; 460 } 461 462 $status = $this->mUpload->performUpload( 463 $this->mComment, 464 $pageText, 465 $this->mWatchthis, 466 $this->getUser() 467 ); 468 469 if ( !$status->isGood() ) { 470 $this->showUploadError( $this->getOutput()->parse( $status->getWikiText() ) ); 471 472 return; 473 } 474 475 // Success, redirect to description page 476 $this->mUploadSuccessful = true; 477 wfRunHooks( 'SpecialUploadComplete', array( &$this ) ); 478 $this->getOutput()->redirect( $this->mLocalFile->getTitle()->getFullURL() ); 479 } 480 481 /** 482 * Get the initial image page text based on a comment and optional file status information 483 * @param string $comment 484 * @param string $license 485 * @param string $copyStatus 486 * @param string $source 487 * @return string 488 * @todo Use Config obj instead of globals 489 */ 490 public static function getInitialPageText( $comment = '', $license = '', 491 $copyStatus = '', $source = '' 492 ) { 493 global $wgUseCopyrightUpload, $wgForceUIMsgAsContentMsg; 494 495 $msg = array(); 496 /* These messages are transcluded into the actual text of the description page. 497 * Thus, forcing them as content messages makes the upload to produce an int: template 498 * instead of hardcoding it there in the uploader language. 499 */ 500 foreach ( array( 'license-header', 'filedesc', 'filestatus', 'filesource' ) as $msgName ) { 501 if ( in_array( $msgName, (array)$wgForceUIMsgAsContentMsg ) ) { 502 $msg[$msgName] = "{{int:$msgName}}"; 503 } else { 504 $msg[$msgName] = wfMessage( $msgName )->inContentLanguage()->text(); 505 } 506 } 507 508 if ( $wgUseCopyrightUpload ) { 509 $licensetxt = ''; 510 if ( $license != '' ) { 511 $licensetxt = '== ' . $msg['license-header'] . " ==\n" . '{{' . $license . '}}' . "\n"; 512 } 513 $pageText = '== ' . $msg['filedesc'] . " ==\n" . $comment . "\n" . 514 '== ' . $msg['filestatus'] . " ==\n" . $copyStatus . "\n" . 515 "$licensetxt" . 516 '== ' . $msg['filesource'] . " ==\n" . $source; 517 } else { 518 if ( $license != '' ) { 519 $filedesc = $comment == '' ? '' : '== ' . $msg['filedesc'] . " ==\n" . $comment . "\n"; 520 $pageText = $filedesc . 521 '== ' . $msg['license-header'] . " ==\n" . '{{' . $license . '}}' . "\n"; 522 } else { 523 $pageText = $comment; 524 } 525 } 526 527 return $pageText; 528 } 529 530 /** 531 * See if we should check the 'watch this page' checkbox on the form 532 * based on the user's preferences and whether we're being asked 533 * to create a new file or update an existing one. 534 * 535 * In the case where 'watch edits' is off but 'watch creations' is on, 536 * we'll leave the box unchecked. 537 * 538 * Note that the page target can be changed *on the form*, so our check 539 * state can get out of sync. 540 * @return bool|string 541 */ 542 protected function getWatchCheck() { 543 if ( $this->getUser()->getOption( 'watchdefault' ) ) { 544 // Watch all edits! 545 return true; 546 } 547 548 $desiredTitleObj = Title::makeTitleSafe( NS_FILE, $this->mDesiredDestName ); 549 if ( $desiredTitleObj instanceof Title && $this->getUser()->isWatched( $desiredTitleObj ) ) { 550 // Already watched, don't change that 551 return true; 552 } 553 554 $local = wfLocalFile( $this->mDesiredDestName ); 555 if ( $local && $local->exists() ) { 556 // We're uploading a new version of an existing file. 557 // No creation, so don't watch it if we're not already. 558 return false; 559 } else { 560 // New page should get watched if that's our option. 561 return $this->getUser()->getOption( 'watchcreations' ); 562 } 563 } 564 565 /** 566 * Provides output to the user for a result of UploadBase::verifyUpload 567 * 568 * @param array $details Result of UploadBase::verifyUpload 569 * @throws MWException 570 */ 571 protected function processVerificationError( $details ) { 572 switch ( $details['status'] ) { 573 574 /** Statuses that only require name changing **/ 575 case UploadBase::MIN_LENGTH_PARTNAME: 576 $this->showRecoverableUploadError( $this->msg( 'minlength1' )->escaped() ); 577 break; 578 case UploadBase::ILLEGAL_FILENAME: 579 $this->showRecoverableUploadError( $this->msg( 'illegalfilename', 580 $details['filtered'] )->parse() ); 581 break; 582 case UploadBase::FILENAME_TOO_LONG: 583 $this->showRecoverableUploadError( $this->msg( 'filename-toolong' )->escaped() ); 584 break; 585 case UploadBase::FILETYPE_MISSING: 586 $this->showRecoverableUploadError( $this->msg( 'filetype-missing' )->parse() ); 587 break; 588 case UploadBase::WINDOWS_NONASCII_FILENAME: 589 $this->showRecoverableUploadError( $this->msg( 'windows-nonascii-filename' )->parse() ); 590 break; 591 592 /** Statuses that require reuploading **/ 593 case UploadBase::EMPTY_FILE: 594 $this->showUploadError( $this->msg( 'emptyfile' )->escaped() ); 595 break; 596 case UploadBase::FILE_TOO_LARGE: 597 $this->showUploadError( $this->msg( 'largefileserver' )->escaped() ); 598 break; 599 case UploadBase::FILETYPE_BADTYPE: 600 $msg = $this->msg( 'filetype-banned-type' ); 601 if ( isset( $details['blacklistedExt'] ) ) { 602 $msg->params( $this->getLanguage()->commaList( $details['blacklistedExt'] ) ); 603 } else { 604 $msg->params( $details['finalExt'] ); 605 } 606 $extensions = array_unique( $this->getConfig()->get( 'FileExtensions' ) ); 607 $msg->params( $this->getLanguage()->commaList( $extensions ), 608 count( $extensions ) ); 609 610 // Add PLURAL support for the first parameter. This results 611 // in a bit unlogical parameter sequence, but does not break 612 // old translations 613 if ( isset( $details['blacklistedExt'] ) ) { 614 $msg->params( count( $details['blacklistedExt'] ) ); 615 } else { 616 $msg->params( 1 ); 617 } 618 619 $this->showUploadError( $msg->parse() ); 620 break; 621 case UploadBase::VERIFICATION_ERROR: 622 unset( $details['status'] ); 623 $code = array_shift( $details['details'] ); 624 $this->showUploadError( $this->msg( $code, $details['details'] )->parse() ); 625 break; 626 case UploadBase::HOOK_ABORTED: 627 if ( is_array( $details['error'] ) ) { # allow hooks to return error details in an array 628 $args = $details['error']; 629 $error = array_shift( $args ); 630 } else { 631 $error = $details['error']; 632 $args = null; 633 } 634 635 $this->showUploadError( $this->msg( $error, $args )->parse() ); 636 break; 637 default: 638 throw new MWException( __METHOD__ . ": Unknown value `{$details['status']}`" ); 639 } 640 } 641 642 /** 643 * Remove a temporarily kept file stashed by saveTempUploadedFile(). 644 * 645 * @return bool Success 646 */ 647 protected function unsaveUploadedFile() { 648 if ( !( $this->mUpload instanceof UploadFromStash ) ) { 649 return true; 650 } 651 $success = $this->mUpload->unsaveUploadedFile(); 652 if ( !$success ) { 653 $this->getOutput()->showFileDeleteError( $this->mUpload->getTempPath() ); 654 655 return false; 656 } else { 657 return true; 658 } 659 } 660 661 /*** Functions for formatting warnings ***/ 662 663 /** 664 * Formats a result of UploadBase::getExistsWarning as HTML 665 * This check is static and can be done pre-upload via AJAX 666 * 667 * @param array $exists The result of UploadBase::getExistsWarning 668 * @return string Empty string if there is no warning or an HTML fragment 669 */ 670 public static function getExistsWarning( $exists ) { 671 if ( !$exists ) { 672 return ''; 673 } 674 675 $file = $exists['file']; 676 $filename = $file->getTitle()->getPrefixedText(); 677 $warning = ''; 678 679 if ( $exists['warning'] == 'exists' ) { 680 // Exact match 681 $warning = wfMessage( 'fileexists', $filename )->parse(); 682 } elseif ( $exists['warning'] == 'page-exists' ) { 683 // Page exists but file does not 684 $warning = wfMessage( 'filepageexists', $filename )->parse(); 685 } elseif ( $exists['warning'] == 'exists-normalized' ) { 686 $warning = wfMessage( 'fileexists-extension', $filename, 687 $exists['normalizedFile']->getTitle()->getPrefixedText() )->parse(); 688 } elseif ( $exists['warning'] == 'thumb' ) { 689 // Swapped argument order compared with other messages for backwards compatibility 690 $warning = wfMessage( 'fileexists-thumbnail-yes', 691 $exists['thumbFile']->getTitle()->getPrefixedText(), $filename )->parse(); 692 } elseif ( $exists['warning'] == 'thumb-name' ) { 693 // Image w/o '180px-' does not exists, but we do not like these filenames 694 $name = $file->getName(); 695 $badPart = substr( $name, 0, strpos( $name, '-' ) + 1 ); 696 $warning = wfMessage( 'file-thumbnail-no', $badPart )->parse(); 697 } elseif ( $exists['warning'] == 'bad-prefix' ) { 698 $warning = wfMessage( 'filename-bad-prefix', $exists['prefix'] )->parse(); 699 } elseif ( $exists['warning'] == 'was-deleted' ) { 700 # If the file existed before and was deleted, warn the user of this 701 $ltitle = SpecialPage::getTitleFor( 'Log' ); 702 $llink = Linker::linkKnown( 703 $ltitle, 704 wfMessage( 'deletionlog' )->escaped(), 705 array(), 706 array( 707 'type' => 'delete', 708 'page' => $filename 709 ) 710 ); 711 $warning = wfMessage( 'filewasdeleted' )->rawParams( $llink )->parseAsBlock(); 712 } 713 714 return $warning; 715 } 716 717 /** 718 * Construct a warning and a gallery from an array of duplicate files. 719 * @param array $dupes 720 * @return string 721 */ 722 public function getDupeWarning( $dupes ) { 723 if ( !$dupes ) { 724 return ''; 725 } 726 727 $gallery = ImageGalleryBase::factory( false, $this->getContext() ); 728 $gallery->setShowBytes( false ); 729 foreach ( $dupes as $file ) { 730 $gallery->add( $file->getTitle() ); 731 } 732 733 return '<li>' . 734 wfMessage( 'file-exists-duplicate' )->numParams( count( $dupes ) )->parse() . 735 $gallery->toHtml() . "</li>\n"; 736 } 737 738 protected function getGroupName() { 739 return 'media'; 740 } 741 742 /** 743 * Should we rotate images in the preview on Special:Upload. 744 * 745 * This controls js: mw.config.get( 'wgFileCanRotate' ) 746 * 747 * @todo What about non-BitmapHandler handled files? 748 */ 749 static public function rotationEnabled() { 750 $bitmapHandler = new BitmapHandler(); 751 return $bitmapHandler->autoRotateEnabled(); 752 } 753 } 754 755 /** 756 * Sub class of HTMLForm that provides the form section of SpecialUpload 757 */ 758 class UploadForm extends HTMLForm { 759 protected $mWatch; 760 protected $mForReUpload; 761 protected $mSessionKey; 762 protected $mHideIgnoreWarning; 763 protected $mDestWarningAck; 764 protected $mDestFile; 765 766 protected $mComment; 767 protected $mTextTop; 768 protected $mTextAfterSummary; 769 770 protected $mSourceIds; 771 772 protected $mMaxFileSize = array(); 773 774 protected $mMaxUploadSize = array(); 775 776 public function __construct( array $options = array(), IContextSource $context = null ) { 777 $this->mWatch = !empty( $options['watch'] ); 778 $this->mForReUpload = !empty( $options['forreupload'] ); 779 $this->mSessionKey = isset( $options['sessionkey'] ) ? $options['sessionkey'] : ''; 780 $this->mHideIgnoreWarning = !empty( $options['hideignorewarning'] ); 781 $this->mDestWarningAck = !empty( $options['destwarningack'] ); 782 $this->mDestFile = isset( $options['destfile'] ) ? $options['destfile'] : ''; 783 784 $this->mComment = isset( $options['description'] ) ? 785 $options['description'] : ''; 786 787 $this->mTextTop = isset( $options['texttop'] ) 788 ? $options['texttop'] : ''; 789 790 $this->mTextAfterSummary = isset( $options['textaftersummary'] ) 791 ? $options['textaftersummary'] : ''; 792 793 $sourceDescriptor = $this->getSourceSection(); 794 $descriptor = $sourceDescriptor 795 + $this->getDescriptionSection() 796 + $this->getOptionsSection(); 797 798 wfRunHooks( 'UploadFormInitDescriptor', array( &$descriptor ) ); 799 parent::__construct( $descriptor, $context, 'upload' ); 800 801 # Add a link to edit MediaWik:Licenses 802 if ( $this->getUser()->isAllowed( 'editinterface' ) ) { 803 $licensesLink = Linker::link( 804 Title::makeTitle( NS_MEDIAWIKI, 'Licenses' ), 805 $this->msg( 'licenses-edit' )->escaped(), 806 array(), 807 array( 'action' => 'edit' ) 808 ); 809 $editLicenses = '<p class="mw-upload-editlicenses">' . $licensesLink . '</p>'; 810 $this->addFooterText( $editLicenses, 'description' ); 811 } 812 813 # Set some form properties 814 $this->setSubmitText( $this->msg( 'uploadbtn' )->text() ); 815 $this->setSubmitName( 'wpUpload' ); 816 # Used message keys: 'accesskey-upload', 'tooltip-upload' 817 $this->setSubmitTooltip( 'upload' ); 818 $this->setId( 'mw-upload-form' ); 819 820 # Build a list of IDs for javascript insertion 821 $this->mSourceIds = array(); 822 foreach ( $sourceDescriptor as $field ) { 823 if ( !empty( $field['id'] ) ) { 824 $this->mSourceIds[] = $field['id']; 825 } 826 } 827 } 828 829 /** 830 * Get the descriptor of the fieldset that contains the file source 831 * selection. The section is 'source' 832 * 833 * @return array Descriptor array 834 */ 835 protected function getSourceSection() { 836 if ( $this->mSessionKey ) { 837 return array( 838 'SessionKey' => array( 839 'type' => 'hidden', 840 'default' => $this->mSessionKey, 841 ), 842 'SourceType' => array( 843 'type' => 'hidden', 844 'default' => 'Stash', 845 ), 846 ); 847 } 848 849 $canUploadByUrl = UploadFromUrl::isEnabled() 850 && ( UploadFromUrl::isAllowed( $this->getUser() ) === true ) 851 && $this->getConfig()->get( 'CopyUploadsFromSpecialUpload' ); 852 $radio = $canUploadByUrl; 853 $selectedSourceType = strtolower( $this->getRequest()->getText( 'wpSourceType', 'File' ) ); 854 855 $descriptor = array(); 856 if ( $this->mTextTop ) { 857 $descriptor['UploadFormTextTop'] = array( 858 'type' => 'info', 859 'section' => 'source', 860 'default' => $this->mTextTop, 861 'raw' => true, 862 ); 863 } 864 865 $this->mMaxUploadSize['file'] = UploadBase::getMaxUploadSize( 'file' ); 866 # Limit to upload_max_filesize unless we are running under HipHop and 867 # that setting doesn't exist 868 if ( !wfIsHHVM() ) { 869 $this->mMaxUploadSize['file'] = min( $this->mMaxUploadSize['file'], 870 wfShorthandToInteger( ini_get( 'upload_max_filesize' ) ), 871 wfShorthandToInteger( ini_get( 'post_max_size' ) ) 872 ); 873 } 874 875 $descriptor['UploadFile'] = array( 876 'class' => 'UploadSourceField', 877 'section' => 'source', 878 'type' => 'file', 879 'id' => 'wpUploadFile', 880 'radio-id' => 'wpSourceTypeFile', 881 'label-message' => 'sourcefilename', 882 'upload-type' => 'File', 883 'radio' => &$radio, 884 'help' => $this->msg( 'upload-maxfilesize', 885 $this->getContext()->getLanguage()->formatSize( $this->mMaxUploadSize['file'] ) 886 )->parse() . 887 $this->msg( 'word-separator' )->escaped() . 888 $this->msg( 'upload_source_file' )->escaped(), 889 'checked' => $selectedSourceType == 'file', 890 ); 891 892 if ( $canUploadByUrl ) { 893 $this->mMaxUploadSize['url'] = UploadBase::getMaxUploadSize( 'url' ); 894 $descriptor['UploadFileURL'] = array( 895 'class' => 'UploadSourceField', 896 'section' => 'source', 897 'id' => 'wpUploadFileURL', 898 'radio-id' => 'wpSourceTypeurl', 899 'label-message' => 'sourceurl', 900 'upload-type' => 'url', 901 'radio' => &$radio, 902 'help' => $this->msg( 'upload-maxfilesize', 903 $this->getContext()->getLanguage()->formatSize( $this->mMaxUploadSize['url'] ) 904 )->parse() . 905 $this->msg( 'word-separator' )->escaped() . 906 $this->msg( 'upload_source_url' )->escaped(), 907 'checked' => $selectedSourceType == 'url', 908 ); 909 } 910 wfRunHooks( 'UploadFormSourceDescriptors', array( &$descriptor, &$radio, $selectedSourceType ) ); 911 912 $descriptor['Extensions'] = array( 913 'type' => 'info', 914 'section' => 'source', 915 'default' => $this->getExtensionsMessage(), 916 'raw' => true, 917 ); 918 919 return $descriptor; 920 } 921 922 /** 923 * Get the messages indicating which extensions are preferred and prohibitted. 924 * 925 * @return string HTML string containing the message 926 */ 927 protected function getExtensionsMessage() { 928 # Print a list of allowed file extensions, if so configured. We ignore 929 # MIME type here, it's incomprehensible to most people and too long. 930 $config = $this->getConfig(); 931 932 if ( $config->get( 'CheckFileExtensions' ) ) { 933 if ( $config->get( 'StrictFileExtensions' ) ) { 934 # Everything not permitted is banned 935 $extensionsList = 936 '<div id="mw-upload-permitted">' . 937 $this->msg( 938 'upload-permitted', 939 $this->getContext()->getLanguage()->commaList( 940 array_unique( $config->get( 'FileExtensions' ) ) 941 ) 942 )->parseAsBlock() . 943 "</div>\n"; 944 } else { 945 # We have to list both preferred and prohibited 946 $extensionsList = 947 '<div id="mw-upload-preferred">' . 948 $this->msg( 949 'upload-preferred', 950 $this->getContext()->getLanguage()->commaList( 951 array_unique( $config->get( 'FileExtensions' ) ) 952 ) 953 )->parseAsBlock() . 954 "</div>\n" . 955 '<div id="mw-upload-prohibited">' . 956 $this->msg( 957 'upload-prohibited', 958 $this->getContext()->getLanguage()->commaList( 959 array_unique( $config->get( 'FileBlacklist' ) ) 960 ) 961 )->parseAsBlock() . 962 "</div>\n"; 963 } 964 } else { 965 # Everything is permitted. 966 $extensionsList = ''; 967 } 968 969 return $extensionsList; 970 } 971 972 /** 973 * Get the descriptor of the fieldset that contains the file description 974 * input. The section is 'description' 975 * 976 * @return array Descriptor array 977 */ 978 protected function getDescriptionSection() { 979 $config = $this->getConfig(); 980 if ( $this->mSessionKey ) { 981 $stash = RepoGroup::singleton()->getLocalRepo()->getUploadStash(); 982 try { 983 $file = $stash->getFile( $this->mSessionKey ); 984 } catch ( MWException $e ) { 985 $file = null; 986 } 987 if ( $file ) { 988 global $wgContLang; 989 990 $mto = $file->transform( array( 'width' => 120 ) ); 991 $this->addHeaderText( 992 '<div class="thumb t' . $wgContLang->alignEnd() . '">' . 993 Html::element( 'img', array( 994 'src' => $mto->getUrl(), 995 'class' => 'thumbimage', 996 ) ) . '</div>', 'description' ); 997 } 998 } 999 1000 $descriptor = array( 1001 'DestFile' => array( 1002 'type' => 'text', 1003 'section' => 'description', 1004 'id' => 'wpDestFile', 1005 'label-message' => 'destfilename', 1006 'size' => 60, 1007 'default' => $this->mDestFile, 1008 # @todo FIXME: Hack to work around poor handling of the 'default' option in HTMLForm 1009 'nodata' => strval( $this->mDestFile ) !== '', 1010 ), 1011 'UploadDescription' => array( 1012 'type' => 'textarea', 1013 'section' => 'description', 1014 'id' => 'wpUploadDescription', 1015 'label-message' => $this->mForReUpload 1016 ? 'filereuploadsummary' 1017 : 'fileuploadsummary', 1018 'default' => $this->mComment, 1019 'cols' => $this->getUser()->getIntOption( 'cols' ), 1020 'rows' => 8, 1021 ) 1022 ); 1023 if ( $this->mTextAfterSummary ) { 1024 $descriptor['UploadFormTextAfterSummary'] = array( 1025 'type' => 'info', 1026 'section' => 'description', 1027 'default' => $this->mTextAfterSummary, 1028 'raw' => true, 1029 ); 1030 } 1031 1032 $descriptor += array( 1033 'EditTools' => array( 1034 'type' => 'edittools', 1035 'section' => 'description', 1036 'message' => 'edittools-upload', 1037 ) 1038 ); 1039 1040 if ( $this->mForReUpload ) { 1041 $descriptor['DestFile']['readonly'] = true; 1042 } else { 1043 $descriptor['License'] = array( 1044 'type' => 'select', 1045 'class' => 'Licenses', 1046 'section' => 'description', 1047 'id' => 'wpLicense', 1048 'label-message' => 'license', 1049 ); 1050 } 1051 1052 if ( $config->get( 'UseCopyrightUpload' ) ) { 1053 $descriptor['UploadCopyStatus'] = array( 1054 'type' => 'text', 1055 'section' => 'description', 1056 'id' => 'wpUploadCopyStatus', 1057 'label-message' => 'filestatus', 1058 ); 1059 $descriptor['UploadSource'] = array( 1060 'type' => 'text', 1061 'section' => 'description', 1062 'id' => 'wpUploadSource', 1063 'label-message' => 'filesource', 1064 ); 1065 } 1066 1067 return $descriptor; 1068 } 1069 1070 /** 1071 * Get the descriptor of the fieldset that contains the upload options, 1072 * such as "watch this file". The section is 'options' 1073 * 1074 * @return array Descriptor array 1075 */ 1076 protected function getOptionsSection() { 1077 $user = $this->getUser(); 1078 if ( $user->isLoggedIn() ) { 1079 $descriptor = array( 1080 'Watchthis' => array( 1081 'type' => 'check', 1082 'id' => 'wpWatchthis', 1083 'label-message' => 'watchthisupload', 1084 'section' => 'options', 1085 'default' => $this->mWatch, 1086 ) 1087 ); 1088 } 1089 if ( !$this->mHideIgnoreWarning ) { 1090 $descriptor['IgnoreWarning'] = array( 1091 'type' => 'check', 1092 'id' => 'wpIgnoreWarning', 1093 'label-message' => 'ignorewarnings', 1094 'section' => 'options', 1095 ); 1096 } 1097 1098 $descriptor['DestFileWarningAck'] = array( 1099 'type' => 'hidden', 1100 'id' => 'wpDestFileWarningAck', 1101 'default' => $this->mDestWarningAck ? '1' : '', 1102 ); 1103 1104 if ( $this->mForReUpload ) { 1105 $descriptor['ForReUpload'] = array( 1106 'type' => 'hidden', 1107 'id' => 'wpForReUpload', 1108 'default' => '1', 1109 ); 1110 } 1111 1112 return $descriptor; 1113 } 1114 1115 /** 1116 * Add the upload JS and show the form. 1117 */ 1118 public function show() { 1119 $this->addUploadJS(); 1120 parent::show(); 1121 } 1122 1123 /** 1124 * Add upload JS to the OutputPage 1125 */ 1126 protected function addUploadJS() { 1127 $config = $this->getConfig(); 1128 1129 $useAjaxDestCheck = $config->get( 'UseAjax' ) && $config->get( 'AjaxUploadDestCheck' ); 1130 $useAjaxLicensePreview = $config->get( 'UseAjax' ) && 1131 $config->get( 'AjaxLicensePreview' ) && $config->get( 'EnableAPI' ); 1132 $this->mMaxUploadSize['*'] = UploadBase::getMaxUploadSize(); 1133 1134 $scriptVars = array( 1135 'wgAjaxUploadDestCheck' => $useAjaxDestCheck, 1136 'wgAjaxLicensePreview' => $useAjaxLicensePreview, 1137 'wgUploadAutoFill' => !$this->mForReUpload && 1138 // If we received mDestFile from the request, don't autofill 1139 // the wpDestFile textbox 1140 $this->mDestFile === '', 1141 'wgUploadSourceIds' => $this->mSourceIds, 1142 'wgStrictFileExtensions' => $config->get( 'StrictFileExtensions' ), 1143 'wgCapitalizeUploads' => MWNamespace::isCapitalized( NS_FILE ), 1144 'wgMaxUploadSize' => $this->mMaxUploadSize, 1145 ); 1146 1147 $out = $this->getOutput(); 1148 $out->addJsConfigVars( $scriptVars ); 1149 1150 $out->addModules( array( 1151 'mediawiki.action.edit', // For <charinsert> support 1152 'mediawiki.special.upload', // Extras for thumbnail and license preview. 1153 ) ); 1154 } 1155 1156 /** 1157 * Empty function; submission is handled elsewhere. 1158 * 1159 * @return bool False 1160 */ 1161 function trySubmit() { 1162 return false; 1163 } 1164 } 1165 1166 /** 1167 * A form field that contains a radio box in the label 1168 */ 1169 class UploadSourceField extends HTMLTextField { 1170 1171 /** 1172 * @param array $cellAttributes 1173 * @return string 1174 */ 1175 function getLabelHtml( $cellAttributes = array() ) { 1176 $id = $this->mParams['id']; 1177 $label = Html::rawElement( 'label', array( 'for' => $id ), $this->mLabel ); 1178 1179 if ( !empty( $this->mParams['radio'] ) ) { 1180 if ( isset( $this->mParams['radio-id'] ) ) { 1181 $radioId = $this->mParams['radio-id']; 1182 } else { 1183 // Old way. For the benefit of extensions that do not define 1184 // the 'radio-id' key. 1185 $radioId = 'wpSourceType' . $this->mParams['upload-type']; 1186 } 1187 1188 $attribs = array( 1189 'name' => 'wpSourceType', 1190 'type' => 'radio', 1191 'id' => $radioId, 1192 'value' => $this->mParams['upload-type'], 1193 ); 1194 1195 if ( !empty( $this->mParams['checked'] ) ) { 1196 $attribs['checked'] = 'checked'; 1197 } 1198 1199 $label .= Html::element( 'input', $attribs ); 1200 } 1201 1202 return Html::rawElement( 'td', array( 'class' => 'mw-label' ) + $cellAttributes, $label ); 1203 } 1204 1205 /** 1206 * @return int 1207 */ 1208 function getSize() { 1209 return isset( $this->mParams['size'] ) 1210 ? $this->mParams['size'] 1211 : 60; 1212 } 1213 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
Generated: Fri Nov 28 14:03:12 2014 | Cross-referenced by PHPXref 0.7.1 |