3 * This program is free software; you can redistribute it and/or modify
4 * it under the terms of the GNU General Public License as published by
5 * the Free Software Foundation; either version 2 of the License, or
6 * (at your option) any later version.
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
13 * You should have received a copy of the GNU General Public License along
14 * with this program; if not, write to the Free Software Foundation, Inc.,
15 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 * http://www.gnu.org/copyleft/gpl.html
21 use MediaWiki\MediaWikiServices
;
22 use MediaWiki\Permissions\Authority
;
23 use MediaWiki\Title\Title
;
24 use MediaWiki\User\UserIdentity
;
25 use MediaWiki\User\UserIdentityValue
;
28 * Foreign file accessible through api.php requests.
30 * @ingroup FileAbstraction
32 class ForeignAPIFile
extends File
{
39 protected $repoClass = ForeignAPIRepo
::class;
42 * @param Title|string|false $title
43 * @param ForeignApiRepo $repo
47 public function __construct( $title, $repo, $info, $exists = false ) {
48 parent
::__construct( $title, $repo );
51 $this->mExists
= $exists;
53 $this->assertRepoDefined();
58 * @param ForeignApiRepo $repo
59 * @return ForeignAPIFile|null
61 public static function newFromTitle( Title
$title, $repo ) {
62 $data = $repo->fetchImageQuery( [
63 'titles' => 'File:' . $title->getDBkey(),
64 'iiprop' => self
::getProps(),
65 'prop' => 'imageinfo',
66 'iimetadataversion' => MediaHandler
::getMetadataVersion(),
67 // extmetadata is language-dependent, accessing the current language here
68 // would be problematic, so we just get them all
69 'iiextmetadatamultilang' => 1,
72 $info = $repo->getImageInfo( $data );
75 $lastRedirect = count( $data['query']['redirects'] ??
[] ) - 1;
76 if ( $lastRedirect >= 0 ) {
77 // @phan-suppress-next-line PhanTypeArraySuspiciousNullable
78 $newtitle = Title
::newFromText( $data['query']['redirects'][$lastRedirect]['to'] );
79 $img = new self( $newtitle, $repo, $info, true );
80 $img->redirectedFrom( $title->getDBkey() );
82 $img = new self( $title, $repo, $info, true );
92 * Get the property string for iiprop and aiprop
95 public static function getProps() {
96 return 'timestamp|user|comment|url|size|sha1|metadata|mime|mediatype|extmetadata';
100 * @return ForeignAPIRepo|false
102 public function getRepo() {
106 // Dummy functions...
111 public function exists() {
112 return $this->mExists
;
118 public function getPath() {
123 * @param array $params
125 * @return MediaTransformOutput|false
127 public function transform( $params, $flags = 0 ) {
128 if ( !$this->canRender() ) {
130 return parent
::transform( $params, $flags );
133 // Note, the this->canRender() check above implies
134 // that we have a handler, and it can do makeParamString.
135 $otherParams = $this->handler
->makeParamString( $params );
136 $width = $params['width'] ??
-1;
137 $height = $params['height'] ??
-1;
140 if ( $width > 0 ||
$height > 0 ) {
141 // Only query the remote if there are dimensions
142 $thumbUrl = $this->repo
->getThumbUrlFromCache(
148 } elseif ( $this->getMediaType() === MEDIATYPE_AUDIO
) {
149 // This has no dimensions, but we still need to pass a value to getTransform()
152 if ( $thumbUrl === false ) {
155 return $this->repo
->getThumbError(
164 return $this->handler
->getTransform( $this, 'bogus', $thumbUrl, $params );
167 // Info we can get from API...
173 public function getWidth( $page = 1 ) {
174 return (int)( $this->mInfo
['width'] ??
0 );
181 public function getHeight( $page = 1 ) {
182 return (int)( $this->mInfo
['height'] ??
0 );
186 * @return string|false
188 public function getMetadata() {
189 if ( isset( $this->mInfo
['metadata'] ) ) {
190 return serialize( self
::parseMetadata( $this->mInfo
['metadata'] ) );
199 public function getMetadataArray(): array {
200 if ( isset( $this->mInfo
['metadata'] ) ) {
201 return self
::parseMetadata( $this->mInfo
['metadata'] );
208 * @return array|null Extended metadata (see imageinfo API for format) or
211 public function getExtendedMetadata() {
212 return $this->mInfo
['extmetadata'] ??
null;
216 * @param mixed $metadata
219 public static function parseMetadata( $metadata ) {
220 if ( !is_array( $metadata ) ) {
221 return [ '_error' => $metadata ];
223 '@phan-var array[] $metadata';
225 foreach ( $metadata as $meta ) {
226 $ret[$meta['name']] = self
::parseMetadataValue( $meta['value'] );
233 * @param mixed $metadata
236 private static function parseMetadataValue( $metadata ) {
237 if ( !is_array( $metadata ) ) {
240 '@phan-var array[] $metadata';
242 foreach ( $metadata as $meta ) {
243 $ret[$meta['name']] = self
::parseMetadataValue( $meta['value'] );
250 * @return int|null|false
252 public function getSize() {
253 return isset( $this->mInfo
['size'] ) ?
intval( $this->mInfo
['size'] ) : null;
257 * @return null|string
259 public function getUrl() {
260 return isset( $this->mInfo
['url'] ) ?
strval( $this->mInfo
['url'] ) : null;
264 * Get short description URL for a file based on the foreign API response,
265 * or if unavailable, the short URL is constructed from the foreign page ID.
267 * @return null|string
270 public function getDescriptionShortUrl() {
271 if ( isset( $this->mInfo
['descriptionshorturl'] ) ) {
272 return $this->mInfo
['descriptionshorturl'];
273 } elseif ( isset( $this->mInfo
['pageid'] ) ) {
274 $url = $this->repo
->makeUrl( [ 'curid' => $this->mInfo
['pageid'] ] );
275 if ( $url !== false ) {
282 public function getUploader( int $audience = self
::FOR_PUBLIC
, ?Authority
$performer = null ): ?UserIdentity
{
283 if ( isset( $this->mInfo
['user'] ) ) {
284 return UserIdentityValue
::newExternal( $this->getRepoName(), $this->mInfo
['user'] );
290 * @param int $audience
291 * @param Authority|null $performer
292 * @return null|string
294 public function getDescription( $audience = self
::FOR_PUBLIC
, ?Authority
$performer = null ) {
295 return isset( $this->mInfo
['comment'] ) ?
strval( $this->mInfo
['comment'] ) : null;
299 * @return null|string
301 public function getSha1() {
302 return isset( $this->mInfo
['sha1'] )
303 ? Wikimedia\base_convert
( strval( $this->mInfo
['sha1'] ), 16, 36, 31 )
308 * @return string|false
310 public function getTimestamp() {
311 return wfTimestamp( TS_MW
,
312 isset( $this->mInfo
['timestamp'] )
313 ?
strval( $this->mInfo
['timestamp'] )
321 public function getMimeType() {
322 if ( !isset( $this->mInfo
['mime'] ) ) {
323 $magic = MediaWikiServices
::getInstance()->getMimeAnalyzer();
324 $this->mInfo
['mime'] = $magic->getMimeTypeFromExtensionOrNull( $this->getExtension() );
327 return $this->mInfo
['mime'];
333 public function getMediaType() {
334 if ( isset( $this->mInfo
['mediatype'] ) ) {
335 return $this->mInfo
['mediatype'];
337 $magic = MediaWikiServices
::getInstance()->getMimeAnalyzer();
339 return $magic->getMediaType( null, $this->getMimeType() );
343 * @return string|false
345 public function getDescriptionUrl() {
346 return $this->mInfo
['descriptionurl'] ??
false;
350 * Only useful if we're locally caching thumbs anyway...
351 * @param string $suffix
352 * @return null|string
354 public function getThumbPath( $suffix = '' ) {
355 if ( !$this->repo
->canCacheThumbs() ) {
359 $path = $this->repo
->getZonePath( 'thumb' ) . '/' . $this->getHashPath();
361 $path .= $suffix . '/';
369 protected function getThumbnails() {
370 $dir = $this->getThumbPath( $this->getName() );
371 $iter = $this->repo
->getBackend()->getFileList( [ 'dir' => $dir ] );
375 foreach ( $iter as $file ) {
383 public function purgeCache( $options = [] ) {
384 $this->purgeThumbnails( $options );
385 $this->purgeDescriptionPage();
388 private function purgeDescriptionPage() {
389 $services = MediaWikiServices
::getInstance();
390 $langCode = $services->getContentLanguageCode()->toString();
392 // Key must match File::getDescriptionText
393 $key = $this->repo
->getLocalCacheKey( 'file-remote-description', $langCode, md5( $this->getName() ) );
394 $services->getMainWANObjectCache()->delete( $key );
398 * @param array $options
400 public function purgeThumbnails( $options = [] ) {
401 $key = $this->repo
->getLocalCacheKey( 'file-thumb-url', sha1( $this->getName() ) );
402 MediaWikiServices
::getInstance()->getMainWANObjectCache()->delete( $key );
404 $files = $this->getThumbnails();
405 // Give media handler a chance to filter the purge list
406 $handler = $this->getHandler();
408 $handler->filterThumbnailPurgeList( $files, $options );
411 $dir = $this->getThumbPath( $this->getName() );
413 foreach ( $files as $file ) {
414 $purgeList[] = "{$dir}{$file}";
417 # Delete the thumbnails
418 $this->repo
->quickPurgeBatch( $purgeList );
419 # Clear out the thumbnail directory if empty
420 $this->repo
->quickCleanDir( $dir );
424 * The thumbnail is created on the foreign server and fetched over internet
428 public function isTransformedLocally() {