Index: branches/uploadwizard-deployment/includes/upload/UploadBase.php |
— | — | @@ -502,6 +502,9 @@ |
503 | 503 | } |
504 | 504 | |
505 | 505 | /** |
| 506 | + * NOTE: Probably should be deprecated in favor of UploadStash, but this is sometimes |
| 507 | + * called outside that context. |
| 508 | + * |
506 | 509 | * Stash a file in a temporary directory for later processing |
507 | 510 | * after the user has confirmed it. |
508 | 511 | * |
— | — | @@ -519,39 +522,36 @@ |
520 | 523 | } |
521 | 524 | |
522 | 525 | /** |
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. |
527 | 528 | * |
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 |
529 | 536 | */ |
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 |
545 | 541 | ); |
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; |
547 | 546 | } |
548 | 547 | |
549 | 548 | /** |
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 |
551 | 553 | */ |
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(); |
556 | 556 | } |
557 | 557 | |
558 | 558 | /** |
— | — | @@ -1098,10 +1098,19 @@ |
1099 | 1099 | return $blacklist; |
1100 | 1100 | } |
1101 | 1101 | |
| 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 | + */ |
1102 | 1112 | public function getImageInfo( $result ) { |
1103 | 1113 | $file = $this->getLocalFile(); |
1104 | 1114 | $imParam = ApiQueryImageInfo::getPropertyNames(); |
1105 | 1115 | return ApiQueryImageInfo::getInfo( $file, array_flip( $imParam ), $result ); |
1106 | 1116 | } |
1107 | | - |
1108 | 1117 | } |
Property changes on: branches/uploadwizard-deployment/includes/upload/UploadBase.php |
___________________________________________________________________ |
Added: svn:mergeinfo |
1109 | 1118 | Merged /branches/sqlite/includes/upload/UploadBase.php:r58211-58321 |
1110 | 1119 | 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 |
1111 | 1120 | Merged /branches/new-installer/phase3/includes/upload/UploadBase.php:r43664-66004 |
1112 | 1121 | Merged /branches/wmf-deployment/includes/upload/UploadBase.php:r53381,60970 |
1113 | 1122 | Merged /branches/uploadwizard/phase3/includes/upload/UploadBase.php:r73550-75905 |
1114 | 1123 | 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 |
1 | 408 | Merged /branches/uploadwizard/extensions/includes/upload/UploadStash.php:r73550-74029 |
2 | 409 | Merged /branches/new-installer/phase3/includes/upload/UploadStash.php:r43664-66004 |
3 | 410 | Merged /branches/REL1_15/phase3/includes/upload/UploadStash.php:r51646 |
4 | 411 | Merged /branches/sqlite/includes/upload/UploadStash.php:r58211-58321 |
5 | 412 | Merged /trunk/phase3/includes/upload/UploadStash.php:r74019-75058,75060-75821 |
Added: svn:eol-style |
6 | 413 | + native |
Index: branches/uploadwizard-deployment/includes/upload/UploadFromFile.php |
— | — | @@ -29,4 +29,14 @@ |
30 | 30 | static function isValidRequest( $request ) { |
31 | 31 | return (bool)$request->getFileTempName( 'wpUploadFile' ); |
32 | 32 | } |
| 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 | + |
33 | 43 | } |
Property changes on: branches/uploadwizard-deployment/includes/upload/UploadFromFile.php |
___________________________________________________________________ |
Added: svn:mergeinfo |
34 | 44 | Merged /branches/new-installer/phase3/includes/upload/UploadFromFile.php:r43664-66004 |
35 | 45 | Merged /branches/wmf-deployment/includes/upload/UploadFromFile.php:r53381,60970 |
36 | 46 | Merged /branches/uploadwizard/phase3/includes/upload/UploadFromFile.php:r73550-75905 |
37 | 47 | Merged /branches/REL1_15/phase3/includes/upload/UploadFromFile.php:r51646 |
38 | 48 | Merged /branches/sqlite/includes/upload/UploadFromFile.php:r58211-58321 |
39 | 49 | 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 @@ |
527 | 527 | * @param array $params An associative array of handler-specific parameters. Typical |
528 | 528 | * keys are width, height and page. |
529 | 529 | * @param integer $flags A bitfield, may contain self::RENDER_NOW to force rendering |
530 | | - * @return MediaTransformOutput |
| 530 | + * @return MediaTransformOutput | false |
531 | 531 | */ |
532 | 532 | function transform( $params, $flags = 0 ) { |
533 | 533 | global $wgUseSquid, $wgIgnoreImageErrors, $wgThumbnailEpoch, $wgServer; |
— | — | @@ -560,7 +560,7 @@ |
561 | 561 | $thumbPath = $this->getThumbPath( $thumbName ); |
562 | 562 | $thumbUrl = $this->getThumbUrl( $thumbName ); |
563 | 563 | |
564 | | - if ( $this->repo->canTransformVia404() && !($flags & self::RENDER_NOW ) ) { |
| 564 | + if ( $this->repo && $this->repo->canTransformVia404() && !($flags & self::RENDER_NOW ) ) { |
565 | 565 | $thumb = $this->handler->getTransform( $this, $thumbPath, $thumbUrl, $params ); |
566 | 566 | break; |
567 | 567 | } |
Property changes on: branches/uploadwizard-deployment/includes/filerepo/File.php |
___________________________________________________________________ |
Added: svn:mergeinfo |
568 | 568 | Merged /branches/REL1_15/phase3/includes/filerepo/File.php:r51646 |
569 | 569 | Merged /branches/sqlite/includes/filerepo/File.php:r58211-58321 |
570 | 570 | Merged /trunk/phase3/includes/filerepo/File.php:r63549,63764,63897-63901,64113,64509,65387,65391,65555,65590,65650,65816,68409,71059,71098,75906 |
571 | 571 | Merged /branches/new-installer/phase3/includes/filerepo/File.php:r43664-66004 |
572 | 572 | Merged /branches/wmf-deployment/includes/filerepo/File.php:r53381,60970 |
573 | 573 | Merged /branches/uploadwizard/phase3/includes/filerepo/File.php:r73550-75905 |
Index: branches/uploadwizard-deployment/includes/api/ApiQueryImageInfo.php |
— | — | @@ -35,8 +35,13 @@ |
36 | 36 | */ |
37 | 37 | class ApiQueryImageInfo extends ApiQueryBase { |
38 | 38 | |
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 ); |
41 | 46 | } |
42 | 47 | |
43 | 48 | public function execute() { |
— | — | @@ -44,16 +49,7 @@ |
45 | 50 | |
46 | 51 | $prop = array_flip( $params['prop'] ); |
47 | 52 | |
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 ); |
58 | 54 | |
59 | 55 | $pageIds = $this->getPageSet()->getAllTitlesByNamespace(); |
60 | 56 | if ( !empty( $pageIds[NS_FILE] ) ) { |
— | — | @@ -175,6 +171,28 @@ |
176 | 172 | } |
177 | 173 | |
178 | 174 | /** |
| 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 | + /** |
179 | 197 | * Get result information for an image revision |
180 | 198 | * @param File f The image |
181 | 199 | * @return array Result array |
— | — | @@ -275,12 +293,12 @@ |
276 | 294 | ApiBase :: PARAM_TYPE => 'timestamp' |
277 | 295 | ), |
278 | 296 | 'urlwidth' => array( |
279 | | - ApiBase :: PARAM_TYPE => 'integer', |
280 | | - ApiBase :: PARAM_DFLT => - 1 |
| 297 | + ApiBase::PARAM_TYPE => 'integer', |
| 298 | + ApiBase::PARAM_DFLT => -1 |
281 | 299 | ), |
282 | 300 | 'urlheight' => array( |
283 | | - ApiBase :: PARAM_TYPE => 'integer', |
284 | | - ApiBase :: PARAM_DFLT => - 1 |
| 301 | + ApiBase::PARAM_TYPE => 'integer', |
| 302 | + ApiBase::PARAM_DFLT => -1 |
285 | 303 | ), |
286 | 304 | 'continue' => null, |
287 | 305 | ); |
— | — | @@ -305,16 +323,38 @@ |
306 | 324 | ); |
307 | 325 | } |
308 | 326 | |
| 327 | + |
| 328 | + /** |
| 329 | + * Return the API documentation for the parameters. |
| 330 | + * @return {Array} parameter documentation. |
| 331 | + */ |
309 | 332 | 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", |
312 | 355 | 'limit' => 'How many image revisions to return', |
313 | 356 | 'start' => 'Timestamp to start listing from', |
314 | 357 | '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' |
319 | 359 | ); |
320 | 360 | } |
321 | 361 | |
Property changes on: branches/uploadwizard-deployment/includes/api/ApiQueryImageInfo.php |
___________________________________________________________________ |
Added: svn:mergeinfo |
322 | 362 | Merged /branches/uploadwizard/phase3/includes/api/ApiQueryImageInfo.php:r73550-75905 |
323 | 363 | Merged /branches/REL1_15/phase3/includes/api/ApiQueryImageInfo.php:r51646 |
324 | 364 | Merged /branches/REL1_16/phase3/includes/api/ApiQueryImageInfo.php:r69357,69932 |
325 | 365 | Merged /branches/sqlite/includes/api/ApiQueryImageInfo.php:r58211-58321 |
326 | 366 | Merged /trunk/phase3/includes/api/ApiQueryImageInfo.php:r63549,63764,63897-63901,64454,66486,69339,69347,69350,69369,69379,69776,69931,70078,71059,71098,75906 |
327 | 367 | Merged /branches/wmf-deployment/includes/api/ApiQueryImageInfo.php:r53381,59952,60970 |
Index: branches/uploadwizard-deployment/includes/api/ApiUpload.php |
— | — | @@ -1,10 +1,11 @@ |
2 | 2 | <?php |
3 | | -/* |
4 | | - * Created on Aug 21, 2008 |
| 3 | +/** |
5 | 4 | * API for MediaWiki 1.8+ |
6 | 5 | * |
7 | | - * Copyright (C) 2008 - 2010 Bryan Tong Minh <Bryan.TongMinh@Gmail.com> |
| 6 | + * Created on Aug 21, 2008 |
8 | 7 | * |
| 8 | + * Copyright © 2008 - 2010 Bryan Tong Minh <Bryan.TongMinh@Gmail.com> |
| 9 | + * |
9 | 10 | * This program is free software; you can redistribute it and/or modify |
10 | 11 | * it under the terms of the GNU General Public License as published by |
11 | 12 | * the Free Software Foundation; either version 2 of the License, or |
— | — | @@ -17,8 +18,10 @@ |
18 | 19 | * |
19 | 20 | * You should have received a copy of the GNU General Public License along |
20 | 21 | * 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. |
22 | 23 | * http://www.gnu.org/copyleft/gpl.html |
| 24 | + * |
| 25 | + * @file |
23 | 26 | */ |
24 | 27 | |
25 | 28 | if ( !defined( 'MEDIAWIKI' ) ) { |
— | — | @@ -38,141 +41,269 @@ |
39 | 42 | } |
40 | 43 | |
41 | 44 | public function execute() { |
42 | | - global $wgUser, $wgAllowCopyUploads; |
| 45 | + global $wgUser; |
43 | 46 | |
44 | 47 | // Check whether upload is enabled |
45 | | - if ( !UploadBase::isEnabled() ) |
| 48 | + if ( !UploadBase::isEnabled() ) { |
46 | 49 | $this->dieUsageMsg( array( 'uploaddisabled' ) ); |
| 50 | + } |
47 | 51 | |
| 52 | + // Parameter handling |
48 | 53 | $this->mParams = $this->extractRequestParams(); |
49 | 54 | $request = $this->getMain()->getRequest(); |
50 | | - |
51 | 55 | // Add the uploaded file to the params array |
52 | 56 | $this->mParams['file'] = $request->getFileName( 'file' ); |
53 | 57 | |
| 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 | + |
54 | 154 | // One and only one of the following parameters is needed |
55 | 155 | $this->requireOnlyOneParameter( $this->mParams, |
56 | | - 'sessionkey', 'file', 'url' ); |
| 156 | + 'sessionkey', 'file', 'url', 'statuskey' ); |
57 | 157 | |
| 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 | + |
58 | 180 | 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 ) ) { |
64 | 184 | $this->dieUsageMsg( array( 'invalid-session-key' ) ); |
| 185 | + } |
65 | 186 | |
66 | 187 | $this->mUpload = new UploadFromStash(); |
67 | 188 | $this->mUpload->initialize( $this->mParams['filename'], |
68 | 189 | $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']] ); |
75 | 191 | |
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' ) ); |
87 | 192 | |
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 | + } |
91 | 204 | |
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' ); |
99 | 210 | } |
| 211 | + |
| 212 | + if ( $this->mParams['leavemessage'] ) { |
| 213 | + $async = 'async-leavemessage'; |
| 214 | + } else { |
| 215 | + $async = 'async'; |
| 216 | + } |
100 | 217 | } |
101 | | - } else $this->dieUsageMsg( array( 'missingparam', 'filename' ) ); |
| 218 | + $this->mUpload = new UploadFromUrl; |
| 219 | + $this->mUpload->initialize( $this->mParams['filename'], |
| 220 | + $this->mParams['url'], $async ); |
102 | 221 | |
103 | | - if ( !isset( $this->mUpload ) ) |
104 | | - $this->dieUsage( 'No upload module set', 'nomodule' ); |
| 222 | + } |
| 223 | + |
| 224 | + return true; |
| 225 | + } |
105 | 226 | |
| 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 ) { |
106 | 233 | // Check whether the user has the appropriate permissions to upload anyway |
107 | | - $permission = $this->mUpload->isAllowed( $wgUser ); |
| 234 | + $permission = $this->mUpload->isAllowed( $user ); |
108 | 235 | |
109 | 236 | if ( $permission !== true ) { |
110 | | - if ( !$wgUser->isLoggedIn() ) |
| 237 | + if ( !$user->isLoggedIn() ) { |
111 | 238 | $this->dieUsageMsg( array( 'mustbeloggedin', 'upload' ) ); |
112 | | - else |
| 239 | + } else { |
113 | 240 | $this->dieUsageMsg( array( 'badaccess-groups' ) ); |
| 241 | + } |
114 | 242 | } |
115 | | - // Perform the upload |
116 | | - $result = $this->performUpload(); |
| 243 | + } |
117 | 244 | |
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; |
120 | 250 | |
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; |
130 | 254 | } |
131 | 255 | |
132 | 256 | // 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; |
176 | 294 | } |
| 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 | + |
177 | 308 | if ( !$this->mParams['ignorewarnings'] ) { |
178 | 309 | $warnings = $this->mUpload->checkWarnings(); |
179 | 310 | if ( $warnings ) { |
— | — | @@ -181,52 +312,71 @@ |
182 | 313 | |
183 | 314 | if ( isset( $warnings['duplicate'] ) ) { |
184 | 315 | $dupes = array(); |
185 | | - foreach ( $warnings['duplicate'] as $key => $dupe ) |
| 316 | + foreach ( $warnings['duplicate'] as $dupe ) { |
186 | 317 | $dupes[] = $dupe->getName(); |
| 318 | + } |
187 | 319 | $this->getResult()->setIndexedTagName( $dupes, 'duplicate' ); |
188 | 320 | $warnings['duplicate'] = $dupes; |
189 | 321 | } |
190 | 322 | |
191 | | - |
192 | 323 | if ( isset( $warnings['exists'] ) ) { |
193 | 324 | $warning = $warnings['exists']; |
194 | 325 | unset( $warnings['exists'] ); |
195 | 326 | $warnings[$warning['warning']] = $warning['file']->getName(); |
196 | 327 | } |
| 328 | + } |
| 329 | + } |
197 | 330 | |
198 | | - $result['result'] = 'Warning'; |
199 | | - $result['warnings'] = $warnings; |
| 331 | + return $warnings; |
| 332 | + } |
200 | 333 | |
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; |
204 | 340 | |
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 | + } |
206 | 345 | |
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; |
209 | 352 | } |
210 | 353 | |
211 | | - // Use comment as initial page text by default |
212 | | - if ( is_null( $this->mParams['text'] ) ) |
213 | | - $this->mParams['text'] = $this->mParams['comment']; |
214 | | - |
215 | 354 | // No errors, no warnings: do the upload |
216 | 355 | $status = $this->mUpload->performUpload( $this->mParams['comment'], |
217 | | - $this->mParams['text'], $this->mParams['watch'], $wgUser ); |
| 356 | + $this->mParams['text'], $watch, $wgUser ); |
218 | 357 | |
219 | 358 | if ( !$status->isGood() ) { |
220 | 359 | $error = $status->getErrorsArray(); |
221 | | - $this->getResult()->setIndexedTagName( $result['details'], 'error' ); |
222 | 360 | |
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 | + } |
224 | 373 | } |
225 | 374 | |
226 | 375 | $file = $this->mUpload->getLocalFile(); |
| 376 | + |
227 | 377 | $result['result'] = 'Success'; |
228 | 378 | $result['filename'] = $file->getName(); |
229 | | - $result['imageinfo'] = $this->mUpload->getImageInfo( $this->getResult() ); |
230 | 379 | |
| 380 | + |
231 | 381 | return $result; |
232 | 382 | } |
233 | 383 | |
— | — | @@ -240,36 +390,70 @@ |
241 | 391 | |
242 | 392 | public function getAllowedParams() { |
243 | 393 | $params = array( |
244 | | - 'filename' => null, |
| 394 | + 'filename' => array( |
| 395 | + ApiBase::PARAM_TYPE => 'string', |
| 396 | + ), |
245 | 397 | 'comment' => array( |
246 | 398 | ApiBase::PARAM_DFLT => '' |
247 | 399 | ), |
248 | 400 | 'text' => null, |
249 | 401 | '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 | + ), |
251 | 414 | 'ignorewarnings' => false, |
252 | 415 | 'file' => null, |
253 | 416 | 'url' => null, |
254 | 417 | 'sessionkey' => null, |
| 418 | + 'stash' => false, |
255 | 419 | ); |
| 420 | + |
| 421 | + global $wgAllowAsyncCopyUploads; |
| 422 | + if ( $wgAllowAsyncCopyUploads ) { |
| 423 | + $params += array( |
| 424 | + 'asyncdownload' => false, |
| 425 | + 'leavemessage' => false, |
| 426 | + 'statuskey' => null, |
| 427 | + ); |
| 428 | + } |
256 | 429 | return $params; |
257 | | - |
258 | 430 | } |
259 | 431 | |
260 | 432 | public function getParamDescription() { |
261 | | - return array( |
| 433 | + $params = array( |
262 | 434 | 'filename' => 'Target filename', |
263 | 435 | 'token' => 'Edit token. You can get one of these through prop=info', |
264 | 436 | 'comment' => 'Upload comment. Also used as the initial page text for new files if "text" is not specified', |
265 | 437 | 'text' => 'Initial page text for new files', |
266 | 438 | 'watch' => 'Watch the page', |
| 439 | + 'watchlist' => 'Unconditionally add or remove the page from your watchlist, use preferences or do not change watch', |
267 | 440 | 'ignorewarnings' => 'Ignore any warnings', |
268 | 441 | 'file' => 'File contents', |
269 | 442 | '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.' |
273 | 445 | ); |
| 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 | + |
274 | 458 | } |
275 | 459 | |
276 | 460 | public function getDescription() { |
— | — | @@ -281,17 +465,16 @@ |
282 | 466 | 'Note that the HTTP POST must be done as a file upload (i.e. using multipart/form-data) when', |
283 | 467 | 'sending the "file". Note also that queries using session keys must be', |
284 | 468 | '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' |
286 | 470 | ); |
287 | 471 | } |
288 | | - |
289 | | - public function getPossibleErrors() { |
| 472 | + |
| 473 | + public function getPossibleErrors() { |
290 | 474 | return array_merge( parent::getPossibleErrors(), array( |
291 | 475 | array( 'uploaddisabled' ), |
292 | 476 | array( 'invalid-session-key' ), |
293 | 477 | array( 'uploaddisabled' ), |
294 | 478 | array( 'badaccess-groups' ), |
295 | | - array( 'missingparam', 'filename' ), |
296 | 479 | array( 'mustbeloggedin', 'upload' ), |
297 | 480 | array( 'badaccess-groups' ), |
298 | 481 | array( 'badaccess-groups' ), |
— | — | @@ -303,9 +486,13 @@ |
304 | 487 | array( 'code' => 'overwrite', 'info' => 'Overwriting an existing file is not allowed' ), |
305 | 488 | array( 'code' => 'stashfailed', 'info' => 'Stashing temporary file failed' ), |
306 | 489 | array( 'code' => 'internal-error', 'info' => 'An internal error occurred' ), |
307 | | - ) ); |
| 490 | + ) ); |
308 | 491 | } |
309 | | - |
| 492 | + |
| 493 | + public function needsToken() { |
| 494 | + return true; |
| 495 | + } |
| 496 | + |
310 | 497 | public function getTokenSalt() { |
311 | 498 | return ''; |
312 | 499 | } |
Property changes on: branches/uploadwizard-deployment/includes/api/ApiUpload.php |
___________________________________________________________________ |
Added: svn:mergeinfo |
313 | 500 | Merged /branches/uploadwizard/phase3/includes/api/ApiUpload.php:r73550-75905 |
314 | 501 | Merged /branches/REL1_15/phase3/includes/api/ApiUpload.php:r51646 |
315 | 502 | Merged /branches/REL1_16/phase3/includes/api/ApiUpload.php:r63621-63636,69357 |
316 | 503 | Merged /branches/wmf/1.16wmf4/includes/api/ApiUpload.php:r69521 |
317 | 504 | Merged /branches/sqlite/includes/api/ApiUpload.php:r58211-58321 |
318 | 505 | Merged /branches/wmf-deployment/includes/api/ApiUpload.php:r53381,59952 |
Index: branches/uploadwizard-deployment/includes/AutoLoader.php |
— | — | @@ -232,10 +232,6 @@ |
233 | 233 | 'TitleListDependency' => 'includes/CacheDependency.php', |
234 | 234 | 'TransformParameterError' => 'includes/MediaTransformOutput.php', |
235 | 235 | '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', |
240 | 236 | 'User' => 'includes/User.php', |
241 | 237 | 'UserArray' => 'includes/UserArray.php', |
242 | 238 | 'UserArrayFromResult' => 'includes/UserArray.php', |
— | — | @@ -567,6 +563,7 @@ |
568 | 564 | 'SpecialRecentChanges' => 'includes/specials/SpecialRecentchanges.php', |
569 | 565 | 'SpecialRecentchangeslinked' => 'includes/specials/SpecialRecentchangeslinked.php', |
570 | 566 | 'SpecialSearch' => 'includes/specials/SpecialSearch.php', |
| 567 | + 'SpecialUploadStash' => 'includes/specials/SpecialUploadStash.php', |
571 | 568 | 'SpecialStatistics' => 'includes/specials/SpecialStatistics.php', |
572 | 569 | 'SpecialTags' => 'includes/specials/SpecialTags.php', |
573 | 570 | 'SpecialUpload' => 'includes/specials/SpecialUpload.php', |
— | — | @@ -598,6 +595,13 @@ |
599 | 596 | 'UsercreateTemplate' => 'includes/templates/Userlogin.php', |
600 | 597 | 'UserloginTemplate' => 'includes/templates/Userlogin.php', |
601 | 598 | |
| 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 | + |
602 | 606 | # languages |
603 | 607 | 'Language' => 'languages/Language.php', |
604 | 608 | 'FakeConverter' => 'languages/Language.php', |
Property changes on: branches/uploadwizard-deployment/includes/AutoLoader.php |
___________________________________________________________________ |
Added: svn:mergeinfo |
605 | 609 | Merged /branches/sqlite/includes/AutoLoader.php:r58211-58321 |
606 | 610 | 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 |
607 | 611 | Merged /branches/new-installer/phase3/includes/AutoLoader.php:r43664-66004 |
608 | 612 | Merged /branches/wmf-deployment/includes/AutoLoader.php:r53381,60970 |
609 | 613 | Merged /branches/uploadwizard/phase3/includes/AutoLoader.php:r73550-75905 |
610 | 614 | 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 |
1 | 142 | Merged /branches/wmf-deployment/includes/specials/SpecialUploadStash.php:r53381,56967 |
2 | 143 | Merged /branches/REL1_15/phase3/includes/specials/SpecialUploadStash.php:r51646 |
3 | 144 | Merged /branches/sqlite/includes/specials/SpecialUploadStash.php:r58211-58321 |
4 | 145 | Merged /trunk/phase3/includes/specials/SpecialUploadStash.php:r73549-75058,75060-75821 |
Added: svn:eol-style |
5 | 146 | + native |
Index: branches/uploadwizard-deployment/includes/SpecialPage.php |
— | — | @@ -142,6 +142,7 @@ |
143 | 143 | 'MIMEsearch' => array( 'SpecialPage', 'MIMEsearch' ), |
144 | 144 | 'FileDuplicateSearch' => array( 'SpecialPage', 'FileDuplicateSearch' ), |
145 | 145 | 'Upload' => 'SpecialUpload', |
| 146 | + 'UploadStash' => 'SpecialUploadStash', |
146 | 147 | |
147 | 148 | # Wiki data and tools |
148 | 149 | 'Statistics' => 'SpecialStatistics', |
Property changes on: branches/uploadwizard-deployment/includes/SpecialPage.php |
___________________________________________________________________ |
Added: svn:mergeinfo |
149 | 150 | Merged /branches/new-installer/phase3/includes/SpecialPage.php:r43664-66004 |
150 | 151 | Merged /branches/wmf-deployment/includes/SpecialPage.php:r53381,60970 |
151 | 152 | Merged /branches/uploadwizard/phase3/includes/SpecialPage.php:r73550-75905 |
152 | 153 | Merged /branches/REL1_15/phase3/includes/SpecialPage.php:r51646 |
153 | 154 | Merged /branches/sqlite/includes/SpecialPage.php:r58211-58321 |
154 | 155 | 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 |