r74536 MediaWiki - Code Review archive

Repository:MediaWiki
Revision:r74535‎ | r74536 | r74537 >
Date:04:20, 9 October 2010
Author:neilk
Status:ok (Comments)
Tags:
Comment:
Refactored so we do not need a special upload module for stashing. Instead, stashing is handled right in main logic.
Much simplified.
For simplicity, made SessionStash the default way of stashing. This is going to require good tests before we can merge with trunk so we can
prove uploads work as they always have.
(although haven't converted UploadFromStash yet as it is compatible with SessionStash anyway)
Modified paths:
  • /branches/uploadwizard/phase3/includes/api/ApiQueryImageInfo.php (modified) (history)
  • /branches/uploadwizard/phase3/includes/api/ApiUpload.php (modified) (history)
  • /branches/uploadwizard/phase3/includes/upload/SessionStash.php (modified) (history)
  • /branches/uploadwizard/phase3/includes/upload/UploadBase.php (modified) (history)
  • /branches/uploadwizard/phase3/includes/upload/UploadFromFileToStash.php (deleted) (history)

Diff [purge]

Index: branches/uploadwizard/phase3/includes/upload/UploadFromFileToStash.php
@@ -1,49 +0,0 @@
2 -<?php
3 -/**
4 - * Implements regular file uploads, except:
5 - * - stores in local temp area, adds record to session (SessionStash)
6 - * - returns lots of mime info and metadata
7 - * - creates thumbnail
8 - * - returns URLs for file and thumbnail accessible only to uploading user
9 - *
10 - * @file
11 - * @ingroup upload
12 - * @author Neil Kandalgaonkar
13 - */
14 -
15 -class UploadFromFileToStash extends UploadFromFile {
16 -
17 - /**
18 - * Overrides performUpload, which normally adds the file to the database and makes it publicly available.
19 - * Instead, we store it in the SessionStash, and return metadata about the file
20 - * We also create a thumbnail, which is visible only to the uploading user.
21 - *
22 - * @param {String} $comment: optional -- should not be used, here for parent class compatibility
23 - * @param {String} $pageText: optional -- should not be used, here only for parent class compatibility
24 - * @param {Boolean} $watch: optional -- whether to watchlist this item, should be unused, only here for parent class compatibility
25 - * @param {User} $user: optional -- current user, should not be used, only here for parent class compatibility
26 - * @return {Status} $status
27 - */
28 - public function performUpload( $comment, $pageText, $watch, $user ) {
29 - $status = new Status();
30 - try {
31 - $stash = new SessionStash();
32 - $data = array(
33 - 'comment' => $comment,
34 - 'pageText' => $pageText,
35 - 'watch' => $watch,
36 - 'mFileProps' => $this->mFileProps
37 - );
38 - // we now change the value of the local file
39 - $this->mLocalFile = $stash->stashFile( $this->mTempPath, $data );
40 - $status->setResult( true, $this->mLocalFile );
41 -
42 - } catch ( Exception $e ) {
43 - $status->setResult( false );
44 - $status->error( $e->getMessage );
45 - }
46 -
47 - return $status;
48 - }
49 -
50 -};
Index: branches/uploadwizard/phase3/includes/upload/UploadBase.php
@@ -600,6 +600,9 @@
601601 }
602602
603603 /**
 604+ * NOTE: Probably should be deprecated in favor of SessionStash, but this is sometimes
 605+ * called outside that context.
 606+ *
604607 * Stash a file in a temporary directory for later processing
605608 * after the user has confirmed it.
606609 *
@@ -617,40 +620,36 @@
618621 }
619622
620623 /**
621 - * Stash a file in a temporary directory for later processing,
622 - * and save the necessary descriptive info into the session.
623 - * Returns a key value which will be passed through a form
624 - * to pick up the path info on a later invocation.
 624+ * If the user does not supply all necessary information in the first upload form submission (either by accident or
 625+ * by design) then we may want to stash the file temporarily, get more information, and publish the file later.
625626 *
626 - * @return Integer: session key
 627+ * This method will stash a file in a temporary directory for later processing, and save the necessary descriptive info
 628+ * into the user's session.
 629+ * This method returns the file object, which also has a 'sessionKey' property which can be passed through a form or
 630+ * API request to find this stashed file again.
 631+ *
 632+ * @param {String}: $key (optional) the session key used to find the file info again. If not supplied, a key will be autogenerated.
 633+ * @return {File}: stashed file
627634 */
628 - public function stashSession( $key = null ) {
629 - $status = $this->saveTempUploadedFile( $this->mDestName, $this->mTempPath );
630 - if( !$status->isOK() ) {
631 - # Couldn't save the file.
632 - return false;
633 - }
634 -
635 - if ( is_null( $key ) ) {
636 - $key = $this->getSessionKey();
637 - }
638 - $_SESSION[self::SESSION_KEYNAME][$key] = array(
639 - 'mTempPath' => $status->value,
640 - 'mFileSize' => $this->mFileSize,
641 - 'mFileProps' => $this->mFileProps,
642 - 'version' => self::SESSION_VERSION,
 635+ public function stashSessionFile( $key = null ) {
 636+ $stash = new SessionStash();
 637+ $data = array(
 638+ 'mFileProps' => $this->mFileProps
643639 );
644 - return $key;
 640+ $file = $stash->stashFile( $this->mTempPath, $data, $key );
 641+ // TODO should we change the "local file" here?
 642+ // $this->mLocalFile = $file;
 643+ return $file;
645644 }
646645
647646 /**
648 - * Generate a random session key from stash in cases where we want
649 - * to start an upload without much information
 647+ * Stash a file in a temporary directory, returning a key which can be used to find the file again. See stashSessionFile().
 648+ *
 649+ * @param {String}: $key (optional) the session key used to find the file info again. If not supplied, a key will be autogenerated.
 650+ * @return {String}: session key
650651 */
651 - protected function getSessionKey() {
652 - $key = mt_rand( 0, 0x7fffffff );
653 - $_SESSION[self::SESSION_KEYNAME][$key] = array();
654 - return $key;
 652+ public function stashSession( $key = null ) {
 653+ return $this->stashSessionFile( $key )->getSessionKey();
655654 }
656655
657656 /**
Index: branches/uploadwizard/phase3/includes/upload/SessionStash.php
@@ -38,12 +38,12 @@
3939
4040 // sanity check repo. If we want to mock the repo later this should be modified.
4141 if ( ! is_dir( $repo->getZonePath( 'temp' ) ) ) {
42 - throw new MWException( 'invalid repo or cannot read repo temp dir' );
 42+ throw new SessionStashNotAvailableException();
4343 }
4444 $this->repo = $repo;
4545
4646 if ( ! isset( $_SESSION ) ) {
47 - throw new MWException( 'session not available' );
 47+ throw new SessionStashNotAvailableException();
4848 }
4949
5050 if ( !isset( $_SESSION[UploadBase::SESSION_KEYNAME] ) ) {
@@ -66,7 +66,7 @@
6767 * May throw exception if session data cannot be parsed due to schema change, or key not found.
6868 * @param {Integer} $key: key
6969 * @throws SessionStashFileNotFoundException
70 - * @throws MWException
 70+ * @throws SessionStashBadVersionException
7171 * @return {SessionStashItem} null if no such item or item out of date, or the item
7272 */
7373 public function getFile( $key ) {
@@ -78,7 +78,7 @@
7979 $data = $_SESSION[UploadBase::SESSION_KEYNAME][$key];
8080 // guards against PHP class changing while session data doesn't
8181 if ($data['version'] !== UploadBase::SESSION_VERSION ) {
82 - throw new MWException( 'outdated session version' );
 82+ throw new SessionStashBadVersionException();
8383 }
8484
8585 // separate the stashData into the path, and then the rest of the data
@@ -137,6 +137,7 @@
138138
139139 return $this->getFile( $key );
140140 }
 141+
141142 }
142143
143144 class SessionStashFile extends UnregisteredLocalFile {
@@ -160,7 +161,7 @@
161162 $this->sessionStash = $stash;
162163 $this->sessionKey = $key;
163164 $this->sessionData = $data;
164 -
 165+
165166 // resolve mwrepo:// urls
166167 if ( $repo->isVirtualUrl( $path ) ) {
167168 $path = $repo->resolveVirtualUrl( $path );
@@ -223,7 +224,7 @@
224225 }
225226
226227 if ( is_null( $extension ) ) {
227 - throw new MWException( 'cannot determine extension' );
 228+ throw new SessionStashFileException();
228229 }
229230
230231 $this->extension = parent::normalizeExtension( $extension );
@@ -317,6 +318,14 @@
318319
319320
320321 /**
 322+ * Getter for session key (the session-unique id by which this file's location & metadata is stored in the session)
 323+ * @return {String} session key
 324+ */
 325+ public function getSessionKey() {
 326+ return $this->sessionKey;
 327+ }
 328+
 329+ /**
321330 * Typically, transform() returns a ThumbnailImage, which you can think of as being the exact
322331 * equivalent of an HTML thumbnail on Wikipedia. So its URL is the full-size file, not the thumbnail's URL.
323332 *
@@ -351,8 +360,19 @@
352361
353362 }
354363
 364+ /**
 365+ * Remove the associated temporary file
 366+ * @return {Status} success
 367+ */
 368+ public function remove() {
 369+ return $this->repo->freeTemp( $this->path );
 370+ }
 371+
355372 }
356373
 374+class SessionStashNotAvailableException extends MWException {};
357375 class SessionStashFileNotFoundException extends MWException {};
358376 class SessionStashBadPathException extends MWException {};
 377+class SessionStashBadVersionException extends MWException {};
 378+class SessionStashFileException extends MWException {};
359379
Index: branches/uploadwizard/phase3/includes/api/ApiQueryImageInfo.php
@@ -36,8 +36,8 @@
3737 */
3838 class ApiQueryImageInfo extends ApiQueryBase {
3939
40 - public function __construct( $query, $moduleName ) {
41 - parent::__construct( $query, $moduleName, 'ii' );
 40+ public function __construct( $query, $moduleName, $prefix = 'ii' ) {
 41+ parent::__construct( $query, $moduleName, $prefix );
4242 }
4343
4444 public function execute() {
@@ -45,18 +45,8 @@
4646
4747 $prop = array_flip( $params['prop'] );
4848
49 - if ( $params['urlheight'] != - 1 && $params['urlwidth'] == - 1 ) {
50 - $this->dieUsage( 'iiurlheight cannot be used without iiurlwidth', 'iiurlwidth' );
51 - }
 49+ $scale = $this->getScale( $params );
5250
53 - if ( $params['urlwidth'] != - 1 ) {
54 - $scale = array();
55 - $scale['width'] = $params['urlwidth'];
56 - $scale['height'] = $params['urlheight'];
57 - } else {
58 - $scale = null;
59 - }
60 -
6151 $pageIds = $this->getPageSet()->getAllTitlesByNamespace();
6252 if ( !empty( $pageIds[NS_FILE] ) ) {
6353 $titles = array_keys( $pageIds[NS_FILE] );
@@ -184,6 +174,27 @@
185175 }
186176
187177 /**
 178+ * From parameters, construct a 'scale' array
 179+ * @param {Array} $params
 180+ * @return {null|Array} key-val array of 'width' and 'height', or null
 181+ */
 182+ public function getScale( $params ) {
 183+ if ( $params['urlheight'] != - 1 && $params['urlwidth'] == - 1 ) {
 184+ $this->dieUsage( 'iiurlheight cannot be used without iiurlwidth', 'iiurlwidth' );
 185+ }
 186+
 187+ if ( $params['urlwidth'] != - 1 ) {
 188+ $scale = array();
 189+ $scale['width'] = $params['urlwidth'];
 190+ $scale['height'] = $params['urlheight'];
 191+ } else {
 192+ $scale = null;
 193+ }
 194+ return $scale;
 195+ }
 196+
 197+
 198+ /**
188199 * Get result information for an image revision
189200 *
190201 * @param $file File object
@@ -349,7 +360,7 @@
350361 );
351362 }
352363
353 - public function getParamDescription() {
 364+ public function getBaseParamDescription() {
354365 $p = $this->getModulePrefix();
355366 return array(
356367 'prop' => array(
@@ -367,17 +378,22 @@
368379 ' metadata - Lists EXIF metadata for the version of the image',
369380 ' archivename - Adds the file name of the archive version for non-latest versions',
370381 ' bitdepth - Adds the bit depth of the version',
371 - ),
372 - 'limit' => 'How many image revisions to return',
373 - 'start' => 'Timestamp to start listing from',
374 - 'end' => 'Timestamp to stop listing at',
 382+ ),
375383 'urlwidth' => array( "If {$p}prop=url is set, a URL to an image scaled to this width will be returned.",
376384 'Only the current version of the image can be scaled' ),
377385 'urlheight' => "Similar to {$p}urlwidth. Cannot be used without {$p}urlwidth",
378386 'continue' => 'When more results are available, use this to continue',
379387 );
 388+
380389 }
381390
 391+ public function getParamDescription() {
 392+ $description = $this->getBaseParamDescription();
 393+ $description['limit'] = 'How many image revisions to return';
 394+ $description['start'] = 'Timestamp to start listing from';
 395+ $description['end'] = 'Timestamp to stop listing at';
 396+ }
 397+
382398 public function getDescription() {
383399 return 'Returns image information and upload history';
384400 }
Index: branches/uploadwizard/phase3/includes/api/ApiUpload.php
@@ -84,28 +84,44 @@
8585 $this->dieUsageMsg( array( 'badaccess-groups' ) );
8686 }
8787
88 - // Check warnings if necessary
 88+ // Check for warnings, api-formatted.
8989 $warnings = $this->getApiWarnings();
9090
91 - if ( $warnings ) {
92 - $sessionKey = $this->mUpload->stashSession();
93 - if ( !$sessionKey ) {
94 - // FIXME: This should not be fatal: the user should be able to see the warnings --RK
95 - $this->dieUsage( 'Stashing temporary file failed', 'stashfailed' );
96 - }
 91+ // Prepare the API result
 92+ $result = array();
9793
98 - $result['sessionkey'] = $sessionKey;
99 -
100 - // is it really necessary to say 'result=warning'
101 - // FIXME: $warnings is already of the form array( 'result' => 'Warning', 'warnings' => ... ) --RK
 94+ if ( $warnings ) {
10295 $result['result'] = 'Warning';
 96+ // in case the warnings can be fixed with some further user action, let's stash this upload
 97+ // and return a key they can use to restart it
 98+ try {
 99+ $result['sessionkey'] = $this->performStash();
 100+ } catch ( MWException $e ) {
 101+ $warnings['stashfailed'] = $e->getMessage();
 102+ }
 103+ } else {
 104+ if ( $this->isForStash() ) {
 105+ // Some uploads can request they be stashed, so as not to publish them immediately.
 106+ // In this case, a failure to stash ought to be fatal
 107+ try {
 108+ $result['result'] = 'Success';
 109+ $result['sessionkey'] = $this->performStash();
 110+ } catch ( MWException $e ) {
 111+ $this->dieUsage( $e->getMessage(), 'stashfailed' );
 112+ }
 113+ } else {
 114+ // This is the most common case -- a normal upload with no warnings
 115+ // $result will be formatted properly for the API already, with a status
 116+ $result = $this->performUpload();
 117+ }
 118+ }
 119+
 120+ if ( $warnings ) {
103121 $result['warnings'] = $warnings;
104 - // FIXME: This double-adds $result , added again just below the else block --RK
105 - $this->getResult()->addValue( null, $this->getModuleName(), $result );
 122+ }
106123
107 - } else {
108 - // Perform the upload
109 - $result = $this->performUpload();
 124+ if ( $result['result'] === 'Success' ) {
 125+ $result['imageinfo'] = $this->mUpload->getImageInfo( $this->getResult() );
110126 }
111127
112128 $this->getResult()->addValue( null, $this->getModuleName(), $result );
@@ -114,8 +130,31 @@
115131 $this->mUpload->cleanupTempFile();
116132 }
117133
 134+ /**
 135+ * Determines if the API caller has requested this file be stashed.
 136+ * @return boolean
 137+ */
 138+ function isForStash() {
 139+ return isset( $this->mParams['stash'] ) and $this->mParams['stash'];
 140+ }
118141
119142 /**
 143+ * Stash the file and return the session key
 144+ * Also re-raises exceptions with slightly more informative message strings (useful for API)
 145+ * @throws MWException
 146+ * @return {String} session key
 147+ */
 148+ function performStash() {
 149+ try {
 150+ $sessionKey = $this->mUpload->stashSessionFile()->getSessionKey();
 151+ } catch ( MWException $e ) {
 152+ throw new MWException( 'Stashing temporary file failed: ' . get_class($e) . ' ' . $e->getMessage() );
 153+ }
 154+ return $sessionKey;
 155+ }
 156+
 157+
 158+ /**
120159 * Select an upload module and set it to mUpload. Dies on failure. If the
121160 * request was a status request and not a true upload, returns false;
122161 * otherwise true
@@ -166,11 +205,7 @@
167206
168207
169208 } elseif ( isset( $this->mParams['file'] ) ) {
170 - if ( $this->mParams['stash'] ) {
171 - $this->mUpload = new UploadFromFileToStash();
172 - } else {
173 - $this->mUpload = new UploadFromFile();
174 - }
 209+ $this->mUpload = new UploadFromFile();
175210 $this->mUpload->initialize(
176211 $this->mParams['filename'],
177212 $request->getUpload( 'file' )
@@ -290,10 +325,9 @@
291326 // Add indices
292327 $this->getResult()->setIndexedTagName( $warnings, 'warning' );
293328
294 - // FIXME: This duplicates logic from transformWarnings(), then *calls* transformWarnings() --RK
295329 if ( isset( $warnings['duplicate'] ) ) {
296330 $dupes = array();
297 - foreach ( $warnings['duplicate'] as $key => $dupe ) {
 331+ foreach ( $warnings['duplicate'] as $dupe ) {
298332 $dupes[] = $dupe->getName();
299333 }
300334 $this->getResult()->setIndexedTagName( $dupes, 'duplicate' );
@@ -305,50 +339,13 @@
306340 unset( $warnings['exists'] );
307341 $warnings[$warning['warning']] = $warning['file']->getName();
308342 }
309 -
310 - $result['result'] = 'Warning';
311 - $result['warnings'] = $this->transformWarnings( $warnings );
312 -
313 - $sessionKey = $this->mUpload->stashSession();
314 - if ( !$sessionKey ) {
315 - $this->dieUsage( 'Stashing temporary file failed', 'stashfailed' );
316 - }
317 -
318 - $result['sessionkey'] = $sessionKey;
319 -
320 - return $result;
321343 }
322344 }
323345
324346 return $warnings;
325347 }
326 -
327 - /**
328 - * Transforms a warnings array returned by mUpload->checkWarnings() into
329 - * something that can be directly used as API result
330 - */
331 - protected function transformWarnings( $warnings ) {
332 - // Add indices
333 - $this->getResult()->setIndexedTagName( $warnings, 'warning' );
334348
335 - if ( isset( $warnings['duplicate'] ) ) {
336 - $dupes = array();
337 - foreach ( $warnings['duplicate'] as $dupe ) {
338 - $dupes[] = $dupe->getName();
339 - }
340 - $this->getResult()->setIndexedTagName( $dupes, 'duplicate' );
341 - $warnings['duplicate'] = $dupes;
342 - }
343349
344 - if ( isset( $warnings['exists'] ) ) {
345 - $warning = $warnings['exists'];
346 - unset( $warnings['exists'] );
347 - $warnings[$warning['warning']] = $warning['file']->getName();
348 - }
349 -
350 - return $warnings;
351 - }
352 -
353350 /**
354351 * Perform the actual upload. Returns a suitable result array on success;
355352 * dies on failure.
@@ -395,45 +392,7 @@
396393 $result['result'] = 'Success';
397394 $result['filename'] = $file->getName();
398395
399 - $result['imageinfo'] = $this->mUpload->getImageInfo( $this->getResult() );
400396
401 - // XXX get default thumbnail width.
402 - // perhaps when initializing... Isn't this a global? Can't find it anywhere in docs.
403 - // XXX It's a user preference, see $wgThumbLimits and the thumbsize user preference.
404 - // The pref is only observed by Linker.php AFAICT and a few other places hardcode 120x120px --RK
405 - $defaultThumbWidth = 120;
406 - if ( isset( $this->mParams['thumbwidth'] ) ) {
407 - $thumbnails = array();
408 - if ( $this->mParams['thumbwidth'] ) {
409 - $widths = explode( ',', $this->mParams['thumbwidth'] );
410 - } else {
411 - $widths = array( $defaultThumbWidth ); // magic number grrrrr....
412 - }
413 -
414 - foreach ( $widths as $width ) {
415 - $thumbnailKey = $width . "px";
416 - if ( $thumbnails[$thumbnailKey] ) {
417 - continue;
418 - }
419 - $width = max( 1, min( $file->getWidth(), $width ) );
420 -
421 - // because the file is a SessionStashFile, this thumbnail will also be stashed,
422 - // and a thumbnailFile will be created
423 - if ( ! $thumbnailImage = $file->getThumbnail( $width ) ) {
424 - $this->dieUsageMsg( 'Could not obtain thumbnail', 'nothumb' );
425 - }
426 -
427 - // now also return metadata about thumbnails, with all possible properties
428 - $thumbnailFile = $thumbnailImage->thumbnailFile;
429 - $allPropertyNames = array_flip( ApiQueryImageInfo::getPropertyNames() );
430 - $thumbnails[$thumbnailKey] = ApiQueryImageInfo::getInfo( $thumbnailFile, $allPropertyNames, $result );
431 -
432 - }
433 - $result['thumbnails'] = $thumbnails;
434 - }
435 -
436 -
437 -
438397 return $result;
439398 }
440399
@@ -472,7 +431,6 @@
473432 'url' => null,
474433 'sessionkey' => null,
475434 'stash' => false,
476 - 'thumbwidth' => null,
477435 );
478436
479437 global $wgAllowAsyncCopyUploads;
@@ -499,7 +457,6 @@
500458 'url' => 'Url to fetch the file from',
501459 'sessionkey' => 'Session key that identifies a previous upload that was stashed temporarily.',
502460 'stash' => 'If set, the server will not add the file to the repository and stash it temporarily.'
503 - 'thumbwidth' => 'thumbwidths'
504461 );
505462
506463 global $wgAllowAsyncCopyUploads;

Follow-up revisions

RevisionCommit summaryAuthorDate
r74766accepted Roan's suggestions on r74536neilk02:48, 14 October 2010
r74768followup to comments on r74539 and r74536neilk04:34, 14 October 2010

Comments

#Comment by Catrope (talk | contribs)   11:14, 11 October 2010
+	public function getParamDescription() {
+		$description = $this->getBaseParamDescription();
+		$description['limit'] = 'How many image revisions to return';
+		$description['start'] = 'Timestamp to start listing from';
+		$description['end'] = 'Timestamp to stop listing at';
+	}

Why did you split these out?

+		if ( $warnings ) {
			[stash upload and warn on failure]
+		} else {
+			if ( $this->isForStash() ) {
+				// Some uploads can request they be stashed, so as not to publish them immediately.
+				// In this case, a failure to stash ought to be fatal

So if stashing was requested, a stashing failure is fatal if there were no other warnings but not fatal if there were warnings. I guess that sort of makes sense (as you want to communicate the rest of the warnings), but it threw me off at first. The implicit reasoning behind this should be made explicit in the comment. Also, the if { ... } else { if { ... } else { ... } } flow can be simplified to if { ... } elseif { ... } else { ... }

+		if ( $warnings ) { 
 			$result['result'] = 'Warning';
			[try to stash upload]
+		}

Then much later:

+		if ( $warnings ) {
 			$result['warnings'] = $warnings;
+		}

These two should be merged to make it easier to see what's going on.

+		return isset( $this->mParams['stash'] ) and $this->mParams['stash'];

Per my earlier revision, the isset() check is useless. The API parameter validation code sets all parameter keys in $this->mParams, always. Some modules use isset( $params['foo'] ) as a misguided way of checking whether $params['foo'] is null (isset returns false for vars set to null), but that's bad style IMO. The stash param is a bool, so it'll always be either true or false. This means the isset call will always return false (it's always set and never null) and you can suffice with simply checking the value of $this->mParams['stash']. Having a dedicated function for that isn't really needed, either, especially given that it's only used once.

#Comment by NeilK (talk | contribs)   02:24, 14 October 2010

getBaseParamDescription() was made so the subclass ApiQueryStashImageInfo.php could reuse the common items. Stashed items do not have a history to query so some parameters are inappropriate.

Added comments to make it explicit in r74762, also fixed a few bugs with this.

#Comment by NeilK (talk | contribs)   02:49, 14 October 2010

all suggestions about the structure of api/ApiUpload.php around line 93 adopted in r74766

#Comment by NeilK (talk | contribs)   02:23, 14 October 2010

getBaseParamDescription() was made so the subclass ApiQueryStashImageInfo.php could reuse the common items. Added comments to make it explicit in r74762.

Status & tagging log