MediaWiki  REL1_20
File.php
Go to the documentation of this file.
00001 <?php
00050 abstract class File {
00051         const DELETED_FILE = 1;
00052         const DELETED_COMMENT = 2;
00053         const DELETED_USER = 4;
00054         const DELETED_RESTRICTED = 8;
00055 
00057         const RENDER_NOW   = 1;
00062         const RENDER_FORCE = 2;
00063 
00064         const DELETE_SOURCE = 1;
00065 
00066         // Audience options for File::getDescription()
00067         const FOR_PUBLIC = 1;
00068         const FOR_THIS_USER = 2;
00069         const RAW = 3;
00070 
00071         // Options for File::thumbName()
00072         const THUMB_FULL_NAME = 1;
00073 
00096         var $repo;
00097 
00101         var $title;
00102 
00103         var $lastError, $redirected, $redirectedTitle;
00104 
00108         protected $fsFile;
00109 
00113         protected $handler;
00114 
00118         protected $url, $extension, $name, $path, $hashPath, $pageCount, $transformScript;
00119 
00120         protected $redirectTitle;
00121 
00125         protected $canRender, $isSafeFile;
00126 
00130         protected $repoClass = 'FileRepo';
00131 
00142         function __construct( $title, $repo ) {
00143                 if ( $title !== false ) { // subclasses may not use MW titles
00144                         $title = self::normalizeTitle( $title, 'exception' );
00145                 }
00146                 $this->title = $title;
00147                 $this->repo = $repo;
00148         }
00149 
00159         static function normalizeTitle( $title, $exception = false ) {
00160                 $ret = $title;
00161                 if ( $ret instanceof Title ) {
00162                         # Normalize NS_MEDIA -> NS_FILE
00163                         if ( $ret->getNamespace() == NS_MEDIA ) {
00164                                 $ret = Title::makeTitleSafe( NS_FILE, $ret->getDBkey() );
00165                         # Sanity check the title namespace
00166                         } elseif ( $ret->getNamespace() !== NS_FILE ) {
00167                                 $ret = null;
00168                         }
00169                 } else {
00170                         # Convert strings to Title objects
00171                         $ret = Title::makeTitleSafe( NS_FILE, (string)$ret );
00172                 }
00173                 if ( !$ret && $exception !== false ) {
00174                         throw new MWException( "`$title` is not a valid file title." );
00175                 }
00176                 return $ret;
00177         }
00178 
00179         function __get( $name ) {
00180                 $function = array( $this, 'get' . ucfirst( $name ) );
00181                 if ( !is_callable( $function ) ) {
00182                         return null;
00183                 } else {
00184                         $this->$name = call_user_func( $function );
00185                         return $this->$name;
00186                 }
00187         }
00188 
00196         static function normalizeExtension( $ext ) {
00197                 $lower = strtolower( $ext );
00198                 $squish = array(
00199                         'htm' => 'html',
00200                         'jpeg' => 'jpg',
00201                         'mpeg' => 'mpg',
00202                         'tiff' => 'tif',
00203                         'ogv' => 'ogg' );
00204                 if( isset( $squish[$lower] ) ) {
00205                         return $squish[$lower];
00206                 } elseif( preg_match( '/^[0-9a-z]+$/', $lower ) ) {
00207                         return $lower;
00208                 } else {
00209                         return '';
00210                 }
00211         }
00212 
00221         static function checkExtensionCompatibility( File $old, $new ) {
00222                 $oldMime = $old->getMimeType();
00223                 $n = strrpos( $new, '.' );
00224                 $newExt = self::normalizeExtension( $n ? substr( $new, $n + 1 ) : '' );
00225                 $mimeMagic = MimeMagic::singleton();
00226                 return $mimeMagic->isMatchingExtension( $newExt, $oldMime );
00227         }
00228 
00234         function upgradeRow() {}
00235 
00243         public static function splitMime( $mime ) {
00244                 if( strpos( $mime, '/' ) !== false ) {
00245                         return explode( '/', $mime, 2 );
00246                 } else {
00247                         return array( $mime, 'unknown' );
00248                 }
00249         }
00250 
00259         public static function compare( File $a, File $b ) {
00260                 return strcmp( $a->getName(), $b->getName() );
00261         }
00262 
00268         public function getName() {
00269                 if ( !isset( $this->name ) ) {
00270                         $this->assertRepoDefined();
00271                         $this->name = $this->repo->getNameFromTitle( $this->title );
00272                 }
00273                 return $this->name;
00274         }
00275 
00281         function getExtension() {
00282                 if ( !isset( $this->extension ) ) {
00283                         $n = strrpos( $this->getName(), '.' );
00284                         $this->extension = self::normalizeExtension(
00285                                 $n ? substr( $this->getName(), $n + 1 ) : '' );
00286                 }
00287                 return $this->extension;
00288         }
00289 
00295         public function getTitle() {
00296                 return $this->title;
00297         }
00298 
00304         public function getOriginalTitle() {
00305                 if ( $this->redirected ) {
00306                         return $this->getRedirectedTitle();
00307                 }
00308                 return $this->title;
00309         }
00310 
00316         public function getUrl() {
00317                 if ( !isset( $this->url ) ) {
00318                         $this->assertRepoDefined();
00319                         $this->url = $this->repo->getZoneUrl( 'public' ) . '/' . $this->getUrlRel();
00320                 }
00321                 return $this->url;
00322         }
00323 
00331         public function getFullUrl() {
00332                 return wfExpandUrl( $this->getUrl(), PROTO_RELATIVE );
00333         }
00334 
00338         public function getCanonicalUrl() {
00339                 return wfExpandUrl( $this->getUrl(), PROTO_CANONICAL );
00340         }
00341 
00345         function getViewURL() {
00346                 if ( $this->mustRender() ) {
00347                         if ( $this->canRender() ) {
00348                                 return $this->createThumb( $this->getWidth() );
00349                         } else {
00350                                 wfDebug( __METHOD__.': supposed to render ' . $this->getName() .
00351                                         ' (' . $this->getMimeType() . "), but can't!\n" );
00352                                 return $this->getURL(); #hm... return NULL?
00353                         }
00354                 } else {
00355                         return $this->getURL();
00356                 }
00357         }
00358 
00372         public function getPath() {
00373                 if ( !isset( $this->path ) ) {
00374                         $this->assertRepoDefined();
00375                         $this->path = $this->repo->getZonePath( 'public' ) . '/' . $this->getRel();
00376                 }
00377                 return $this->path;
00378         }
00379 
00387         public function getLocalRefPath() {
00388                 $this->assertRepoDefined();
00389                 if ( !isset( $this->fsFile ) ) {
00390                         $this->fsFile = $this->repo->getLocalReference( $this->getPath() );
00391                         if ( !$this->fsFile ) {
00392                                 $this->fsFile = false; // null => false; cache negative hits
00393                         }
00394                 }
00395                 return ( $this->fsFile )
00396                         ? $this->fsFile->getPath()
00397                         : false;
00398         }
00399 
00411         public function getWidth( $page = 1 ) {
00412                 return false;
00413         }
00414 
00426         public function getHeight( $page = 1 ) {
00427                 return false;
00428         }
00429 
00438         public function getUser( $type = 'text' ) {
00439                 return null;
00440         }
00441 
00447         public function getLength() {
00448                 $handler = $this->getHandler();
00449                 if ( $handler ) {
00450                         return $handler->getLength( $this );
00451                 } else {
00452                         return 0;
00453                 }
00454         }
00455 
00461         public function isVectorized() {
00462                 $handler = $this->getHandler();
00463                 if ( $handler ) {
00464                         return $handler->isVectorized( $this );
00465                 } else {
00466                         return false;
00467                 }
00468         }
00469 
00480         public function canAnimateThumbIfAppropriate() {
00481                 $handler = $this->getHandler();
00482                 if ( !$handler ) {
00483                         // We cannot handle image whatsoever, thus
00484                         // one would not expect it to be animated
00485                         // so true.
00486                         return true;
00487                 } else {
00488                         if ( $this->allowInlineDisplay()
00489                                 && $handler->isAnimatedImage( $this )
00490                                 && !$handler->canAnimateThumbnail( $this )
00491                         ) {
00492                                 // Image is animated, but thumbnail isn't.
00493                                 // This is unexpected to the user.
00494                                 return false;
00495                         } else {
00496                                 // Image is not animated, so one would
00497                                 // not expect thumb to be
00498                                 return true;
00499                         }
00500                 }
00501         }
00502 
00509         public function getMetadata() {
00510                 return false;
00511         }
00512 
00520         public function convertMetadataVersion($metadata, $version) {
00521                 $handler = $this->getHandler();
00522                 if ( !is_array( $metadata ) ) {
00523                         // Just to make the return type consistent
00524                         $metadata = unserialize( $metadata );
00525                 }
00526                 if ( $handler ) {
00527                         return $handler->convertMetadataVersion( $metadata, $version );
00528                 } else {
00529                         return $metadata;
00530                 }
00531         }
00532 
00539         public function getBitDepth() {
00540                 return 0;
00541         }
00542 
00549         public function getSize() {
00550                 return false;
00551         }
00552 
00560         function getMimeType() {
00561                 return 'unknown/unknown';
00562         }
00563 
00571         function getMediaType() {
00572                 return MEDIATYPE_UNKNOWN;
00573         }
00574 
00587         function canRender() {
00588                 if ( !isset( $this->canRender ) ) {
00589                         $this->canRender = $this->getHandler() && $this->handler->canRender( $this );
00590                 }
00591                 return $this->canRender;
00592         }
00593 
00598         protected function getCanRender() {
00599                 return $this->canRender();
00600         }
00601 
00612         function mustRender() {
00613                 return $this->getHandler() && $this->handler->mustRender( $this );
00614         }
00615 
00621         function allowInlineDisplay() {
00622                 return $this->canRender();
00623         }
00624 
00638         function isSafeFile() {
00639                 if ( !isset( $this->isSafeFile ) ) {
00640                         $this->isSafeFile = $this->_getIsSafeFile();
00641                 }
00642                 return $this->isSafeFile;
00643         }
00644 
00650         protected function getIsSafeFile() {
00651                 return $this->isSafeFile();
00652         }
00653 
00659         protected function _getIsSafeFile() {
00660                 global $wgTrustedMediaFormats;
00661 
00662                 if ( $this->allowInlineDisplay() ) {
00663                         return true;
00664                 }
00665                 if ($this->isTrustedFile()) {
00666                         return true;
00667                 }
00668 
00669                 $type = $this->getMediaType();
00670                 $mime = $this->getMimeType();
00671                 #wfDebug("LocalFile::isSafeFile: type= $type, mime= $mime\n");
00672 
00673                 if ( !$type || $type === MEDIATYPE_UNKNOWN ) {
00674                         return false; #unknown type, not trusted
00675                 }
00676                 if ( in_array( $type, $wgTrustedMediaFormats ) ) {
00677                         return true;
00678                 }
00679 
00680                 if ( $mime === "unknown/unknown" ) {
00681                         return false; #unknown type, not trusted
00682                 }
00683                 if ( in_array( $mime, $wgTrustedMediaFormats) ) {
00684                         return true;
00685                 }
00686 
00687                 return false;
00688         }
00689 
00703         function isTrustedFile() {
00704                 #this could be implemented to check a flag in the database,
00705                 #look for signatures, etc
00706                 return false;
00707         }
00708 
00716         public function exists() {
00717                 return $this->getPath() && $this->repo->fileExists( $this->path );
00718         }
00719 
00726         public function isVisible() {
00727                 return $this->exists();
00728         }
00729 
00733         function getTransformScript() {
00734                 if ( !isset( $this->transformScript ) ) {
00735                         $this->transformScript = false;
00736                         if ( $this->repo ) {
00737                                 $script = $this->repo->getThumbScriptUrl();
00738                                 if ( $script ) {
00739                                         $this->transformScript = "$script?f=" . urlencode( $this->getName() );
00740                                 }
00741                         }
00742                 }
00743                 return $this->transformScript;
00744         }
00745 
00753         function getUnscaledThumb( $handlerParams = array() ) {
00754                 $hp =& $handlerParams;
00755                 $page = isset( $hp['page'] ) ? $hp['page'] : false;
00756                 $width = $this->getWidth( $page );
00757                 if ( !$width ) {
00758                         return $this->iconThumb();
00759                 }
00760                 $hp['width'] = $width;
00761                 return $this->transform( $hp );
00762         }
00763 
00773         public function thumbName( $params, $flags = 0 ) {
00774                 $name = ( $this->repo && !( $flags & self::THUMB_FULL_NAME ) )
00775                         ? $this->repo->nameForThumb( $this->getName() )
00776                         : $this->getName();
00777                 return $this->generateThumbName( $name, $params );
00778         }
00779 
00788         public function generateThumbName( $name, $params ) {
00789                 if ( !$this->getHandler() ) {
00790                         return null;
00791                 }
00792                 $extension = $this->getExtension();
00793                 list( $thumbExt, $thumbMime ) = $this->handler->getThumbType(
00794                         $extension, $this->getMimeType(), $params );
00795                 $thumbName = $this->handler->makeParamString( $params ) . '-' . $name;
00796                 if ( $thumbExt != $extension ) {
00797                         $thumbName .= ".$thumbExt";
00798                 }
00799                 return $thumbName;
00800         }
00801 
00819         public function createThumb( $width, $height = -1 ) {
00820                 $params = array( 'width' => $width );
00821                 if ( $height != -1 ) {
00822                         $params['height'] = $height;
00823                 }
00824                 $thumb = $this->transform( $params );
00825                 if ( is_null( $thumb ) || $thumb->isError() ) {
00826                         return '';
00827                 }
00828                 return $thumb->getUrl();
00829         }
00830 
00840         protected function transformErrorOutput( $thumbPath, $thumbUrl, $params, $flags ) {
00841                 global $wgIgnoreImageErrors;
00842 
00843                 if ( $wgIgnoreImageErrors && !( $flags & self::RENDER_NOW ) ) {
00844                         return $this->handler->getTransform( $this, $thumbPath, $thumbUrl, $params );
00845                 } else {
00846                         return new MediaTransformError( 'thumbnail_error',
00847                                 $params['width'], 0, wfMessage( 'thumbnail-dest-create' )->text() );
00848                 }
00849         }
00850 
00859         function transform( $params, $flags = 0 ) {
00860                 global $wgUseSquid, $wgIgnoreImageErrors, $wgThumbnailEpoch;
00861 
00862                 wfProfileIn( __METHOD__ );
00863                 do {
00864                         if ( !$this->canRender() ) {
00865                                 $thumb = $this->iconThumb();
00866                                 break; // not a bitmap or renderable image, don't try
00867                         }
00868 
00869                         // Get the descriptionUrl to embed it as comment into the thumbnail. Bug 19791.
00870                         $descriptionUrl = $this->getDescriptionUrl();
00871                         if ( $descriptionUrl ) {
00872                                 $params['descriptionUrl'] = wfExpandUrl( $descriptionUrl, PROTO_CANONICAL );
00873                         }
00874 
00875                         $script = $this->getTransformScript();
00876                         if ( $script && !( $flags & self::RENDER_NOW ) ) {
00877                                 // Use a script to transform on client request, if possible
00878                                 $thumb = $this->handler->getScriptedTransform( $this, $script, $params );
00879                                 if ( $thumb ) {
00880                                         break;
00881                                 }
00882                         }
00883 
00884                         $normalisedParams = $params;
00885                         $this->handler->normaliseParams( $this, $normalisedParams );
00886 
00887                         $thumbName = $this->thumbName( $normalisedParams );
00888                         $thumbUrl = $this->getThumbUrl( $thumbName );
00889                         $thumbPath = $this->getThumbPath( $thumbName ); // final thumb path
00890 
00891                         if ( $this->repo ) {
00892                                 // Defer rendering if a 404 handler is set up...
00893                                 if ( $this->repo->canTransformVia404() && !( $flags & self::RENDER_NOW ) ) {
00894                                         wfDebug( __METHOD__ . " transformation deferred." );
00895                                         // XXX: Pass in the storage path even though we are not rendering anything
00896                                         // and the path is supposed to be an FS path. This is due to getScalerType()
00897                                         // getting called on the path and clobbering $thumb->getUrl() if it's false.
00898                                         $thumb = $this->handler->getTransform( $this, $thumbPath, $thumbUrl, $params );
00899                                         break;
00900                                 }
00901                                 // Clean up broken thumbnails as needed
00902                                 $this->migrateThumbFile( $thumbName );
00903                                 // Check if an up-to-date thumbnail already exists...
00904                                 wfDebug( __METHOD__.": Doing stat for $thumbPath\n" );
00905                                 if ( $this->repo->fileExists( $thumbPath ) && !( $flags & self::RENDER_FORCE ) ) {
00906                                         $timestamp = $this->repo->getFileTimestamp( $thumbPath );
00907                                         if ( $timestamp !== false && $timestamp >= $wgThumbnailEpoch ) {
00908                                                 // XXX: Pass in the storage path even though we are not rendering anything
00909                                                 // and the path is supposed to be an FS path. This is due to getScalerType()
00910                                                 // getting called on the path and clobbering $thumb->getUrl() if it's false.
00911                                                 $thumb = $this->handler->getTransform( $this, $thumbPath, $thumbUrl, $params );
00912                                                 $thumb->setStoragePath( $thumbPath );
00913                                                 break;
00914                                         }
00915                                 } elseif ( $flags & self::RENDER_FORCE ) {
00916                                         wfDebug( __METHOD__ . " forcing rendering per flag File::RENDER_FORCE\n" );
00917                                 }
00918                         }
00919 
00920                         // If the backend is ready-only, don't keep generating thumbnails
00921                         // only to return transformation errors, just return the error now.
00922                         if ( $this->repo->getReadOnlyReason() !== false ) {
00923                                 $thumb = $this->transformErrorOutput( $thumbPath, $thumbUrl, $params, $flags );
00924                                 break;
00925                         }
00926 
00927                         // Create a temp FS file with the same extension and the thumbnail
00928                         $thumbExt = FileBackend::extensionFromPath( $thumbPath );
00929                         $tmpFile = TempFSFile::factory( 'transform_', $thumbExt );
00930                         if ( !$tmpFile ) {
00931                                 $thumb = $this->transformErrorOutput( $thumbPath, $thumbUrl, $params, $flags );
00932                                 break;
00933                         }
00934                         $tmpThumbPath = $tmpFile->getPath(); // path of 0-byte temp file
00935 
00936                         // Actually render the thumbnail...
00937                         wfProfileIn( __METHOD__ . '-doTransform' );
00938                         $thumb = $this->handler->doTransform( $this, $tmpThumbPath, $thumbUrl, $params );
00939                         wfProfileOut( __METHOD__ . '-doTransform' );
00940                         $tmpFile->bind( $thumb ); // keep alive with $thumb
00941 
00942                         if ( !$thumb ) { // bad params?
00943                                 $thumb = null;
00944                         } elseif ( $thumb->isError() ) { // transform error
00945                                 $this->lastError = $thumb->toText();
00946                                 // Ignore errors if requested
00947                                 if ( $wgIgnoreImageErrors && !( $flags & self::RENDER_NOW ) ) {
00948                                         $thumb = $this->handler->getTransform( $this, $tmpThumbPath, $thumbUrl, $params );
00949                                 }
00950                         } elseif ( $this->repo && $thumb->hasFile() && !$thumb->fileIsSource() ) {
00951                                 // Copy the thumbnail from the file system into storage...
00952                                 $disposition = $this->getThumbDisposition( $thumbName );
00953                                 $status = $this->repo->quickImport( $tmpThumbPath, $thumbPath, $disposition );
00954                                 if ( $status->isOK() ) {
00955                                         $thumb->setStoragePath( $thumbPath );
00956                                 } else {
00957                                         $thumb = $this->transformErrorOutput( $thumbPath, $thumbUrl, $params, $flags );
00958                                 }
00959                                 // Give extensions a chance to do something with this thumbnail...
00960                                 wfRunHooks( 'FileTransformed', array( $this, $thumb, $tmpThumbPath, $thumbPath ) );
00961                         }
00962 
00963                         // Purge. Useful in the event of Core -> Squid connection failure or squid
00964                         // purge collisions from elsewhere during failure. Don't keep triggering for
00965                         // "thumbs" which have the main image URL though (bug 13776)
00966                         if ( $wgUseSquid ) {
00967                                 if ( !$thumb || $thumb->isError() || $thumb->getUrl() != $this->getURL() ) {
00968                                         SquidUpdate::purge( array( $thumbUrl ) );
00969                                 }
00970                         }
00971                 } while ( false );
00972 
00973                 wfProfileOut( __METHOD__ );
00974                 return is_object( $thumb ) ? $thumb : false;
00975         }
00976 
00981         function getThumbDisposition( $thumbName ) {
00982                 $fileName = $this->name; // file name to suggest
00983                 $thumbExt = FileBackend::extensionFromPath( $thumbName );
00984                 if ( $thumbExt != '' && $thumbExt !== $this->getExtension() ) {
00985                         $fileName .= ".$thumbExt";
00986                 }
00987                 return FileBackend::makeContentDisposition( 'inline', $fileName );
00988         }
00989 
00995         function migrateThumbFile( $thumbName ) {}
00996 
01002         function getHandler() {
01003                 if ( !isset( $this->handler ) ) {
01004                         $this->handler = MediaHandler::getHandler( $this->getMimeType() );
01005                 }
01006                 return $this->handler;
01007         }
01008 
01014         function iconThumb() {
01015                 global $wgStylePath, $wgStyleDirectory;
01016 
01017                 $try = array( 'fileicon-' . $this->getExtension() . '.png', 'fileicon.png' );
01018                 foreach ( $try as $icon ) {
01019                         $path = '/common/images/icons/' . $icon;
01020                         $filepath = $wgStyleDirectory . $path;
01021                         if ( file_exists( $filepath ) ) { // always FS
01022                                 $params = array( 'width' => 120, 'height' => 120 );
01023                                 return new ThumbnailImage( $this, $wgStylePath . $path, false, $params );
01024                         }
01025                 }
01026                 return null;
01027         }
01028 
01033         function getLastError() {
01034                 return $this->lastError;
01035         }
01036 
01043         function getThumbnails() {
01044                 return array();
01045         }
01046 
01054         function purgeCache( $options = array() ) {}
01055 
01061         function purgeDescription() {
01062                 $title = $this->getTitle();
01063                 if ( $title ) {
01064                         $title->invalidateCache();
01065                         $title->purgeSquid();
01066                 }
01067         }
01068 
01073         function purgeEverything() {
01074                 // Delete thumbnails and refresh file metadata cache
01075                 $this->purgeCache();
01076                 $this->purgeDescription();
01077 
01078                 // Purge cache of all pages using this file
01079                 $title = $this->getTitle();
01080                 if ( $title ) {
01081                         $update = new HTMLCacheUpdate( $title, 'imagelinks' );
01082                         $update->doUpdate();
01083                 }
01084         }
01085 
01097         function getHistory( $limit = null, $start = null, $end = null, $inc=true ) {
01098                 return array();
01099         }
01100 
01110         public function nextHistoryLine() {
01111                 return false;
01112         }
01113 
01120         public function resetHistory() {}
01121 
01129         function getHashPath() {
01130                 if ( !isset( $this->hashPath ) ) {
01131                         $this->assertRepoDefined();
01132                         $this->hashPath = $this->repo->getHashPath( $this->getName() );
01133                 }
01134                 return $this->hashPath;
01135         }
01136 
01143         function getRel() {
01144                 return $this->getHashPath() . $this->getName();
01145         }
01146 
01154         function getArchiveRel( $suffix = false ) {
01155                 $path = 'archive/' . $this->getHashPath();
01156                 if ( $suffix === false ) {
01157                         $path = substr( $path, 0, -1 );
01158                 } else {
01159                         $path .= $suffix;
01160                 }
01161                 return $path;
01162         }
01163 
01172         function getThumbRel( $suffix = false ) {
01173                 $path = $this->getRel();
01174                 if ( $suffix !== false ) {
01175                         $path .= '/' . $suffix;
01176                 }
01177                 return $path;
01178         }
01179 
01186         function getUrlRel() {
01187                 return $this->getHashPath() . rawurlencode( $this->getName() );
01188         }
01189 
01199         function getArchiveThumbRel( $archiveName, $suffix = false ) {
01200                 $path = 'archive/' . $this->getHashPath() . $archiveName . "/";
01201                 if ( $suffix === false ) {
01202                         $path = substr( $path, 0, -1 );
01203                 } else {
01204                         $path .= $suffix;
01205                 }
01206                 return $path;
01207         }
01208 
01216         function getArchivePath( $suffix = false ) {
01217                 $this->assertRepoDefined();
01218                 return $this->repo->getZonePath( 'public' ) . '/' . $this->getArchiveRel( $suffix );
01219         }
01220 
01229         function getArchiveThumbPath( $archiveName, $suffix = false ) {
01230                 $this->assertRepoDefined();
01231                 return $this->repo->getZonePath( 'thumb' ) . '/' .
01232                         $this->getArchiveThumbRel( $archiveName, $suffix );
01233         }
01234 
01242         function getThumbPath( $suffix = false ) {
01243                 $this->assertRepoDefined();
01244                 return $this->repo->getZonePath( 'thumb' ) . '/' . $this->getThumbRel( $suffix );
01245         }
01246 
01254         function getArchiveUrl( $suffix = false ) {
01255                 $this->assertRepoDefined();
01256                 $path = $this->repo->getZoneUrl( 'public' ) . '/archive/' . $this->getHashPath();
01257                 if ( $suffix === false ) {
01258                         $path = substr( $path, 0, -1 );
01259                 } else {
01260                         $path .= rawurlencode( $suffix );
01261                 }
01262                 return $path;
01263         }
01264 
01273         function getArchiveThumbUrl( $archiveName, $suffix = false ) {
01274                 $this->assertRepoDefined();
01275                 $path = $this->repo->getZoneUrl( 'thumb' ) . '/archive/' .
01276                         $this->getHashPath() . rawurlencode( $archiveName ) . "/";
01277                 if ( $suffix === false ) {
01278                         $path = substr( $path, 0, -1 );
01279                 } else {
01280                         $path .= rawurlencode( $suffix );
01281                 }
01282                 return $path;
01283         }
01284 
01292         function getThumbUrl( $suffix = false ) {
01293                 $this->assertRepoDefined();
01294                 $path = $this->repo->getZoneUrl( 'thumb' ) . '/' . $this->getUrlRel();
01295                 if ( $suffix !== false ) {
01296                         $path .= '/' . rawurlencode( $suffix );
01297                 }
01298                 return $path;
01299         }
01300 
01308         function getVirtualUrl( $suffix = false ) {
01309                 $this->assertRepoDefined();
01310                 $path = $this->repo->getVirtualUrl() . '/public/' . $this->getUrlRel();
01311                 if ( $suffix !== false ) {
01312                         $path .= '/' . rawurlencode( $suffix );
01313                 }
01314                 return $path;
01315         }
01316 
01324         function getArchiveVirtualUrl( $suffix = false ) {
01325                 $this->assertRepoDefined();
01326                 $path = $this->repo->getVirtualUrl() . '/public/archive/' . $this->getHashPath();
01327                 if ( $suffix === false ) {
01328                         $path = substr( $path, 0, -1 );
01329                 } else {
01330                         $path .= rawurlencode( $suffix );
01331                 }
01332                 return $path;
01333         }
01334 
01342         function getThumbVirtualUrl( $suffix = false ) {
01343                 $this->assertRepoDefined();
01344                 $path = $this->repo->getVirtualUrl() . '/thumb/' . $this->getUrlRel();
01345                 if ( $suffix !== false ) {
01346                         $path .= '/' . rawurlencode( $suffix );
01347                 }
01348                 return $path;
01349         }
01350 
01354         function isHashed() {
01355                 $this->assertRepoDefined();
01356                 return (bool)$this->repo->getHashLevels();
01357         }
01358 
01362         function readOnlyError() {
01363                 throw new MWException( get_class($this) . ': write operations are not supported' );
01364         }
01365 
01377         function recordUpload( $oldver, $desc, $license = '', $copyStatus = '', $source = '', $watch = false ) {
01378                 $this->readOnlyError();
01379         }
01380 
01399         function publish( $srcPath, $flags = 0 ) {
01400                 $this->readOnlyError();
01401         }
01402 
01406         function formatMetadata() {
01407                 if ( !$this->getHandler() ) {
01408                         return false;
01409                 }
01410                 return $this->getHandler()->formatMetadata( $this, $this->getMetadata() );
01411         }
01412 
01418         function isLocal() {
01419                 return $this->repo && $this->repo->isLocal();
01420         }
01421 
01427         function getRepoName() {
01428                 return $this->repo ? $this->repo->getName() : 'unknown';
01429         }
01430 
01436         function getRepo() {
01437                 return $this->repo;
01438         }
01439 
01446         function isOld() {
01447                 return false;
01448         }
01449 
01458         function isDeleted( $field ) {
01459                 return false;
01460         }
01461 
01467         function getVisibility() {
01468                 return 0;
01469         }
01470 
01476         function wasDeleted() {
01477                 $title = $this->getTitle();
01478                 return $title && $title->isDeletedQuick();
01479         }
01480 
01493          function move( $target ) {
01494                 $this->readOnlyError();
01495          }
01496 
01511         function delete( $reason, $suppress = false ) {
01512                 $this->readOnlyError();
01513         }
01514 
01529         function restore( $versions = array(), $unsuppress = false ) {
01530                 $this->readOnlyError();
01531         }
01532 
01540         function isMultipage() {
01541                 return $this->getHandler() && $this->handler->isMultiPage( $this );
01542         }
01543 
01550         function pageCount() {
01551                 if ( !isset( $this->pageCount ) ) {
01552                         if ( $this->getHandler() && $this->handler->isMultiPage( $this ) ) {
01553                                 $this->pageCount = $this->handler->pageCount( $this );
01554                         } else {
01555                                 $this->pageCount = false;
01556                         }
01557                 }
01558                 return $this->pageCount;
01559         }
01560 
01570         static function scaleHeight( $srcWidth, $srcHeight, $dstWidth ) {
01571                 // Exact integer multiply followed by division
01572                 if ( $srcWidth == 0 ) {
01573                         return 0;
01574                 } else {
01575                         return round( $srcHeight * $dstWidth / $srcWidth );
01576                 }
01577         }
01578 
01586         function getImageSize( $fileName ) {
01587                 if ( !$this->getHandler() ) {
01588                         return false;
01589                 }
01590                 return $this->handler->getImageSize( $this, $fileName );
01591         }
01592 
01599         function getDescriptionUrl() {
01600                 if ( $this->repo ) {
01601                         return $this->repo->getDescriptionUrl( $this->getName() );
01602                 } else {
01603                         return false;
01604                 }
01605         }
01606 
01612         function getDescriptionText() {
01613                 global $wgMemc, $wgLang;
01614                 if ( !$this->repo || !$this->repo->fetchDescription ) {
01615                         return false;
01616                 }
01617                 $renderUrl = $this->repo->getDescriptionRenderUrl( $this->getName(), $wgLang->getCode() );
01618                 if ( $renderUrl ) {
01619                         if ( $this->repo->descriptionCacheExpiry > 0 ) {
01620                                 wfDebug("Attempting to get the description from cache...");
01621                                 $key = $this->repo->getLocalCacheKey( 'RemoteFileDescription', 'url', $wgLang->getCode(),
01622                                                                         $this->getName() );
01623                                 $obj = $wgMemc->get($key);
01624                                 if ($obj) {
01625                                         wfDebug("success!\n");
01626                                         return $obj;
01627                                 }
01628                                 wfDebug("miss\n");
01629                         }
01630                         wfDebug( "Fetching shared description from $renderUrl\n" );
01631                         $res = Http::get( $renderUrl );
01632                         if ( $res && $this->repo->descriptionCacheExpiry > 0 ) {
01633                                 $wgMemc->set( $key, $res, $this->repo->descriptionCacheExpiry );
01634                         }
01635                         return $res;
01636                 } else {
01637                         return false;
01638                 }
01639         }
01640 
01653         function getDescription( $audience = self::FOR_PUBLIC, User $user = null ) {
01654                 return null;
01655         }
01656 
01662         function getTimestamp() {
01663                 $this->assertRepoDefined();
01664                 return $this->repo->getFileTimestamp( $this->getPath() );
01665         }
01666 
01672         function getSha1() {
01673                 $this->assertRepoDefined();
01674                 return $this->repo->getFileSha1( $this->getPath() );
01675         }
01676 
01682         function getStorageKey() {
01683                 $hash = $this->getSha1();
01684                 if ( !$hash ) {
01685                         return false;
01686                 }
01687                 $ext = $this->getExtension();
01688                 $dotExt = $ext === '' ? '' : ".$ext";
01689                 return $hash . $dotExt;
01690         }
01691 
01700         function userCan( $field, User $user = null ) {
01701                 return true;
01702         }
01703 
01713         static function getPropsFromPath( $path, $ext = true ) {
01714                 wfDebug( __METHOD__.": Getting file info for $path\n" );
01715                 wfDeprecated( __METHOD__, '1.19' );
01716 
01717                 $fsFile = new FSFile( $path );
01718                 return $fsFile->getProps();
01719         }
01720 
01732         static function sha1Base36( $path ) {
01733                 wfDeprecated( __METHOD__, '1.19' );
01734 
01735                 $fsFile = new FSFile( $path );
01736                 return $fsFile->getSha1Base36();
01737         }
01738 
01742         function getLongDesc() {
01743                 $handler = $this->getHandler();
01744                 if ( $handler ) {
01745                         return $handler->getLongDesc( $this );
01746                 } else {
01747                         return MediaHandler::getGeneralLongDesc( $this );
01748                 }
01749         }
01750 
01754         function getShortDesc() {
01755                 $handler = $this->getHandler();
01756                 if ( $handler ) {
01757                         return $handler->getShortDesc( $this );
01758                 } else {
01759                         return MediaHandler::getGeneralShortDesc( $this );
01760                 }
01761         }
01762 
01766         function getDimensionsString() {
01767                 $handler = $this->getHandler();
01768                 if ( $handler ) {
01769                         return $handler->getDimensionsString( $this );
01770                 } else {
01771                         return '';
01772                 }
01773         }
01774 
01778         function getRedirected() {
01779                 return $this->redirected;
01780         }
01781 
01785         function getRedirectedTitle() {
01786                 if ( $this->redirected ) {
01787                         if ( !$this->redirectTitle ) {
01788                                 $this->redirectTitle = Title::makeTitle( NS_FILE, $this->redirected );
01789                         }
01790                         return $this->redirectTitle;
01791                 }
01792         }
01793 
01798         function redirectedFrom( $from ) {
01799                 $this->redirected = $from;
01800         }
01801 
01805         function isMissing() {
01806                 return false;
01807         }
01808 
01813         public function isCacheable() {
01814                 return true;
01815         }
01816 
01821         protected function assertRepoDefined() {
01822                 if ( !( $this->repo instanceof $this->repoClass ) ) {
01823                         throw new MWException( "A {$this->repoClass} object is not set for this File.\n" );
01824                 }
01825         }
01826 
01831         protected function assertTitleDefined() {
01832                 if ( !( $this->title instanceof Title ) ) {
01833                         throw new MWException( "A Title object is not set for this File.\n" );
01834                 }
01835         }
01836 }