MediaWiki  REL1_19
ApiQueryImageInfo.php
Go to the documentation of this file.
00001 <?php
00032 class ApiQueryImageInfo extends ApiQueryBase {
00033 
00034         public function __construct( $query, $moduleName, $prefix = 'ii' ) {
00035                 // We allow a subclass to override the prefix, to create a related API module.
00036                 // Some other parts of MediaWiki construct this with a null $prefix, which used to be ignored when this only took two arguments
00037                 if ( is_null( $prefix ) ) {
00038                         $prefix = 'ii';
00039                 }
00040                 parent::__construct( $query, $moduleName, $prefix );
00041         }
00042 
00043         public function execute() {
00044                 $params = $this->extractRequestParams();
00045 
00046                 $prop = array_flip( $params['prop'] );
00047 
00048                 $scale = $this->getScale( $params );
00049 
00050                 $pageIds = $this->getPageSet()->getAllTitlesByNamespace();
00051                 if ( !empty( $pageIds[NS_FILE] ) ) {
00052                         $titles = array_keys( $pageIds[NS_FILE] );
00053                         asort( $titles ); // Ensure the order is always the same
00054 
00055                         $skip = false;
00056                         if ( !is_null( $params['continue'] ) ) {
00057                                 $skip = true;
00058                                 $cont = explode( '|', $params['continue'] );
00059                                 if ( count( $cont ) != 2 ) {
00060                                         $this->dieUsage( 'Invalid continue param. You should pass the original ' .
00061                                                         'value returned by the previous query', '_badcontinue' );
00062                                 }
00063                                 $fromTitle = strval( $cont[0] );
00064                                 $fromTimestamp = $cont[1];
00065                                 // Filter out any titles before $fromTitle
00066                                 foreach ( $titles as $key => $title ) {
00067                                         if ( $title < $fromTitle ) {
00068                                                 unset( $titles[$key] );
00069                                         } else {
00070                                                 break;
00071                                         }
00072                                 }
00073                         }
00074 
00075                         $result = $this->getResult();
00076                         $images = RepoGroup::singleton()->findFiles( $titles );
00077                         foreach ( $images as $img ) {
00078                                 // Skip redirects
00079                                 if ( $img->getOriginalTitle()->isRedirect() ) {
00080                                         continue;
00081                                 }
00082 
00083                                 $start = $skip ? $fromTimestamp : $params['start'];
00084                                 $pageId = $pageIds[NS_IMAGE][ $img->getOriginalTitle()->getDBkey() ];
00085 
00086                                 $fit = $result->addValue(
00087                                         array( 'query', 'pages', intval( $pageId ) ),
00088                                         'imagerepository', $img->getRepoName()
00089                                 );
00090                                 if ( !$fit ) {
00091                                         if ( count( $pageIds[NS_IMAGE] ) == 1 ) {
00092                                                 // The user is screwed. imageinfo can't be solely
00093                                                 // responsible for exceeding the limit in this case,
00094                                                 // so set a query-continue that just returns the same
00095                                                 // thing again. When the violating queries have been
00096                                                 // out-continued, the result will get through
00097                                                 $this->setContinueEnumParameter( 'start',
00098                                                         wfTimestamp( TS_ISO_8601, $img->getTimestamp() ) );
00099                                         } else {
00100                                                 $this->setContinueEnumParameter( 'continue',
00101                                                         $this->getContinueStr( $img ) );
00102                                         }
00103                                         break;
00104                                 }
00105 
00106                                 // Check if we can make the requested thumbnail, and get transform parameters.
00107                                 $finalThumbParams = $this->mergeThumbParams( $img, $scale, $params['urlparam'] );
00108 
00109                                 // Get information about the current version first
00110                                 // Check that the current version is within the start-end boundaries
00111                                 $gotOne = false;
00112                                 if (
00113                                         ( is_null( $start ) || $img->getTimestamp() <= $start ) &&
00114                                         ( is_null( $params['end'] ) || $img->getTimestamp() >= $params['end'] )
00115                                 ) {
00116                                         $gotOne = true;
00117 
00118                                         $fit = $this->addPageSubItem( $pageId,
00119                                                 self::getInfo( $img, $prop, $result,
00120                                                         $finalThumbParams, $params['metadataversion'] ) );
00121                                         if ( !$fit ) {
00122                                                 if ( count( $pageIds[NS_IMAGE] ) == 1 ) {
00123                                                         // See the 'the user is screwed' comment above
00124                                                         $this->setContinueEnumParameter( 'start',
00125                                                                 wfTimestamp( TS_ISO_8601, $img->getTimestamp() ) );
00126                                                 } else {
00127                                                         $this->setContinueEnumParameter( 'continue',
00128                                                                 $this->getContinueStr( $img ) );
00129                                                 }
00130                                                 break;
00131                                         }
00132                                 }
00133 
00134                                 // Now get the old revisions
00135                                 // Get one more to facilitate query-continue functionality
00136                                 $count = ( $gotOne ? 1 : 0 );
00137                                 $oldies = $img->getHistory( $params['limit'] - $count + 1, $start, $params['end'] );
00138                                 foreach ( $oldies as $oldie ) {
00139                                         if ( ++$count > $params['limit'] ) {
00140                                                 // We've reached the extra one which shows that there are additional pages to be had. Stop here...
00141                                                 // Only set a query-continue if there was only one title
00142                                                 if ( count( $pageIds[NS_FILE] ) == 1 ) {
00143                                                         $this->setContinueEnumParameter( 'start',
00144                                                                 wfTimestamp( TS_ISO_8601, $oldie->getTimestamp() ) );
00145                                                 }
00146                                                 break;
00147                                         }
00148                                         $fit = $this->addPageSubItem( $pageId,
00149                                                 self::getInfo( $oldie, $prop, $result,
00150                                                         $finalThumbParams, $params['metadataversion'] ) );
00151                                         if ( !$fit ) {
00152                                                 if ( count( $pageIds[NS_IMAGE] ) == 1 ) {
00153                                                         $this->setContinueEnumParameter( 'start',
00154                                                                 wfTimestamp( TS_ISO_8601, $oldie->getTimestamp() ) );
00155                                                 } else {
00156                                                         $this->setContinueEnumParameter( 'continue',
00157                                                                 $this->getContinueStr( $oldie ) );
00158                                                 }
00159                                                 break;
00160                                         }
00161                                 }
00162                                 if ( !$fit ) {
00163                                         break;
00164                                 }
00165                                 $skip = false;
00166                         }
00167 
00168                         $data = $this->getResultData();
00169                         foreach ( $data['query']['pages'] as $pageid => $arr ) {
00170                                 if ( !isset( $arr['imagerepository'] ) ) {
00171                                         $result->addValue(
00172                                                 array( 'query', 'pages', $pageid ),
00173                                                 'imagerepository', ''
00174                                         );
00175                                 }
00176                                 // The above can't fail because it doesn't increase the result size
00177                         }
00178                 }
00179         }
00180 
00186         public function getScale( $params ) {
00187                 $p = $this->getModulePrefix();
00188 
00189                 // Height and width.
00190                 if ( $params['urlheight'] != -1 && $params['urlwidth'] == -1 ) {
00191                         $this->dieUsage( "{$p}urlheight cannot be used without {$p}urlwidth", "{$p}urlwidth" );
00192                 }
00193 
00194                 if ( $params['urlwidth'] != -1 ) {
00195                         $scale = array();
00196                         $scale['width'] = $params['urlwidth'];
00197                         $scale['height'] = $params['urlheight'];
00198                 } else {
00199                         $scale = null;
00200                         if ( $params['urlparam'] ) {
00201                                 $this->dieUsage( "{$p}urlparam requires {$p}urlwidth", "urlparam_no_width" );
00202                         }
00203                         return $scale;
00204                 }
00205 
00206                 return $scale;
00207         }
00208 
00218         protected function mergeThumbParams ( $image, $thumbParams, $otherParams ) {
00219                 if ( !$otherParams ) {
00220                         return $thumbParams;
00221                 }
00222                 $p = $this->getModulePrefix();
00223 
00224                 $h = $image->getHandler();
00225                 if ( !$h ) {
00226                         $this->setWarning( 'Could not create thumbnail because ' .
00227                                 $image->getName() . ' does not have an associated image handler' );
00228                         return $thumbParams;
00229                 }
00230 
00231                 $paramList = $h->parseParamString( $otherParams );
00232                 if ( !$paramList ) {
00233                         // Just set a warning (instead of dieUsage), as in many cases
00234                         // we could still render the image using width and height parameters,
00235                         // and this type of thing could happen between different versions of
00236                         // handlers.
00237                         $this->setWarning( "Could not parse {$p}urlparam for " . $image->getName()
00238                                 . '. Using only width and height' );
00239                         return $thumbParams;
00240                 }
00241 
00242                 if ( isset( $paramList['width'] ) ) {
00243                         if ( intval( $paramList['width'] ) != intval( $thumbParams['width'] ) ) {
00244                                 $this->dieUsage( "{$p}urlparam had width of {$paramList['width']} but "
00245                                         . "{$p}urlwidth was {$thumbParams['width']}", "urlparam_urlwidth_mismatch" );
00246                         }
00247                 }
00248 
00249                 foreach ( $paramList as $name => $value ) {
00250                         if ( !$h->validateParam( $name, $value ) ) {
00251                                 $this->dieUsage( "Invalid value for {$p}urlparam ($name=$value)", "urlparam" );
00252                         }
00253                 }
00254 
00255                 return $thumbParams + $paramList;
00256         }
00257 
00268         static function getInfo( $file, $prop, $result, $thumbParams = null, $version = 'latest' ) {
00269                 $vals = array();
00270                 // Timestamp is shown even if the file is revdelete'd in interface
00271                 // so do same here.
00272                 if ( isset( $prop['timestamp'] ) ) {
00273                         $vals['timestamp'] = wfTimestamp( TS_ISO_8601, $file->getTimestamp() );
00274                 }
00275 
00276                 $user = isset( $prop['user'] );
00277                 $userid = isset( $prop['userid'] );
00278 
00279                 if ( $user || $userid ) {
00280                         if ( $file->isDeleted( File::DELETED_USER ) ) {
00281                                 $vals['userhidden'] = '';
00282                         } else {
00283                                 if ( $user ) {
00284                                         $vals['user'] = $file->getUser();
00285                                 }
00286                                 if ( $userid ) {
00287                                         $vals['userid'] = $file->getUser( 'id' );
00288                                 }
00289                                 if ( !$file->getUser( 'id' ) ) {
00290                                         $vals['anon'] = '';
00291                                 }
00292                         }
00293                 }
00294 
00295                 // This is shown even if the file is revdelete'd in interface
00296                 // so do same here.
00297                 if ( isset( $prop['size'] ) || isset( $prop['dimensions'] ) ) {
00298                         $vals['size'] = intval( $file->getSize() );
00299                         $vals['width'] = intval( $file->getWidth() );
00300                         $vals['height'] = intval( $file->getHeight() );
00301 
00302                         $pageCount = $file->pageCount();
00303                         if ( $pageCount !== false ) {
00304                                 $vals['pagecount'] = $pageCount;
00305                         }
00306                 }
00307 
00308                 $pcomment = isset( $prop['parsedcomment'] );
00309                 $comment = isset( $prop['comment'] );
00310 
00311                 if ( $pcomment || $comment ) {
00312                         if ( $file->isDeleted( File::DELETED_COMMENT ) ) {
00313                                 $vals['commenthidden'] = '';
00314                         } else {
00315                                 if ( $pcomment ) {
00316                                         $vals['parsedcomment'] = Linker::formatComment(
00317                                                 $file->getDescription(), $file->getTitle() );
00318                                 }
00319                                 if ( $comment ) {
00320                                         $vals['comment'] = $file->getDescription();
00321                                 }
00322                         }
00323                 }
00324 
00325                 $url = isset( $prop['url'] );
00326                 $sha1 = isset( $prop['sha1'] );
00327                 $meta = isset( $prop['metadata'] );
00328                 $mime = isset( $prop['mime'] );
00329                 $mediatype = isset( $prop['mediatype'] );
00330                 $archive = isset( $prop['archivename'] );
00331                 $bitdepth = isset( $prop['bitdepth'] );
00332 
00333                 if ( ( $url || $sha1 || $meta || $mime || $mediatype || $archive || $bitdepth )
00334                                 && $file->isDeleted( File::DELETED_FILE ) ) {
00335                         $vals['filehidden'] = '';
00336 
00337                         //Early return, tidier than indenting all following things one level
00338                         return $vals;
00339                 }
00340 
00341                 if ( $url ) {
00342                         if ( !is_null( $thumbParams ) ) {
00343                                 $mto = $file->transform( $thumbParams );
00344                                 if ( $mto && !$mto->isError() ) {
00345                                         $vals['thumburl'] = wfExpandUrl( $mto->getUrl(), PROTO_CURRENT );
00346 
00347                                         // bug 23834 - If the URL's are the same, we haven't resized it, so shouldn't give the wanted
00348                                         // thumbnail sizes for the thumbnail actual size
00349                                         if ( $mto->getUrl() !== $file->getUrl() ) {
00350                                                 $vals['thumbwidth'] = intval( $mto->getWidth() );
00351                                                 $vals['thumbheight'] = intval( $mto->getHeight() );
00352                                         } else {
00353                                                 $vals['thumbwidth'] = intval( $file->getWidth() );
00354                                                 $vals['thumbheight'] = intval( $file->getHeight() );
00355                                         }
00356 
00357                                         if ( isset( $prop['thumbmime'] ) && $file->getHandler() ) {
00358                                                 list( $ext, $mime ) = $file->getHandler()->getThumbType(
00359                                                         substr( $mto->getPath(), strrpos( $mto->getPath(), '.' ) + 1 ),
00360                                                         $file->getMimeType(), $thumbParams );
00361                                                 $vals['thumbmime'] = $mime;
00362                                         }
00363                                 } elseif ( $mto && $mto->isError() ) {
00364                                         $vals['thumberror'] = $mto->toText();
00365                                 }
00366                         }
00367                         $vals['url'] = wfExpandUrl( $file->getFullURL(), PROTO_CURRENT );
00368                         $vals['descriptionurl'] = wfExpandUrl( $file->getDescriptionUrl(), PROTO_CURRENT );
00369                 }
00370 
00371                 if ( $sha1 ) {
00372                         $vals['sha1'] = wfBaseConvert( $file->getSha1(), 36, 16, 40 );
00373                 }
00374 
00375                 if ( $meta ) {
00376                         $metadata = unserialize( $file->getMetadata() );
00377                         if ( $version !== 'latest' ) {
00378                                 $metadata = $file->convertMetadataVersion( $metadata, $version );
00379                         }
00380                         $vals['metadata'] = $metadata ? self::processMetaData( $metadata, $result ) : null;
00381                 }
00382 
00383                 if ( $mime ) {
00384                         $vals['mime'] = $file->getMimeType();
00385                 }
00386 
00387                 if ( $mediatype ) {
00388                         $vals['mediatype'] = $file->getMediaType();
00389                 }
00390 
00391                 if ( $archive && $file->isOld() ) {
00392                         $vals['archivename'] = $file->getArchiveName();
00393                 }
00394 
00395                 if ( $bitdepth ) {
00396                         $vals['bitdepth'] = $file->getBitDepth();
00397                 }
00398 
00399                 return $vals;
00400         }
00401 
00408         public static function processMetaData( $metadata, $result ) {
00409                 $retval = array();
00410                 if ( is_array( $metadata ) ) {
00411                         foreach ( $metadata as $key => $value ) {
00412                                 $r = array( 'name' => $key );
00413                                 if ( is_array( $value ) ) {
00414                                         $r['value'] = self::processMetaData( $value, $result );
00415                                 } else {
00416                                         $r['value'] = $value;
00417                                 }
00418                                 $retval[] = $r;
00419                         }
00420                 }
00421                 $result->setIndexedTagName( $retval, 'metadata' );
00422                 return $retval;
00423         }
00424 
00425         public function getCacheMode( $params ) {
00426                 return 'public';
00427         }
00428 
00433         private function getContinueStr( $img ) {
00434                 return $img->getOriginalTitle()->getText() .
00435                         '|' .  $img->getTimestamp();
00436         }
00437 
00438         public function getAllowedParams() {
00439                 return array(
00440                         'prop' => array(
00441                                 ApiBase::PARAM_ISMULTI => true,
00442                                 ApiBase::PARAM_DFLT => 'timestamp|user',
00443                                 ApiBase::PARAM_TYPE => self::getPropertyNames()
00444                         ),
00445                         'limit' => array(
00446                                 ApiBase::PARAM_TYPE => 'limit',
00447                                 ApiBase::PARAM_DFLT => 1,
00448                                 ApiBase::PARAM_MIN => 1,
00449                                 ApiBase::PARAM_MAX => ApiBase::LIMIT_BIG1,
00450                                 ApiBase::PARAM_MAX2 => ApiBase::LIMIT_BIG2
00451                         ),
00452                         'start' => array(
00453                                 ApiBase::PARAM_TYPE => 'timestamp'
00454                         ),
00455                         'end' => array(
00456                                 ApiBase::PARAM_TYPE => 'timestamp'
00457                         ),
00458                         'urlwidth' => array(
00459                                 ApiBase::PARAM_TYPE => 'integer',
00460                                 ApiBase::PARAM_DFLT => -1
00461                         ),
00462                         'urlheight' => array(
00463                                 ApiBase::PARAM_TYPE => 'integer',
00464                                 ApiBase::PARAM_DFLT => -1
00465                         ),
00466                         'metadataversion' => array(
00467                                 ApiBase::PARAM_TYPE => 'string',
00468                                 ApiBase::PARAM_DFLT => '1',
00469                         ),
00470                         'urlparam' => array(
00471                                 ApiBase::PARAM_DFLT => '',
00472                                 ApiBase::PARAM_TYPE => 'string',
00473                         ),
00474                         'continue' => null,
00475                 );
00476         }
00477 
00485         public static function getPropertyNames( $filter = array() ) {
00486                 return array_diff( array_keys( self::getProperties() ), $filter );
00487         }
00488 
00494         private static function getProperties() {
00495                 return array(
00496                         'timestamp' =>      ' timestamp     - Adds timestamp for the uploaded version',
00497                         'user' =>           ' user          - Adds the user who uploaded the image version',
00498                         'userid' =>         ' userid        - Add the user ID that uploaded the image version',
00499                         'comment' =>        ' comment       - Comment on the version',
00500                         'parsedcomment' =>  ' parsedcomment - Parse the comment on the version',
00501                         'url' =>            ' url           - Gives URL to the image and the description page',
00502                         'size' =>           ' size          - Adds the size of the image in bytes and the height, width and page count (if applicable)',
00503                         'dimensions' =>     ' dimensions    - Alias for size', // For backwards compatibility with Allimages
00504                         'sha1' =>           ' sha1          - Adds SHA-1 hash for the image',
00505                         'mime' =>           ' mime          - Adds MIME type of the image',
00506                         'thumbmime' =>      ' thumbmime     - Adds MIME type of the image thumbnail (requires url)',
00507                         'mediatype' =>      ' mediatype     - Adds the media type of the image',
00508                         'metadata' =>       ' metadata      - Lists EXIF metadata for the version of the image',
00509                         'archivename' =>    ' archivename   - Adds the file name of the archive version for non-latest versions',
00510                         'bitdepth' =>       ' bitdepth      - Adds the bit depth of the version',
00511                 );
00512         }
00513 
00521         public static function getPropertyDescriptions( $filter = array() ) {
00522                 return array_merge(
00523                         array( 'What image information to get:' ),
00524                         array_values( array_diff_key( self::getProperties(), array_flip( $filter ) ) )
00525                 );
00526         }
00527 
00532         public function getParamDescription() {
00533                 $p = $this->getModulePrefix();
00534                 return array(
00535                         'prop' => self::getPropertyDescriptions(),
00536                         'urlwidth' => array( "If {$p}prop=url is set, a URL to an image scaled to this width will be returned.",
00537                                             'Only the current version of the image can be scaled' ),
00538                         'urlheight' => "Similar to {$p}urlwidth. Cannot be used without {$p}urlwidth",
00539                         'urlparam' => array( "A handler specific parameter string. For example, pdf's ",
00540                                 "might use 'page15-100px'. {$p}urlwidth must be used and be consistent with {$p}urlparam" ),
00541                         'limit' => 'How many image revisions to return',
00542                         'start' => 'Timestamp to start listing from',
00543                         'end' => 'Timestamp to stop listing at',
00544                         'metadataversion' => array( "Version of metadata to use. if 'latest' is specified, use latest version.",
00545                                                 "Defaults to '1' for backwards compatibility" ),
00546                         'continue' => 'If the query response includes a continue value, use it here to get another page of results'
00547                 );
00548         }
00549 
00550         public function getDescription() {
00551                 return 'Returns image information and upload history';
00552         }
00553 
00554         public function getPossibleErrors() {
00555                 $p = $this->getModulePrefix();
00556                 return array_merge( parent::getPossibleErrors(), array(
00557                         array( 'code' => "{$p}urlwidth", 'info' => "{$p}urlheight cannot be used without {$p}urlwidth" ),
00558                         array( 'code' => 'urlparam', 'info' => "Invalid value for {$p}urlparam" ),
00559                         array( 'code' => 'urlparam_no_width', 'info' => "{$p}urlparam requires {$p}urlwidth" ),
00560                         array( 'code' => 'urlparam_urlwidth_mismatch', 'info' => "The width set in {$p}urlparm doesnt't " .
00561                                 "match the one in {$p}urlwidth" ),
00562                 ) );
00563         }
00564 
00565         public function getExamples() {
00566                 return array(
00567                         'api.php?action=query&titles=File:Albert%20Einstein%20Head.jpg&prop=imageinfo',
00568                         'api.php?action=query&titles=File:Test.jpg&prop=imageinfo&iilimit=50&iiend=20071231235959&iiprop=timestamp|user|url',
00569                 );
00570         }
00571 
00572         public function getHelpUrls() {
00573                 return 'https://www.mediawiki.org/wiki/API:Properties#imageinfo_.2F_ii';
00574         }
00575 
00576         public function getVersion() {
00577                 return __CLASS__ . ': $Id$';
00578         }
00579 }