File.php 34 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270
  1. <?php
  2. /**
  3. * Implements some public methods and some protected utility functions which
  4. * are required by multiple child classes. Contains stub functionality for
  5. * unimplemented public methods.
  6. *
  7. * Stub functions which should be overridden are marked with STUB. Some more
  8. * concrete functions are also typically overridden by child classes.
  9. *
  10. * Note that only the repo object knows what its file class is called. You should
  11. * never name a file class explictly outside of the repo class. Instead use the
  12. * repo's factory functions to generate file objects, for example:
  13. *
  14. * RepoGroup::singleton()->getLocalRepo()->newFile($title);
  15. *
  16. * The convenience functions wfLocalFile() and wfFindFile() should be sufficient
  17. * in most cases.
  18. *
  19. * @ingroup FileRepo
  20. */
  21. abstract class File {
  22. const DELETED_FILE = 1;
  23. const DELETED_COMMENT = 2;
  24. const DELETED_USER = 4;
  25. const DELETED_RESTRICTED = 8;
  26. const RENDER_NOW = 1;
  27. const DELETE_SOURCE = 1;
  28. /**
  29. * Some member variables can be lazy-initialised using __get(). The
  30. * initialisation function for these variables is always a function named
  31. * like getVar(), where Var is the variable name with upper-case first
  32. * letter.
  33. *
  34. * The following variables are initialised in this way in this base class:
  35. * name, extension, handler, path, canRender, isSafeFile,
  36. * transformScript, hashPath, pageCount, url
  37. *
  38. * Code within this class should generally use the accessor function
  39. * directly, since __get() isn't re-entrant and therefore causes bugs that
  40. * depend on initialisation order.
  41. */
  42. /**
  43. * The following member variables are not lazy-initialised
  44. */
  45. var $repo, $title, $lastError, $redirected, $redirectedTitle;
  46. /**
  47. * Call this constructor from child classes
  48. */
  49. function __construct( $title, $repo ) {
  50. $this->title = $title;
  51. $this->repo = $repo;
  52. }
  53. function __get( $name ) {
  54. $function = array( $this, 'get' . ucfirst( $name ) );
  55. if ( !is_callable( $function ) ) {
  56. return null;
  57. } else {
  58. $this->$name = call_user_func( $function );
  59. return $this->$name;
  60. }
  61. }
  62. /**
  63. * Normalize a file extension to the common form, and ensure it's clean.
  64. * Extensions with non-alphanumeric characters will be discarded.
  65. *
  66. * @param $ext string (without the .)
  67. * @return string
  68. */
  69. static function normalizeExtension( $ext ) {
  70. $lower = strtolower( $ext );
  71. $squish = array(
  72. 'htm' => 'html',
  73. 'jpeg' => 'jpg',
  74. 'mpeg' => 'mpg',
  75. 'tiff' => 'tif',
  76. 'ogv' => 'ogg' );
  77. if( isset( $squish[$lower] ) ) {
  78. return $squish[$lower];
  79. } elseif( preg_match( '/^[0-9a-z]+$/', $lower ) ) {
  80. return $lower;
  81. } else {
  82. return '';
  83. }
  84. }
  85. /**
  86. * Checks if file extensions are compatible
  87. *
  88. * @param $old File Old file
  89. * @param $new string New name
  90. */
  91. static function checkExtensionCompatibility( File $old, $new ) {
  92. $oldMime = $old->getMimeType();
  93. $n = strrpos( $new, '.' );
  94. $newExt = self::normalizeExtension(
  95. $n ? substr( $new, $n + 1 ) : '' );
  96. $mimeMagic = MimeMagic::singleton();
  97. return $mimeMagic->isMatchingExtension( $newExt, $oldMime );
  98. }
  99. /**
  100. * Upgrade the database row if there is one
  101. * Called by ImagePage
  102. * STUB
  103. */
  104. function upgradeRow() {}
  105. /**
  106. * Split an internet media type into its two components; if not
  107. * a two-part name, set the minor type to 'unknown'.
  108. *
  109. * @param $mime "text/html" etc
  110. * @return array ("text", "html") etc
  111. */
  112. static function splitMime( $mime ) {
  113. if( strpos( $mime, '/' ) !== false ) {
  114. return explode( '/', $mime, 2 );
  115. } else {
  116. return array( $mime, 'unknown' );
  117. }
  118. }
  119. /**
  120. * Return the name of this file
  121. */
  122. public function getName() {
  123. if ( !isset( $this->name ) ) {
  124. $this->name = $this->repo->getNameFromTitle( $this->title );
  125. }
  126. return $this->name;
  127. }
  128. /**
  129. * Get the file extension, e.g. "svg"
  130. */
  131. function getExtension() {
  132. if ( !isset( $this->extension ) ) {
  133. $n = strrpos( $this->getName(), '.' );
  134. $this->extension = self::normalizeExtension(
  135. $n ? substr( $this->getName(), $n + 1 ) : '' );
  136. }
  137. return $this->extension;
  138. }
  139. /**
  140. * Return the associated title object
  141. */
  142. public function getTitle() { return $this->title; }
  143. /**
  144. * Return the title used to find this file
  145. */
  146. public function getOriginalTitle() {
  147. if ( $this->redirected )
  148. return $this->getRedirectedTitle();
  149. return $this->title;
  150. }
  151. /**
  152. * Return the URL of the file
  153. */
  154. public function getUrl() {
  155. if ( !isset( $this->url ) ) {
  156. $this->url = $this->repo->getZoneUrl( 'public' ) . '/' . $this->getUrlRel();
  157. }
  158. return $this->url;
  159. }
  160. /**
  161. * Return a fully-qualified URL to the file.
  162. * Upload URL paths _may or may not_ be fully qualified, so
  163. * we check. Local paths are assumed to belong on $wgServer.
  164. * @return string
  165. */
  166. public function getFullUrl() {
  167. return wfExpandUrl( $this->getUrl() );
  168. }
  169. function getViewURL() {
  170. if( $this->mustRender()) {
  171. if( $this->canRender() ) {
  172. return $this->createThumb( $this->getWidth() );
  173. }
  174. else {
  175. wfDebug(__METHOD__.': supposed to render '.$this->getName().' ('.$this->getMimeType()."), but can't!\n");
  176. return $this->getURL(); #hm... return NULL?
  177. }
  178. } else {
  179. return $this->getURL();
  180. }
  181. }
  182. /**
  183. * Return the full filesystem path to the file. Note that this does
  184. * not mean that a file actually exists under that location.
  185. *
  186. * This path depends on whether directory hashing is active or not,
  187. * i.e. whether the files are all found in the same directory,
  188. * or in hashed paths like /images/3/3c.
  189. *
  190. * May return false if the file is not locally accessible.
  191. */
  192. public function getPath() {
  193. if ( !isset( $this->path ) ) {
  194. $this->path = $this->repo->getZonePath('public') . '/' . $this->getRel();
  195. }
  196. return $this->path;
  197. }
  198. /**
  199. * Alias for getPath()
  200. */
  201. public function getFullPath() {
  202. return $this->getPath();
  203. }
  204. /**
  205. * Return the width of the image. Returns false if the width is unknown
  206. * or undefined.
  207. *
  208. * STUB
  209. * Overridden by LocalFile, UnregisteredLocalFile
  210. */
  211. public function getWidth( $page = 1 ) { return false; }
  212. /**
  213. * Return the height of the image. Returns false if the height is unknown
  214. * or undefined
  215. *
  216. * STUB
  217. * Overridden by LocalFile, UnregisteredLocalFile
  218. */
  219. public function getHeight( $page = 1 ) { return false; }
  220. /**
  221. * Returns ID or name of user who uploaded the file
  222. * STUB
  223. *
  224. * @param $type string 'text' or 'id'
  225. */
  226. public function getUser( $type='text' ) { return null; }
  227. /**
  228. * Get the duration of a media file in seconds
  229. */
  230. public function getLength() {
  231. $handler = $this->getHandler();
  232. if ( $handler ) {
  233. return $handler->getLength( $this );
  234. } else {
  235. return 0;
  236. }
  237. }
  238. /**
  239. * Get handler-specific metadata
  240. * Overridden by LocalFile, UnregisteredLocalFile
  241. * STUB
  242. */
  243. public function getMetadata() { return false; }
  244. /**
  245. * Return the bit depth of the file
  246. * Overridden by LocalFile
  247. * STUB
  248. */
  249. public function getBitDepth() { return 0; }
  250. /**
  251. * Return the size of the image file, in bytes
  252. * Overridden by LocalFile, UnregisteredLocalFile
  253. * STUB
  254. */
  255. public function getSize() { return false; }
  256. /**
  257. * Returns the mime type of the file.
  258. * Overridden by LocalFile, UnregisteredLocalFile
  259. * STUB
  260. */
  261. function getMimeType() { return 'unknown/unknown'; }
  262. /**
  263. * Return the type of the media in the file.
  264. * Use the value returned by this function with the MEDIATYPE_xxx constants.
  265. * Overridden by LocalFile,
  266. * STUB
  267. */
  268. function getMediaType() { return MEDIATYPE_UNKNOWN; }
  269. /**
  270. * Checks if the output of transform() for this file is likely
  271. * to be valid. If this is false, various user elements will
  272. * display a placeholder instead.
  273. *
  274. * Currently, this checks if the file is an image format
  275. * that can be converted to a format
  276. * supported by all browsers (namely GIF, PNG and JPEG),
  277. * or if it is an SVG image and SVG conversion is enabled.
  278. */
  279. function canRender() {
  280. if ( !isset( $this->canRender ) ) {
  281. $this->canRender = $this->getHandler() && $this->handler->canRender( $this );
  282. }
  283. return $this->canRender;
  284. }
  285. /**
  286. * Accessor for __get()
  287. */
  288. protected function getCanRender() {
  289. return $this->canRender();
  290. }
  291. /**
  292. * Return true if the file is of a type that can't be directly
  293. * rendered by typical browsers and needs to be re-rasterized.
  294. *
  295. * This returns true for everything but the bitmap types
  296. * supported by all browsers, i.e. JPEG; GIF and PNG. It will
  297. * also return true for any non-image formats.
  298. *
  299. * @return bool
  300. */
  301. function mustRender() {
  302. return $this->getHandler() && $this->handler->mustRender( $this );
  303. }
  304. /**
  305. * Alias for canRender()
  306. */
  307. function allowInlineDisplay() {
  308. return $this->canRender();
  309. }
  310. /**
  311. * Determines if this media file is in a format that is unlikely to
  312. * contain viruses or malicious content. It uses the global
  313. * $wgTrustedMediaFormats list to determine if the file is safe.
  314. *
  315. * This is used to show a warning on the description page of non-safe files.
  316. * It may also be used to disallow direct [[media:...]] links to such files.
  317. *
  318. * Note that this function will always return true if allowInlineDisplay()
  319. * or isTrustedFile() is true for this file.
  320. */
  321. function isSafeFile() {
  322. if ( !isset( $this->isSafeFile ) ) {
  323. $this->isSafeFile = $this->_getIsSafeFile();
  324. }
  325. return $this->isSafeFile;
  326. }
  327. /** Accessor for __get() */
  328. protected function getIsSafeFile() {
  329. return $this->isSafeFile();
  330. }
  331. /** Uncached accessor */
  332. protected function _getIsSafeFile() {
  333. if ($this->allowInlineDisplay()) return true;
  334. if ($this->isTrustedFile()) return true;
  335. global $wgTrustedMediaFormats;
  336. $type= $this->getMediaType();
  337. $mime= $this->getMimeType();
  338. #wfDebug("LocalFile::isSafeFile: type= $type, mime= $mime\n");
  339. if (!$type || $type===MEDIATYPE_UNKNOWN) return false; #unknown type, not trusted
  340. if ( in_array( $type, $wgTrustedMediaFormats) ) return true;
  341. if ($mime==="unknown/unknown") return false; #unknown type, not trusted
  342. if ( in_array( $mime, $wgTrustedMediaFormats) ) return true;
  343. return false;
  344. }
  345. /** Returns true if the file is flagged as trusted. Files flagged that way
  346. * can be linked to directly, even if that is not allowed for this type of
  347. * file normally.
  348. *
  349. * This is a dummy function right now and always returns false. It could be
  350. * implemented to extract a flag from the database. The trusted flag could be
  351. * set on upload, if the user has sufficient privileges, to bypass script-
  352. * and html-filters. It may even be coupled with cryptographics signatures
  353. * or such.
  354. */
  355. function isTrustedFile() {
  356. #this could be implemented to check a flag in the databas,
  357. #look for signatures, etc
  358. return false;
  359. }
  360. /**
  361. * Returns true if file exists in the repository.
  362. *
  363. * Overridden by LocalFile to avoid unnecessary stat calls.
  364. *
  365. * @return boolean Whether file exists in the repository.
  366. */
  367. public function exists() {
  368. return $this->getPath() && file_exists( $this->path );
  369. }
  370. /**
  371. * Returns true if file exists in the repository and can be included in a page.
  372. * It would be unsafe to include private images, making public thumbnails inadvertently
  373. *
  374. * @return boolean Whether file exists in the repository and is includable.
  375. * @public
  376. */
  377. function isVisible() {
  378. return $this->exists();
  379. }
  380. function getTransformScript() {
  381. if ( !isset( $this->transformScript ) ) {
  382. $this->transformScript = false;
  383. if ( $this->repo ) {
  384. $script = $this->repo->getThumbScriptUrl();
  385. if ( $script ) {
  386. $this->transformScript = "$script?f=" . urlencode( $this->getName() );
  387. }
  388. }
  389. }
  390. return $this->transformScript;
  391. }
  392. /**
  393. * Get a ThumbnailImage which is the same size as the source
  394. */
  395. function getUnscaledThumb( $page = false ) {
  396. $width = $this->getWidth( $page );
  397. if ( !$width ) {
  398. return $this->iconThumb();
  399. }
  400. if ( $page ) {
  401. $params = array(
  402. 'page' => $page,
  403. 'width' => $this->getWidth( $page )
  404. );
  405. } else {
  406. $params = array( 'width' => $this->getWidth() );
  407. }
  408. return $this->transform( $params );
  409. }
  410. /**
  411. * Return the file name of a thumbnail with the specified parameters
  412. *
  413. * @param array $params Handler-specific parameters
  414. * @private -ish
  415. */
  416. function thumbName( $params ) {
  417. if ( !$this->getHandler() ) {
  418. return null;
  419. }
  420. $extension = $this->getExtension();
  421. list( $thumbExt, $thumbMime ) = $this->handler->getThumbType( $extension, $this->getMimeType() );
  422. $thumbName = $this->handler->makeParamString( $params ) . '-' . $this->getName();
  423. if ( $thumbExt != $extension ) {
  424. $thumbName .= ".$thumbExt";
  425. }
  426. return $thumbName;
  427. }
  428. /**
  429. * Create a thumbnail of the image having the specified width/height.
  430. * The thumbnail will not be created if the width is larger than the
  431. * image's width. Let the browser do the scaling in this case.
  432. * The thumbnail is stored on disk and is only computed if the thumbnail
  433. * file does not exist OR if it is older than the image.
  434. * Returns the URL.
  435. *
  436. * Keeps aspect ratio of original image. If both width and height are
  437. * specified, the generated image will be no bigger than width x height,
  438. * and will also have correct aspect ratio.
  439. *
  440. * @param integer $width maximum width of the generated thumbnail
  441. * @param integer $height maximum height of the image (optional)
  442. */
  443. public function createThumb( $width, $height = -1 ) {
  444. $params = array( 'width' => $width );
  445. if ( $height != -1 ) {
  446. $params['height'] = $height;
  447. }
  448. $thumb = $this->transform( $params );
  449. if( is_null( $thumb ) || $thumb->isError() ) return '';
  450. return $thumb->getUrl();
  451. }
  452. /**
  453. * As createThumb, but returns a ThumbnailImage object. This can
  454. * provide access to the actual file, the real size of the thumb,
  455. * and can produce a convenient <img> tag for you.
  456. *
  457. * For non-image formats, this may return a filetype-specific icon.
  458. *
  459. * @param integer $width maximum width of the generated thumbnail
  460. * @param integer $height maximum height of the image (optional)
  461. * @param boolean $render Deprecated
  462. *
  463. * @return ThumbnailImage or null on failure
  464. *
  465. * @deprecated use transform()
  466. */
  467. public function getThumbnail( $width, $height=-1, $render = true ) {
  468. $params = array( 'width' => $width );
  469. if ( $height != -1 ) {
  470. $params['height'] = $height;
  471. }
  472. return $this->transform( $params, 0 );
  473. }
  474. /**
  475. * Transform a media file
  476. *
  477. * @param array $params An associative array of handler-specific parameters. Typical
  478. * keys are width, height and page.
  479. * @param integer $flags A bitfield, may contain self::RENDER_NOW to force rendering
  480. * @return MediaTransformOutput
  481. */
  482. function transform( $params, $flags = 0 ) {
  483. global $wgUseSquid, $wgIgnoreImageErrors;
  484. wfProfileIn( __METHOD__ );
  485. do {
  486. if ( !$this->canRender() ) {
  487. // not a bitmap or renderable image, don't try.
  488. $thumb = $this->iconThumb();
  489. break;
  490. }
  491. $script = $this->getTransformScript();
  492. if ( $script && !($flags & self::RENDER_NOW) ) {
  493. // Use a script to transform on client request, if possible
  494. $thumb = $this->handler->getScriptedTransform( $this, $script, $params );
  495. if( $thumb ) {
  496. break;
  497. }
  498. }
  499. $normalisedParams = $params;
  500. $this->handler->normaliseParams( $this, $normalisedParams );
  501. $thumbName = $this->thumbName( $normalisedParams );
  502. $thumbPath = $this->getThumbPath( $thumbName );
  503. $thumbUrl = $this->getThumbUrl( $thumbName );
  504. if ( $this->repo->canTransformVia404() && !($flags & self::RENDER_NOW ) ) {
  505. $thumb = $this->handler->getTransform( $this, $thumbPath, $thumbUrl, $params );
  506. break;
  507. }
  508. wfDebug( __METHOD__.": Doing stat for $thumbPath\n" );
  509. $this->migrateThumbFile( $thumbName );
  510. if ( file_exists( $thumbPath ) ) {
  511. $thumb = $this->handler->getTransform( $this, $thumbPath, $thumbUrl, $params );
  512. break;
  513. }
  514. $thumb = $this->handler->doTransform( $this, $thumbPath, $thumbUrl, $params );
  515. // Ignore errors if requested
  516. if ( !$thumb ) {
  517. $thumb = null;
  518. } elseif ( $thumb->isError() ) {
  519. $this->lastError = $thumb->toText();
  520. if ( $wgIgnoreImageErrors && !($flags & self::RENDER_NOW) ) {
  521. $thumb = $this->handler->getTransform( $this, $thumbPath, $thumbUrl, $params );
  522. }
  523. }
  524. // Purge. Useful in the event of Core -> Squid connection failure or squid
  525. // purge collisions from elsewhere during failure. Don't keep triggering for
  526. // "thumbs" which have the main image URL though (bug 13776)
  527. if ( $wgUseSquid && ( !$thumb || $thumb->isError() || $thumb->getUrl() != $this->getURL()) ) {
  528. SquidUpdate::purge( array( $thumbUrl ) );
  529. }
  530. } while (false);
  531. wfProfileOut( __METHOD__ );
  532. return is_object( $thumb ) ? $thumb : false;
  533. }
  534. /**
  535. * Hook into transform() to allow migration of thumbnail files
  536. * STUB
  537. * Overridden by LocalFile
  538. */
  539. function migrateThumbFile( $thumbName ) {}
  540. /**
  541. * Get a MediaHandler instance for this file
  542. */
  543. function getHandler() {
  544. if ( !isset( $this->handler ) ) {
  545. $this->handler = MediaHandler::getHandler( $this->getMimeType() );
  546. }
  547. return $this->handler;
  548. }
  549. /**
  550. * Get a ThumbnailImage representing a file type icon
  551. * @return ThumbnailImage
  552. */
  553. function iconThumb() {
  554. global $wgStylePath, $wgStyleDirectory;
  555. $try = array( 'fileicon-' . $this->getExtension() . '.png', 'fileicon.png' );
  556. foreach( $try as $icon ) {
  557. $path = '/common/images/icons/' . $icon;
  558. $filepath = $wgStyleDirectory . $path;
  559. if( file_exists( $filepath ) ) {
  560. return new ThumbnailImage( $this, $wgStylePath . $path, 120, 120 );
  561. }
  562. }
  563. return null;
  564. }
  565. /**
  566. * Get last thumbnailing error.
  567. * Largely obsolete.
  568. */
  569. function getLastError() {
  570. return $this->lastError;
  571. }
  572. /**
  573. * Get all thumbnail names previously generated for this file
  574. * STUB
  575. * Overridden by LocalFile
  576. */
  577. function getThumbnails() { return array(); }
  578. /**
  579. * Purge shared caches such as thumbnails and DB data caching
  580. * STUB
  581. * Overridden by LocalFile
  582. */
  583. function purgeCache() {}
  584. /**
  585. * Purge the file description page, but don't go after
  586. * pages using the file. Use when modifying file history
  587. * but not the current data.
  588. */
  589. function purgeDescription() {
  590. $title = $this->getTitle();
  591. if ( $title ) {
  592. $title->invalidateCache();
  593. $title->purgeSquid();
  594. }
  595. }
  596. /**
  597. * Purge metadata and all affected pages when the file is created,
  598. * deleted, or majorly updated.
  599. */
  600. function purgeEverything() {
  601. // Delete thumbnails and refresh file metadata cache
  602. $this->purgeCache();
  603. $this->purgeDescription();
  604. // Purge cache of all pages using this file
  605. $title = $this->getTitle();
  606. if ( $title ) {
  607. $update = new HTMLCacheUpdate( $title, 'imagelinks' );
  608. $update->doUpdate();
  609. }
  610. }
  611. /**
  612. * Return a fragment of the history of file.
  613. *
  614. * STUB
  615. * @param $limit integer Limit of rows to return
  616. * @param $start timestamp Only revisions older than $start will be returned
  617. * @param $end timestamp Only revisions newer than $end will be returned
  618. * @param $inc bool Include the endpoints of the time range
  619. */
  620. function getHistory($limit = null, $start = null, $end = null, $inc=true) {
  621. return array();
  622. }
  623. /**
  624. * Return the history of this file, line by line. Starts with current version,
  625. * then old versions. Should return an object similar to an image/oldimage
  626. * database row.
  627. *
  628. * STUB
  629. * Overridden in LocalFile
  630. */
  631. public function nextHistoryLine() {
  632. return false;
  633. }
  634. /**
  635. * Reset the history pointer to the first element of the history.
  636. * Always call this function after using nextHistoryLine() to free db resources
  637. * STUB
  638. * Overridden in LocalFile.
  639. */
  640. public function resetHistory() {}
  641. /**
  642. * Get the filename hash component of the directory including trailing slash,
  643. * e.g. f/fa/
  644. * If the repository is not hashed, returns an empty string.
  645. */
  646. function getHashPath() {
  647. if ( !isset( $this->hashPath ) ) {
  648. $this->hashPath = $this->repo->getHashPath( $this->getName() );
  649. }
  650. return $this->hashPath;
  651. }
  652. /**
  653. * Get the path of the file relative to the public zone root
  654. */
  655. function getRel() {
  656. return $this->getHashPath() . $this->getName();
  657. }
  658. /**
  659. * Get urlencoded relative path of the file
  660. */
  661. function getUrlRel() {
  662. return $this->getHashPath() . rawurlencode( $this->getName() );
  663. }
  664. /** Get the relative path for an archive file */
  665. function getArchiveRel( $suffix = false ) {
  666. $path = 'archive/' . $this->getHashPath();
  667. if ( $suffix === false ) {
  668. $path = substr( $path, 0, -1 );
  669. } else {
  670. $path .= $suffix;
  671. }
  672. return $path;
  673. }
  674. /** Get relative path for a thumbnail file */
  675. function getThumbRel( $suffix = false ) {
  676. $path = 'thumb/' . $this->getRel();
  677. if ( $suffix !== false ) {
  678. $path .= '/' . $suffix;
  679. }
  680. return $path;
  681. }
  682. /** Get the path of the archive directory, or a particular file if $suffix is specified */
  683. function getArchivePath( $suffix = false ) {
  684. return $this->repo->getZonePath('public') . '/' . $this->getArchiveRel( $suffix );
  685. }
  686. /** Get the path of the thumbnail directory, or a particular file if $suffix is specified */
  687. function getThumbPath( $suffix = false ) {
  688. return $this->repo->getZonePath('public') . '/' . $this->getThumbRel( $suffix );
  689. }
  690. /** Get the URL of the archive directory, or a particular file if $suffix is specified */
  691. function getArchiveUrl( $suffix = false ) {
  692. $path = $this->repo->getZoneUrl('public') . '/archive/' . $this->getHashPath();
  693. if ( $suffix === false ) {
  694. $path = substr( $path, 0, -1 );
  695. } else {
  696. $path .= rawurlencode( $suffix );
  697. }
  698. return $path;
  699. }
  700. /** Get the URL of the thumbnail directory, or a particular file if $suffix is specified */
  701. function getThumbUrl( $suffix = false ) {
  702. $path = $this->repo->getZoneUrl('public') . '/thumb/' . $this->getUrlRel();
  703. if ( $suffix !== false ) {
  704. $path .= '/' . rawurlencode( $suffix );
  705. }
  706. return $path;
  707. }
  708. /** Get the virtual URL for an archive file or directory */
  709. function getArchiveVirtualUrl( $suffix = false ) {
  710. $path = $this->repo->getVirtualUrl() . '/public/archive/' . $this->getHashPath();
  711. if ( $suffix === false ) {
  712. $path = substr( $path, 0, -1 );
  713. } else {
  714. $path .= rawurlencode( $suffix );
  715. }
  716. return $path;
  717. }
  718. /** Get the virtual URL for a thumbnail file or directory */
  719. function getThumbVirtualUrl( $suffix = false ) {
  720. $path = $this->repo->getVirtualUrl() . '/public/thumb/' . $this->getUrlRel();
  721. if ( $suffix !== false ) {
  722. $path .= '/' . rawurlencode( $suffix );
  723. }
  724. return $path;
  725. }
  726. /** Get the virtual URL for the file itself */
  727. function getVirtualUrl( $suffix = false ) {
  728. $path = $this->repo->getVirtualUrl() . '/public/' . $this->getUrlRel();
  729. if ( $suffix !== false ) {
  730. $path .= '/' . rawurlencode( $suffix );
  731. }
  732. return $path;
  733. }
  734. /**
  735. * @return bool
  736. */
  737. function isHashed() {
  738. return $this->repo->isHashed();
  739. }
  740. function readOnlyError() {
  741. throw new MWException( get_class($this) . ': write operations are not supported' );
  742. }
  743. /**
  744. * Record a file upload in the upload log and the image table
  745. * STUB
  746. * Overridden by LocalFile
  747. */
  748. function recordUpload( $oldver, $desc, $license = '', $copyStatus = '', $source = '', $watch = false ) {
  749. $this->readOnlyError();
  750. }
  751. /**
  752. * Move or copy a file to its public location. If a file exists at the
  753. * destination, move it to an archive. Returns the archive name on success
  754. * or an empty string if it was a new file, and a wikitext-formatted
  755. * WikiError object on failure.
  756. *
  757. * The archive name should be passed through to recordUpload for database
  758. * registration.
  759. *
  760. * @param string $sourcePath Local filesystem path to the source image
  761. * @param integer $flags A bitwise combination of:
  762. * File::DELETE_SOURCE Delete the source file, i.e. move
  763. * rather than copy
  764. * @return The archive name on success or an empty string if it was a new
  765. * file, and a wikitext-formatted WikiError object on failure.
  766. *
  767. * STUB
  768. * Overridden by LocalFile
  769. */
  770. function publish( $srcPath, $flags = 0 ) {
  771. $this->readOnlyError();
  772. }
  773. /**
  774. * Get an array of Title objects which are articles which use this file
  775. * Also adds their IDs to the link cache
  776. *
  777. * This is mostly copied from Title::getLinksTo()
  778. *
  779. * @deprecated Use HTMLCacheUpdate, this function uses too much memory
  780. */
  781. function getLinksTo( $options = array() ) {
  782. wfProfileIn( __METHOD__ );
  783. // Note: use local DB not repo DB, we want to know local links
  784. if ( count( $options ) > 0 ) {
  785. $db = wfGetDB( DB_MASTER );
  786. } else {
  787. $db = wfGetDB( DB_SLAVE );
  788. }
  789. $linkCache = LinkCache::singleton();
  790. $encName = $db->addQuotes( $this->getName() );
  791. $res = $db->select( array( 'page', 'imagelinks'),
  792. array( 'page_namespace', 'page_title', 'page_id', 'page_len', 'page_is_redirect' ),
  793. array( 'page_id' => 'il_from', 'il_to' => $encName ),
  794. __METHOD__,
  795. $options );
  796. $retVal = array();
  797. if ( $db->numRows( $res ) ) {
  798. while ( $row = $db->fetchObject( $res ) ) {
  799. if ( $titleObj = Title::newFromRow( $row ) ) {
  800. $linkCache->addGoodLinkObj( $row->page_id, $titleObj, $row->page_len, $row->page_is_redirect );
  801. $retVal[] = $titleObj;
  802. }
  803. }
  804. }
  805. $db->freeResult( $res );
  806. wfProfileOut( __METHOD__ );
  807. return $retVal;
  808. }
  809. function formatMetadata() {
  810. if ( !$this->getHandler() ) {
  811. return false;
  812. }
  813. return $this->getHandler()->formatMetadata( $this, $this->getMetadata() );
  814. }
  815. /**
  816. * Returns true if the file comes from the local file repository.
  817. *
  818. * @return bool
  819. */
  820. function isLocal() {
  821. return $this->getRepoName() == 'local';
  822. }
  823. /**
  824. * Returns the name of the repository.
  825. *
  826. * @return string
  827. */
  828. function getRepoName() {
  829. return $this->repo ? $this->repo->getName() : 'unknown';
  830. }
  831. /*
  832. * Returns the repository
  833. */
  834. function getRepo() {
  835. return $this->repo;
  836. }
  837. /**
  838. * Returns true if the image is an old version
  839. * STUB
  840. */
  841. function isOld() {
  842. return false;
  843. }
  844. /**
  845. * Is this file a "deleted" file in a private archive?
  846. * STUB
  847. */
  848. function isDeleted( $field ) {
  849. return false;
  850. }
  851. /**
  852. * Was this file ever deleted from the wiki?
  853. *
  854. * @return bool
  855. */
  856. function wasDeleted() {
  857. $title = $this->getTitle();
  858. return $title && $title->isDeletedQuick();
  859. }
  860. /**
  861. * Move file to the new title
  862. *
  863. * Move current, old version and all thumbnails
  864. * to the new filename. Old file is deleted.
  865. *
  866. * Cache purging is done; checks for validity
  867. * and logging are caller's responsibility
  868. *
  869. * @param $target Title New file name
  870. * @return FileRepoStatus object.
  871. */
  872. function move( $target ) {
  873. $this->readOnlyError();
  874. }
  875. /**
  876. * Delete all versions of the file.
  877. *
  878. * Moves the files into an archive directory (or deletes them)
  879. * and removes the database rows.
  880. *
  881. * Cache purging is done; logging is caller's responsibility.
  882. *
  883. * @param $reason
  884. * @param $suppress, hide content from sysops?
  885. * @return true on success, false on some kind of failure
  886. * STUB
  887. * Overridden by LocalFile
  888. */
  889. function delete( $reason, $suppress = false ) {
  890. $this->readOnlyError();
  891. }
  892. /**
  893. * Restore all or specified deleted revisions to the given file.
  894. * Permissions and logging are left to the caller.
  895. *
  896. * May throw database exceptions on error.
  897. *
  898. * @param $versions set of record ids of deleted items to restore,
  899. * or empty to restore all revisions.
  900. * @param $unsuppress, remove restrictions on content upon restoration?
  901. * @return the number of file revisions restored if successful,
  902. * or false on failure
  903. * STUB
  904. * Overridden by LocalFile
  905. */
  906. function restore( $versions=array(), $unsuppress=false ) {
  907. $this->readOnlyError();
  908. }
  909. /**
  910. * Returns 'true' if this image is a multipage document, e.g. a DJVU
  911. * document.
  912. *
  913. * @return Bool
  914. */
  915. function isMultipage() {
  916. return $this->getHandler() && $this->handler->isMultiPage( $this );
  917. }
  918. /**
  919. * Returns the number of pages of a multipage document, or NULL for
  920. * documents which aren't multipage documents
  921. */
  922. function pageCount() {
  923. if ( !isset( $this->pageCount ) ) {
  924. if ( $this->getHandler() && $this->handler->isMultiPage( $this ) ) {
  925. $this->pageCount = $this->handler->pageCount( $this );
  926. } else {
  927. $this->pageCount = false;
  928. }
  929. }
  930. return $this->pageCount;
  931. }
  932. /**
  933. * Calculate the height of a thumbnail using the source and destination width
  934. */
  935. static function scaleHeight( $srcWidth, $srcHeight, $dstWidth ) {
  936. // Exact integer multiply followed by division
  937. if ( $srcWidth == 0 ) {
  938. return 0;
  939. } else {
  940. return round( $srcHeight * $dstWidth / $srcWidth );
  941. }
  942. }
  943. /**
  944. * Get an image size array like that returned by getimagesize(), or false if it
  945. * can't be determined.
  946. *
  947. * @param string $fileName The filename
  948. * @return array
  949. */
  950. function getImageSize( $fileName ) {
  951. if ( !$this->getHandler() ) {
  952. return false;
  953. }
  954. return $this->handler->getImageSize( $this, $fileName );
  955. }
  956. /**
  957. * Get the URL of the image description page. May return false if it is
  958. * unknown or not applicable.
  959. */
  960. function getDescriptionUrl() {
  961. return $this->repo->getDescriptionUrl( $this->getName() );
  962. }
  963. /**
  964. * Get the HTML text of the description page, if available
  965. */
  966. function getDescriptionText() {
  967. global $wgMemc, $wgContLang;
  968. if ( !$this->repo->fetchDescription ) {
  969. return false;
  970. }
  971. $renderUrl = $this->repo->getDescriptionRenderUrl( $this->getName(), $wgContLang->getCode() );
  972. if ( $renderUrl ) {
  973. if ( $this->repo->descriptionCacheExpiry > 0 ) {
  974. wfDebug("Attempting to get the description from cache...");
  975. $key = wfMemcKey( 'RemoteFileDescription', 'url', $wgContLang->getCode(),
  976. $this->getName() );
  977. $obj = $wgMemc->get($key);
  978. if ($obj) {
  979. wfDebug("success!\n");
  980. return $obj;
  981. }
  982. wfDebug("miss\n");
  983. }
  984. wfDebug( "Fetching shared description from $renderUrl\n" );
  985. $res = Http::get( $renderUrl );
  986. if ( $res && $this->repo->descriptionCacheExpiry > 0 ) {
  987. $wgMemc->set( $key, $res, $this->repo->descriptionCacheExpiry );
  988. }
  989. return $res;
  990. } else {
  991. return false;
  992. }
  993. }
  994. /**
  995. * Get discription of file revision
  996. * STUB
  997. */
  998. function getDescription() {
  999. return null;
  1000. }
  1001. /**
  1002. * Get the 14-character timestamp of the file upload, or false if
  1003. * it doesn't exist
  1004. */
  1005. function getTimestamp() {
  1006. $path = $this->getPath();
  1007. if ( !file_exists( $path ) ) {
  1008. return false;
  1009. }
  1010. return wfTimestamp( TS_MW, filemtime( $path ) );
  1011. }
  1012. /**
  1013. * Get the SHA-1 base 36 hash of the file
  1014. */
  1015. function getSha1() {
  1016. return self::sha1Base36( $this->getPath() );
  1017. }
  1018. /**
  1019. * Determine if the current user is allowed to view a particular
  1020. * field of this file, if it's marked as deleted.
  1021. * STUB
  1022. * @param int $field
  1023. * @return bool
  1024. */
  1025. function userCan( $field ) {
  1026. return true;
  1027. }
  1028. /**
  1029. * Get an associative array containing information about a file in the local filesystem.
  1030. *
  1031. * @param string $path Absolute local filesystem path
  1032. * @param mixed $ext The file extension, or true to extract it from the filename.
  1033. * Set it to false to ignore the extension.
  1034. */
  1035. static function getPropsFromPath( $path, $ext = true ) {
  1036. wfProfileIn( __METHOD__ );
  1037. wfDebug( __METHOD__.": Getting file info for $path\n" );
  1038. $info = array(
  1039. 'fileExists' => file_exists( $path ) && !is_dir( $path )
  1040. );
  1041. $gis = false;
  1042. if ( $info['fileExists'] ) {
  1043. $magic = MimeMagic::singleton();
  1044. $info['mime'] = $magic->guessMimeType( $path, $ext );
  1045. list( $info['major_mime'], $info['minor_mime'] ) = self::splitMime( $info['mime'] );
  1046. $info['media_type'] = $magic->getMediaType( $path, $info['mime'] );
  1047. # Get size in bytes
  1048. $info['size'] = filesize( $path );
  1049. # Height, width and metadata
  1050. $handler = MediaHandler::getHandler( $info['mime'] );
  1051. if ( $handler ) {
  1052. $tempImage = (object)array();
  1053. $info['metadata'] = $handler->getMetadata( $tempImage, $path );
  1054. $gis = $handler->getImageSize( $tempImage, $path, $info['metadata'] );
  1055. } else {
  1056. $gis = false;
  1057. $info['metadata'] = '';
  1058. }
  1059. $info['sha1'] = self::sha1Base36( $path );
  1060. wfDebug(__METHOD__.": $path loaded, {$info['size']} bytes, {$info['mime']}.\n");
  1061. } else {
  1062. $info['mime'] = NULL;
  1063. $info['media_type'] = MEDIATYPE_UNKNOWN;
  1064. $info['metadata'] = '';
  1065. $info['sha1'] = '';
  1066. wfDebug(__METHOD__.": $path NOT FOUND!\n");
  1067. }
  1068. if( $gis ) {
  1069. # NOTE: $gis[2] contains a code for the image type. This is no longer used.
  1070. $info['width'] = $gis[0];
  1071. $info['height'] = $gis[1];
  1072. if ( isset( $gis['bits'] ) ) {
  1073. $info['bits'] = $gis['bits'];
  1074. } else {
  1075. $info['bits'] = 0;
  1076. }
  1077. } else {
  1078. $info['width'] = 0;
  1079. $info['height'] = 0;
  1080. $info['bits'] = 0;
  1081. }
  1082. wfProfileOut( __METHOD__ );
  1083. return $info;
  1084. }
  1085. /**
  1086. * Get a SHA-1 hash of a file in the local filesystem, in base-36 lower case
  1087. * encoding, zero padded to 31 digits.
  1088. *
  1089. * 160 log 2 / log 36 = 30.95, so the 160-bit hash fills 31 digits in base 36
  1090. * fairly neatly.
  1091. *
  1092. * Returns false on failure
  1093. */
  1094. static function sha1Base36( $path ) {
  1095. wfSuppressWarnings();
  1096. $hash = sha1_file( $path );
  1097. wfRestoreWarnings();
  1098. if ( $hash === false ) {
  1099. return false;
  1100. } else {
  1101. return wfBaseConvert( $hash, 16, 36, 31 );
  1102. }
  1103. }
  1104. function getLongDesc() {
  1105. $handler = $this->getHandler();
  1106. if ( $handler ) {
  1107. return $handler->getLongDesc( $this );
  1108. } else {
  1109. return MediaHandler::getGeneralLongDesc( $this );
  1110. }
  1111. }
  1112. function getShortDesc() {
  1113. $handler = $this->getHandler();
  1114. if ( $handler ) {
  1115. return $handler->getShortDesc( $this );
  1116. } else {
  1117. return MediaHandler::getGeneralShortDesc( $this );
  1118. }
  1119. }
  1120. function getDimensionsString() {
  1121. $handler = $this->getHandler();
  1122. if ( $handler ) {
  1123. return $handler->getDimensionsString( $this );
  1124. } else {
  1125. return '';
  1126. }
  1127. }
  1128. function getRedirected() {
  1129. return $this->redirected;
  1130. }
  1131. function getRedirectedTitle() {
  1132. if ( $this->redirected ) {
  1133. if ( !$this->redirectTitle )
  1134. $this->redirectTitle = Title::makeTitle( NS_FILE, $this->redirected );
  1135. return $this->redirectTitle;
  1136. }
  1137. }
  1138. function redirectedFrom( $from ) {
  1139. $this->redirected = $from;
  1140. }
  1141. }
  1142. /**
  1143. * Aliases for backwards compatibility with 1.6
  1144. */
  1145. define( 'MW_IMG_DELETED_FILE', File::DELETED_FILE );
  1146. define( 'MW_IMG_DELETED_COMMENT', File::DELETED_COMMENT );
  1147. define( 'MW_IMG_DELETED_USER', File::DELETED_USER );
  1148. define( 'MW_IMG_DELETED_RESTRICTED', File::DELETED_RESTRICTED );