ApiQueryImageInfo.php 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786
  1. <?php
  2. /**
  3. * Copyright © 2006 Yuri Astrakhan "<Firstname><Lastname>@gmail.com"
  4. *
  5. * This program is free software; you can redistribute it and/or modify
  6. * it under the terms of the GNU General Public License as published by
  7. * the Free Software Foundation; either version 2 of the License, or
  8. * (at your option) any later version.
  9. *
  10. * This program is distributed in the hope that it will be useful,
  11. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13. * GNU General Public License for more details.
  14. *
  15. * You should have received a copy of the GNU General Public License along
  16. * with this program; if not, write to the Free Software Foundation, Inc.,
  17. * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
  18. * http://www.gnu.org/copyleft/gpl.html
  19. *
  20. * @file
  21. */
  22. use MediaWiki\MediaWikiServices;
  23. /**
  24. * A query action to get image information and upload history.
  25. *
  26. * @ingroup API
  27. */
  28. class ApiQueryImageInfo extends ApiQueryBase {
  29. const TRANSFORM_LIMIT = 50;
  30. private static $transformCount = 0;
  31. public function __construct( ApiQuery $query, $moduleName, $prefix = 'ii' ) {
  32. // We allow a subclass to override the prefix, to create a related API
  33. // module. Some other parts of MediaWiki construct this with a null
  34. // $prefix, which used to be ignored when this only took two arguments
  35. if ( is_null( $prefix ) ) {
  36. $prefix = 'ii';
  37. }
  38. parent::__construct( $query, $moduleName, $prefix );
  39. }
  40. public function execute() {
  41. $params = $this->extractRequestParams();
  42. $prop = array_flip( $params['prop'] );
  43. $scale = $this->getScale( $params );
  44. $opts = [
  45. 'version' => $params['metadataversion'],
  46. 'language' => $params['extmetadatalanguage'],
  47. 'multilang' => $params['extmetadatamultilang'],
  48. 'extmetadatafilter' => $params['extmetadatafilter'],
  49. 'revdelUser' => $this->getUser(),
  50. ];
  51. if ( isset( $params['badfilecontexttitle'] ) ) {
  52. $badFileContextTitle = Title::newFromText( $params['badfilecontexttitle'] );
  53. if ( !$badFileContextTitle ) {
  54. $p = $this->getModulePrefix();
  55. $this->dieWithError( [ 'apierror-bad-badfilecontexttitle', $p ], 'invalid-title' );
  56. }
  57. } else {
  58. $badFileContextTitle = null;
  59. }
  60. $pageIds = $this->getPageSet()->getGoodAndMissingTitlesByNamespace();
  61. if ( !empty( $pageIds[NS_FILE] ) ) {
  62. $titles = array_keys( $pageIds[NS_FILE] );
  63. asort( $titles ); // Ensure the order is always the same
  64. $fromTitle = null;
  65. if ( !is_null( $params['continue'] ) ) {
  66. $cont = explode( '|', $params['continue'] );
  67. $this->dieContinueUsageIf( count( $cont ) != 2 );
  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. $user = $this->getUser();
  80. $findTitles = array_map( function ( $title ) use ( $user ) {
  81. return [
  82. 'title' => $title,
  83. 'private' => $user,
  84. ];
  85. }, $titles );
  86. if ( $params['localonly'] ) {
  87. $images = RepoGroup::singleton()->getLocalRepo()->findFiles( $findTitles );
  88. } else {
  89. $images = RepoGroup::singleton()->findFiles( $findTitles );
  90. }
  91. $result = $this->getResult();
  92. foreach ( $titles as $title ) {
  93. $info = [];
  94. $pageId = $pageIds[NS_FILE][$title];
  95. $start = $title === $fromTitle ? $fromTimestamp : $params['start'];
  96. if ( !isset( $images[$title] ) ) {
  97. if ( isset( $prop['uploadwarning'] ) || isset( $prop['badfile'] ) ) {
  98. // uploadwarning and badfile need info about non-existing files
  99. $images[$title] = MediaWikiServices::getInstance()->getRepoGroup()
  100. ->getLocalRepo()->newFile( $title );
  101. // Doesn't exist, so set an empty image repository
  102. $info['imagerepository'] = '';
  103. } else {
  104. $result->addValue(
  105. [ 'query', 'pages', (int)$pageId ],
  106. 'imagerepository', ''
  107. );
  108. // The above can't fail because it doesn't increase the result size
  109. continue;
  110. }
  111. }
  112. /** @var File $img */
  113. $img = $images[$title];
  114. if ( self::getTransformCount() >= self::TRANSFORM_LIMIT ) {
  115. if ( count( $pageIds[NS_FILE] ) == 1 ) {
  116. // See the 'the user is screwed' comment below
  117. $this->setContinueEnumParameter( 'start',
  118. $start ?? wfTimestamp( TS_ISO_8601, $img->getTimestamp() )
  119. );
  120. } else {
  121. $this->setContinueEnumParameter( 'continue',
  122. $this->getContinueStr( $img, $start ) );
  123. }
  124. break;
  125. }
  126. if ( !isset( $info['imagerepository'] ) ) {
  127. $info['imagerepository'] = $img->getRepoName();
  128. }
  129. if ( isset( $prop['badfile'] ) ) {
  130. $info['badfile'] = (bool)MediaWikiServices::getInstance()->getBadFileLookup()
  131. ->isBadFile( $title, $badFileContextTitle );
  132. }
  133. $fit = $result->addValue( [ 'query', 'pages' ], (int)$pageId, $info );
  134. if ( !$fit ) {
  135. if ( count( $pageIds[NS_FILE] ) == 1 ) {
  136. // The user is screwed. imageinfo can't be solely
  137. // responsible for exceeding the limit in this case,
  138. // so set a query-continue that just returns the same
  139. // thing again. When the violating queries have been
  140. // out-continued, the result will get through
  141. $this->setContinueEnumParameter( 'start',
  142. $start ?? wfTimestamp( TS_ISO_8601, $img->getTimestamp() )
  143. );
  144. } else {
  145. $this->setContinueEnumParameter( 'continue',
  146. $this->getContinueStr( $img, $start ) );
  147. }
  148. break;
  149. }
  150. // Check if we can make the requested thumbnail, and get transform parameters.
  151. $finalThumbParams = $this->mergeThumbParams( $img, $scale, $params['urlparam'] );
  152. // Get information about the current version first
  153. // Check that the current version is within the start-end boundaries
  154. $gotOne = false;
  155. if (
  156. ( is_null( $start ) || $img->getTimestamp() <= $start ) &&
  157. ( is_null( $params['end'] ) || $img->getTimestamp() >= $params['end'] )
  158. ) {
  159. $gotOne = true;
  160. $fit = $this->addPageSubItem( $pageId,
  161. static::getInfo( $img, $prop, $result,
  162. $finalThumbParams, $opts
  163. )
  164. );
  165. if ( !$fit ) {
  166. if ( count( $pageIds[NS_FILE] ) == 1 ) {
  167. // See the 'the user is screwed' comment above
  168. $this->setContinueEnumParameter( 'start',
  169. wfTimestamp( TS_ISO_8601, $img->getTimestamp() ) );
  170. } else {
  171. $this->setContinueEnumParameter( 'continue',
  172. $this->getContinueStr( $img ) );
  173. }
  174. break;
  175. }
  176. }
  177. // Now get the old revisions
  178. // Get one more to facilitate query-continue functionality
  179. $count = ( $gotOne ? 1 : 0 );
  180. $oldies = $img->getHistory( $params['limit'] - $count + 1, $start, $params['end'] );
  181. /** @var File $oldie */
  182. foreach ( $oldies as $oldie ) {
  183. if ( ++$count > $params['limit'] ) {
  184. // We've reached the extra one which shows that there are
  185. // additional pages to be had. Stop here...
  186. // Only set a query-continue if there was only one title
  187. if ( count( $pageIds[NS_FILE] ) == 1 ) {
  188. $this->setContinueEnumParameter( 'start',
  189. wfTimestamp( TS_ISO_8601, $oldie->getTimestamp() ) );
  190. }
  191. break;
  192. }
  193. $fit = self::getTransformCount() < self::TRANSFORM_LIMIT &&
  194. $this->addPageSubItem( $pageId,
  195. static::getInfo( $oldie, $prop, $result,
  196. $finalThumbParams, $opts
  197. )
  198. );
  199. if ( !$fit ) {
  200. if ( count( $pageIds[NS_FILE] ) == 1 ) {
  201. $this->setContinueEnumParameter( 'start',
  202. wfTimestamp( TS_ISO_8601, $oldie->getTimestamp() ) );
  203. } else {
  204. $this->setContinueEnumParameter( 'continue',
  205. $this->getContinueStr( $oldie ) );
  206. }
  207. break;
  208. }
  209. }
  210. if ( !$fit ) {
  211. break;
  212. }
  213. }
  214. }
  215. }
  216. /**
  217. * From parameters, construct a 'scale' array
  218. * @param array $params Parameters passed to api.
  219. * @return array|null Key-val array of 'width' and 'height', or null
  220. */
  221. public function getScale( $params ) {
  222. if ( $params['urlwidth'] != -1 ) {
  223. $scale = [];
  224. $scale['width'] = $params['urlwidth'];
  225. $scale['height'] = $params['urlheight'];
  226. } elseif ( $params['urlheight'] != -1 ) {
  227. // Height is specified but width isn't
  228. // Don't set $scale['width']; this signals mergeThumbParams() to fill it with the image's width
  229. $scale = [];
  230. $scale['height'] = $params['urlheight'];
  231. } elseif ( $params['urlparam'] ) {
  232. // Audio files might not have a width/height.
  233. $scale = [];
  234. } else {
  235. $scale = null;
  236. }
  237. return $scale;
  238. }
  239. /** Validate and merge scale parameters with handler thumb parameters, give error if invalid.
  240. *
  241. * We do this later than getScale, since we need the image
  242. * to know which handler, since handlers can make their own parameters.
  243. * @param File $image Image that params are for.
  244. * @param array $thumbParams Thumbnail parameters from getScale
  245. * @param string $otherParams String of otherParams (iiurlparam).
  246. * @return array Array of parameters for transform.
  247. */
  248. protected function mergeThumbParams( $image, $thumbParams, $otherParams ) {
  249. if ( $thumbParams === null ) {
  250. // No scaling requested
  251. return null;
  252. }
  253. if ( !isset( $thumbParams['width'] ) && isset( $thumbParams['height'] ) ) {
  254. // We want to limit only by height in this situation, so pass the
  255. // image's full width as the limiting width. But some file types
  256. // don't have a width of their own, so pick something arbitrary so
  257. // thumbnailing the default icon works.
  258. if ( $image->getWidth() <= 0 ) {
  259. $thumbParams['width'] = max( $this->getConfig()->get( 'ThumbLimits' ) );
  260. } else {
  261. $thumbParams['width'] = $image->getWidth();
  262. }
  263. }
  264. if ( !$otherParams ) {
  265. $this->checkParameterNormalise( $image, $thumbParams );
  266. return $thumbParams;
  267. }
  268. $p = $this->getModulePrefix();
  269. $h = $image->getHandler();
  270. if ( !$h ) {
  271. $this->addWarning( [ 'apiwarn-nothumb-noimagehandler', wfEscapeWikiText( $image->getName() ) ] );
  272. return $thumbParams;
  273. }
  274. $paramList = $h->parseParamString( $otherParams );
  275. if ( !$paramList ) {
  276. // Just set a warning (instead of dieWithError), as in many cases
  277. // we could still render the image using width and height parameters,
  278. // and this type of thing could happen between different versions of
  279. // handlers.
  280. $this->addWarning( [ 'apiwarn-badurlparam', $p, wfEscapeWikiText( $image->getName() ) ] );
  281. $this->checkParameterNormalise( $image, $thumbParams );
  282. return $thumbParams;
  283. }
  284. if ( isset( $paramList['width'] ) && isset( $thumbParams['width'] ) ) {
  285. if ( (int)$paramList['width'] != (int)$thumbParams['width'] ) {
  286. $this->addWarning(
  287. [ 'apiwarn-urlparamwidth', $p, $paramList['width'], $thumbParams['width'] ]
  288. );
  289. }
  290. }
  291. foreach ( $paramList as $name => $value ) {
  292. if ( !$h->validateParam( $name, $value ) ) {
  293. $this->dieWithError(
  294. [ 'apierror-invalidurlparam', $p, wfEscapeWikiText( $name ), wfEscapeWikiText( $value ) ]
  295. );
  296. }
  297. }
  298. $finalParams = $thumbParams + $paramList;
  299. $this->checkParameterNormalise( $image, $finalParams );
  300. return $finalParams;
  301. }
  302. /**
  303. * Verify that the final image parameters can be normalised.
  304. *
  305. * This doesn't use the normalised parameters, since $file->transform
  306. * expects the pre-normalised parameters, but doing the normalisation
  307. * allows us to catch certain error conditions early (such as missing
  308. * required parameter).
  309. *
  310. * @param File $image
  311. * @param array $finalParams List of parameters to transform image with
  312. */
  313. protected function checkParameterNormalise( $image, $finalParams ) {
  314. $h = $image->getHandler();
  315. if ( !$h ) {
  316. return;
  317. }
  318. // Note: normaliseParams modifies the array in place, but we aren't interested
  319. // in the actual normalised version, only if we can actually normalise them,
  320. // so we use the functions scope to throw away the normalisations.
  321. if ( !$h->normaliseParams( $image, $finalParams ) ) {
  322. $this->dieWithError( [ 'apierror-urlparamnormal', wfEscapeWikiText( $image->getName() ) ] );
  323. }
  324. }
  325. /**
  326. * Get result information for an image revision
  327. *
  328. * @param File $file
  329. * @param array $prop Array of properties to get (in the keys)
  330. * @param ApiResult $result
  331. * @param array|null $thumbParams Containing 'width' and 'height' items, or null
  332. * @param array|bool|string $opts Options for data fetching.
  333. * This is an array consisting of the keys:
  334. * 'version': The metadata version for the metadata option
  335. * 'language': The language for extmetadata property
  336. * 'multilang': Return all translations in extmetadata property
  337. * 'revdelUser': User to use when checking whether to show revision-deleted fields.
  338. * @return array Result array
  339. */
  340. public static function getInfo( $file, $prop, $result, $thumbParams = null, $opts = false ) {
  341. $anyHidden = false;
  342. if ( !$opts || is_string( $opts ) ) {
  343. $opts = [
  344. 'version' => $opts ?: 'latest',
  345. 'language' => MediaWikiServices::getInstance()->getContentLanguage(),
  346. 'multilang' => false,
  347. 'extmetadatafilter' => [],
  348. 'revdelUser' => null,
  349. ];
  350. }
  351. $version = $opts['version'];
  352. $vals = [
  353. ApiResult::META_TYPE => 'assoc',
  354. ];
  355. // Some information will be unavailable if the file does not exist. T221812
  356. $exists = $file->exists();
  357. // Timestamp is shown even if the file is revdelete'd in interface
  358. // so do same here.
  359. if ( isset( $prop['timestamp'] ) && $exists ) {
  360. $vals['timestamp'] = wfTimestamp( TS_ISO_8601, $file->getTimestamp() );
  361. }
  362. // Handle external callers who don't pass revdelUser
  363. if ( isset( $opts['revdelUser'] ) && $opts['revdelUser'] ) {
  364. $revdelUser = $opts['revdelUser'];
  365. $canShowField = function ( $field ) use ( $file, $revdelUser ) {
  366. return $file->userCan( $field, $revdelUser );
  367. };
  368. } else {
  369. $canShowField = function ( $field ) use ( $file ) {
  370. return !$file->isDeleted( $field );
  371. };
  372. }
  373. $user = isset( $prop['user'] );
  374. $userid = isset( $prop['userid'] );
  375. if ( ( $user || $userid ) && $exists ) {
  376. if ( $file->isDeleted( File::DELETED_USER ) ) {
  377. $vals['userhidden'] = true;
  378. $anyHidden = true;
  379. }
  380. if ( $canShowField( File::DELETED_USER ) ) {
  381. if ( $user ) {
  382. $vals['user'] = $file->getUser();
  383. }
  384. if ( $userid ) {
  385. $vals['userid'] = $file->getUser( 'id' );
  386. }
  387. if ( !$file->getUser( 'id' ) ) {
  388. $vals['anon'] = true;
  389. }
  390. }
  391. }
  392. // This is shown even if the file is revdelete'd in interface
  393. // so do same here.
  394. if ( ( isset( $prop['size'] ) || isset( $prop['dimensions'] ) ) && $exists ) {
  395. $vals['size'] = (int)$file->getSize();
  396. $vals['width'] = (int)$file->getWidth();
  397. $vals['height'] = (int)$file->getHeight();
  398. $pageCount = $file->pageCount();
  399. if ( $pageCount !== false ) {
  400. $vals['pagecount'] = $pageCount;
  401. }
  402. // length as in how many seconds long a video is.
  403. $length = $file->getLength();
  404. if ( $length ) {
  405. // Call it duration, because "length" can be ambiguous.
  406. $vals['duration'] = (float)$length;
  407. }
  408. }
  409. $pcomment = isset( $prop['parsedcomment'] );
  410. $comment = isset( $prop['comment'] );
  411. if ( ( $pcomment || $comment ) && $exists ) {
  412. if ( $file->isDeleted( File::DELETED_COMMENT ) ) {
  413. $vals['commenthidden'] = true;
  414. $anyHidden = true;
  415. }
  416. if ( $canShowField( File::DELETED_COMMENT ) ) {
  417. if ( $pcomment ) {
  418. $vals['parsedcomment'] = Linker::formatComment(
  419. $file->getDescription( File::RAW ), $file->getTitle() );
  420. }
  421. if ( $comment ) {
  422. $vals['comment'] = $file->getDescription( File::RAW );
  423. }
  424. }
  425. }
  426. $canonicaltitle = isset( $prop['canonicaltitle'] );
  427. $url = isset( $prop['url'] );
  428. $sha1 = isset( $prop['sha1'] );
  429. $meta = isset( $prop['metadata'] );
  430. $extmetadata = isset( $prop['extmetadata'] );
  431. $commonmeta = isset( $prop['commonmetadata'] );
  432. $mime = isset( $prop['mime'] );
  433. $mediatype = isset( $prop['mediatype'] );
  434. $archive = isset( $prop['archivename'] );
  435. $bitdepth = isset( $prop['bitdepth'] );
  436. $uploadwarning = isset( $prop['uploadwarning'] );
  437. if ( $uploadwarning ) {
  438. $vals['html'] = SpecialUpload::getExistsWarning( UploadBase::getExistsWarning( $file ) );
  439. }
  440. if ( $file->isDeleted( File::DELETED_FILE ) ) {
  441. $vals['filehidden'] = true;
  442. $anyHidden = true;
  443. }
  444. if ( $anyHidden && $file->isDeleted( File::DELETED_RESTRICTED ) ) {
  445. $vals['suppressed'] = true;
  446. }
  447. if ( !$canShowField( File::DELETED_FILE ) ) {
  448. // Early return, tidier than indenting all following things one level
  449. return $vals;
  450. }
  451. if ( $canonicaltitle ) {
  452. $vals['canonicaltitle'] = $file->getTitle()->getPrefixedText();
  453. }
  454. if ( $url ) {
  455. if ( $exists ) {
  456. if ( !is_null( $thumbParams ) ) {
  457. $mto = $file->transform( $thumbParams );
  458. self::$transformCount++;
  459. if ( $mto && !$mto->isError() ) {
  460. $vals['thumburl'] = wfExpandUrl( $mto->getUrl(), PROTO_CURRENT );
  461. // T25834 - If the URLs are the same, we haven't resized it, so shouldn't give the wanted
  462. // thumbnail sizes for the thumbnail actual size
  463. if ( $mto->getUrl() !== $file->getUrl() ) {
  464. $vals['thumbwidth'] = (int)$mto->getWidth();
  465. $vals['thumbheight'] = (int)$mto->getHeight();
  466. } else {
  467. $vals['thumbwidth'] = (int)$file->getWidth();
  468. $vals['thumbheight'] = (int)$file->getHeight();
  469. }
  470. if ( isset( $prop['thumbmime'] ) && $file->getHandler() ) {
  471. list( , $mime ) = $file->getHandler()->getThumbType(
  472. $mto->getExtension(), $file->getMimeType(), $thumbParams );
  473. $vals['thumbmime'] = $mime;
  474. }
  475. } elseif ( $mto && $mto->isError() ) {
  476. /** @var MediaTransformError $mto */
  477. '@phan-var MediaTransformError $mto';
  478. $vals['thumberror'] = $mto->toText();
  479. }
  480. }
  481. $vals['url'] = wfExpandUrl( $file->getFullUrl(), PROTO_CURRENT );
  482. }
  483. $vals['descriptionurl'] = wfExpandUrl( $file->getDescriptionUrl(), PROTO_CURRENT );
  484. $shortDescriptionUrl = $file->getDescriptionShortUrl();
  485. if ( $shortDescriptionUrl !== null ) {
  486. $vals['descriptionshorturl'] = wfExpandUrl( $shortDescriptionUrl, PROTO_CURRENT );
  487. }
  488. }
  489. if ( !$exists ) {
  490. $vals['filemissing'] = true;
  491. }
  492. if ( $sha1 && $exists ) {
  493. $vals['sha1'] = Wikimedia\base_convert( $file->getSha1(), 36, 16, 40 );
  494. }
  495. if ( $meta && $exists ) {
  496. Wikimedia\suppressWarnings();
  497. $metadata = unserialize( $file->getMetadata() );
  498. Wikimedia\restoreWarnings();
  499. if ( $metadata && $version !== 'latest' ) {
  500. $metadata = $file->convertMetadataVersion( $metadata, $version );
  501. }
  502. $vals['metadata'] = $metadata ? static::processMetaData( $metadata, $result ) : null;
  503. }
  504. if ( $commonmeta && $exists ) {
  505. $metaArray = $file->getCommonMetaArray();
  506. $vals['commonmetadata'] = $metaArray ? static::processMetaData( $metaArray, $result ) : [];
  507. }
  508. if ( $extmetadata && $exists ) {
  509. // Note, this should return an array where all the keys
  510. // start with a letter, and all the values are strings.
  511. // Thus there should be no issue with format=xml.
  512. $format = new FormatMetadata;
  513. $format->setSingleLanguage( !$opts['multilang'] );
  514. // @phan-suppress-next-line PhanUndeclaredMethod
  515. $format->getContext()->setLanguage( $opts['language'] );
  516. $extmetaArray = $format->fetchExtendedMetadata( $file );
  517. if ( $opts['extmetadatafilter'] ) {
  518. $extmetaArray = array_intersect_key(
  519. $extmetaArray, array_flip( $opts['extmetadatafilter'] )
  520. );
  521. }
  522. $vals['extmetadata'] = $extmetaArray;
  523. }
  524. if ( $mime && $exists ) {
  525. $vals['mime'] = $file->getMimeType();
  526. }
  527. if ( $mediatype && $exists ) {
  528. $vals['mediatype'] = $file->getMediaType();
  529. }
  530. if ( $archive && $file->isOld() ) {
  531. /** @var OldLocalFile $file */
  532. '@phan-var OldLocalFile $file';
  533. $vals['archivename'] = $file->getArchiveName();
  534. }
  535. if ( $bitdepth && $exists ) {
  536. $vals['bitdepth'] = $file->getBitDepth();
  537. }
  538. return $vals;
  539. }
  540. /**
  541. * Get the count of image transformations performed
  542. *
  543. * If this is >= TRANSFORM_LIMIT, you should probably stop processing images.
  544. *
  545. * @return int Count
  546. */
  547. static function getTransformCount() {
  548. return self::$transformCount;
  549. }
  550. /**
  551. *
  552. * @param array $metadata
  553. * @param ApiResult $result
  554. * @return array
  555. */
  556. public static function processMetaData( $metadata, $result ) {
  557. $retval = [];
  558. if ( is_array( $metadata ) ) {
  559. foreach ( $metadata as $key => $value ) {
  560. $r = [
  561. 'name' => $key,
  562. ApiResult::META_BC_BOOLS => [ 'value' ],
  563. ];
  564. if ( is_array( $value ) ) {
  565. $r['value'] = static::processMetaData( $value, $result );
  566. } else {
  567. $r['value'] = $value;
  568. }
  569. $retval[] = $r;
  570. }
  571. }
  572. ApiResult::setIndexedTagName( $retval, 'metadata' );
  573. return $retval;
  574. }
  575. public function getCacheMode( $params ) {
  576. if ( $this->userCanSeeRevDel() ) {
  577. return 'private';
  578. }
  579. return 'public';
  580. }
  581. /**
  582. * @param File $img
  583. * @param null|string $start
  584. * @return string
  585. */
  586. protected function getContinueStr( $img, $start = null ) {
  587. if ( $start === null ) {
  588. $start = $img->getTimestamp();
  589. }
  590. return $img->getOriginalTitle()->getDBkey() . '|' . $start;
  591. }
  592. public function getAllowedParams() {
  593. return [
  594. 'prop' => [
  595. ApiBase::PARAM_ISMULTI => true,
  596. ApiBase::PARAM_DFLT => 'timestamp|user',
  597. ApiBase::PARAM_TYPE => static::getPropertyNames(),
  598. ApiBase::PARAM_HELP_MSG_PER_VALUE => static::getPropertyMessages(),
  599. ],
  600. 'limit' => [
  601. ApiBase::PARAM_TYPE => 'limit',
  602. ApiBase::PARAM_DFLT => 1,
  603. ApiBase::PARAM_MIN => 1,
  604. ApiBase::PARAM_MAX => ApiBase::LIMIT_BIG1,
  605. ApiBase::PARAM_MAX2 => ApiBase::LIMIT_BIG2
  606. ],
  607. 'start' => [
  608. ApiBase::PARAM_TYPE => 'timestamp'
  609. ],
  610. 'end' => [
  611. ApiBase::PARAM_TYPE => 'timestamp'
  612. ],
  613. 'urlwidth' => [
  614. ApiBase::PARAM_TYPE => 'integer',
  615. ApiBase::PARAM_DFLT => -1,
  616. ApiBase::PARAM_HELP_MSG => [
  617. 'apihelp-query+imageinfo-param-urlwidth',
  618. self::TRANSFORM_LIMIT,
  619. ],
  620. ],
  621. 'urlheight' => [
  622. ApiBase::PARAM_TYPE => 'integer',
  623. ApiBase::PARAM_DFLT => -1
  624. ],
  625. 'metadataversion' => [
  626. ApiBase::PARAM_TYPE => 'string',
  627. ApiBase::PARAM_DFLT => '1',
  628. ],
  629. 'extmetadatalanguage' => [
  630. ApiBase::PARAM_TYPE => 'string',
  631. ApiBase::PARAM_DFLT =>
  632. MediaWikiServices::getInstance()->getContentLanguage()->getCode(),
  633. ],
  634. 'extmetadatamultilang' => [
  635. ApiBase::PARAM_TYPE => 'boolean',
  636. ApiBase::PARAM_DFLT => false,
  637. ],
  638. 'extmetadatafilter' => [
  639. ApiBase::PARAM_TYPE => 'string',
  640. ApiBase::PARAM_ISMULTI => true,
  641. ],
  642. 'urlparam' => [
  643. ApiBase::PARAM_DFLT => '',
  644. ApiBase::PARAM_TYPE => 'string',
  645. ],
  646. 'badfilecontexttitle' => [
  647. ApiBase::PARAM_TYPE => 'string',
  648. ],
  649. 'continue' => [
  650. ApiBase::PARAM_HELP_MSG => 'api-help-param-continue',
  651. ],
  652. 'localonly' => false,
  653. ];
  654. }
  655. /**
  656. * Returns all possible parameters to iiprop
  657. *
  658. * @param array $filter List of properties to filter out
  659. * @return array
  660. */
  661. public static function getPropertyNames( $filter = [] ) {
  662. return array_keys( static::getPropertyMessages( $filter ) );
  663. }
  664. /**
  665. * Returns messages for all possible parameters to iiprop
  666. *
  667. * @param array $filter List of properties to filter out
  668. * @return array
  669. */
  670. public static function getPropertyMessages( $filter = [] ) {
  671. return array_diff_key(
  672. [
  673. 'timestamp' => 'apihelp-query+imageinfo-paramvalue-prop-timestamp',
  674. 'user' => 'apihelp-query+imageinfo-paramvalue-prop-user',
  675. 'userid' => 'apihelp-query+imageinfo-paramvalue-prop-userid',
  676. 'comment' => 'apihelp-query+imageinfo-paramvalue-prop-comment',
  677. 'parsedcomment' => 'apihelp-query+imageinfo-paramvalue-prop-parsedcomment',
  678. 'canonicaltitle' => 'apihelp-query+imageinfo-paramvalue-prop-canonicaltitle',
  679. 'url' => 'apihelp-query+imageinfo-paramvalue-prop-url',
  680. 'size' => 'apihelp-query+imageinfo-paramvalue-prop-size',
  681. 'dimensions' => 'apihelp-query+imageinfo-paramvalue-prop-dimensions',
  682. 'sha1' => 'apihelp-query+imageinfo-paramvalue-prop-sha1',
  683. 'mime' => 'apihelp-query+imageinfo-paramvalue-prop-mime',
  684. 'thumbmime' => 'apihelp-query+imageinfo-paramvalue-prop-thumbmime',
  685. 'mediatype' => 'apihelp-query+imageinfo-paramvalue-prop-mediatype',
  686. 'metadata' => 'apihelp-query+imageinfo-paramvalue-prop-metadata',
  687. 'commonmetadata' => 'apihelp-query+imageinfo-paramvalue-prop-commonmetadata',
  688. 'extmetadata' => 'apihelp-query+imageinfo-paramvalue-prop-extmetadata',
  689. 'archivename' => 'apihelp-query+imageinfo-paramvalue-prop-archivename',
  690. 'bitdepth' => 'apihelp-query+imageinfo-paramvalue-prop-bitdepth',
  691. 'uploadwarning' => 'apihelp-query+imageinfo-paramvalue-prop-uploadwarning',
  692. 'badfile' => 'apihelp-query+imageinfo-paramvalue-prop-badfile',
  693. ],
  694. array_flip( $filter )
  695. );
  696. }
  697. protected function getExamplesMessages() {
  698. return [
  699. 'action=query&titles=File:Albert%20Einstein%20Head.jpg&prop=imageinfo'
  700. => 'apihelp-query+imageinfo-example-simple',
  701. 'action=query&titles=File:Test.jpg&prop=imageinfo&iilimit=50&' .
  702. 'iiend=2007-12-31T23:59:59Z&iiprop=timestamp|user|url'
  703. => 'apihelp-query+imageinfo-example-dated',
  704. ];
  705. }
  706. public function getHelpUrls() {
  707. return 'https://www.mediawiki.org/wiki/Special:MyLanguage/API:Imageinfo';
  708. }
  709. }