ForeignAPIFile.php 9.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401
  1. <?php
  2. /**
  3. * Foreign file accessible through api.php requests.
  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. * @ingroup FileAbstraction
  22. */
  23. /**
  24. * Foreign file accessible through api.php requests.
  25. * Very hacky and inefficient, do not use :D
  26. *
  27. * @ingroup FileAbstraction
  28. */
  29. class ForeignAPIFile extends File {
  30. /** @var bool */
  31. private $mExists;
  32. /** @var array */
  33. private $mInfo = [];
  34. protected $repoClass = ForeignApiRepo::class;
  35. /**
  36. * @param Title|string|bool $title
  37. * @param ForeignApiRepo $repo
  38. * @param array $info
  39. * @param bool $exists
  40. */
  41. function __construct( $title, $repo, $info, $exists = false ) {
  42. parent::__construct( $title, $repo );
  43. $this->mInfo = $info;
  44. $this->mExists = $exists;
  45. $this->assertRepoDefined();
  46. }
  47. /**
  48. * @param Title $title
  49. * @param ForeignApiRepo $repo
  50. * @return ForeignAPIFile|null
  51. */
  52. static function newFromTitle( Title $title, $repo ) {
  53. $data = $repo->fetchImageQuery( [
  54. 'titles' => 'File:' . $title->getDBkey(),
  55. 'iiprop' => self::getProps(),
  56. 'prop' => 'imageinfo',
  57. 'iimetadataversion' => MediaHandler::getMetadataVersion(),
  58. // extmetadata is language-dependant, accessing the current language here
  59. // would be problematic, so we just get them all
  60. 'iiextmetadatamultilang' => 1,
  61. ] );
  62. $info = $repo->getImageInfo( $data );
  63. if ( $info ) {
  64. $lastRedirect = isset( $data['query']['redirects'] )
  65. ? count( $data['query']['redirects'] ) - 1
  66. : -1;
  67. if ( $lastRedirect >= 0 ) {
  68. $newtitle = Title::newFromText( $data['query']['redirects'][$lastRedirect]['to'] );
  69. $img = new self( $newtitle, $repo, $info, true );
  70. if ( $img ) {
  71. $img->redirectedFrom( $title->getDBkey() );
  72. }
  73. } else {
  74. $img = new self( $title, $repo, $info, true );
  75. }
  76. return $img;
  77. } else {
  78. return null;
  79. }
  80. }
  81. /**
  82. * Get the property string for iiprop and aiprop
  83. * @return string
  84. */
  85. static function getProps() {
  86. return 'timestamp|user|comment|url|size|sha1|metadata|mime|mediatype|extmetadata';
  87. }
  88. // Dummy functions...
  89. /**
  90. * @return bool
  91. */
  92. public function exists() {
  93. return $this->mExists;
  94. }
  95. /**
  96. * @return bool
  97. */
  98. public function getPath() {
  99. return false;
  100. }
  101. /**
  102. * @param array $params
  103. * @param int $flags
  104. * @return bool|MediaTransformOutput
  105. */
  106. function transform( $params, $flags = 0 ) {
  107. if ( !$this->canRender() ) {
  108. // show icon
  109. return parent::transform( $params, $flags );
  110. }
  111. // Note, the this->canRender() check above implies
  112. // that we have a handler, and it can do makeParamString.
  113. $otherParams = $this->handler->makeParamString( $params );
  114. $width = isset( $params['width'] ) ? $params['width'] : -1;
  115. $height = isset( $params['height'] ) ? $params['height'] : -1;
  116. $thumbUrl = $this->repo->getThumbUrlFromCache(
  117. $this->getName(),
  118. $width,
  119. $height,
  120. $otherParams
  121. );
  122. if ( $thumbUrl === false ) {
  123. global $wgLang;
  124. return $this->repo->getThumbError(
  125. $this->getName(),
  126. $width,
  127. $height,
  128. $otherParams,
  129. $wgLang->getCode()
  130. );
  131. }
  132. return $this->handler->getTransform( $this, 'bogus', $thumbUrl, $params );
  133. }
  134. // Info we can get from API...
  135. /**
  136. * @param int $page
  137. * @return int|number
  138. */
  139. public function getWidth( $page = 1 ) {
  140. return isset( $this->mInfo['width'] ) ? intval( $this->mInfo['width'] ) : 0;
  141. }
  142. /**
  143. * @param int $page
  144. * @return int
  145. */
  146. public function getHeight( $page = 1 ) {
  147. return isset( $this->mInfo['height'] ) ? intval( $this->mInfo['height'] ) : 0;
  148. }
  149. /**
  150. * @return bool|null|string
  151. */
  152. public function getMetadata() {
  153. if ( isset( $this->mInfo['metadata'] ) ) {
  154. return serialize( self::parseMetadata( $this->mInfo['metadata'] ) );
  155. }
  156. return null;
  157. }
  158. /**
  159. * @return array|null Extended metadata (see imageinfo API for format) or
  160. * null on error
  161. */
  162. public function getExtendedMetadata() {
  163. if ( isset( $this->mInfo['extmetadata'] ) ) {
  164. return $this->mInfo['extmetadata'];
  165. }
  166. return null;
  167. }
  168. /**
  169. * @param mixed $metadata
  170. * @return mixed
  171. */
  172. public static function parseMetadata( $metadata ) {
  173. if ( !is_array( $metadata ) ) {
  174. return $metadata;
  175. }
  176. $ret = [];
  177. foreach ( $metadata as $meta ) {
  178. $ret[$meta['name']] = self::parseMetadata( $meta['value'] );
  179. }
  180. return $ret;
  181. }
  182. /**
  183. * @return bool|int|null
  184. */
  185. public function getSize() {
  186. return isset( $this->mInfo['size'] ) ? intval( $this->mInfo['size'] ) : null;
  187. }
  188. /**
  189. * @return null|string
  190. */
  191. public function getUrl() {
  192. return isset( $this->mInfo['url'] ) ? strval( $this->mInfo['url'] ) : null;
  193. }
  194. /**
  195. * Get short description URL for a file based on the foreign API response,
  196. * or if unavailable, the short URL is constructed from the foreign page ID.
  197. *
  198. * @return null|string
  199. * @since 1.27
  200. */
  201. public function getDescriptionShortUrl() {
  202. if ( isset( $this->mInfo['descriptionshorturl'] ) ) {
  203. return $this->mInfo['descriptionshorturl'];
  204. } elseif ( isset( $this->mInfo['pageid'] ) ) {
  205. $url = $this->repo->makeUrl( [ 'curid' => $this->mInfo['pageid'] ] );
  206. if ( $url !== false ) {
  207. return $url;
  208. }
  209. }
  210. return null;
  211. }
  212. /**
  213. * @param string $type
  214. * @return int|null|string
  215. */
  216. public function getUser( $type = 'text' ) {
  217. if ( $type == 'text' ) {
  218. return isset( $this->mInfo['user'] ) ? strval( $this->mInfo['user'] ) : null;
  219. } else {
  220. return 0; // What makes sense here, for a remote user?
  221. }
  222. }
  223. /**
  224. * @param int $audience
  225. * @param User|null $user
  226. * @return null|string
  227. */
  228. public function getDescription( $audience = self::FOR_PUBLIC, User $user = null ) {
  229. return isset( $this->mInfo['comment'] ) ? strval( $this->mInfo['comment'] ) : null;
  230. }
  231. /**
  232. * @return null|string
  233. */
  234. function getSha1() {
  235. return isset( $this->mInfo['sha1'] )
  236. ? Wikimedia\base_convert( strval( $this->mInfo['sha1'] ), 16, 36, 31 )
  237. : null;
  238. }
  239. /**
  240. * @return bool|string
  241. */
  242. function getTimestamp() {
  243. return wfTimestamp( TS_MW,
  244. isset( $this->mInfo['timestamp'] )
  245. ? strval( $this->mInfo['timestamp'] )
  246. : null
  247. );
  248. }
  249. /**
  250. * @return string
  251. */
  252. function getMimeType() {
  253. if ( !isset( $this->mInfo['mime'] ) ) {
  254. $magic = MediaWiki\MediaWikiServices::getInstance()->getMimeAnalyzer();
  255. $this->mInfo['mime'] = $magic->guessTypesForExtension( $this->getExtension() );
  256. }
  257. return $this->mInfo['mime'];
  258. }
  259. /**
  260. * @return int|string
  261. */
  262. function getMediaType() {
  263. if ( isset( $this->mInfo['mediatype'] ) ) {
  264. return $this->mInfo['mediatype'];
  265. }
  266. $magic = MediaWiki\MediaWikiServices::getInstance()->getMimeAnalyzer();
  267. return $magic->getMediaType( null, $this->getMimeType() );
  268. }
  269. /**
  270. * @return bool|string
  271. */
  272. function getDescriptionUrl() {
  273. return isset( $this->mInfo['descriptionurl'] )
  274. ? $this->mInfo['descriptionurl']
  275. : false;
  276. }
  277. /**
  278. * Only useful if we're locally caching thumbs anyway...
  279. * @param string $suffix
  280. * @return null|string
  281. */
  282. function getThumbPath( $suffix = '' ) {
  283. if ( $this->repo->canCacheThumbs() ) {
  284. $path = $this->repo->getZonePath( 'thumb' ) . '/' . $this->getHashPath( $this->getName() );
  285. if ( $suffix ) {
  286. $path = $path . $suffix . '/';
  287. }
  288. return $path;
  289. } else {
  290. return null;
  291. }
  292. }
  293. /**
  294. * @return string[]
  295. */
  296. function getThumbnails() {
  297. $dir = $this->getThumbPath( $this->getName() );
  298. $iter = $this->repo->getBackend()->getFileList( [ 'dir' => $dir ] );
  299. $files = [];
  300. if ( $iter ) {
  301. foreach ( $iter as $file ) {
  302. $files[] = $file;
  303. }
  304. }
  305. return $files;
  306. }
  307. function purgeCache( $options = [] ) {
  308. $this->purgeThumbnails( $options );
  309. $this->purgeDescriptionPage();
  310. }
  311. function purgeDescriptionPage() {
  312. global $wgContLang;
  313. $url = $this->repo->getDescriptionRenderUrl( $this->getName(), $wgContLang->getCode() );
  314. $key = $this->repo->getLocalCacheKey( 'RemoteFileDescription', 'url', md5( $url ) );
  315. ObjectCache::getMainWANInstance()->delete( $key );
  316. }
  317. /**
  318. * @param array $options
  319. */
  320. function purgeThumbnails( $options = [] ) {
  321. $key = $this->repo->getLocalCacheKey( 'ForeignAPIRepo', 'ThumbUrl', $this->getName() );
  322. ObjectCache::getMainWANInstance()->delete( $key );
  323. $files = $this->getThumbnails();
  324. // Give media handler a chance to filter the purge list
  325. $handler = $this->getHandler();
  326. if ( $handler ) {
  327. $handler->filterThumbnailPurgeList( $files, $options );
  328. }
  329. $dir = $this->getThumbPath( $this->getName() );
  330. $purgeList = [];
  331. foreach ( $files as $file ) {
  332. $purgeList[] = "{$dir}{$file}";
  333. }
  334. # Delete the thumbnails
  335. $this->repo->quickPurgeBatch( $purgeList );
  336. # Clear out the thumbnail directory if empty
  337. $this->repo->quickCleanDir( $dir );
  338. }
  339. /**
  340. * The thumbnail is created on the foreign server and fetched over internet
  341. * @since 1.25
  342. * @return bool
  343. */
  344. public function isTransformedLocally() {
  345. return false;
  346. }
  347. }