]> scripts.mit.edu Git - autoinstalls/mediawiki.git/blob - includes/api/ApiQueryImageInfo.php
MediaWiki 1.17.4
[autoinstalls/mediawiki.git] / includes / api / ApiQueryImageInfo.php
1 <?php
2 /**
3  * API for MediaWiki 1.8+
4  *
5  * Created on July 6, 2007
6  *
7  * Copyright © 2006 Yuri Astrakhan <Firstname><Lastname>@gmail.com
8  *
9  * This program is free software; you can redistribute it and/or modify
10  * it under the terms of the GNU General Public License as published by
11  * the Free Software Foundation; either version 2 of the License, or
12  * (at your option) any later version.
13  *
14  * This program is distributed in the hope that it will be useful,
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17  * GNU General Public License for more details.
18  *
19  * You should have received a copy of the GNU General Public License along
20  * with this program; if not, write to the Free Software Foundation, Inc.,
21  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
22  * http://www.gnu.org/copyleft/gpl.html
23  *
24  * @file
25  */
26
27 if ( !defined( 'MEDIAWIKI' ) ) {
28         // Eclipse helper - will be ignored in production
29         require_once( 'ApiQueryBase.php' );
30 }
31
32 /**
33  * A query action to get image information and upload history.
34  *
35  * @ingroup API
36  */
37 class ApiQueryImageInfo extends ApiQueryBase {
38
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 );
46         }
47
48         public function execute() {
49                 $params = $this->extractRequestParams();
50
51                 $prop = array_flip( $params['prop'] );
52
53                 $scale = $this->getScale( $params );
54
55                 $pageIds = $this->getPageSet()->getAllTitlesByNamespace();
56                 if ( !empty( $pageIds[NS_FILE] ) ) {
57                         $titles = array_keys( $pageIds[NS_FILE] );
58                         asort( $titles ); // Ensure the order is always the same
59
60                         $skip = false;
61                         if ( !is_null( $params['continue'] ) ) {
62                                 $skip = true;
63                                 $cont = explode( '|', $params['continue'] );
64                                 if ( count( $cont ) != 2 ) {
65                                         $this->dieUsage( 'Invalid continue param. You should pass the original ' .
66                                                         'value returned by the previous query', '_badcontinue' );
67                                 }
68                                 $fromTitle = strval( $cont[0] );
69                                 $fromTimestamp = $cont[1];
70                                 // Filter out any titles before $fromTitle
71                                 foreach ( $titles as $key => $title ) {
72                                         if ( $title < $fromTitle ) {
73                                                 unset( $titles[$key] );
74                                         } else {
75                                                 break;
76                                         }
77                                 }
78                         }
79
80                         $result = $this->getResult();
81                         $images = RepoGroup::singleton()->findFiles( $titles );
82                         foreach ( $images as $img ) {
83                                 // Skip redirects
84                                 if ( $img->getOriginalTitle()->isRedirect() ) {
85                                         continue;
86                                 }
87
88                                 $start = $skip ? $fromTimestamp : $params['start'];
89                                 $pageId = $pageIds[NS_IMAGE][ $img->getOriginalTitle()->getDBkey() ];
90
91                                 $fit = $result->addValue(
92                                         array( 'query', 'pages', intval( $pageId ) ),
93                                         'imagerepository', $img->getRepoName()
94                                 );
95                                 if ( !$fit ) {
96                                         if ( count( $pageIds[NS_IMAGE] ) == 1 ) {
97                                                 // The user is screwed. imageinfo can't be solely
98                                                 // responsible for exceeding the limit in this case,
99                                                 // so set a query-continue that just returns the same
100                                                 // thing again. When the violating queries have been
101                                                 // out-continued, the result will get through
102                                                 $this->setContinueEnumParameter( 'start',
103                                                         wfTimestamp( TS_ISO_8601, $img->getTimestamp() ) );
104                                         } else {
105                                                 $this->setContinueEnumParameter( 'continue',
106                                                         $this->getContinueStr( $img ) );
107                                         }
108                                         break;
109                                 }
110
111                                 // Get information about the current version first
112                                 // Check that the current version is within the start-end boundaries
113                                 $gotOne = false;
114                                 if (
115                                         ( is_null( $start ) || $img->getTimestamp() <= $start ) &&
116                                         ( is_null( $params['end'] ) || $img->getTimestamp() >= $params['end'] )
117                                 )
118                                 {
119                                         $gotOne = true;
120                                         $fit = $this->addPageSubItem( $pageId,
121                                                 self::getInfo( $img, $prop, $result, $scale ) );
122                                         if ( !$fit ) {
123                                                 if ( count( $pageIds[NS_IMAGE] ) == 1 ) {
124                                                         // See the 'the user is screwed' comment above
125                                                         $this->setContinueEnumParameter( 'start',
126                                                                 wfTimestamp( TS_ISO_8601, $img->getTimestamp() ) );
127                                                 } else {
128                                                         $this->setContinueEnumParameter( 'continue',
129                                                                 $this->getContinueStr( $img ) );
130                                                 }
131                                                 break;
132                                         }
133                                 }
134
135                                 // Now get the old revisions
136                                 // Get one more to facilitate query-continue functionality
137                                 $count = ( $gotOne ? 1 : 0 );
138                                 $oldies = $img->getHistory( $params['limit'] - $count + 1, $start, $params['end'] );
139                                 foreach ( $oldies as $oldie ) {
140                                         if ( ++$count > $params['limit'] ) {
141                                                 // We've reached the extra one which shows that there are additional pages to be had. Stop here...
142                                                 // Only set a query-continue if there was only one title
143                                                 if ( count( $pageIds[NS_FILE] ) == 1 ) {
144                                                         $this->setContinueEnumParameter( 'start',
145                                                                 wfTimestamp( TS_ISO_8601, $oldie->getTimestamp() ) );
146                                                 }
147                                                 break;
148                                         }
149                                         $fit = $this->addPageSubItem( $pageId,
150                                                 self::getInfo( $oldie, $prop, $result ) );
151                                         if ( !$fit ) {
152                                                 if ( count( $pageIds[NS_IMAGE] ) == 1 ) {
153                                                         $this->setContinueEnumParameter( 'start',
154                                                                 wfTimestamp( TS_ISO_8601, $oldie->getTimestamp() ) );
155                                                 } else {
156                                                         $this->setContinueEnumParameter( 'continue',
157                                                                 $this->getContinueStr( $oldie ) );
158                                                 }
159                                                 break;
160                                         }
161                                 }
162                                 if ( !$fit ) {
163                                         break;
164                                 }
165                                 $skip = false;
166                         }
167
168                         $data = $this->getResultData();
169                         foreach ( $data['query']['pages'] as $pageid => $arr ) {
170                                 if ( !isset( $arr['imagerepository'] ) ) {
171                                         $result->addValue(
172                                                 array( 'query', 'pages', $pageid ),
173                                                 'imagerepository', ''
174                                         );
175                                 }
176                                 // The above can't fail because it doesn't increase the result size
177                         }
178                 }
179         }
180
181         /**
182          * From parameters, construct a 'scale' array 
183          * @param $params Array: 
184          * @return Array or Null: key-val array of 'width' and 'height', or null
185          */     
186         public function getScale( $params ) {
187                 $p = $this->getModulePrefix();
188                 if ( $params['urlheight'] != -1 && $params['urlwidth'] == -1 ) {
189                         $this->dieUsage( "${p}urlheight cannot be used without {$p}urlwidth", "{$p}urlwidth" );
190                 }
191
192                 if ( $params['urlwidth'] != -1 ) {
193                         $scale = array();
194                         $scale['width'] = $params['urlwidth'];
195                         $scale['height'] = $params['urlheight'];
196                 } else {
197                         $scale = null;
198                 }
199                 return $scale;
200         }
201
202
203         /**
204          * Get result information for an image revision
205          *
206          * @param $file File object
207          * @param $prop Array of properties to get (in the keys)
208          * @param $result ApiResult object
209          * @param $scale Array containing 'width' and 'height' items, or null
210          * @return Array: result array
211          */
212         static function getInfo( $file, $prop, $result, $scale = null ) {
213                 $vals = array();
214                 // Timestamp is shown even if the file is revdelete'd in interface
215                 // so do same here.
216                 if ( isset( $prop['timestamp'] ) ) {
217                         $vals['timestamp'] = wfTimestamp( TS_ISO_8601, $file->getTimestamp() );
218                 }
219
220                 $user = isset( $prop['user'] );
221                 $userid = isset( $prop['userid'] );
222
223                 if ( $user || $userid ) {
224                         if ( $file->isDeleted( File::DELETED_USER ) ) {
225                                 $vals['userhidden'] = '';
226                         } else {
227                                 if ( $user ) {
228                                         $vals['user'] = $file->getUser();
229                                 }
230                                 if ( $userid ) {
231                                         $vals['userid'] = $file->getUser( 'id' );
232                                 }
233                                 if ( !$file->getUser( 'id' ) ) {
234                                         $vals['anon'] = '';
235                                 }
236                         }
237                 }
238
239                 // This is shown even if the file is revdelete'd in interface
240                 // so do same here.
241                 if ( isset( $prop['size'] ) || isset( $prop['dimensions'] ) ) {
242                         $vals['size'] = intval( $file->getSize() );
243                         $vals['width'] = intval( $file->getWidth() );
244                         $vals['height'] = intval( $file->getHeight() );
245                         
246                         $pageCount = $file->pageCount();
247                         if ( $pageCount !== false ) {
248                                 $vals['pagecount'] = $pageCount;
249                         }
250                 }
251
252                 $pcomment = isset( $prop['parsedcomment'] );
253                 $comment = isset( $prop['comment'] );
254
255                 if ( $pcomment || $comment ) {
256                         if ( $file->isDeleted( File::DELETED_COMMENT ) ) {
257                                 $vals['commenthidden'] = '';
258                         } else {
259                                 if ( $pcomment ) {
260                                         global $wgUser;
261                                         $vals['parsedcomment'] = $wgUser->getSkin()->formatComment(
262                                                 $file->getDescription(), $file->getTitle() );
263                                 }
264                                 if ( $comment ) {
265                                         $vals['comment'] = $file->getDescription();
266                                 }
267                         }
268                 }
269
270                 $url = isset( $prop['url'] );
271                 $sha1 = isset( $prop['sha1'] );
272                 $meta = isset( $prop['metadata'] );
273                 $mime = isset( $prop['mime'] );
274                 $archive = isset( $prop['archivename'] );
275                 $bitdepth = isset( $prop['bitdepth'] );
276
277                 if ( ( $url || $sha1 || $meta || $mime || $archive || $bitdepth )
278                                 && $file->isDeleted( File::DELETED_FILE ) ) {
279                         $vals['filehidden'] = '';
280
281                         //Early return, tidier than indenting all following things one level
282                         return $vals;
283                 }
284
285                 if ( $url ) {
286                         if ( !is_null( $scale ) && !$file->isOld() ) {
287                                 $mto = $file->transform( array( 'width' => $scale['width'], 'height' => $scale['height'] ) );
288                                 if ( $mto && !$mto->isError() ) {
289                                         $vals['thumburl'] = wfExpandUrl( $mto->getUrl() );
290
291                                         // bug 23834 - If the URL's are the same, we haven't resized it, so shouldn't give the wanted
292                                         // thumbnail sizes for the thumbnail actual size
293                                         if ( $mto->getUrl() !== $file->getUrl() ) {
294                                                 $vals['thumbwidth'] = intval( $mto->getWidth() );
295                                                 $vals['thumbheight'] = intval( $mto->getHeight() );
296                                         } else {
297                                                 $vals['thumbwidth'] = intval( $file->getWidth() );
298                                                 $vals['thumbheight'] = intval( $file->getHeight() );
299                                         }
300
301                                         if ( isset( $prop['thumbmime'] ) && $file->getHandler() ) {
302                                                 list( $ext, $mime ) = $file->getHandler()->getThumbType( 
303                                                         substr( $mto->getPath(), strrpos( $mto->getPath(), '.' ) + 1 ), 
304                                                         $file->getMimeType(), $thumbParams );
305                                                 $vals['thumbmime'] = $mime;
306                                         }
307                                 } else if ( $mto && $mto->isError() ) {
308                                         $vals['thumberror'] = $mto->toText();
309                                 }
310                         }
311                         $vals['url'] = $file->getFullURL();
312                         $vals['descriptionurl'] = wfExpandUrl( $file->getDescriptionUrl() );
313                 }
314                 
315                 if ( $sha1 ) {
316                         $vals['sha1'] = wfBaseConvert( $file->getSha1(), 36, 16, 40 );
317                 }
318
319                 if ( $meta ) {
320                         $metadata = $file->getMetadata();
321                         $vals['metadata'] = $metadata ? self::processMetaData( unserialize( $metadata ), $result ) : null;
322                 }
323
324                 if ( $mime ) {
325                         $vals['mime'] = $file->getMimeType();
326                 }
327
328                 if ( $archive && $file->isOld() ) {
329                         $vals['archivename'] = $file->getArchiveName();
330                 }
331
332                 if ( $bitdepth ) {
333                         $vals['bitdepth'] = $file->getBitDepth();
334                 }
335
336                 return $vals;
337         }
338
339         /*
340          *
341          * @param $metadata Array
342          * @param $result ApiResult
343          * @return Array
344          */
345         public static function processMetaData( $metadata, $result ) {
346                 $retval = array();
347                 if ( is_array( $metadata ) ) {
348                         foreach ( $metadata as $key => $value ) {
349                                 $r = array( 'name' => $key );
350                                 if ( is_array( $value ) ) {
351                                         $r['value'] = self::processMetaData( $value, $result );
352                                 } else {
353                                         $r['value'] = $value;
354                                 }
355                                 $retval[] = $r;
356                         }
357                 }
358                 $result->setIndexedTagName( $retval, 'metadata' );
359                 return $retval;
360         }
361
362         public function getCacheMode( $params ) {
363                 return 'public';
364         }
365
366         private function getContinueStr( $img ) {
367                 return $img->getOriginalTitle()->getText() .
368                         '|' .  $img->getTimestamp();
369         }
370
371         public function getAllowedParams() {
372                 return array(
373                         'prop' => array(
374                                 ApiBase::PARAM_ISMULTI => true,
375                                 ApiBase::PARAM_DFLT => 'timestamp|user',
376                                 ApiBase::PARAM_TYPE => self::getPropertyNames()
377                         ),
378                         'limit' => array(
379                                 ApiBase::PARAM_TYPE => 'limit',
380                                 ApiBase::PARAM_DFLT => 1,
381                                 ApiBase::PARAM_MIN => 1,
382                                 ApiBase::PARAM_MAX => ApiBase::LIMIT_BIG1,
383                                 ApiBase::PARAM_MAX2 => ApiBase::LIMIT_BIG2
384                         ),
385                         'start' => array(
386                                 ApiBase::PARAM_TYPE => 'timestamp'
387                         ),
388                         'end' => array(
389                                 ApiBase::PARAM_TYPE => 'timestamp'
390                         ),
391                         'urlwidth' => array(
392                                 ApiBase::PARAM_TYPE => 'integer',
393                                 ApiBase::PARAM_DFLT => -1
394                         ),
395                         'urlheight' => array(
396                                 ApiBase::PARAM_TYPE => 'integer',
397                                 ApiBase::PARAM_DFLT => -1
398                         ),
399                         'continue' => null,
400                 );
401         }
402
403         /**
404          * Returns all possible parameters to iiprop
405          */
406         public static function getPropertyNames() {
407                 return array(
408                         'timestamp',
409                         'user',
410                         'userid',
411                         'comment',
412                         'parsedcomment',
413                         'url',
414                         'size',
415                         'dimensions', // For backwards compatibility with Allimages
416                         'sha1',
417                         'mime',
418                         'thumbmime',
419                         'metadata',
420                         'archivename',
421                         'bitdepth',
422                 );
423         }
424
425
426         /**
427          * Return the API documentation for the parameters. 
428          * @return {Array} parameter documentation.
429          */
430         public function getParamDescription() {
431                 $p = $this->getModulePrefix();
432                 return array(
433                         'prop' => array(
434                                 'What image information to get:',
435                                 ' timestamp     - Adds timestamp for the uploaded version',
436                                 ' user          - Adds the user who uploaded the image version',
437                                 ' userid        - Add the user id that uploaded the image version',
438                                 ' comment       - Comment on the version',
439                                 ' parsedcomment - Parse the comment on the version',
440                                 ' url           - Gives URL to the image and the description page',
441                                 ' size          - Adds the size of the image in bytes and the height and width',
442                                 ' dimensions    - Alias for size',
443                                 ' sha1          - Adds sha1 hash for the image',
444                                 ' mime          - Adds MIME of the image',
445                                 ' thumbmime     - Adss MIME of the image thumbnail (requires url)',
446                                 ' metadata      - Lists EXIF metadata for the version of the image',
447                                 ' archivename   - Adds the file name of the archive version for non-latest versions',
448                                 ' bitdepth      - Adds the bit depth of the version',
449                         ),
450                         'urlwidth' => array( "If {$p}prop=url is set, a URL to an image scaled to this width will be returned.",
451                                             'Only the current version of the image can be scaled' ),
452                         'urlheight' => "Similar to {$p}urlwidth. Cannot be used without {$p}urlwidth",
453                         'limit' => 'How many image revisions to return',
454                         'start' => 'Timestamp to start listing from',
455                         'end' => 'Timestamp to stop listing at',
456                         'continue' => 'If the query response includes a continue value, use it here to get another page of results'
457                 );
458         }
459
460         public function getDescription() {
461                 return 'Returns image information and upload history';
462         }
463
464         public function getPossibleErrors() {
465                 return array_merge( parent::getPossibleErrors(), array(
466                         array( 'code' => 'iiurlwidth', 'info' => 'iiurlheight cannot be used without iiurlwidth' ),
467                 ) );
468         }
469
470         protected function getExamples() {
471                 return array(
472                         'api.php?action=query&titles=File:Albert%20Einstein%20Head.jpg&prop=imageinfo',
473                         'api.php?action=query&titles=File:Test.jpg&prop=imageinfo&iilimit=50&iiend=20071231235959&iiprop=timestamp|user|url',
474                 );
475         }
476
477         public function getVersion() {
478                 return __CLASS__ . ': $Id$';
479         }
480 }