[ Index ]

PHP Cross Reference of MediaWiki-1.24.0

title

Body

[close]

/includes/specials/ -> SpecialUpload.php (source)

   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  }


Generated: Fri Nov 28 14:03:12 2014 Cross-referenced by PHPXref 0.7.1