r76014 MediaWiki - Code Review archive

Repository:MediaWiki
Revision:r76013‎ | r76014 | r76015 >
Date:15:32, 4 November 2010
Author:catrope
Status:deferred (Comments)
Tags:
Comment:
Initial untested attempt at merging upload backend changes for UploadWizard. This naively just merges r75906 from trunk (with conflicts resolved) and prays it'll work. ApiUpload.php was copied from trunk directly: it's changed so much recently that sorting out the merge conflicts would've been hell. It remains to be seen whether copying ApiUpload.php straight from trunk will break it.
Modified paths:
  • /branches/uploadwizard-deployment/includes/AutoLoader.php (modified) (history)
  • /branches/uploadwizard-deployment/includes/SpecialPage.php (modified) (history)
  • /branches/uploadwizard-deployment/includes/api/ApiQueryImageInfo.php (modified) (history)
  • /branches/uploadwizard-deployment/includes/api/ApiUpload.php (replaced) (history)
  • /branches/uploadwizard-deployment/includes/filerepo/File.php (modified) (history)
  • /branches/uploadwizard-deployment/includes/specials/SpecialUploadStash.php (added) (history)
  • /branches/uploadwizard-deployment/includes/upload/UploadBase.php (modified) (history)
  • /branches/uploadwizard-deployment/includes/upload/UploadFromFile.php (modified) (history)
  • /branches/uploadwizard-deployment/includes/upload/UploadStash.php (added) (history)

Diff [purge]

Index: branches/uploadwizard-deployment/includes/upload/UploadBase.php
@@ -502,6 +502,9 @@
503503 }
504504
505505 /**
 506+ * NOTE: Probably should be deprecated in favor of UploadStash, but this is sometimes
 507+ * called outside that context.
 508+ *
506509 * Stash a file in a temporary directory for later processing
507510 * after the user has confirmed it.
508511 *
@@ -519,39 +522,36 @@
520523 }
521524
522525 /**
523 - * Stash a file in a temporary directory for later processing,
524 - * and save the necessary descriptive info into the session.
525 - * Returns a key value which will be passed through a form
526 - * to pick up the path info on a later invocation.
 526+ * If the user does not supply all necessary information in the first upload form submission (either by accident or
 527+ * by design) then we may want to stash the file temporarily, get more information, and publish the file later.
527528 *
528 - * @return int Session key
 529+ * This method will stash a file in a temporary directory for later processing, and save the necessary descriptive info
 530+ * into the user's session.
 531+ * This method returns the file object, which also has a 'sessionKey' property which can be passed through a form or
 532+ * API request to find this stashed file again.
 533+ *
 534+ * @param {String}: $key (optional) the session key used to find the file info again. If not supplied, a key will be autogenerated.
 535+ * @return {File}: stashed file
529536 */
530 - public function stashSession() {
531 - $status = $this->saveTempUploadedFile( $this->mDestName, $this->mTempPath );
532 - if( !$status->isOK() ) {
533 - # Couldn't save the file.
534 - return false;
535 - }
536 - if( !isset( $_SESSION ) ) {
537 - session_start(); // start up the session (might have been previously closed to prevent php session locking)
538 - }
539 - $key = $this->getSessionKey();
540 - $_SESSION['wsUploadData'][$key] = array(
541 - 'mTempPath' => $status->value,
542 - 'mFileSize' => $this->mFileSize,
543 - 'mFileProps' => $this->mFileProps,
544 - 'version' => self::SESSION_VERSION,
 537+ public function stashSessionFile( $key = null ) {
 538+ $stash = new UploadStash();
 539+ $data = array(
 540+ 'mFileProps' => $this->mFileProps
545541 );
546 - return $key;
 542+ $file = $stash->stashFile( $this->mTempPath, $data, $key );
 543+ // TODO should we change the "local file" here?
 544+ // $this->mLocalFile = $file;
 545+ return $file;
547546 }
548547
549548 /**
550 - * Generate a random session key from stash in cases where we want to start an upload without much information
 549+ * Stash a file in a temporary directory, returning a key which can be used to find the file again. See stashSessionFile().
 550+ *
 551+ * @param {String}: $key (optional) the session key used to find the file info again. If not supplied, a key will be autogenerated.
 552+ * @return {String}: session key
551553 */
552 - protected function getSessionKey() {
553 - $key = mt_rand( 0, 0x7fffffff );
554 - $_SESSION['wsUploadData'][$key] = array();
555 - return $key;
 554+ public function stashSession( $key = null ) {
 555+ return $this->stashSessionFile( $key )->getSessionKey();
556556 }
557557
558558 /**
@@ -1098,10 +1098,19 @@
10991099 return $blacklist;
11001100 }
11011101
 1102+ /**
 1103+ * Gets image info about the file just uploaded.
 1104+ *
 1105+ * Also has the effect of setting metadata to be an 'indexed tag name' in returned API result if
 1106+ * 'metadata' was requested. Oddly, we have to pass the "result" object down just so it can do that
 1107+ * with the appropriate format, presumably.
 1108+ *
 1109+ * @param {ApiResult}
 1110+ * @return {Array} image info
 1111+ */
11021112 public function getImageInfo( $result ) {
11031113 $file = $this->getLocalFile();
11041114 $imParam = ApiQueryImageInfo::getPropertyNames();
11051115 return ApiQueryImageInfo::getInfo( $file, array_flip( $imParam ), $result );
11061116 }
1107 -
11081117 }
Property changes on: branches/uploadwizard-deployment/includes/upload/UploadBase.php
___________________________________________________________________
Added: svn:mergeinfo
11091118 Merged /branches/sqlite/includes/upload/UploadBase.php:r58211-58321
11101119 Merged /trunk/phase3/includes/upload/UploadBase.php:r63549,63764,63897-63901,64113,64509,65387,65391,65555,65590,65650,65816,71059,71098,71942,72024,72120,72525,75906
11111120 Merged /branches/new-installer/phase3/includes/upload/UploadBase.php:r43664-66004
11121121 Merged /branches/wmf-deployment/includes/upload/UploadBase.php:r53381,60970
11131122 Merged /branches/uploadwizard/phase3/includes/upload/UploadBase.php:r73550-75905
11141123 Merged /branches/REL1_15/phase3/includes/upload/UploadBase.php:r51646
Index: branches/uploadwizard-deployment/includes/upload/UploadStash.php
@@ -0,0 +1,406 @@
 2+<?php
 3+/**
 4+ * UploadStash is intended to accomplish a few things:
 5+ * - enable applications to temporarily stash files without publishing them to the wiki.
 6+ * - Several parts of MediaWiki do this in similar ways: UploadBase, UploadWizard, and FirefoggChunkedExtension
 7+ * And there are several that reimplement stashing from scratch, in idiosyncratic ways. The idea is to unify them all here.
 8+ * Mostly all of them are the same except for storing some custom fields, which we subsume into the data array.
 9+ * - enable applications to find said files later, as long as the session or temp files haven't been purged.
 10+ * - enable the uploading user (and *ONLY* the uploading user) to access said files, and thumbnails of said files, via a URL.
 11+ * We accomplish this by making the session serve as a URL->file mapping, on the assumption that nobody else can access
 12+ * the session, even the uploading user. See SpecialUploadStash, which implements a web interface to some files stored this way.
 13+ *
 14+ */
 15+class UploadStash {
 16+ // Format of the key for files -- has to be suitable as a filename itself in some cases.
 17+ // This should encompass a sha1 content hash in hex (new style), or an integer (old style),
 18+ // and also thumbnails with prepended strings like "120px-".
 19+ // The file extension should not be part of the key.
 20+ const KEY_FORMAT_REGEX = '/^[\w-]+$/';
 21+
 22+ // repository that this uses to store temp files
 23+ protected $repo;
 24+
 25+ // array of initialized objects obtained from session (lazily initialized upon getFile())
 26+ private $files = array();
 27+
 28+ // the base URL for files in the stash
 29+ private $baseUrl;
 30+
 31+ // TODO: Once UploadBase starts using this, switch to use these constants rather than UploadBase::SESSION*
 32+ // const SESSION_VERSION = 2;
 33+ // const SESSION_KEYNAME = 'wsUploadData';
 34+
 35+ /**
 36+ * Represents the session which contains temporarily stored files.
 37+ * Designed to be compatible with the session stashing code in UploadBase (should replace it eventually)
 38+ * @param {FileRepo} $repo: optional -- repo in which to store files. Will choose LocalRepo if not supplied.
 39+ */
 40+ public function __construct( $repo = null ) {
 41+
 42+ if ( is_null( $repo ) ) {
 43+ $repo = RepoGroup::singleton()->getLocalRepo();
 44+ }
 45+
 46+ $this->repo = $repo;
 47+
 48+ if ( ! isset( $_SESSION ) ) {
 49+ throw new UploadStashNotAvailableException( 'no session variable' );
 50+ }
 51+
 52+ if ( !isset( $_SESSION[UploadBase::SESSION_KEYNAME] ) ) {
 53+ $_SESSION[UploadBase::SESSION_KEYNAME] = array();
 54+ }
 55+
 56+ $this->baseUrl = SpecialPage::getTitleFor( 'UploadStash' )->getLocalURL();
 57+ }
 58+
 59+ /**
 60+ * Get the base of URLs by which one can access the files
 61+ * @return {String} url
 62+ */
 63+ public function getBaseUrl() {
 64+ return $this->baseUrl;
 65+ }
 66+
 67+ /**
 68+ * Get a file and its metadata from the stash.
 69+ * May throw exception if session data cannot be parsed due to schema change, or key not found.
 70+ * @param {Integer} $key: key
 71+ * @throws UploadStashFileNotFoundException
 72+ * @throws UploadStashBadVersionException
 73+ * @return {UploadStashItem} null if no such item or item out of date, or the item
 74+ */
 75+ public function getFile( $key ) {
 76+ if ( ! preg_match( self::KEY_FORMAT_REGEX, $key ) ) {
 77+ throw new UploadStashBadPathException( "key '$key' is not in a proper format" );
 78+ }
 79+
 80+ if ( !isset( $this->files[$key] ) ) {
 81+ if ( !isset( $_SESSION[UploadBase::SESSION_KEYNAME][$key] ) ) {
 82+ throw new UploadStashFileNotFoundException( "key '$key' not found in session" );
 83+ }
 84+
 85+ $data = $_SESSION[UploadBase::SESSION_KEYNAME][$key];
 86+ // guards against PHP class changing while session data doesn't
 87+ if ($data['version'] !== UploadBase::SESSION_VERSION ) {
 88+ throw new UploadStashBadVersionException( $data['version'] . " does not match current version " . UploadBase::SESSION_VERSION );
 89+ }
 90+
 91+ // separate the stashData into the path, and then the rest of the data
 92+ $path = $data['mTempPath'];
 93+ unset( $data['mTempPath'] );
 94+
 95+ $file = new UploadStashFile( $this, $this->repo, $path, $key, $data );
 96+
 97+ $this->files[$key] = $file;
 98+
 99+ }
 100+ return $this->files[$key];
 101+ }
 102+
 103+ /**
 104+ * Stash a file in a temp directory and record that we did this in the session, along with other metadata.
 105+ * We store data in a flat key-val namespace because that's how UploadBase did it. This also means we have to
 106+ * ensure that the key-val pairs in $data do not overwrite other required fields.
 107+ *
 108+ * @param {String} $path: path to file you want stashed
 109+ * @param {Array} $data: optional, other data you want associated with the file. Do not use 'mTempPath', 'mFileProps', 'mFileSize', or 'version' as keys here
 110+ * @param {String} $key: optional, unique key for this file in this session. Used for directory hashing when storing, otherwise not important
 111+ * @throws UploadStashBadPathException
 112+ * @throws UploadStashFileException
 113+ * @return {null|UploadStashFile} file, or null on failure
 114+ */
 115+ public function stashFile( $path, $data = array(), $key = null ) {
 116+ if ( ! file_exists( $path ) ) {
 117+ throw new UploadStashBadPathException( "path '$path' doesn't exist" );
 118+ }
 119+ $fileProps = File::getPropsFromPath( $path );
 120+
 121+ // If no key was supplied, use content hash. Also has the nice property of collapsing multiple identical files
 122+ // uploaded this session, which could happen if uploads had failed.
 123+ if ( is_null( $key ) ) {
 124+ $key = $fileProps['sha1'];
 125+ }
 126+
 127+ if ( ! preg_match( self::KEY_FORMAT_REGEX, $key ) ) {
 128+ throw new UploadStashBadPathException( "key '$key' is not in a proper format" );
 129+ }
 130+
 131+ // if not already in a temporary area, put it there
 132+ $status = $this->repo->storeTemp( basename( $path ), $path );
 133+ if( ! $status->isOK() ) {
 134+ // It is a convention in MediaWiki to only return one error per API exception, even if multiple errors
 135+ // are available. We use reset() to pick the "first" thing that was wrong, preferring errors to warnings.
 136+ // This is a bit lame, as we may have more info in the $status and we're throwing it away, but to fix it means
 137+ // redesigning API errors significantly.
 138+ // $status->value just contains the virtual URL (if anything) which is probably useless to the caller
 139+ $error = reset( $status->getErrorsArray() );
 140+ if ( ! count( $error ) ) {
 141+ $error = reset( $status->getWarningsArray() );
 142+ if ( ! count( $error ) ) {
 143+ $error = array( 'unknown', 'no error recorded' );
 144+ }
 145+ }
 146+ throw new UploadStashFileException( "error storing file in '$path': " . implode( '; ', $error ) );
 147+ }
 148+ $stashPath = $status->value;
 149+
 150+ // required info we always store. Must trump any other application info in $data
 151+ // 'mTempPath', 'mFileSize', and 'mFileProps' are arbitrary names
 152+ // chosen for compatibility with UploadBase's way of doing this.
 153+ $requiredData = array(
 154+ 'mTempPath' => $stashPath,
 155+ 'mFileSize' => $fileProps['size'],
 156+ 'mFileProps' => $fileProps,
 157+ 'version' => UploadBase::SESSION_VERSION
 158+ );
 159+
 160+ // now, merge required info and extra data into the session. (The extra data changes from application to application.
 161+ // UploadWizard wants different things than say FirefoggChunkedUpload.)
 162+ $_SESSION[UploadBase::SESSION_KEYNAME][$key] = array_merge( $data, $requiredData );
 163+
 164+ return $this->getFile( $key );
 165+ }
 166+
 167+}
 168+
 169+class UploadStashFile extends UnregisteredLocalFile {
 170+ private $sessionStash;
 171+ private $sessionKey;
 172+ private $sessionData;
 173+ private $urlName;
 174+
 175+ /**
 176+ * A LocalFile wrapper around a file that has been temporarily stashed, so we can do things like create thumbnails for it
 177+ * Arguably UnregisteredLocalFile should be handling its own file repo but that class is a bit retarded currently
 178+ * @param {UploadStash} $stash: UploadStash, useful for obtaining config, stashing transformed files
 179+ * @param {FileRepo} $repo: repository where we should find the path
 180+ * @param {String} $path: path to file
 181+ * @param {String} $key: key to store the path and any stashed data under
 182+ * @param {String} $data: any other data we want stored with this file
 183+ * @throws UploadStashBadPathException
 184+ * @throws UploadStashFileNotFoundException
 185+ */
 186+ public function __construct( $stash, $repo, $path, $key, $data ) {
 187+ $this->sessionStash = $stash;
 188+ $this->sessionKey = $key;
 189+ $this->sessionData = $data;
 190+
 191+ // resolve mwrepo:// urls
 192+ if ( $repo->isVirtualUrl( $path ) ) {
 193+ $path = $repo->resolveVirtualUrl( $path );
 194+ }
 195+
 196+ // check if path appears to be sane, no parent traversals, and is in this repo's temp zone.
 197+ $repoTempPath = $repo->getZonePath( 'temp' );
 198+ if ( ( ! $repo->validateFilename( $path ) ) ||
 199+ ( strpos( $path, $repoTempPath ) !== 0 ) ) {
 200+ throw new UploadStashBadPathException( "path '$path' is not valid or is not in repo temp area: '$repoTempPath'" );
 201+ }
 202+
 203+ // check if path exists! and is a plain file.
 204+ if ( ! $repo->fileExists( $path, FileRepo::FILES_ONLY ) ) {
 205+ throw new UploadStashFileNotFoundException( "cannot find path '$path'" );
 206+ }
 207+
 208+ parent::__construct( false, $repo, $path, false );
 209+
 210+ // we will be initializing from some tmpnam files that don't have extensions.
 211+ // most of MediaWiki assumes all uploaded files have good extensions. So, we fix this.
 212+ $this->name = basename( $this->path );
 213+ $this->setExtension();
 214+
 215+ }
 216+
 217+ /**
 218+ * A method needed by the file transforming and scaling routines in File.php
 219+ * We do not necessarily care about doing the description at this point
 220+ * However, we also can't return the empty string, as the rest of MediaWiki demands this (and calls to imagemagick
 221+ * convert require it to be there)
 222+ * @return {String} dummy value
 223+ */
 224+ public function getDescriptionUrl() {
 225+ return $this->getUrl();
 226+ }
 227+
 228+ /**
 229+ * Find or guess extension -- ensuring that our extension matches our mime type.
 230+ * Since these files are constructed from php tempnames they may not start off
 231+ * with an extension.
 232+ * This does not override getExtension() because things like getMimeType() already call getExtension(),
 233+ * and that results in infinite recursion. So, we preemptively *set* the extension so getExtension() can find it.
 234+ * For obvious reasons this should be called as early as possible, as part of initialization
 235+ */
 236+ public function setExtension() {
 237+ // Does this have an extension?
 238+ $n = strrpos( $this->path, '.' );
 239+ $extension = null;
 240+ if ( $n !== false ) {
 241+ $extension = $n ? substr( $this->path, $n + 1 ) : '';
 242+ } else {
 243+ // If not, assume that it should be related to the mime type of the original file.
 244+ //
 245+ // This entire thing is backwards -- we *should* just create an extension based on
 246+ // the mime type of the transformed file, *after* transformation. But File.php demands
 247+ // to know the name of the transformed file before creating it.
 248+ $mimeType = $this->getMimeType();
 249+ $extensions = explode( ' ', MimeMagic::singleton()->getExtensionsForType( $mimeType ) );
 250+ if ( count( $extensions ) ) {
 251+ $extension = $extensions[0];
 252+ }
 253+ }
 254+
 255+ if ( is_null( $extension ) ) {
 256+ throw new UploadStashFileException( "extension '$extension' is null" );
 257+ }
 258+
 259+ $this->extension = parent::normalizeExtension( $extension );
 260+ }
 261+
 262+ /**
 263+ * Get the path for the thumbnail (actually any transformation of this file)
 264+ * The actual argument is the result of thumbName although we seem to have
 265+ * buggy code elsewhere that expects a boolean 'suffix'
 266+ *
 267+ * @param {String|false} $thumbName: name of thumbnail (e.g. "120px-123456.jpg" ), or false to just get the path
 268+ * @return {String} path thumbnail should take on filesystem, or containing directory if thumbname is false
 269+ */
 270+ public function getThumbPath( $thumbName = false ) {
 271+ $path = dirname( $this->path );
 272+ if ( $thumbName !== false ) {
 273+ $path .= "/$thumbName";
 274+ }
 275+ return $path;
 276+ }
 277+
 278+ /**
 279+ * Return the file/url base name of a thumbnail with the specified parameters
 280+ *
 281+ * @param {Array} $params: handler-specific parameters
 282+ * @return {String|null} base name for URL, like '120px-12345.jpg', or null if there is no handler
 283+ */
 284+ function thumbName( $params ) {
 285+ if ( !$this->getHandler() ) {
 286+ return null;
 287+ }
 288+ $extension = $this->getExtension();
 289+ list( $thumbExt, $thumbMime ) = $this->handler->getThumbType( $extension, $this->getMimeType(), $params );
 290+ $thumbName = $this->getHandler()->makeParamString( $params ) . '-' . $this->getUrlName();
 291+ if ( $thumbExt != $extension ) {
 292+ $thumbName .= ".$thumbExt";
 293+ }
 294+ return $thumbName;
 295+ }
 296+
 297+ /**
 298+ * Get a URL to access the thumbnail
 299+ * This is required because the model of how files work requires that
 300+ * the thumbnail urls be predictable. However, in our model the URL is not based on the filename
 301+ * (that's hidden in the session)
 302+ *
 303+ * @param {String} $thumbName: basename of thumbnail file -- however, we don't want to use the file exactly
 304+ * @return {String} URL to access thumbnail, or URL with partial path
 305+ */
 306+ public function getThumbUrl( $thumbName = false ) {
 307+ $path = $this->sessionStash->getBaseUrl();
 308+ if ( $thumbName !== false ) {
 309+ $path .= '/' . rawurlencode( $thumbName );
 310+ }
 311+ return $path;
 312+ }
 313+
 314+ /**
 315+ * The basename for the URL, which we want to not be related to the filename.
 316+ * Will also be used as the lookup key for a thumbnail file.
 317+ * @return {String} base url name, like '120px-123456.jpg'
 318+ */
 319+ public function getUrlName() {
 320+ if ( ! $this->urlName ) {
 321+ $this->urlName = $this->sessionKey . '.' . $this->getExtension();
 322+ }
 323+ return $this->urlName;
 324+ }
 325+
 326+ /**
 327+ * Return the URL of the file, if for some reason we wanted to download it
 328+ * We tend not to do this for the original file, but we do want thumb icons
 329+ * @return {String} url
 330+ */
 331+ public function getUrl() {
 332+ if ( !isset( $this->url ) ) {
 333+ $this->url = $this->sessionStash->getBaseUrl() . '/' . $this->getUrlName();
 334+ }
 335+ return $this->url;
 336+ }
 337+
 338+ /**
 339+ * Parent classes use this method, for no obvious reason, to return the path (relative to wiki root, I assume).
 340+ * But with this class, the URL is unrelated to the path.
 341+ *
 342+ * @return {String} url
 343+ */
 344+ public function getFullUrl() {
 345+ return $this->getUrl();
 346+ }
 347+
 348+
 349+ /**
 350+ * Getter for session key (the session-unique id by which this file's location & metadata is stored in the session)
 351+ * @return {String} session key
 352+ */
 353+ public function getSessionKey() {
 354+ return $this->sessionKey;
 355+ }
 356+
 357+ /**
 358+ * Typically, transform() returns a ThumbnailImage, which you can think of as being the exact
 359+ * equivalent of an HTML thumbnail on Wikipedia. So its URL is the full-size file, not the thumbnail's URL.
 360+ *
 361+ * Here we override transform() to stash the thumbnail file, and then
 362+ * provide a way to get at the stashed thumbnail file to extract properties such as its URL
 363+ *
 364+ * @param {Array} $params: parameters suitable for File::transform()
 365+ * @param {Bitmask} $flags: flags suitable for File::transform()
 366+ * @return {ThumbnailImage} with additional File thumbnailFile property
 367+ */
 368+ public function transform( $params, $flags = 0 ) {
 369+
 370+ // force it to get a thumbnail right away
 371+ $flags |= self::RENDER_NOW;
 372+
 373+ // returns a ThumbnailImage object containing the url and path. Note. NOT A FILE OBJECT.
 374+ $thumb = parent::transform( $params, $flags );
 375+ $key = $this->thumbName($params);
 376+
 377+ // remove extension, so it's stored in the session under '120px-123456'
 378+ // this makes it uniform with the other session key for the original, '123456'
 379+ $n = strrpos( $key, '.' );
 380+ if ( $n !== false ) {
 381+ $key = substr( $key, 0, $n );
 382+ }
 383+
 384+ // stash the thumbnail File, and provide our caller with a way to get at its properties
 385+ $stashedThumbFile = $this->sessionStash->stashFile( $thumb->path, array(), $key );
 386+ $thumb->thumbnailFile = $stashedThumbFile;
 387+
 388+ return $thumb;
 389+
 390+ }
 391+
 392+ /**
 393+ * Remove the associated temporary file
 394+ * @return {Status} success
 395+ */
 396+ public function remove() {
 397+ return $this->repo->freeTemp( $this->path );
 398+ }
 399+
 400+}
 401+
 402+class UploadStashNotAvailableException extends MWException {};
 403+class UploadStashFileNotFoundException extends MWException {};
 404+class UploadStashBadPathException extends MWException {};
 405+class UploadStashBadVersionException extends MWException {};
 406+class UploadStashFileException extends MWException {};
 407+
Property changes on: branches/uploadwizard-deployment/includes/upload/UploadStash.php
___________________________________________________________________
Added: svn:mergeinfo
1408 Merged /branches/uploadwizard/extensions/includes/upload/UploadStash.php:r73550-74029
2409 Merged /branches/new-installer/phase3/includes/upload/UploadStash.php:r43664-66004
3410 Merged /branches/REL1_15/phase3/includes/upload/UploadStash.php:r51646
4411 Merged /branches/sqlite/includes/upload/UploadStash.php:r58211-58321
5412 Merged /trunk/phase3/includes/upload/UploadStash.php:r74019-75058,75060-75821
Added: svn:eol-style
6413 + native
Index: branches/uploadwizard-deployment/includes/upload/UploadFromFile.php
@@ -29,4 +29,14 @@
3030 static function isValidRequest( $request ) {
3131 return (bool)$request->getFileTempName( 'wpUploadFile' );
3232 }
 33+
 34+ /**
 35+ * Get the path to the file underlying the upload
 36+ * @return String path to file
 37+ */
 38+ public function getFileTempname() {
 39+ return $this->mUpload->getTempname();
 40+ }
 41+
 42+
3343 }
Property changes on: branches/uploadwizard-deployment/includes/upload/UploadFromFile.php
___________________________________________________________________
Added: svn:mergeinfo
3444 Merged /branches/new-installer/phase3/includes/upload/UploadFromFile.php:r43664-66004
3545 Merged /branches/wmf-deployment/includes/upload/UploadFromFile.php:r53381,60970
3646 Merged /branches/uploadwizard/phase3/includes/upload/UploadFromFile.php:r73550-75905
3747 Merged /branches/REL1_15/phase3/includes/upload/UploadFromFile.php:r51646
3848 Merged /branches/sqlite/includes/upload/UploadFromFile.php:r58211-58321
3949 Merged /trunk/phase3/includes/upload/UploadFromFile.php:r63549,63764,63897-63901,64113,64509,65387,65391,65555,65590,65650,65816,71059,71098,71942,72024,72120,72525,75906
Index: branches/uploadwizard-deployment/includes/filerepo/File.php
@@ -526,7 +526,7 @@
527527 * @param array $params An associative array of handler-specific parameters. Typical
528528 * keys are width, height and page.
529529 * @param integer $flags A bitfield, may contain self::RENDER_NOW to force rendering
530 - * @return MediaTransformOutput
 530+ * @return MediaTransformOutput | false
531531 */
532532 function transform( $params, $flags = 0 ) {
533533 global $wgUseSquid, $wgIgnoreImageErrors, $wgThumbnailEpoch, $wgServer;
@@ -560,7 +560,7 @@
561561 $thumbPath = $this->getThumbPath( $thumbName );
562562 $thumbUrl = $this->getThumbUrl( $thumbName );
563563
564 - if ( $this->repo->canTransformVia404() && !($flags & self::RENDER_NOW ) ) {
 564+ if ( $this->repo && $this->repo->canTransformVia404() && !($flags & self::RENDER_NOW ) ) {
565565 $thumb = $this->handler->getTransform( $this, $thumbPath, $thumbUrl, $params );
566566 break;
567567 }
Property changes on: branches/uploadwizard-deployment/includes/filerepo/File.php
___________________________________________________________________
Added: svn:mergeinfo
568568 Merged /branches/REL1_15/phase3/includes/filerepo/File.php:r51646
569569 Merged /branches/sqlite/includes/filerepo/File.php:r58211-58321
570570 Merged /trunk/phase3/includes/filerepo/File.php:r63549,63764,63897-63901,64113,64509,65387,65391,65555,65590,65650,65816,68409,71059,71098,75906
571571 Merged /branches/new-installer/phase3/includes/filerepo/File.php:r43664-66004
572572 Merged /branches/wmf-deployment/includes/filerepo/File.php:r53381,60970
573573 Merged /branches/uploadwizard/phase3/includes/filerepo/File.php:r73550-75905
Index: branches/uploadwizard-deployment/includes/api/ApiQueryImageInfo.php
@@ -35,8 +35,13 @@
3636 */
3737 class ApiQueryImageInfo extends ApiQueryBase {
3838
39 - public function __construct( $query, $moduleName ) {
40 - parent :: __construct( $query, $moduleName, 'ii' );
 39+ public function __construct( $query, $moduleName, $prefix = 'ii' ) {
 40+ // We allow a subclass to override the prefix, to create a related API module.
 41+ // Some other parts of MediaWiki construct this with a null $prefix, which used to be ignored when this only took two arguments
 42+ if ( is_null( $prefix ) ) {
 43+ $prefix = 'ii';
 44+ }
 45+ parent::__construct( $query, $moduleName, $prefix );
4146 }
4247
4348 public function execute() {
@@ -44,16 +49,7 @@
4550
4651 $prop = array_flip( $params['prop'] );
4752
48 - if ( $params['urlheight'] != - 1 && $params['urlwidth'] == - 1 )
49 - $this->dieUsage( "iiurlheight cannot be used without iiurlwidth", 'iiurlwidth' );
50 -
51 - if ( $params['urlwidth'] != - 1 ) {
52 - $scale = array();
53 - $scale['width'] = $params['urlwidth'];
54 - $scale['height'] = $params['urlheight'];
55 - } else {
56 - $scale = null;
57 - }
 53+ $scale = $this->getScale( $params );
5854
5955 $pageIds = $this->getPageSet()->getAllTitlesByNamespace();
6056 if ( !empty( $pageIds[NS_FILE] ) ) {
@@ -175,6 +171,28 @@
176172 }
177173
178174 /**
 175+ * From parameters, construct a 'scale' array
 176+ * @param {Array} $params
 177+ * @return {null|Array} key-val array of 'width' and 'height', or null
 178+ */
 179+ public function getScale( $params ) {
 180+ $p = $this->getModulePrefix();
 181+ if ( $params['urlheight'] != -1 && $params['urlwidth'] == -1 ) {
 182+ $this->dieUsage( "${p}urlheight cannot be used without {$p}urlwidth", "{$p}urlwidth" );
 183+ }
 184+
 185+ if ( $params['urlwidth'] != -1 ) {
 186+ $scale = array();
 187+ $scale['width'] = $params['urlwidth'];
 188+ $scale['height'] = $params['urlheight'];
 189+ } else {
 190+ $scale = null;
 191+ }
 192+ return $scale;
 193+ }
 194+
 195+
 196+ /**
179197 * Get result information for an image revision
180198 * @param File f The image
181199 * @return array Result array
@@ -275,12 +293,12 @@
276294 ApiBase :: PARAM_TYPE => 'timestamp'
277295 ),
278296 'urlwidth' => array(
279 - ApiBase :: PARAM_TYPE => 'integer',
280 - ApiBase :: PARAM_DFLT => - 1
 297+ ApiBase::PARAM_TYPE => 'integer',
 298+ ApiBase::PARAM_DFLT => -1
281299 ),
282300 'urlheight' => array(
283 - ApiBase :: PARAM_TYPE => 'integer',
284 - ApiBase :: PARAM_DFLT => - 1
 301+ ApiBase::PARAM_TYPE => 'integer',
 302+ ApiBase::PARAM_DFLT => -1
285303 ),
286304 'continue' => null,
287305 );
@@ -305,16 +323,38 @@
306324 );
307325 }
308326
 327+
 328+ /**
 329+ * Return the API documentation for the parameters.
 330+ * @return {Array} parameter documentation.
 331+ */
309332 public function getParamDescription() {
310 - return array (
311 - 'prop' => 'What image information to get.',
 333+ $p = $this->getModulePrefix();
 334+ return array(
 335+ 'prop' => array(
 336+ 'What image information to get:',
 337+ ' timestamp - Adds timestamp for the uploaded version',
 338+ ' user - Adds the user who uploaded the image version',
 339+ ' userid - Add the user id that uploaded the image version',
 340+ ' comment - Comment on the version',
 341+ ' parsedcomment - Parse the comment on the version',
 342+ ' url - Gives URL to the image and the description page',
 343+ ' size - Adds the size of the image in bytes and the height and width',
 344+ ' dimensions - Alias for size',
 345+ ' sha1 - Adds sha1 hash for the image',
 346+ ' mime - Adds MIME of the image',
 347+ ' thumbmime - Adss MIME of the image thumbnail (requires url)',
 348+ ' metadata - Lists EXIF metadata for the version of the image',
 349+ ' archivename - Adds the file name of the archive version for non-latest versions',
 350+ ' bitdepth - Adds the bit depth of the version',
 351+ ),
 352+ 'urlwidth' => array( "If {$p}prop=url is set, a URL to an image scaled to this width will be returned.",
 353+ 'Only the current version of the image can be scaled' ),
 354+ 'urlheight' => "Similar to {$p}urlwidth. Cannot be used without {$p}urlwidth",
312355 'limit' => 'How many image revisions to return',
313356 'start' => 'Timestamp to start listing from',
314357 'end' => 'Timestamp to stop listing at',
315 - 'urlwidth' => array( 'If iiprop=url is set, a URL to an image scaled to this width will be returned.',
316 - 'Only the current version of the image can be scaled.' ),
317 - 'urlheight' => 'Similar to iiurlwidth. Cannot be used without iiurlwidth',
318 - 'continue' => 'When more results are available, use this to continue',
 358+ 'continue' => 'If the query response includes a continue value, use it here to get another page of results'
319359 );
320360 }
321361
Property changes on: branches/uploadwizard-deployment/includes/api/ApiQueryImageInfo.php
___________________________________________________________________
Added: svn:mergeinfo
322362 Merged /branches/uploadwizard/phase3/includes/api/ApiQueryImageInfo.php:r73550-75905
323363 Merged /branches/REL1_15/phase3/includes/api/ApiQueryImageInfo.php:r51646
324364 Merged /branches/REL1_16/phase3/includes/api/ApiQueryImageInfo.php:r69357,69932
325365 Merged /branches/sqlite/includes/api/ApiQueryImageInfo.php:r58211-58321
326366 Merged /trunk/phase3/includes/api/ApiQueryImageInfo.php:r63549,63764,63897-63901,64454,66486,69339,69347,69350,69369,69379,69776,69931,70078,71059,71098,75906
327367 Merged /branches/wmf-deployment/includes/api/ApiQueryImageInfo.php:r53381,59952,60970
Index: branches/uploadwizard-deployment/includes/api/ApiUpload.php
@@ -1,10 +1,11 @@
22 <?php
3 -/*
4 - * Created on Aug 21, 2008
 3+/**
54 * API for MediaWiki 1.8+
65 *
7 - * Copyright (C) 2008 - 2010 Bryan Tong Minh <Bryan.TongMinh@Gmail.com>
 6+ * Created on Aug 21, 2008
87 *
 8+ * Copyright © 2008 - 2010 Bryan Tong Minh <Bryan.TongMinh@Gmail.com>
 9+ *
910 * This program is free software; you can redistribute it and/or modify
1011 * it under the terms of the GNU General Public License as published by
1112 * the Free Software Foundation; either version 2 of the License, or
@@ -17,8 +18,10 @@
1819 *
1920 * You should have received a copy of the GNU General Public License along
2021 * with this program; if not, write to the Free Software Foundation, Inc.,
21 - * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
 22+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
2223 * http://www.gnu.org/copyleft/gpl.html
 24+ *
 25+ * @file
2326 */
2427
2528 if ( !defined( 'MEDIAWIKI' ) ) {
@@ -38,141 +41,269 @@
3942 }
4043
4144 public function execute() {
42 - global $wgUser, $wgAllowCopyUploads;
 45+ global $wgUser;
4346
4447 // Check whether upload is enabled
45 - if ( !UploadBase::isEnabled() )
 48+ if ( !UploadBase::isEnabled() ) {
4649 $this->dieUsageMsg( array( 'uploaddisabled' ) );
 50+ }
4751
 52+ // Parameter handling
4853 $this->mParams = $this->extractRequestParams();
4954 $request = $this->getMain()->getRequest();
50 -
5155 // Add the uploaded file to the params array
5256 $this->mParams['file'] = $request->getFileName( 'file' );
5357
 58+ // Select an upload module
 59+ if ( !$this->selectUploadModule() ) {
 60+ // This is not a true upload, but a status request or similar
 61+ return;
 62+ }
 63+ if ( !isset( $this->mUpload ) ) {
 64+ $this->dieUsage( 'No upload module set', 'nomodule' );
 65+ }
 66+
 67+ // First check permission to upload
 68+ $this->checkPermissions( $wgUser );
 69+
 70+ // Fetch the file
 71+ $status = $this->mUpload->fetchFile();
 72+ if ( !$status->isGood() ) {
 73+ $errors = $status->getErrorsArray();
 74+ $error = array_shift( $errors[0] );
 75+ $this->dieUsage( 'Error fetching file from remote source', $error, 0, $errors[0] );
 76+ }
 77+
 78+ // Check if the uploaded file is sane
 79+ $this->verifyUpload();
 80+
 81+ // Check permission to upload this file
 82+ $permErrors = $this->mUpload->verifyPermissions( $wgUser );
 83+ if ( $permErrors !== true ) {
 84+ // TODO: stash the upload and allow choosing a new name
 85+ $this->dieUsageMsg( array( 'badaccess-groups' ) );
 86+ }
 87+
 88+ // Prepare the API result
 89+ $result = array();
 90+
 91+ $warnings = $this->getApiWarnings();
 92+ if ( $warnings ) {
 93+ $result['result'] = 'Warning';
 94+ $result['warnings'] = $warnings;
 95+ // in case the warnings can be fixed with some further user action, let's stash this upload
 96+ // and return a key they can use to restart it
 97+ try {
 98+ $result['sessionkey'] = $this->performStash();
 99+ } catch ( MWException $e ) {
 100+ $result['warnings']['stashfailed'] = $e->getMessage();
 101+ }
 102+ } elseif ( $this->mParams['stash'] ) {
 103+ // Some uploads can request they be stashed, so as not to publish them immediately.
 104+ // In this case, a failure to stash ought to be fatal
 105+ try {
 106+ $result['result'] = 'Success';
 107+ $result['sessionkey'] = $this->performStash();
 108+ } catch ( MWException $e ) {
 109+ $this->dieUsage( $e->getMessage(), 'stashfailed' );
 110+ }
 111+ } else {
 112+ // This is the most common case -- a normal upload with no warnings
 113+ // $result will be formatted properly for the API already, with a status
 114+ $result = $this->performUpload();
 115+ }
 116+
 117+ if ( $result['result'] === 'Success' ) {
 118+ $result['imageinfo'] = $this->mUpload->getImageInfo( $this->getResult() );
 119+ }
 120+
 121+ $this->getResult()->addValue( null, $this->getModuleName(), $result );
 122+
 123+ // Cleanup any temporary mess
 124+ $this->mUpload->cleanupTempFile();
 125+ }
 126+
 127+ /**
 128+ * Stash the file and return the session key
 129+ * Also re-raises exceptions with slightly more informative message strings (useful for API)
 130+ * @throws MWException
 131+ * @return {String} session key
 132+ */
 133+ function performStash() {
 134+ try {
 135+ $sessionKey = $this->mUpload->stashSessionFile()->getSessionKey();
 136+ } catch ( MWException $e ) {
 137+ throw new MWException( 'Stashing temporary file failed: ' . get_class($e) . ' ' . $e->getMessage() );
 138+ }
 139+ return $sessionKey;
 140+ }
 141+
 142+
 143+ /**
 144+ * Select an upload module and set it to mUpload. Dies on failure. If the
 145+ * request was a status request and not a true upload, returns false;
 146+ * otherwise true
 147+ *
 148+ * @return bool
 149+ */
 150+ protected function selectUploadModule() {
 151+ global $wgAllowAsyncCopyUploads;
 152+ $request = $this->getMain()->getRequest();
 153+
54154 // One and only one of the following parameters is needed
55155 $this->requireOnlyOneParameter( $this->mParams,
56 - 'sessionkey', 'file', 'url' );
 156+ 'sessionkey', 'file', 'url', 'statuskey' );
57157
 158+ if ( $wgAllowAsyncCopyUploads && $this->mParams['statuskey'] ) {
 159+ // Status request for an async upload
 160+ $sessionData = UploadFromUrlJob::getSessionData( $this->mParams['statuskey'] );
 161+ if ( !isset( $sessionData['result'] ) ) {
 162+ $this->dieUsage( 'No result in session data', 'missingresult');
 163+ }
 164+ if ( $sessionData['result'] == 'Warning' ) {
 165+ $sessionData['warnings'] = $this->transformWarnings( $sessionData['warnings'] );
 166+ $sessionData['sessionkey'] = $this->mParams['statuskey'];
 167+ }
 168+ $this->getResult()->addValue( null, $this->getModuleName(), $sessionData );
 169+ return false;
 170+
 171+ }
 172+
 173+
 174+ // The following modules all require the filename parameter to be set
 175+ if ( is_null( $this->mParams['filename'] ) ) {
 176+ $this->dieUsageMsg( array( 'missingparam', 'filename' ) );
 177+ }
 178+
 179+
58180 if ( $this->mParams['sessionkey'] ) {
59 - /**
60 - * Upload stashed in a previous request
61 - */
62 - // Check the session key
63 - if ( !isset( $_SESSION['wsUploadData'][$this->mParams['sessionkey']] ) )
 181+ // Upload stashed in a previous request
 182+ $sessionData = $request->getSessionData( UploadBase::getSessionKeyName() );
 183+ if ( !UploadFromStash::isValidSessionKey( $this->mParams['sessionkey'], $sessionData ) ) {
64184 $this->dieUsageMsg( array( 'invalid-session-key' ) );
 185+ }
65186
66187 $this->mUpload = new UploadFromStash();
67188 $this->mUpload->initialize( $this->mParams['filename'],
68189 $this->mParams['sessionkey'],
69 - $_SESSION['wsUploadData'][$this->mParams['sessionkey']] );
70 - } elseif ( isset( $this->mParams['filename'] ) ) {
71 - /**
72 - * Upload from url, etc
73 - * Parameter filename is required
74 - */
 190+ $sessionData[$this->mParams['sessionkey']] );
75191
76 - if ( isset( $this->mParams['file'] ) ) {
77 - $this->mUpload = new UploadFromFile();
78 - $this->mUpload->initialize(
79 - $this->mParams['filename'],
80 - $request->getFileTempName( 'file' ),
81 - $request->getFileSize( 'file' )
82 - );
83 - } elseif ( isset( $this->mParams['url'] ) ) {
84 - // make sure upload by url is enabled:
85 - if ( !$wgAllowCopyUploads )
86 - $this->dieUsageMsg( array( 'uploaddisabled' ) );
87192
88 - // make sure the current user can upload
89 - if ( ! $wgUser->isAllowed( 'upload_by_url' ) )
90 - $this->dieUsageMsg( array( 'badaccess-groups' ) );
 193+ } elseif ( isset( $this->mParams['file'] ) ) {
 194+ $this->mUpload = new UploadFromFile();
 195+ $this->mUpload->initialize(
 196+ $this->mParams['filename'],
 197+ $request->getUpload( 'file' )
 198+ );
 199+ } elseif ( isset( $this->mParams['url'] ) ) {
 200+ // Make sure upload by URL is enabled:
 201+ if ( !UploadFromUrl::isEnabled() ) {
 202+ $this->dieUsageMsg( array( 'copyuploaddisabled' ) );
 203+ }
91204
92 - $this->mUpload = new UploadFromUrl();
93 - $this->mUpload->initialize( $this->mParams['filename'],
94 - $this->mParams['url'] );
95 -
96 - $status = $this->mUpload->fetchFile();
97 - if ( !$status->isOK() ) {
98 - $this->dieUsage( $status->getWikiText(), 'fetchfileerror' );
 205+ $async = false;
 206+ if ( $this->mParams['asyncdownload'] ) {
 207+ if ( $this->mParams['leavemessage'] && !$this->mParams['ignorewarnings'] ) {
 208+ $this->dieUsage( 'Using leavemessage without ignorewarnings is not supported',
 209+ 'missing-ignorewarnings' );
99210 }
 211+
 212+ if ( $this->mParams['leavemessage'] ) {
 213+ $async = 'async-leavemessage';
 214+ } else {
 215+ $async = 'async';
 216+ }
100217 }
101 - } else $this->dieUsageMsg( array( 'missingparam', 'filename' ) );
 218+ $this->mUpload = new UploadFromUrl;
 219+ $this->mUpload->initialize( $this->mParams['filename'],
 220+ $this->mParams['url'], $async );
102221
103 - if ( !isset( $this->mUpload ) )
104 - $this->dieUsage( 'No upload module set', 'nomodule' );
 222+ }
 223+
 224+ return true;
 225+ }
105226
 227+ /**
 228+ * Checks that the user has permissions to perform this upload.
 229+ * Dies with usage message on inadequate permissions.
 230+ * @param $user User The user to check.
 231+ */
 232+ protected function checkPermissions( $user ) {
106233 // Check whether the user has the appropriate permissions to upload anyway
107 - $permission = $this->mUpload->isAllowed( $wgUser );
 234+ $permission = $this->mUpload->isAllowed( $user );
108235
109236 if ( $permission !== true ) {
110 - if ( !$wgUser->isLoggedIn() )
 237+ if ( !$user->isLoggedIn() ) {
111238 $this->dieUsageMsg( array( 'mustbeloggedin', 'upload' ) );
112 - else
 239+ } else {
113240 $this->dieUsageMsg( array( 'badaccess-groups' ) );
 241+ }
114242 }
115 - // Perform the upload
116 - $result = $this->performUpload();
 243+ }
117244
118 - // Cleanup any temporary mess
119 - $this->mUpload->cleanupTempFile();
 245+ /**
 246+ * Performs file verification, dies on error.
 247+ */
 248+ protected function verifyUpload( ) {
 249+ global $wgFileExtensions;
120250
121 - $this->getResult()->addValue( null, $this->getModuleName(), $result );
122 - }
123 -
124 - protected function performUpload() {
125 - global $wgUser;
126 - $result = array();
127 - $permErrors = $this->mUpload->verifyPermissions( $wgUser );
128 - if ( $permErrors !== true ) {
129 - $this->dieUsageMsg( array( 'badaccess-groups' ) );
 251+ $verification = $this->mUpload->verifyUpload( );
 252+ if ( $verification['status'] === UploadBase::OK ) {
 253+ return;
130254 }
131255
132256 // TODO: Move them to ApiBase's message map
133 - $verification = $this->mUpload->verifyUpload();
134 - if ( $verification['status'] !== UploadBase::OK ) {
135 - $result['result'] = 'Failure';
136 - switch( $verification['status'] ) {
137 - case UploadBase::EMPTY_FILE:
138 - $this->dieUsage( 'The file you submitted was empty', 'empty-file' );
139 - break;
140 - case UploadBase::FILETYPE_MISSING:
141 - $this->dieUsage( 'The file is missing an extension', 'filetype-missing' );
142 - break;
143 - case UploadBase::FILETYPE_BADTYPE:
144 - global $wgFileExtensions;
145 - $this->dieUsage( 'This type of file is banned', 'filetype-banned',
146 - 0, array(
147 - 'filetype' => $verification['finalExt'],
148 - 'allowed' => $wgFileExtensions
149 - ) );
150 - break;
151 - case UploadBase::MIN_LENGTH_PARTNAME:
152 - $this->dieUsage( 'The filename is too short', 'filename-tooshort' );
153 - break;
154 - case UploadBase::ILLEGAL_FILENAME:
155 - $this->dieUsage( 'The filename is not allowed', 'illegal-filename',
156 - 0, array( 'filename' => $verification['filtered'] ) );
157 - break;
158 - case UploadBase::OVERWRITE_EXISTING_FILE:
159 - $this->dieUsage( 'Overwriting an existing file is not allowed', 'overwrite' );
160 - break;
161 - case UploadBase::VERIFICATION_ERROR:
162 - $this->getResult()->setIndexedTagName( $verification['details'], 'detail' );
163 - $this->dieUsage( 'This file did not pass file verification', 'verification-error',
164 - 0, array( 'details' => $verification['details'] ) );
165 - break;
166 - case UploadBase::HOOK_ABORTED:
167 - $this->dieUsage( "The modification you tried to make was aborted by an extension hook",
168 - 'hookaborted', 0, array( 'error' => $verification['error'] ) );
169 - break;
170 - default:
171 - $this->dieUsage( 'An unknown error occurred', 'unknown-error',
172 - 0, array( 'code' => $verification['status'] ) );
173 - break;
174 - }
175 - return $result;
 257+ switch( $verification['status'] ) {
 258+ case UploadBase::EMPTY_FILE:
 259+ $this->dieUsage( 'The file you submitted was empty', 'empty-file' );
 260+ break;
 261+ case UploadBase::FILE_TOO_LARGE:
 262+ $this->dieUsage( 'The file you submitted was too large', 'file-too-large' );
 263+ break;
 264+ case UploadBase::FILETYPE_MISSING:
 265+ $this->dieUsage( 'The file is missing an extension', 'filetype-missing' );
 266+ break;
 267+ case UploadBase::FILETYPE_BADTYPE:
 268+ $this->dieUsage( 'This type of file is banned', 'filetype-banned',
 269+ 0, array(
 270+ 'filetype' => $verification['finalExt'],
 271+ 'allowed' => $wgFileExtensions
 272+ ) );
 273+ break;
 274+ case UploadBase::MIN_LENGTH_PARTNAME:
 275+ $this->dieUsage( 'The filename is too short', 'filename-tooshort' );
 276+ break;
 277+ case UploadBase::ILLEGAL_FILENAME:
 278+ $this->dieUsage( 'The filename is not allowed', 'illegal-filename',
 279+ 0, array( 'filename' => $verification['filtered'] ) );
 280+ break;
 281+ case UploadBase::VERIFICATION_ERROR:
 282+ $this->getResult()->setIndexedTagName( $verification['details'], 'detail' );
 283+ $this->dieUsage( 'This file did not pass file verification', 'verification-error',
 284+ 0, array( 'details' => $verification['details'] ) );
 285+ break;
 286+ case UploadBase::HOOK_ABORTED:
 287+ $this->dieUsage( "The modification you tried to make was aborted by an extension hook",
 288+ 'hookaborted', 0, array( 'error' => $verification['error'] ) );
 289+ break;
 290+ default:
 291+ $this->dieUsage( 'An unknown error occurred', 'unknown-error',
 292+ 0, array( 'code' => $verification['status'] ) );
 293+ break;
176294 }
 295+ }
 296+
 297+
 298+ /**
 299+ * Check warnings if ignorewarnings is not set.
 300+ * Returns a suitable array for inclusion into API results if there were warnings
 301+ * Returns the empty array if there were no warnings
 302+ *
 303+ * @return array
 304+ */
 305+ protected function getApiWarnings() {
 306+ $warnings = array();
 307+
177308 if ( !$this->mParams['ignorewarnings'] ) {
178309 $warnings = $this->mUpload->checkWarnings();
179310 if ( $warnings ) {
@@ -181,52 +312,71 @@
182313
183314 if ( isset( $warnings['duplicate'] ) ) {
184315 $dupes = array();
185 - foreach ( $warnings['duplicate'] as $key => $dupe )
 316+ foreach ( $warnings['duplicate'] as $dupe ) {
186317 $dupes[] = $dupe->getName();
 318+ }
187319 $this->getResult()->setIndexedTagName( $dupes, 'duplicate' );
188320 $warnings['duplicate'] = $dupes;
189321 }
190322
191 -
192323 if ( isset( $warnings['exists'] ) ) {
193324 $warning = $warnings['exists'];
194325 unset( $warnings['exists'] );
195326 $warnings[$warning['warning']] = $warning['file']->getName();
196327 }
 328+ }
 329+ }
197330
198 - $result['result'] = 'Warning';
199 - $result['warnings'] = $warnings;
 331+ return $warnings;
 332+ }
200333
201 - $sessionKey = $this->mUpload->stashSession();
202 - if ( !$sessionKey )
203 - $this->dieUsage( 'Stashing temporary file failed', 'stashfailed' );
 334+ /**
 335+ * Perform the actual upload. Returns a suitable result array on success;
 336+ * dies on failure.
 337+ */
 338+ protected function performUpload() {
 339+ global $wgUser;
204340
205 - $result['sessionkey'] = $sessionKey;
 341+ // Use comment as initial page text by default
 342+ if ( is_null( $this->mParams['text'] ) ) {
 343+ $this->mParams['text'] = $this->mParams['comment'];
 344+ }
206345
207 - return $result;
208 - }
 346+ $file = $this->mUpload->getLocalFile();
 347+ $watch = $this->getWatchlistValue( $this->mParams['watchlist'], $file->getTitle() );
 348+
 349+ // Deprecated parameters
 350+ if ( $this->mParams['watch'] ) {
 351+ $watch = true;
209352 }
210353
211 - // Use comment as initial page text by default
212 - if ( is_null( $this->mParams['text'] ) )
213 - $this->mParams['text'] = $this->mParams['comment'];
214 -
215354 // No errors, no warnings: do the upload
216355 $status = $this->mUpload->performUpload( $this->mParams['comment'],
217 - $this->mParams['text'], $this->mParams['watch'], $wgUser );
 356+ $this->mParams['text'], $watch, $wgUser );
218357
219358 if ( !$status->isGood() ) {
220359 $error = $status->getErrorsArray();
221 - $this->getResult()->setIndexedTagName( $result['details'], 'error' );
222360
223 - $this->dieUsage( 'An internal error occurred', 'internal-error', 0, $error );
 361+ if ( count( $error ) == 1 && $error[0][0] == 'async' ) {
 362+ // The upload can not be performed right now, because the user
 363+ // requested so
 364+ return array(
 365+ 'result' => 'Queued',
 366+ 'statuskey' => $error[0][1],
 367+ );
 368+ } else {
 369+ $this->getResult()->setIndexedTagName( $error, 'error' );
 370+
 371+ $this->dieUsage( 'An internal error occurred', 'internal-error', 0, $error );
 372+ }
224373 }
225374
226375 $file = $this->mUpload->getLocalFile();
 376+
227377 $result['result'] = 'Success';
228378 $result['filename'] = $file->getName();
229 - $result['imageinfo'] = $this->mUpload->getImageInfo( $this->getResult() );
230379
 380+
231381 return $result;
232382 }
233383
@@ -240,36 +390,70 @@
241391
242392 public function getAllowedParams() {
243393 $params = array(
244 - 'filename' => null,
 394+ 'filename' => array(
 395+ ApiBase::PARAM_TYPE => 'string',
 396+ ),
245397 'comment' => array(
246398 ApiBase::PARAM_DFLT => ''
247399 ),
248400 'text' => null,
249401 'token' => null,
250 - 'watch' => false,
 402+ 'watch' => array(
 403+ ApiBase::PARAM_DFLT => false,
 404+ ApiBase::PARAM_DEPRECATED => true,
 405+ ),
 406+ 'watchlist' => array(
 407+ ApiBase::PARAM_DFLT => 'preferences',
 408+ ApiBase::PARAM_TYPE => array(
 409+ 'watch',
 410+ 'preferences',
 411+ 'nochange'
 412+ ),
 413+ ),
251414 'ignorewarnings' => false,
252415 'file' => null,
253416 'url' => null,
254417 'sessionkey' => null,
 418+ 'stash' => false,
255419 );
 420+
 421+ global $wgAllowAsyncCopyUploads;
 422+ if ( $wgAllowAsyncCopyUploads ) {
 423+ $params += array(
 424+ 'asyncdownload' => false,
 425+ 'leavemessage' => false,
 426+ 'statuskey' => null,
 427+ );
 428+ }
256429 return $params;
257 -
258430 }
259431
260432 public function getParamDescription() {
261 - return array(
 433+ $params = array(
262434 'filename' => 'Target filename',
263435 'token' => 'Edit token. You can get one of these through prop=info',
264436 'comment' => 'Upload comment. Also used as the initial page text for new files if "text" is not specified',
265437 'text' => 'Initial page text for new files',
266438 'watch' => 'Watch the page',
 439+ 'watchlist' => 'Unconditionally add or remove the page from your watchlist, use preferences or do not change watch',
267440 'ignorewarnings' => 'Ignore any warnings',
268441 'file' => 'File contents',
269442 'url' => 'Url to fetch the file from',
270 - 'sessionkey' => array(
271 - 'Session key returned by a previous upload that failed due to warnings',
272 - ),
 443+ 'sessionkey' => 'Session key that identifies a previous upload that was stashed temporarily.',
 444+ 'stash' => 'If set, the server will not add the file to the repository and stash it temporarily.'
273445 );
 446+
 447+ global $wgAllowAsyncCopyUploads;
 448+ if ( $wgAllowAsyncCopyUploads ) {
 449+ $params += array(
 450+ 'asyncdownload' => 'Make fetching a URL asynchronous',
 451+ 'leavemessage' => 'If asyncdownload is used, leave a message on the user talk page if finished',
 452+ 'statuskey' => 'Fetch the upload status for this session key',
 453+ );
 454+ }
 455+
 456+ return $params;
 457+
274458 }
275459
276460 public function getDescription() {
@@ -281,17 +465,16 @@
282466 'Note that the HTTP POST must be done as a file upload (i.e. using multipart/form-data) when',
283467 'sending the "file". Note also that queries using session keys must be',
284468 'done in the same login session as the query that originally returned the key (i.e. do not',
285 - 'log out and then log back in). Also you must get and send an edit token before doing any upload stuff.'
 469+ 'log out and then log back in). Also you must get and send an edit token before doing any upload stuff'
286470 );
287471 }
288 -
289 - public function getPossibleErrors() {
 472+
 473+ public function getPossibleErrors() {
290474 return array_merge( parent::getPossibleErrors(), array(
291475 array( 'uploaddisabled' ),
292476 array( 'invalid-session-key' ),
293477 array( 'uploaddisabled' ),
294478 array( 'badaccess-groups' ),
295 - array( 'missingparam', 'filename' ),
296479 array( 'mustbeloggedin', 'upload' ),
297480 array( 'badaccess-groups' ),
298481 array( 'badaccess-groups' ),
@@ -303,9 +486,13 @@
304487 array( 'code' => 'overwrite', 'info' => 'Overwriting an existing file is not allowed' ),
305488 array( 'code' => 'stashfailed', 'info' => 'Stashing temporary file failed' ),
306489 array( 'code' => 'internal-error', 'info' => 'An internal error occurred' ),
307 - ) );
 490+ ) );
308491 }
309 -
 492+
 493+ public function needsToken() {
 494+ return true;
 495+ }
 496+
310497 public function getTokenSalt() {
311498 return '';
312499 }
Property changes on: branches/uploadwizard-deployment/includes/api/ApiUpload.php
___________________________________________________________________
Added: svn:mergeinfo
313500 Merged /branches/uploadwizard/phase3/includes/api/ApiUpload.php:r73550-75905
314501 Merged /branches/REL1_15/phase3/includes/api/ApiUpload.php:r51646
315502 Merged /branches/REL1_16/phase3/includes/api/ApiUpload.php:r63621-63636,69357
316503 Merged /branches/wmf/1.16wmf4/includes/api/ApiUpload.php:r69521
317504 Merged /branches/sqlite/includes/api/ApiUpload.php:r58211-58321
318505 Merged /branches/wmf-deployment/includes/api/ApiUpload.php:r53381,59952
Index: branches/uploadwizard-deployment/includes/AutoLoader.php
@@ -232,10 +232,6 @@
233233 'TitleListDependency' => 'includes/CacheDependency.php',
234234 'TransformParameterError' => 'includes/MediaTransformOutput.php',
235235 'UnlistedSpecialPage' => 'includes/SpecialPage.php',
236 - 'UploadBase' => 'includes/upload/UploadBase.php',
237 - 'UploadFromStash' => 'includes/upload/UploadFromStash.php',
238 - 'UploadFromFile' => 'includes/upload/UploadFromFile.php',
239 - 'UploadFromUrl' => 'includes/upload/UploadFromUrl.php',
240236 'User' => 'includes/User.php',
241237 'UserArray' => 'includes/UserArray.php',
242238 'UserArrayFromResult' => 'includes/UserArray.php',
@@ -567,6 +563,7 @@
568564 'SpecialRecentChanges' => 'includes/specials/SpecialRecentchanges.php',
569565 'SpecialRecentchangeslinked' => 'includes/specials/SpecialRecentchangeslinked.php',
570566 'SpecialSearch' => 'includes/specials/SpecialSearch.php',
 567+ 'SpecialUploadStash' => 'includes/specials/SpecialUploadStash.php',
571568 'SpecialStatistics' => 'includes/specials/SpecialStatistics.php',
572569 'SpecialTags' => 'includes/specials/SpecialTags.php',
573570 'SpecialUpload' => 'includes/specials/SpecialUpload.php',
@@ -598,6 +595,13 @@
599596 'UsercreateTemplate' => 'includes/templates/Userlogin.php',
600597 'UserloginTemplate' => 'includes/templates/Userlogin.php',
601598
 599+ # includes/upload
 600+ 'UploadStash' => 'includes/upload/UploadStash.php',
 601+ 'UploadBase' => 'includes/upload/UploadBase.php',
 602+ 'UploadFromStash' => 'includes/upload/UploadFromStash.php',
 603+ 'UploadFromFile' => 'includes/upload/UploadFromFile.php',
 604+ 'UploadFromUrl' => 'includes/upload/UploadFromUrl.php',
 605+
602606 # languages
603607 'Language' => 'languages/Language.php',
604608 'FakeConverter' => 'languages/Language.php',
Property changes on: branches/uploadwizard-deployment/includes/AutoLoader.php
___________________________________________________________________
Added: svn:mergeinfo
605609 Merged /branches/sqlite/includes/AutoLoader.php:r58211-58321
606610 Merged /trunk/phase3/includes/AutoLoader.php:r63549,63764,63897-63901,64113,64509,65387,65391,65555,65590,65650,65816,71059,71098,71942,72024,72120,72525,75906
607611 Merged /branches/new-installer/phase3/includes/AutoLoader.php:r43664-66004
608612 Merged /branches/wmf-deployment/includes/AutoLoader.php:r53381,60970
609613 Merged /branches/uploadwizard/phase3/includes/AutoLoader.php:r73550-75905
610614 Merged /branches/REL1_15/phase3/includes/AutoLoader.php:r51646
Index: branches/uploadwizard-deployment/includes/specials/SpecialUploadStash.php
@@ -0,0 +1,140 @@
 2+<?php
 3+/**
 4+ * Special:UploadStash
 5+ *
 6+ * Web access for files temporarily stored by UploadStash.
 7+ *
 8+ * For example -- files that were uploaded with the UploadWizard extension are stored temporarily
 9+ * before committing them to the db. But we want to see their thumbnails and get other information
 10+ * about them.
 11+ *
 12+ * Since this is based on the user's session, in effect this creates a private temporary file area.
 13+ * However, the URLs for the files cannot be shared.
 14+ *
 15+ * @file
 16+ * @ingroup SpecialPage
 17+ * @ingroup Upload
 18+ */
 19+
 20+class SpecialUploadStash extends SpecialPage {
 21+
 22+ static $HttpErrors = array( // FIXME: Use OutputPage::getStatusMessage() --RK
 23+ 400 => 'Bad Request',
 24+ 403 => 'Access Denied',
 25+ 404 => 'File not found',
 26+ 500 => 'Internal Server Error',
 27+ );
 28+
 29+ // UploadStash
 30+ private $stash;
 31+
 32+ // we should not be reading in really big files and serving them out
 33+ private $maxServeFileSize = 262144; // 256K
 34+
 35+ // $request is the request (usually wgRequest)
 36+ // $subpage is everything in the URL after Special:UploadStash
 37+ // FIXME: These parameters don't match SpecialPage::__construct()'s params at all, and are unused --RK
 38+ public function __construct( $request = null, $subpage = null ) {
 39+ parent::__construct( 'UploadStash', 'upload' );
 40+ $this->stash = new UploadStash();
 41+ }
 42+
 43+ /**
 44+ * If file available in stash, cats it out to the client as a simple HTTP response.
 45+ * n.b. Most sanity checking done in UploadStashLocalFile, so this is straightforward.
 46+ *
 47+ * @param {String} $subPage: subpage, e.g. in http://example.com/wiki/Special:UploadStash/foo.jpg, the "foo.jpg" part
 48+ * @return {Boolean} success
 49+ */
 50+ public function execute( $subPage ) {
 51+ global $wgOut, $wgUser;
 52+
 53+ if ( !$this->userCanExecute( $wgUser ) ) {
 54+ $this->displayRestrictionError();
 55+ return;
 56+ }
 57+
 58+ // prevent callers from doing standard HTML output -- we'll take it from here
 59+ $wgOut->disable();
 60+
 61+ try {
 62+ $file = $this->getStashFile( $subPage );
 63+ if ( $file->getSize() > $this->maxServeFileSize ) {
 64+ throw new MWException( 'file size too large' );
 65+ }
 66+ $this->outputFile( $file );
 67+ return true;
 68+
 69+ } catch( UploadStashFileNotFoundException $e ) {
 70+ $code = 404;
 71+ } catch( UploadStashBadPathException $e ) {
 72+ $code = 403;
 73+ } catch( Exception $e ) {
 74+ $code = 500;
 75+ }
 76+
 77+ wfHttpError( $code, self::$HttpErrors[$code], $e->getCode(), $e->getMessage() );
 78+ return false;
 79+ }
 80+
 81+
 82+ /**
 83+ * Convert the incoming url portion (subpage of Special page) into a stashed file, if available.
 84+ * @param {String} $subPage
 85+ * @return {File} file object
 86+ * @throws MWException, UploadStashFileNotFoundException, UploadStashBadPathException
 87+ */
 88+ private function getStashFile( $subPage ) {
 89+ // due to an implementation quirk (and trying to be compatible with older method)
 90+ // the stash key doesn't have an extension
 91+ $key = $subPage;
 92+ $n = strrpos( $subPage, '.' );
 93+ if ( $n !== false ) {
 94+ $key = $n ? substr( $subPage, 0, $n ) : $subPage;
 95+ }
 96+
 97+ try {
 98+ $file = $this->stash->getFile( $key );
 99+ } catch ( UploadStashFileNotFoundException $e ) {
 100+ // if we couldn't find it, and it looks like a thumbnail,
 101+ // and it looks like we have the original, go ahead and generate it
 102+ $matches = array();
 103+ if ( ! preg_match( '/^(\d+)px-(.*)$/', $key, $matches ) ) {
 104+ // that doesn't look like a thumbnail. re-raise exception
 105+ throw $e;
 106+ }
 107+
 108+ list( $dummy, $width, $origKey ) = $matches;
 109+
 110+ // do not trap exceptions, if key is in bad format, or file not found,
 111+ // let exceptions propagate to caller.
 112+ $origFile = $this->stash->getFile( $origKey );
 113+
 114+ // ok we're here so the original must exist. Generate the thumbnail.
 115+ // because the file is a UploadStashFile, this thumbnail will also be stashed,
 116+ // and a thumbnailFile will be created in the thumbnailImage composite object
 117+ $thumbnailImage = null;
 118+ if ( !( $thumbnailImage = $origFile->getThumbnail( $width ) ) ) {
 119+ throw new MWException( 'Could not obtain thumbnail' );
 120+ }
 121+ $file = $thumbnailImage->thumbnailFile;
 122+ }
 123+
 124+ return $file;
 125+ }
 126+
 127+ /**
 128+ * Output HTTP response for file
 129+ * Side effects, obviously, of echoing lots of stuff to stdout.
 130+ * @param {File} file
 131+ */
 132+ private function outputFile( $file ) {
 133+ header( 'Content-Type: ' . $file->getMimeType(), true );
 134+ header( 'Content-Transfer-Encoding: binary', true );
 135+ header( 'Expires: Sun, 17-Jan-2038 19:14:07 GMT', true );
 136+ header( 'Pragma: public', true );
 137+ header( 'Content-Length: ' . $file->getSize(), true ); // FIXME: PHP can handle Content-Length for you just fine --RK
 138+ readfile( $file->getPath() );
 139+ }
 140+}
 141+
Property changes on: branches/uploadwizard-deployment/includes/specials/SpecialUploadStash.php
___________________________________________________________________
Added: svn:mergeinfo
1142 Merged /branches/wmf-deployment/includes/specials/SpecialUploadStash.php:r53381,56967
2143 Merged /branches/REL1_15/phase3/includes/specials/SpecialUploadStash.php:r51646
3144 Merged /branches/sqlite/includes/specials/SpecialUploadStash.php:r58211-58321
4145 Merged /trunk/phase3/includes/specials/SpecialUploadStash.php:r73549-75058,75060-75821
Added: svn:eol-style
5146 + native
Index: branches/uploadwizard-deployment/includes/SpecialPage.php
@@ -142,6 +142,7 @@
143143 'MIMEsearch' => array( 'SpecialPage', 'MIMEsearch' ),
144144 'FileDuplicateSearch' => array( 'SpecialPage', 'FileDuplicateSearch' ),
145145 'Upload' => 'SpecialUpload',
 146+ 'UploadStash' => 'SpecialUploadStash',
146147
147148 # Wiki data and tools
148149 'Statistics' => 'SpecialStatistics',
Property changes on: branches/uploadwizard-deployment/includes/SpecialPage.php
___________________________________________________________________
Added: svn:mergeinfo
149150 Merged /branches/new-installer/phase3/includes/SpecialPage.php:r43664-66004
150151 Merged /branches/wmf-deployment/includes/SpecialPage.php:r53381,60970
151152 Merged /branches/uploadwizard/phase3/includes/SpecialPage.php:r73550-75905
152153 Merged /branches/REL1_15/phase3/includes/SpecialPage.php:r51646
153154 Merged /branches/sqlite/includes/SpecialPage.php:r58211-58321
154155 Merged /trunk/phase3/includes/SpecialPage.php:r63549,63764,63897-63901,64113,64509,65387,65391,65555,65590,65650,65816,71059,71098,71942,72024,72120,72525,75906

Follow-up revisions

RevisionCommit summaryAuthorDate
r76016Followup r76014: try to really copy ApiUpload.php from trunk nowcatrope17:04, 4 November 2010

Past revisions this follows-up on

RevisionCommit summaryAuthorDate
r75906core changes for UploadWizard (merged from r73549 to HEAD in branches/uploadw...neilk04:32, 3 November 2010

Comments

#Comment by NeilK (talk | contribs)   02:04, 5 November 2010

The new ApiUploadTest in the phpunit tests may help you determine if anything broke. There aren't a lot of tests, but unlike the others, they work. ;)

I wrote those specifically so this transition would be easier.

#Comment by Catrope (talk | contribs)   12:06, 5 November 2010

...except there's no phpunit framework in 1.16wmf4, and if there somehow is one, it's probably not usable with modern-day tests.

Status & tagging log