MediaWiki
master
|
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 //search only inside the local repo 00077 if( $params['localonly'] ) { 00078 $images = RepoGroup::singleton()->getLocalRepo()->findFiles( $titles ); 00079 } else { 00080 $images = RepoGroup::singleton()->findFiles( $titles ); 00081 } 00082 foreach ( $images as $img ) { 00083 // Skip redirects 00084 if ( $img->getOriginalTitle()->isRedirect() ) { 00085 continue; 00086 } 00087 00088 $start = $skip ? $fromTimestamp : $params['start']; 00089 $pageId = $pageIds[NS_FILE][ $img->getOriginalTitle()->getDBkey() ]; 00090 00091 $fit = $result->addValue( 00092 array( 'query', 'pages', intval( $pageId ) ), 00093 'imagerepository', $img->getRepoName() 00094 ); 00095 if ( !$fit ) { 00096 if ( count( $pageIds[NS_FILE] ) == 1 ) { 00097 // The user is screwed. imageinfo can't be solely 00098 // responsible for exceeding the limit in this case, 00099 // so set a query-continue that just returns the same 00100 // thing again. When the violating queries have been 00101 // out-continued, the result will get through 00102 $this->setContinueEnumParameter( 'start', 00103 wfTimestamp( TS_ISO_8601, $img->getTimestamp() ) ); 00104 } else { 00105 $this->setContinueEnumParameter( 'continue', 00106 $this->getContinueStr( $img ) ); 00107 } 00108 break; 00109 } 00110 00111 // Check if we can make the requested thumbnail, and get transform parameters. 00112 $finalThumbParams = $this->mergeThumbParams( $img, $scale, $params['urlparam'] ); 00113 00114 // Get information about the current version first 00115 // Check that the current version is within the start-end boundaries 00116 $gotOne = false; 00117 if ( 00118 ( is_null( $start ) || $img->getTimestamp() <= $start ) && 00119 ( is_null( $params['end'] ) || $img->getTimestamp() >= $params['end'] ) 00120 ) { 00121 $gotOne = true; 00122 00123 $fit = $this->addPageSubItem( $pageId, 00124 self::getInfo( $img, $prop, $result, 00125 $finalThumbParams, $params['metadataversion'] ) ); 00126 if ( !$fit ) { 00127 if ( count( $pageIds[NS_FILE] ) == 1 ) { 00128 // See the 'the user is screwed' comment above 00129 $this->setContinueEnumParameter( 'start', 00130 wfTimestamp( TS_ISO_8601, $img->getTimestamp() ) ); 00131 } else { 00132 $this->setContinueEnumParameter( 'continue', 00133 $this->getContinueStr( $img ) ); 00134 } 00135 break; 00136 } 00137 } 00138 00139 // Now get the old revisions 00140 // Get one more to facilitate query-continue functionality 00141 $count = ( $gotOne ? 1 : 0 ); 00142 $oldies = $img->getHistory( $params['limit'] - $count + 1, $start, $params['end'] ); 00143 foreach ( $oldies as $oldie ) { 00144 if ( ++$count > $params['limit'] ) { 00145 // We've reached the extra one which shows that there are additional pages to be had. Stop here... 00146 // Only set a query-continue if there was only one title 00147 if ( count( $pageIds[NS_FILE] ) == 1 ) { 00148 $this->setContinueEnumParameter( 'start', 00149 wfTimestamp( TS_ISO_8601, $oldie->getTimestamp() ) ); 00150 } 00151 break; 00152 } 00153 $fit = $this->addPageSubItem( $pageId, 00154 self::getInfo( $oldie, $prop, $result, 00155 $finalThumbParams, $params['metadataversion'] ) ); 00156 if ( !$fit ) { 00157 if ( count( $pageIds[NS_FILE] ) == 1 ) { 00158 $this->setContinueEnumParameter( 'start', 00159 wfTimestamp( TS_ISO_8601, $oldie->getTimestamp() ) ); 00160 } else { 00161 $this->setContinueEnumParameter( 'continue', 00162 $this->getContinueStr( $oldie ) ); 00163 } 00164 break; 00165 } 00166 } 00167 if ( !$fit ) { 00168 break; 00169 } 00170 $skip = false; 00171 } 00172 00173 $data = $this->getResultData(); 00174 foreach ( $data['query']['pages'] as $pageid => $arr ) { 00175 if ( is_array( $arr ) && !isset( $arr['imagerepository'] ) ) { 00176 $result->addValue( 00177 array( 'query', 'pages', $pageid ), 00178 'imagerepository', '' 00179 ); 00180 } 00181 // The above can't fail because it doesn't increase the result size 00182 } 00183 } 00184 } 00185 00191 public function getScale( $params ) { 00192 $p = $this->getModulePrefix(); 00193 00194 // Height and width. 00195 if ( $params['urlheight'] != -1 && $params['urlwidth'] == -1 ) { 00196 $this->dieUsage( "{$p}urlheight cannot be used without {$p}urlwidth", "{$p}urlwidth" ); 00197 } 00198 00199 if ( $params['urlwidth'] != -1 ) { 00200 $scale = array(); 00201 $scale['width'] = $params['urlwidth']; 00202 $scale['height'] = $params['urlheight']; 00203 } else { 00204 $scale = null; 00205 if ( $params['urlparam'] ) { 00206 $this->dieUsage( "{$p}urlparam requires {$p}urlwidth", "urlparam_no_width" ); 00207 } 00208 return $scale; 00209 } 00210 00211 return $scale; 00212 } 00213 00223 protected function mergeThumbParams ( $image, $thumbParams, $otherParams ) { 00224 if ( !$otherParams ) { 00225 return $thumbParams; 00226 } 00227 $p = $this->getModulePrefix(); 00228 00229 $h = $image->getHandler(); 00230 if ( !$h ) { 00231 $this->setWarning( 'Could not create thumbnail because ' . 00232 $image->getName() . ' does not have an associated image handler' ); 00233 return $thumbParams; 00234 } 00235 00236 $paramList = $h->parseParamString( $otherParams ); 00237 if ( !$paramList ) { 00238 // Just set a warning (instead of dieUsage), as in many cases 00239 // we could still render the image using width and height parameters, 00240 // and this type of thing could happen between different versions of 00241 // handlers. 00242 $this->setWarning( "Could not parse {$p}urlparam for " . $image->getName() 00243 . '. Using only width and height' ); 00244 return $thumbParams; 00245 } 00246 00247 if ( isset( $paramList['width'] ) ) { 00248 if ( intval( $paramList['width'] ) != intval( $thumbParams['width'] ) ) { 00249 $this->dieUsage( "{$p}urlparam had width of {$paramList['width']} but " 00250 . "{$p}urlwidth was {$thumbParams['width']}", "urlparam_urlwidth_mismatch" ); 00251 } 00252 } 00253 00254 foreach ( $paramList as $name => $value ) { 00255 if ( !$h->validateParam( $name, $value ) ) { 00256 $this->dieUsage( "Invalid value for {$p}urlparam ($name=$value)", "urlparam" ); 00257 } 00258 } 00259 00260 return $thumbParams + $paramList; 00261 } 00262 00273 static function getInfo( $file, $prop, $result, $thumbParams = null, $version = 'latest' ) { 00274 $vals = array(); 00275 // Timestamp is shown even if the file is revdelete'd in interface 00276 // so do same here. 00277 if ( isset( $prop['timestamp'] ) ) { 00278 $vals['timestamp'] = wfTimestamp( TS_ISO_8601, $file->getTimestamp() ); 00279 } 00280 00281 $user = isset( $prop['user'] ); 00282 $userid = isset( $prop['userid'] ); 00283 00284 if ( $user || $userid ) { 00285 if ( $file->isDeleted( File::DELETED_USER ) ) { 00286 $vals['userhidden'] = ''; 00287 } else { 00288 if ( $user ) { 00289 $vals['user'] = $file->getUser(); 00290 } 00291 if ( $userid ) { 00292 $vals['userid'] = $file->getUser( 'id' ); 00293 } 00294 if ( !$file->getUser( 'id' ) ) { 00295 $vals['anon'] = ''; 00296 } 00297 } 00298 } 00299 00300 // This is shown even if the file is revdelete'd in interface 00301 // so do same here. 00302 if ( isset( $prop['size'] ) || isset( $prop['dimensions'] ) ) { 00303 $vals['size'] = intval( $file->getSize() ); 00304 $vals['width'] = intval( $file->getWidth() ); 00305 $vals['height'] = intval( $file->getHeight() ); 00306 00307 $pageCount = $file->pageCount(); 00308 if ( $pageCount !== false ) { 00309 $vals['pagecount'] = $pageCount; 00310 } 00311 } 00312 00313 $pcomment = isset( $prop['parsedcomment'] ); 00314 $comment = isset( $prop['comment'] ); 00315 00316 if ( $pcomment || $comment ) { 00317 if ( $file->isDeleted( File::DELETED_COMMENT ) ) { 00318 $vals['commenthidden'] = ''; 00319 } else { 00320 if ( $pcomment ) { 00321 $vals['parsedcomment'] = Linker::formatComment( 00322 $file->getDescription(), $file->getTitle() ); 00323 } 00324 if ( $comment ) { 00325 $vals['comment'] = $file->getDescription(); 00326 } 00327 } 00328 } 00329 00330 $url = isset( $prop['url'] ); 00331 $sha1 = isset( $prop['sha1'] ); 00332 $meta = isset( $prop['metadata'] ); 00333 $mime = isset( $prop['mime'] ); 00334 $mediatype = isset( $prop['mediatype'] ); 00335 $archive = isset( $prop['archivename'] ); 00336 $bitdepth = isset( $prop['bitdepth'] ); 00337 00338 if ( ( $url || $sha1 || $meta || $mime || $mediatype || $archive || $bitdepth ) 00339 && $file->isDeleted( File::DELETED_FILE ) ) { 00340 $vals['filehidden'] = ''; 00341 00342 //Early return, tidier than indenting all following things one level 00343 return $vals; 00344 } 00345 00346 if ( $url ) { 00347 if ( !is_null( $thumbParams ) ) { 00348 $mto = $file->transform( $thumbParams ); 00349 if ( $mto && !$mto->isError() ) { 00350 $vals['thumburl'] = wfExpandUrl( $mto->getUrl(), PROTO_CURRENT ); 00351 00352 // bug 23834 - If the URL's are the same, we haven't resized it, so shouldn't give the wanted 00353 // thumbnail sizes for the thumbnail actual size 00354 if ( $mto->getUrl() !== $file->getUrl() ) { 00355 $vals['thumbwidth'] = intval( $mto->getWidth() ); 00356 $vals['thumbheight'] = intval( $mto->getHeight() ); 00357 } else { 00358 $vals['thumbwidth'] = intval( $file->getWidth() ); 00359 $vals['thumbheight'] = intval( $file->getHeight() ); 00360 } 00361 00362 if ( isset( $prop['thumbmime'] ) && $file->getHandler() ) { 00363 list( $ext, $mime ) = $file->getHandler()->getThumbType( 00364 $mto->getExtension(), $file->getMimeType(), $thumbParams ); 00365 $vals['thumbmime'] = $mime; 00366 } 00367 } elseif ( $mto && $mto->isError() ) { 00368 $vals['thumberror'] = $mto->toText(); 00369 } 00370 } 00371 $vals['url'] = wfExpandUrl( $file->getFullURL(), PROTO_CURRENT ); 00372 $vals['descriptionurl'] = wfExpandUrl( $file->getDescriptionUrl(), PROTO_CURRENT ); 00373 } 00374 00375 if ( $sha1 ) { 00376 $vals['sha1'] = wfBaseConvert( $file->getSha1(), 36, 16, 40 ); 00377 } 00378 00379 if ( $meta ) { 00380 wfSuppressWarnings(); 00381 $metadata = unserialize( $file->getMetadata() ); 00382 wfRestoreWarnings(); 00383 if ( $metadata && $version !== 'latest' ) { 00384 $metadata = $file->convertMetadataVersion( $metadata, $version ); 00385 } 00386 $vals['metadata'] = $metadata ? self::processMetaData( $metadata, $result ) : null; 00387 } 00388 00389 if ( $mime ) { 00390 $vals['mime'] = $file->getMimeType(); 00391 } 00392 00393 if ( $mediatype ) { 00394 $vals['mediatype'] = $file->getMediaType(); 00395 } 00396 00397 if ( $archive && $file->isOld() ) { 00398 $vals['archivename'] = $file->getArchiveName(); 00399 } 00400 00401 if ( $bitdepth ) { 00402 $vals['bitdepth'] = $file->getBitDepth(); 00403 } 00404 00405 return $vals; 00406 } 00407 00414 public static function processMetaData( $metadata, $result ) { 00415 $retval = array(); 00416 if ( is_array( $metadata ) ) { 00417 foreach ( $metadata as $key => $value ) { 00418 $r = array( 'name' => $key ); 00419 if ( is_array( $value ) ) { 00420 $r['value'] = self::processMetaData( $value, $result ); 00421 } else { 00422 $r['value'] = $value; 00423 } 00424 $retval[] = $r; 00425 } 00426 } 00427 $result->setIndexedTagName( $retval, 'metadata' ); 00428 return $retval; 00429 } 00430 00431 public function getCacheMode( $params ) { 00432 return 'public'; 00433 } 00434 00439 protected function getContinueStr( $img ) { 00440 return $img->getOriginalTitle()->getText() . 00441 '|' . $img->getTimestamp(); 00442 } 00443 00444 public function getAllowedParams() { 00445 return array( 00446 'prop' => array( 00447 ApiBase::PARAM_ISMULTI => true, 00448 ApiBase::PARAM_DFLT => 'timestamp|user', 00449 ApiBase::PARAM_TYPE => self::getPropertyNames() 00450 ), 00451 'limit' => array( 00452 ApiBase::PARAM_TYPE => 'limit', 00453 ApiBase::PARAM_DFLT => 1, 00454 ApiBase::PARAM_MIN => 1, 00455 ApiBase::PARAM_MAX => ApiBase::LIMIT_BIG1, 00456 ApiBase::PARAM_MAX2 => ApiBase::LIMIT_BIG2 00457 ), 00458 'start' => array( 00459 ApiBase::PARAM_TYPE => 'timestamp' 00460 ), 00461 'end' => array( 00462 ApiBase::PARAM_TYPE => 'timestamp' 00463 ), 00464 'urlwidth' => array( 00465 ApiBase::PARAM_TYPE => 'integer', 00466 ApiBase::PARAM_DFLT => -1 00467 ), 00468 'urlheight' => array( 00469 ApiBase::PARAM_TYPE => 'integer', 00470 ApiBase::PARAM_DFLT => -1 00471 ), 00472 'metadataversion' => array( 00473 ApiBase::PARAM_TYPE => 'string', 00474 ApiBase::PARAM_DFLT => '1', 00475 ), 00476 'urlparam' => array( 00477 ApiBase::PARAM_DFLT => '', 00478 ApiBase::PARAM_TYPE => 'string', 00479 ), 00480 'continue' => null, 00481 'localonly' => false, 00482 ); 00483 } 00484 00492 public static function getPropertyNames( $filter = array() ) { 00493 return array_diff( array_keys( self::getProperties() ), $filter ); 00494 } 00495 00501 private static function getProperties( $modulePrefix = '' ) { 00502 return array( 00503 'timestamp' => ' timestamp - Adds timestamp for the uploaded version', 00504 'user' => ' user - Adds the user who uploaded the image version', 00505 'userid' => ' userid - Add the user ID that uploaded the image version', 00506 'comment' => ' comment - Comment on the version', 00507 'parsedcomment' => ' parsedcomment - Parse the comment on the version', 00508 'url' => ' url - Gives URL to the image and the description page', 00509 'size' => ' size - Adds the size of the image in bytes and the height, width and page count (if applicable)', 00510 'dimensions' => ' dimensions - Alias for size', // For backwards compatibility with Allimages 00511 'sha1' => ' sha1 - Adds SHA-1 hash for the image', 00512 'mime' => ' mime - Adds MIME type of the image', 00513 'thumbmime' => ' thumbmime - Adds MIME type of the image thumbnail' . 00514 ' (requires url and param ' . $modulePrefix . 'urlwidth)', 00515 'mediatype' => ' mediatype - Adds the media type of the image', 00516 'metadata' => ' metadata - Lists EXIF metadata for the version of the image', 00517 'archivename' => ' archivename - Adds the file name of the archive version for non-latest versions', 00518 'bitdepth' => ' bitdepth - Adds the bit depth of the version', 00519 ); 00520 } 00521 00529 public static function getPropertyDescriptions( $filter = array(), $modulePrefix = '' ) { 00530 return array_merge( 00531 array( 'What image information to get:' ), 00532 array_values( array_diff_key( self::getProperties( $modulePrefix ), array_flip( $filter ) ) ) 00533 ); 00534 } 00535 00540 public function getParamDescription() { 00541 $p = $this->getModulePrefix(); 00542 return array( 00543 'prop' => self::getPropertyDescriptions( array(), $p ), 00544 'urlwidth' => array( "If {$p}prop=url is set, a URL to an image scaled to this width will be returned.", 00545 'Only the current version of the image can be scaled' ), 00546 'urlheight' => "Similar to {$p}urlwidth. Cannot be used without {$p}urlwidth", 00547 'urlparam' => array( "A handler specific parameter string. For example, pdf's ", 00548 "might use 'page15-100px'. {$p}urlwidth must be used and be consistent with {$p}urlparam" ), 00549 'limit' => 'How many image revisions to return', 00550 'start' => 'Timestamp to start listing from', 00551 'end' => 'Timestamp to stop listing at', 00552 'metadataversion' => array( "Version of metadata to use. if 'latest' is specified, use latest version.", 00553 "Defaults to '1' for backwards compatibility" ), 00554 'continue' => 'If the query response includes a continue value, use it here to get another page of results', 00555 'localonly' => 'Look only for files in the local repository', 00556 ); 00557 } 00558 00559 public static function getResultPropertiesFiltered( $filter = array() ) { 00560 $props = array( 00561 'timestamp' => array( 00562 'timestamp' => 'timestamp' 00563 ), 00564 'user' => array( 00565 'userhidden' => 'boolean', 00566 'user' => 'string', 00567 'anon' => 'boolean' 00568 ), 00569 'userid' => array( 00570 'userhidden' => 'boolean', 00571 'userid' => 'integer', 00572 'anon' => 'boolean' 00573 ), 00574 'size' => array( 00575 'size' => 'integer', 00576 'width' => 'integer', 00577 'height' => 'integer', 00578 'pagecount' => array( 00579 ApiBase::PROP_TYPE => 'integer', 00580 ApiBase::PROP_NULLABLE => true 00581 ) 00582 ), 00583 'comment' => array( 00584 'commenthidden' => 'boolean', 00585 'comment' => array( 00586 ApiBase::PROP_TYPE => 'string', 00587 ApiBase::PROP_NULLABLE => true 00588 ) 00589 ), 00590 'parsedcomment' => array( 00591 'commenthidden' => 'boolean', 00592 'parsedcomment' => array( 00593 ApiBase::PROP_TYPE => 'string', 00594 ApiBase::PROP_NULLABLE => true 00595 ) 00596 ), 00597 'url' => array( 00598 'filehidden' => 'boolean', 00599 'thumburl' => array( 00600 ApiBase::PROP_TYPE => 'string', 00601 ApiBase::PROP_NULLABLE => true 00602 ), 00603 'thumbwidth' => array( 00604 ApiBase::PROP_TYPE => 'integer', 00605 ApiBase::PROP_NULLABLE => true 00606 ), 00607 'thumbheight' => array( 00608 ApiBase::PROP_TYPE => 'integer', 00609 ApiBase::PROP_NULLABLE => true 00610 ), 00611 'thumberror' => array( 00612 ApiBase::PROP_TYPE => 'string', 00613 ApiBase::PROP_NULLABLE => true 00614 ), 00615 'url' => array( 00616 ApiBase::PROP_TYPE => 'string', 00617 ApiBase::PROP_NULLABLE => true 00618 ), 00619 'descriptionurl' => array( 00620 ApiBase::PROP_TYPE => 'string', 00621 ApiBase::PROP_NULLABLE => true 00622 ) 00623 ), 00624 'sha1' => array( 00625 'filehidden' => 'boolean', 00626 'sha1' => array( 00627 ApiBase::PROP_TYPE => 'string', 00628 ApiBase::PROP_NULLABLE => true 00629 ) 00630 ), 00631 'mime' => array( 00632 'filehidden' => 'boolean', 00633 'mime' => array( 00634 ApiBase::PROP_TYPE => 'string', 00635 ApiBase::PROP_NULLABLE => true 00636 ) 00637 ), 00638 'mediatype' => array( 00639 'filehidden' => 'boolean', 00640 'mediatype' => array( 00641 ApiBase::PROP_TYPE => 'string', 00642 ApiBase::PROP_NULLABLE => true 00643 ) 00644 ), 00645 'archivename' => array( 00646 'filehidden' => 'boolean', 00647 'archivename' => array( 00648 ApiBase::PROP_TYPE => 'string', 00649 ApiBase::PROP_NULLABLE => true 00650 ) 00651 ), 00652 'bitdepth' => array( 00653 'filehidden' => 'boolean', 00654 'bitdepth' => array( 00655 ApiBase::PROP_TYPE => 'integer', 00656 ApiBase::PROP_NULLABLE => true 00657 ) 00658 ), 00659 ); 00660 return array_diff_key( $props, array_flip( $filter ) ); 00661 } 00662 00663 public function getResultProperties() { 00664 return self::getResultPropertiesFiltered(); 00665 } 00666 00667 public function getDescription() { 00668 return 'Returns image information and upload history'; 00669 } 00670 00671 public function getPossibleErrors() { 00672 $p = $this->getModulePrefix(); 00673 return array_merge( parent::getPossibleErrors(), array( 00674 array( 'code' => "{$p}urlwidth", 'info' => "{$p}urlheight cannot be used without {$p}urlwidth" ), 00675 array( 'code' => 'urlparam', 'info' => "Invalid value for {$p}urlparam" ), 00676 array( 'code' => 'urlparam_no_width', 'info' => "{$p}urlparam requires {$p}urlwidth" ), 00677 array( 'code' => 'urlparam_urlwidth_mismatch', 'info' => "The width set in {$p}urlparm doesnt't " . 00678 "match the one in {$p}urlwidth" ), 00679 ) ); 00680 } 00681 00682 public function getExamples() { 00683 return array( 00684 'api.php?action=query&titles=File:Albert%20Einstein%20Head.jpg&prop=imageinfo', 00685 'api.php?action=query&titles=File:Test.jpg&prop=imageinfo&iilimit=50&iiend=20071231235959&iiprop=timestamp|user|url', 00686 ); 00687 } 00688 00689 public function getHelpUrls() { 00690 return 'https://www.mediawiki.org/wiki/API:Properties#imageinfo_.2F_ii'; 00691 } 00692 00693 public function getVersion() { 00694 return __CLASS__ . ': $Id$'; 00695 } 00696 }