r77463 MediaWiki - Code Review archive

Repository:MediaWiki
Revision:r77462‎ | r77463 | r77464 >
Date:09:52, 30 November 2010
Author:catrope
Status:ok
Tags:
Comment:
1.16wmf4: Merge UploadStash fixes from trunk: r77451, r77453, r77454, r77455, r77461
Modified paths:
  • /branches/wmf/1.16wmf4/includes/specials/SpecialUploadStash.php (modified) (history)
  • /branches/wmf/1.16wmf4/includes/upload/UploadStash.php (modified) (history)

Diff [purge]

Index: branches/wmf/1.16wmf4/includes/upload/UploadStash.php
@@ -12,14 +12,13 @@
1313 *
1414 */
1515 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-]+$/';
2116
 17+ // Format of the key for files -- has to be suitable as a filename itself (e.g. ab12cd34ef.jpg)
 18+ const KEY_FORMAT_REGEX = '/^[\w-]+\.\w+$/';
 19+
2220 // repository that this uses to store temp files
23 - protected $repo;
 21+ // public because we sometimes need to get a LocalFile within the same repo.
 22+ public $repo;
2423
2524 // array of initialized objects obtained from session (lazily initialized upon getFile())
2625 private $files = array();
@@ -80,7 +79,9 @@
8180 unset( $data['mTempPath'] );
8281
8382 $file = new UploadStashFile( $this, $this->repo, $path, $key, $data );
84 -
 83+ if ( $file->getSize === 0 ) {
 84+ throw new UploadStashZeroLengthFileException( "File is zero length" );
 85+ }
8586 $this->files[$key] = $file;
8687
8788 }
@@ -106,18 +107,31 @@
107108 }
108109 $fileProps = File::getPropsFromPath( $path );
109110
 111+ // we will be initializing from some tmpnam files that don't have extensions.
 112+ // most of MediaWiki assumes all uploaded files have good extensions. So, we fix this.
 113+ $extension = self::getExtensionForPath( $path );
 114+ if ( ! preg_match( "/\\.\\Q$extension\\E$/", $path ) ) {
 115+ $pathWithGoodExtension = "$path.$extension";
 116+ if ( ! rename( $path, $pathWithGoodExtension ) ) {
 117+ throw new UploadStashFileException( "couldn't rename $path to have a better extension at $pathWithGoodExtension" );
 118+ }
 119+ $path = $pathWithGoodExtension;
 120+ }
 121+
110122 // If no key was supplied, use content hash. Also has the nice property of collapsing multiple identical files
111123 // uploaded this session, which could happen if uploads had failed.
112124 if ( is_null( $key ) ) {
113 - $key = $fileProps['sha1'];
 125+ $key = $fileProps['sha1'] . "." . $extension;
114126 }
115127
116128 if ( ! preg_match( self::KEY_FORMAT_REGEX, $key ) ) {
117129 throw new UploadStashBadPathException( "key '$key' is not in a proper format" );
118130 }
119131
120 - // if not already in a temporary area, put it there
 132+
 133+ // if not already in a temporary area, put it there
121134 $status = $this->repo->storeTemp( basename( $path ), $path );
 135+
122136 if( ! $status->isOK() ) {
123137 // It is a convention in MediaWiki to only return one error per API exception, even if multiple errors
124138 // are available. We use reset() to pick the "first" thing that was wrong, preferring errors to warnings.
@@ -134,7 +148,7 @@
135149 throw new UploadStashFileException( "error storing file in '$path': " . implode( '; ', $error ) );
136150 }
137151 $stashPath = $status->value;
138 -
 152+
139153 // required info we always store. Must trump any other application info in $data
140154 // 'mTempPath', 'mFileSize', and 'mFileProps' are arbitrary names
141155 // chosen for compatibility with UploadBase's way of doing this.
@@ -147,11 +161,42 @@
148162
149163 // now, merge required info and extra data into the session. (The extra data changes from application to application.
150164 // UploadWizard wants different things than say FirefoggChunkedUpload.)
 165+ wfDebug( __METHOD__ . " storing under $key\n" );
151166 $_SESSION[UploadBase::SESSION_KEYNAME][$key] = array_merge( $data, $requiredData );
152167
153168 return $this->getFile( $key );
154169 }
155170
 171+ /**
 172+ * Find or guess extension -- ensuring that our extension matches our mime type.
 173+ * Since these files are constructed from php tempnames they may not start off
 174+ * with an extension.
 175+ * XXX this is somewhat redundant with the checks that ApiUpload.php does with incoming
 176+ * uploads versus the desired filename. Maybe we can get that passed to us...
 177+ */
 178+ public static function getExtensionForPath( $path ) {
 179+ // Does this have an extension?
 180+ $n = strrpos( $path, '.' );
 181+ $extension = null;
 182+ if ( $n !== false ) {
 183+ $extension = $n ? substr( $path, $n + 1 ) : '';
 184+ } else {
 185+ // If not, assume that it should be related to the mime type of the original file.
 186+ $magic = MimeMagic::singleton();
 187+ $mimeType = $magic->guessMimeType( $path );
 188+ $extensions = explode( ' ', MimeMagic::singleton()->getExtensionsForType( $mimeType ) );
 189+ if ( count( $extensions ) ) {
 190+ $extension = $extensions[0];
 191+ }
 192+ }
 193+
 194+ if ( is_null( $extension ) ) {
 195+ throw new UploadStashFileException( "extension is null" );
 196+ }
 197+
 198+ return File::normalizeExtension( $extension );
 199+ }
 200+
156201 }
157202
158203 class UploadStashFile extends UnregisteredLocalFile {
@@ -195,13 +240,11 @@
196241 throw new UploadStashFileNotFoundException( 'cannot find path, or not a plain file' );
197242 }
198243
 244+
 245+
199246 parent::__construct( false, $repo, $path, false );
200247
201 - // we will be initializing from some tmpnam files that don't have extensions.
202 - // most of MediaWiki assumes all uploaded files have good extensions. So, we fix this.
203248 $this->name = basename( $this->path );
204 - $this->setExtension();
205 -
206249 }
207250
208251 /**
@@ -216,40 +259,6 @@
217260 }
218261
219262 /**
220 - * Find or guess extension -- ensuring that our extension matches our mime type.
221 - * Since these files are constructed from php tempnames they may not start off
222 - * with an extension.
223 - * This does not override getExtension() because things like getMimeType() already call getExtension(),
224 - * and that results in infinite recursion. So, we preemptively *set* the extension so getExtension() can find it.
225 - * For obvious reasons this should be called as early as possible, as part of initialization
226 - */
227 - public function setExtension() {
228 - // Does this have an extension?
229 - $n = strrpos( $this->path, '.' );
230 - $extension = null;
231 - if ( $n !== false ) {
232 - $extension = $n ? substr( $this->path, $n + 1 ) : '';
233 - } else {
234 - // If not, assume that it should be related to the mime type of the original file.
235 - //
236 - // This entire thing is backwards -- we *should* just create an extension based on
237 - // the mime type of the transformed file, *after* transformation. But File.php demands
238 - // to know the name of the transformed file before creating it.
239 - $mimeType = $this->getMimeType();
240 - $extensions = explode( ' ', MimeMagic::singleton()->getExtensionsForType( $mimeType ) );
241 - if ( count( $extensions ) ) {
242 - $extension = $extensions[0];
243 - }
244 - }
245 -
246 - if ( is_null( $extension ) ) {
247 - throw new UploadStashFileException( "extension is null" );
248 - }
249 -
250 - $this->extension = parent::normalizeExtension( $extension );
251 - }
252 -
253 - /**
254263 * Get the path for the thumbnail (actually any transformation of this file)
255264 * The actual argument is the result of thumbName although we seem to have
256265 * buggy code elsewhere that expects a boolean 'suffix'
@@ -272,12 +281,26 @@
273282 * @return {String|null} base name for URL, like '120px-12345.jpg', or null if there is no handler
274283 */
275284 function thumbName( $params ) {
 285+ return $this->getParamThumbName( $this->getUrlName(), $params );
 286+ }
 287+
 288+
 289+ /**
 290+ * Given the name of the original, i.e. Foo.jpg, and scaling parameters, returns filename with appropriate extension
 291+ * This is abstracted from getThumbName because we also use it to calculate the thumbname the file should have on
 292+ * remote image scalers
 293+ *
 294+ * @param String $urlName: A filename, like MyMovie.ogx
 295+ * @param Array $parameters: scaling parameters, like array( 'width' => '120' );
 296+ * @return String|null parameterized thumb name, like 120px-MyMovie.ogx.jpg, or null if no handler found
 297+ */
 298+ function getParamThumbName( $urlName, $params ) {
276299 if ( !$this->getHandler() ) {
277300 return null;
278301 }
279302 $extension = $this->getExtension();
280303 list( $thumbExt, $thumbMime ) = $this->handler->getThumbType( $extension, $this->getMimeType(), $params );
281 - $thumbName = $this->getHandler()->makeParamString( $params ) . '-' . $this->getUrlName();
 304+ $thumbName = $this->getHandler()->makeParamString( $params ) . '-' . $urlName;
282305 if ( $thumbExt != $extension ) {
283306 $thumbName .= ".$thumbExt";
284307 }
@@ -304,7 +327,8 @@
305328 * @return {String} URL to access thumbnail, or URL with partial path
306329 */
307330 public function getThumbUrl( $thumbName = false ) {
308 - return self::getSpecialUrl( $thumbName );
 331+ wfDebug( __METHOD__ . " getting for $thumbName \n" );
 332+ return $this->getSpecialUrl( $thumbName );
309333 }
310334
311335 /**
@@ -314,7 +338,7 @@
315339 */
316340 public function getUrlName() {
317341 if ( ! $this->urlName ) {
318 - $this->urlName = $this->sessionKey . '.' . $this->getExtension();
 342+ $this->urlName = $this->sessionKey;
319343 }
320344 return $this->urlName;
321345 }
@@ -326,7 +350,7 @@
327351 */
328352 public function getUrl() {
329353 if ( !isset( $this->url ) ) {
330 - $this->url = self::getSpecialUrl( $this->getUrlName() );
 354+ $this->url = $this->getSpecialUrl( $this->getUrlName() );
331355 }
332356 return $this->url;
333357 }
@@ -351,43 +375,6 @@
352376 }
353377
354378 /**
355 - * Typically, transform() returns a ThumbnailImage, which you can think of as being the exact
356 - * equivalent of an HTML thumbnail on Wikipedia. So its URL is the full-size file, not the thumbnail's URL.
357 - *
358 - * Here we override transform() to stash the thumbnail file, and then
359 - * provide a way to get at the stashed thumbnail file to extract properties such as its URL
360 - *
361 - * @param {Array} $params: parameters suitable for File::transform()
362 - * @param {Bitmask} $flags: flags suitable for File::transform()
363 - * @return {ThumbnailImage} with additional File thumbnailFile property
364 - */
365 - public function transform( $params, $flags = 0 ) {
366 -
367 - // force it to get a thumbnail right away
368 - $flags |= self::RENDER_NOW;
369 -
370 - // returns a ThumbnailImage object containing the url and path. Note. NOT A FILE OBJECT.
371 - $thumb = parent::transform( $params, $flags );
372 - wfDebug( "UploadStash: generating thumbnail\n" );
373 - wfDebug( print_r( $thumb, 1 ) );
374 - $key = $this->thumbName($params);
375 -
376 - // remove extension, so it's stored in the session under '120px-123456'
377 - // this makes it uniform with the other session key for the original, '123456'
378 - $n = strrpos( $key, '.' );
379 - if ( $n !== false ) {
380 - $key = substr( $key, 0, $n );
381 - }
382 -
383 - // stash the thumbnail File, and provide our caller with a way to get at its properties
384 - $stashedThumbFile = $this->sessionStash->stashFile( $thumb->getPath(), array(), $key );
385 - $thumb->thumbnailFile = $stashedThumbFile;
386 -
387 - return $thumb;
388 -
389 - }
390 -
391 - /**
392379 * Remove the associated temporary file
393380 * @return {Status} success
394381 */
@@ -402,4 +389,5 @@
403390 class UploadStashBadPathException extends MWException {};
404391 class UploadStashBadVersionException extends MWException {};
405392 class UploadStashFileException extends MWException {};
 393+class UploadStashZeroLengthFileException extends MWException {};
406394
Property changes on: branches/wmf/1.16wmf4/includes/upload/UploadStash.php
___________________________________________________________________
Modified: svn:mergeinfo
407395 Merged /trunk/phase3/includes/upload/UploadStash.php:r77391,77451,77453
Index: branches/wmf/1.16wmf4/includes/specials/SpecialUploadStash.php
@@ -57,9 +57,6 @@
5858 // prevent callers from doing standard HTML output -- we'll take it from here
5959 $wgOut->disable();
6060
61 - $code = 500;
62 - $message = 'Unknown error';
63 -
6461 if ( !isset( $subPage ) || $subPage === '' ) {
6562 // the user probably visited the page just to see what would happen, so explain it a bit.
6663 $code = '400';
@@ -69,24 +66,24 @@
7067 . 'use the URL of this page, with a slash and the key of the stashed file appended.';
7168 } else {
7269 try {
73 - $file = $this->getStashFile( $subPage );
74 - $size = $file->getSize();
75 - if ( $size === 0 ) {
76 - $code = 500;
77 - $message = 'File is zero length';
78 - } else if ( $size > self::MAX_SERVE_BYTES ) {
79 - $code = 500;
80 - $message = 'Cannot serve a file larger than ' . self::MAX_SERVE_BYTES . ' bytes';
 70+ if ( preg_match( '/^(\d+)px-(.*)$/', $subPage, $matches ) ) {
 71+ list( /* full match */, $width, $key ) = $matches;
 72+ return $this->outputThumbFromStash( $key, $width );
8173 } else {
82 - $this->outputFile( $file );
83 - return true;
 74+ return $this->outputFileFromStash( $subPage );
8475 }
8576 } catch( UploadStashFileNotFoundException $e ) {
8677 $code = 404;
8778 $message = $e->getMessage();
 79+ } catch( UploadStashZeroLengthFileException $e ) {
 80+ $code = 500;
 81+ $message = $e->getMessage();
8882 } catch( UploadStashBadPathException $e ) {
8983 $code = 500;
9084 $message = $e->getMessage();
 85+ } catch( SpecialUploadStashTooLargeException $e ) {
 86+ $code = 500;
 87+ $message = 'Cannot serve a file larger than ' . self::MAX_SERVE_BYTES . ' bytes. ' . $e->getMessage();
9188 } catch( Exception $e ) {
9289 $code = 500;
9390 $message = $e->getMessage();
@@ -96,63 +93,170 @@
9794 wfHttpError( $code, OutputPage::getStatusMessage( $code ), $message );
9895 return false;
9996 }
 97+
 98+ /**
 99+ * Get a file from stash and stream it out. Rely on parent to catch exceptions and transform them into HTTP
 100+ * @param String: $key - key of this file in the stash, which probably looks like a filename with extension.
 101+ * @throws ....?
 102+ * @return boolean
 103+ */
 104+ private function outputFileFromStash( $key ) {
 105+ $file = $this->stash->getFile( $key );
 106+ $this->outputLocalFile( $file );
 107+ return true;
 108+ }
100109
101110
102 - /**
103 - * Convert the incoming url portion (subpage of Special page) into a stashed file, if available.
104 - * @param {String} $subPage
105 - * @return {File} file object
106 - * @throws MWException, UploadStashFileNotFoundException, UploadStashBadPathException
 111+ /**
 112+ * Get a thumbnail for file, either generated locally or remotely, and stream it out
 113+ * @param String $key: key for the file in the stash
 114+ * @param int $width: width of desired thumbnail
 115+ * @return ??
 116+ */
 117+ private function outputThumbFromStash( $key, $width ) {
 118+
 119+ // this global, if it exists, points to a "scaler", as you might find in the Wikimedia Foundation cluster. See outputRemoteScaledThumb()
 120+ global $wgUploadStashScalerBaseUrl;
 121+
 122+ // let exceptions propagate to caller.
 123+ $file = $this->stash->getFile( $key );
 124+
 125+ // OK, we're here and no exception was thrown,
 126+ // so the original file must exist.
 127+
 128+ // let's get ready to transform the original -- these are standard
 129+ $params = array( 'width' => $width );
 130+ $flags = 0;
 131+
 132+ return $wgUploadStashScalerBaseUrl ? $this->outputRemoteScaledThumb( $file, $params, $flags )
 133+ : $this->outputLocallyScaledThumb( $file, $params, $flags );
 134+
 135+ }
 136+
 137+
 138+ /**
 139+ * Scale a file (probably with a locally installed imagemagick, or similar) and output it to STDOUT.
 140+ * @param $file: File object
 141+ * @param $params: scaling parameters ( e.g. array( width => '50' ) );
 142+ * @param $flags: scaling flags ( see File:: constants )
 143+ * @throws MWException
 144+ * @return boolean success
107145 */
108 - private function getStashFile( $subPage ) {
109 - // due to an implementation quirk (and trying to be compatible with older method)
110 - // the stash key doesn't have an extension
111 - $key = $subPage;
112 - $n = strrpos( $subPage, '.' );
113 - if ( $n !== false ) {
114 - $key = $n ? substr( $subPage, 0, $n ) : $subPage;
115 - }
 146+ private function outputLocallyScaledThumb( $file, $params, $flags ) {
116147
117 - try {
118 - $file = $this->stash->getFile( $key );
119 - } catch ( UploadStashFileNotFoundException $e ) {
120 - // if we couldn't find it, and it looks like a thumbnail,
121 - // and it looks like we have the original, go ahead and generate it
122 - $matches = array();
123 - if ( ! preg_match( '/^(\d+)px-(.*)$/', $key, $matches ) ) {
124 - // that doesn't look like a thumbnail. re-raise exception
125 - throw $e;
126 - }
 148+ // n.b. this is stupid, we insist on re-transforming the file every time we are invoked. We rely
 149+ // on HTTP caching to ensure this doesn't happen.
 150+
 151+ $flags |= File::RENDER_NOW;
127152
128 - list( $dummy, $width, $origKey ) = $matches;
 153+ $thumbnailImage = $file->transform( $params, $flags );
 154+ if ( !$thumbnailImage ) {
 155+ throw new MWException( 'Could not obtain thumbnail' );
 156+ }
129157
130 - // do not trap exceptions, if key is in bad format, or file not found,
131 - // let exceptions propagate to caller.
132 - $origFile = $this->stash->getFile( $origKey );
 158+ // we should have just generated it locally
 159+ if ( ! $thumbnailImage->getPath() ) {
 160+ throw new UploadStashFileNotFoundException( "no local path for scaled item" );
 161+ }
133162
134 - // ok we're here so the original must exist. Generate the thumbnail.
135 - // because the file is a UploadStashFile, this thumbnail will also be stashed,
136 - // and a thumbnailFile will be created in the thumbnailImage composite object
137 - $thumbnailImage = $origFile->transform( array( 'width' => $width ) );
138 - if ( !$thumbnailImage ) {
139 - throw new MWException( 'Could not obtain thumbnail' );
140 - }
141 - $file = $thumbnailImage->thumbnailFile;
 163+ // now we should construct a File, so we can get mime and other such info in a standard way
 164+ // n.b. mimetype may be different from original (ogx original -> jpeg thumb)
 165+ $thumbFile = new UnregisteredLocalFile( false, $this->stash->repo, $thumbnailImage->getPath(), false );
 166+ if ( ! $thumbFile ) {
 167+ throw new UploadStashFileNotFoundException( "couldn't create local file object for thumbnail" );
142168 }
143169
144 - return $file;
 170+ return $this->outputLocalFile( $thumbFile );
 171+
145172 }
 173+
 174+ /**
 175+ * Scale a file with a remote "scaler", as exists on the Wikimedia Foundation cluster, and output it to STDOUT.
 176+ * Note: unlike the usual thumbnail process, the web client never sees the cluster URL; we do the whole HTTP transaction to the scaler ourselves
 177+ * and cat the results out.
 178+ * Note: We rely on NFS to have propagated the file contents to the scaler. However, we do not rely on the thumbnail being created in NFS and then
 179+ * propagated back to our filesystem. Instead we take the results of the HTTP request instead.
 180+ * Note: no caching is being done here, although we are instructing the client to cache it forever.
 181+ * @param $file: File object
 182+ * @param $params: scaling parameters ( e.g. array( width => '50' ) );
 183+ * @param $flags: scaling flags ( see File:: constants )
 184+ * @throws MWException
 185+ * @return boolean success
 186+ */
 187+ private function outputRemoteScaledThumb( $file, $params, $flags ) {
 188+
 189+ // this global probably looks something like 'http://upload.wikimedia.org/wikipedia/test/thumb/temp'
 190+ // do not use trailing slash
 191+ global $wgUploadStashScalerBaseUrl;
146192
 193+ $scalerThumbName = $file->getParamThumbName( $file->name, $params );
 194+ $scalerThumbUrl = $wgUploadStashScalerBaseUrl . '/' . $file->getRel() . '/' . $scalerThumbName;
 195+
 196+ // make a curl call to the scaler to create a thumbnail
 197+ $httpOptions = array(
 198+ 'method' => 'GET',
 199+ 'timeout' => 'default'
 200+ );
 201+ $req = MWHttpRequest::factory( $scalerThumbUrl, $httpOptions );
 202+ $status = $req->execute();
 203+ if ( ! $status->isOK() ) {
 204+ $errors = $status->getErrorsArray();
 205+ throw new MWException( "Fetching thumbnail failed: " . join( ", ", $errors ) );
 206+ }
 207+ $contentType = $req->getResponseHeader( "content-type" );
 208+ if ( ! $contentType ) {
 209+ throw new MWException( "Missing content-type header" );
 210+ }
 211+ return $this->outputContents( $req->getContent(), $contentType );
 212+ }
 213+
147214 /**
148215 * Output HTTP response for file
149 - * Side effects, obviously, of echoing lots of stuff to stdout.
150 - * @param {File} file
 216+ * Side effect: writes HTTP response to STDOUT.
 217+ * XXX could use wfStreamfile (in includes/Streamfile.php), but for consistency with outputContents() doing it this way.
 218+ * XXX is mimeType really enough, or do we need encoding for full Content-Type header?
 219+ *
 220+ * @param $file File object with a local path (e.g. UnregisteredLocalFile, LocalFile. Oddly these don't share an ancestor!)
151221 */
152 - private function outputFile( $file ) {
153 - header( 'Content-Type: ' . $file->getMimeType(), true );
 222+ private function outputLocalFile( $file ) {
 223+ if ( $file->getSize() > self::MAX_SERVE_BYTES ) {
 224+ throw new SpecialUploadStashTooLargeException();
 225+ }
 226+ self::outputHeaders( $file->getMimeType(), $file->getSize() );
 227+ readfile( $file->getPath() );
 228+ return true;
 229+ }
 230+
 231+ /**
 232+ * Output HTTP response of raw content
 233+ * Side effect: writes HTTP response to STDOUT.
 234+ * @param String $content: content
 235+ * @param String $mimeType: mime type
 236+ */
 237+ private function outputContents( $content, $contentType ) {
 238+ $size = strlen( $content );
 239+ if ( $size > self::MAX_SERVE_BYTES ) {
 240+ throw new SpecialUploadStashTooLargeException();
 241+ }
 242+ self::outputHeaders( $contentType, $size );
 243+ print $content;
 244+ return true;
 245+ }
 246+
 247+ /**
 248+ * Output headers for streaming
 249+ * XXX unsure about encoding as binary; if we received from HTTP perhaps we should use that encoding, concatted with semicolon to mimeType as it usually is.
 250+ * Side effect: preps PHP to write headers to STDOUT.
 251+ * @param String $contentType : string suitable for content-type header
 252+ * @param String $size: length in bytes
 253+ */
 254+ private static function outputHeaders( $contentType, $size ) {
 255+ header( "Content-Type: $contentType", true );
154256 header( 'Content-Transfer-Encoding: binary', true );
155257 header( 'Expires: Sun, 17-Jan-2038 19:14:07 GMT', true );
156 - header( 'Content-Length: ' . $file->getSize(), true );
157 - readfile( $file->getPath() );
 258+ header( "Content-Length: $size", true );
158259 }
 260+
159261 }
 262+
 263+class SpecialUploadStashTooLargeException extends MWException {};
Property changes on: branches/wmf/1.16wmf4/includes/specials/SpecialUploadStash.php
___________________________________________________________________
Modified: svn:mergeinfo
160264 Merged /trunk/phase3/includes/specials/SpecialUploadStash.php:r77239-77240,77393,77451,77454-77455,77461

Follow-up revisions

RevisionCommit summaryAuthorDate
r77464Fix r77463: MWHttpRequest doesn't exist in 1.16wmf4catrope09:58, 30 November 2010

Past revisions this follows-up on

RevisionCommit summaryAuthorDate
r77451Dual strategy thumbnailing -- locally for development and simpler wikis, or i...neilk02:45, 30 November 2010
r77453removed debug statementneilk04:40, 30 November 2010
r77454better error checking, fixed a few bugs with remote content scalingneilk04:40, 30 November 2010
r77455matching returnsneilk04:47, 30 November 2010
r77461forgot to change the function signature after refactor hereneilk05:43, 30 November 2010

Status & tagging log