MediaWiki  master
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                         $ext = $this->getExtension();
00320                         $this->url = $this->repo->getZoneUrl( 'public', $ext ) . '/' . $this->getUrlRel();
00321                 }
00322                 return $this->url;
00323         }
00324 
00332         public function getFullUrl() {
00333                 return wfExpandUrl( $this->getUrl(), PROTO_RELATIVE );
00334         }
00335 
00339         public function getCanonicalUrl() {
00340                 return wfExpandUrl( $this->getUrl(), PROTO_CANONICAL );
00341         }
00342 
00346         function getViewURL() {
00347                 if ( $this->mustRender() ) {
00348                         if ( $this->canRender() ) {
00349                                 return $this->createThumb( $this->getWidth() );
00350                         } else {
00351                                 wfDebug( __METHOD__.': supposed to render ' . $this->getName() .
00352                                         ' (' . $this->getMimeType() . "), but can't!\n" );
00353                                 return $this->getURL(); #hm... return NULL?
00354                         }
00355                 } else {
00356                         return $this->getURL();
00357                 }
00358         }
00359 
00373         public function getPath() {
00374                 if ( !isset( $this->path ) ) {
00375                         $this->assertRepoDefined();
00376                         $this->path = $this->repo->getZonePath( 'public' ) . '/' . $this->getRel();
00377                 }
00378                 return $this->path;
00379         }
00380 
00388         public function getLocalRefPath() {
00389                 $this->assertRepoDefined();
00390                 if ( !isset( $this->fsFile ) ) {
00391                         $this->fsFile = $this->repo->getLocalReference( $this->getPath() );
00392                         if ( !$this->fsFile ) {
00393                                 $this->fsFile = false; // null => false; cache negative hits
00394                         }
00395                 }
00396                 return ( $this->fsFile )
00397                         ? $this->fsFile->getPath()
00398                         : false;
00399         }
00400 
00412         public function getWidth( $page = 1 ) {
00413                 return false;
00414         }
00415 
00427         public function getHeight( $page = 1 ) {
00428                 return false;
00429         }
00430 
00439         public function getUser( $type = 'text' ) {
00440                 return null;
00441         }
00442 
00448         public function getLength() {
00449                 $handler = $this->getHandler();
00450                 if ( $handler ) {
00451                         return $handler->getLength( $this );
00452                 } else {
00453                         return 0;
00454                 }
00455         }
00456 
00462         public function isVectorized() {
00463                 $handler = $this->getHandler();
00464                 if ( $handler ) {
00465                         return $handler->isVectorized( $this );
00466                 } else {
00467                         return false;
00468                 }
00469         }
00470 
00481         public function canAnimateThumbIfAppropriate() {
00482                 $handler = $this->getHandler();
00483                 if ( !$handler ) {
00484                         // We cannot handle image whatsoever, thus
00485                         // one would not expect it to be animated
00486                         // so true.
00487                         return true;
00488                 } else {
00489                         if ( $this->allowInlineDisplay()
00490                                 && $handler->isAnimatedImage( $this )
00491                                 && !$handler->canAnimateThumbnail( $this )
00492                         ) {
00493                                 // Image is animated, but thumbnail isn't.
00494                                 // This is unexpected to the user.
00495                                 return false;
00496                         } else {
00497                                 // Image is not animated, so one would
00498                                 // not expect thumb to be
00499                                 return true;
00500                         }
00501                 }
00502         }
00503 
00510         public function getMetadata() {
00511                 return false;
00512         }
00513 
00521         public function convertMetadataVersion($metadata, $version) {
00522                 $handler = $this->getHandler();
00523                 if ( !is_array( $metadata ) ) {
00524                         // Just to make the return type consistent
00525                         $metadata = unserialize( $metadata );
00526                 }
00527                 if ( $handler ) {
00528                         return $handler->convertMetadataVersion( $metadata, $version );
00529                 } else {
00530                         return $metadata;
00531                 }
00532         }
00533 
00540         public function getBitDepth() {
00541                 return 0;
00542         }
00543 
00550         public function getSize() {
00551                 return false;
00552         }
00553 
00561         function getMimeType() {
00562                 return 'unknown/unknown';
00563         }
00564 
00572         function getMediaType() {
00573                 return MEDIATYPE_UNKNOWN;
00574         }
00575 
00588         function canRender() {
00589                 if ( !isset( $this->canRender ) ) {
00590                         $this->canRender = $this->getHandler() && $this->handler->canRender( $this );
00591                 }
00592                 return $this->canRender;
00593         }
00594 
00599         protected function getCanRender() {
00600                 return $this->canRender();
00601         }
00602 
00613         function mustRender() {
00614                 return $this->getHandler() && $this->handler->mustRender( $this );
00615         }
00616 
00622         function allowInlineDisplay() {
00623                 return $this->canRender();
00624         }
00625 
00639         function isSafeFile() {
00640                 if ( !isset( $this->isSafeFile ) ) {
00641                         $this->isSafeFile = $this->_getIsSafeFile();
00642                 }
00643                 return $this->isSafeFile;
00644         }
00645 
00651         protected function getIsSafeFile() {
00652                 return $this->isSafeFile();
00653         }
00654 
00660         protected function _getIsSafeFile() {
00661                 global $wgTrustedMediaFormats;
00662 
00663                 if ( $this->allowInlineDisplay() ) {
00664                         return true;
00665                 }
00666                 if ($this->isTrustedFile()) {
00667                         return true;
00668                 }
00669 
00670                 $type = $this->getMediaType();
00671                 $mime = $this->getMimeType();
00672                 #wfDebug("LocalFile::isSafeFile: type= $type, mime= $mime\n");
00673 
00674                 if ( !$type || $type === MEDIATYPE_UNKNOWN ) {
00675                         return false; #unknown type, not trusted
00676                 }
00677                 if ( in_array( $type, $wgTrustedMediaFormats ) ) {
00678                         return true;
00679                 }
00680 
00681                 if ( $mime === "unknown/unknown" ) {
00682                         return false; #unknown type, not trusted
00683                 }
00684                 if ( in_array( $mime, $wgTrustedMediaFormats) ) {
00685                         return true;
00686                 }
00687 
00688                 return false;
00689         }
00690 
00704         function isTrustedFile() {
00705                 #this could be implemented to check a flag in the database,
00706                 #look for signatures, etc
00707                 return false;
00708         }
00709 
00717         public function exists() {
00718                 return $this->getPath() && $this->repo->fileExists( $this->path );
00719         }
00720 
00727         public function isVisible() {
00728                 return $this->exists();
00729         }
00730 
00734         function getTransformScript() {
00735                 if ( !isset( $this->transformScript ) ) {
00736                         $this->transformScript = false;
00737                         if ( $this->repo ) {
00738                                 $script = $this->repo->getThumbScriptUrl();
00739                                 if ( $script ) {
00740                                         $this->transformScript = "$script?f=" . urlencode( $this->getName() );
00741                                 }
00742                         }
00743                 }
00744                 return $this->transformScript;
00745         }
00746 
00754         function getUnscaledThumb( $handlerParams = array() ) {
00755                 $hp =& $handlerParams;
00756                 $page = isset( $hp['page'] ) ? $hp['page'] : false;
00757                 $width = $this->getWidth( $page );
00758                 if ( !$width ) {
00759                         return $this->iconThumb();
00760                 }
00761                 $hp['width'] = $width;
00762                 return $this->transform( $hp );
00763         }
00764 
00774         public function thumbName( $params, $flags = 0 ) {
00775                 $name = ( $this->repo && !( $flags & self::THUMB_FULL_NAME ) )
00776                         ? $this->repo->nameForThumb( $this->getName() )
00777                         : $this->getName();
00778                 return $this->generateThumbName( $name, $params );
00779         }
00780 
00789         public function generateThumbName( $name, $params ) {
00790                 if ( !$this->getHandler() ) {
00791                         return null;
00792                 }
00793                 $extension = $this->getExtension();
00794                 list( $thumbExt, $thumbMime ) = $this->handler->getThumbType(
00795                         $extension, $this->getMimeType(), $params );
00796                 $thumbName = $this->handler->makeParamString( $params ) . '-' . $name;
00797                 if ( $thumbExt != $extension ) {
00798                         $thumbName .= ".$thumbExt";
00799                 }
00800                 return $thumbName;
00801         }
00802 
00820         public function createThumb( $width, $height = -1 ) {
00821                 $params = array( 'width' => $width );
00822                 if ( $height != -1 ) {
00823                         $params['height'] = $height;
00824                 }
00825                 $thumb = $this->transform( $params );
00826                 if ( is_null( $thumb ) || $thumb->isError() ) {
00827                         return '';
00828                 }
00829                 return $thumb->getUrl();
00830         }
00831 
00841         protected function transformErrorOutput( $thumbPath, $thumbUrl, $params, $flags ) {
00842                 global $wgIgnoreImageErrors;
00843 
00844                 if ( $wgIgnoreImageErrors && !( $flags & self::RENDER_NOW ) ) {
00845                         return $this->handler->getTransform( $this, $thumbPath, $thumbUrl, $params );
00846                 } else {
00847                         return new MediaTransformError( 'thumbnail_error',
00848                                 $params['width'], 0, wfMessage( 'thumbnail-dest-create' )->text() );
00849                 }
00850         }
00851 
00860         function transform( $params, $flags = 0 ) {
00861                 global $wgUseSquid, $wgIgnoreImageErrors, $wgThumbnailEpoch;
00862 
00863                 wfProfileIn( __METHOD__ );
00864                 do {
00865                         if ( !$this->canRender() ) {
00866                                 $thumb = $this->iconThumb();
00867                                 break; // not a bitmap or renderable image, don't try
00868                         }
00869 
00870                         // Get the descriptionUrl to embed it as comment into the thumbnail. Bug 19791.
00871                         $descriptionUrl = $this->getDescriptionUrl();
00872                         if ( $descriptionUrl ) {
00873                                 $params['descriptionUrl'] = wfExpandUrl( $descriptionUrl, PROTO_CANONICAL );
00874                         }
00875 
00876                         $script = $this->getTransformScript();
00877                         if ( $script && !( $flags & self::RENDER_NOW ) ) {
00878                                 // Use a script to transform on client request, if possible
00879                                 $thumb = $this->handler->getScriptedTransform( $this, $script, $params );
00880                                 if ( $thumb ) {
00881                                         break;
00882                                 }
00883                         }
00884 
00885                         $normalisedParams = $params;
00886                         $this->handler->normaliseParams( $this, $normalisedParams );
00887 
00888                         $thumbName = $this->thumbName( $normalisedParams );
00889                         $thumbUrl = $this->getThumbUrl( $thumbName );
00890                         $thumbPath = $this->getThumbPath( $thumbName ); // final thumb path
00891 
00892                         if ( $this->repo ) {
00893                                 // Defer rendering if a 404 handler is set up...
00894                                 if ( $this->repo->canTransformVia404() && !( $flags & self::RENDER_NOW ) ) {
00895                                         wfDebug( __METHOD__ . " transformation deferred." );
00896                                         // XXX: Pass in the storage path even though we are not rendering anything
00897                                         // and the path is supposed to be an FS path. This is due to getScalerType()
00898                                         // getting called on the path and clobbering $thumb->getUrl() if it's false.
00899                                         $thumb = $this->handler->getTransform( $this, $thumbPath, $thumbUrl, $params );
00900                                         break;
00901                                 }
00902                                 // Clean up broken thumbnails as needed
00903                                 $this->migrateThumbFile( $thumbName );
00904                                 // Check if an up-to-date thumbnail already exists...
00905                                 wfDebug( __METHOD__.": Doing stat for $thumbPath\n" );
00906                                 if ( $this->repo->fileExists( $thumbPath ) && !( $flags & self::RENDER_FORCE ) ) {
00907                                         $timestamp = $this->repo->getFileTimestamp( $thumbPath );
00908                                         if ( $timestamp !== false && $timestamp >= $wgThumbnailEpoch ) {
00909                                                 // XXX: Pass in the storage path even though we are not rendering anything
00910                                                 // and the path is supposed to be an FS path. This is due to getScalerType()
00911                                                 // getting called on the path and clobbering $thumb->getUrl() if it's false.
00912                                                 $thumb = $this->handler->getTransform( $this, $thumbPath, $thumbUrl, $params );
00913                                                 $thumb->setStoragePath( $thumbPath );
00914                                                 break;
00915                                         }
00916                                 } elseif ( $flags & self::RENDER_FORCE ) {
00917                                         wfDebug( __METHOD__ . " forcing rendering per flag File::RENDER_FORCE\n" );
00918                                 }
00919                         }
00920 
00921                         // If the backend is ready-only, don't keep generating thumbnails
00922                         // only to return transformation errors, just return the error now.
00923                         if ( $this->repo->getReadOnlyReason() !== false ) {
00924                                 $thumb = $this->transformErrorOutput( $thumbPath, $thumbUrl, $params, $flags );
00925                                 break;
00926                         }
00927 
00928                         // Create a temp FS file with the same extension and the thumbnail
00929                         $thumbExt = FileBackend::extensionFromPath( $thumbPath );
00930                         $tmpFile = TempFSFile::factory( 'transform_', $thumbExt );
00931                         if ( !$tmpFile ) {
00932                                 $thumb = $this->transformErrorOutput( $thumbPath, $thumbUrl, $params, $flags );
00933                                 break;
00934                         }
00935                         $tmpThumbPath = $tmpFile->getPath(); // path of 0-byte temp file
00936 
00937                         // Actually render the thumbnail...
00938                         wfProfileIn( __METHOD__ . '-doTransform' );
00939                         $thumb = $this->handler->doTransform( $this, $tmpThumbPath, $thumbUrl, $params );
00940                         wfProfileOut( __METHOD__ . '-doTransform' );
00941                         $tmpFile->bind( $thumb ); // keep alive with $thumb
00942 
00943                         if ( !$thumb ) { // bad params?
00944                                 $thumb = null;
00945                         } elseif ( $thumb->isError() ) { // transform error
00946                                 $this->lastError = $thumb->toText();
00947                                 // Ignore errors if requested
00948                                 if ( $wgIgnoreImageErrors && !( $flags & self::RENDER_NOW ) ) {
00949                                         $thumb = $this->handler->getTransform( $this, $tmpThumbPath, $thumbUrl, $params );
00950                                 }
00951                         } elseif ( $this->repo && $thumb->hasFile() && !$thumb->fileIsSource() ) {
00952                                 // Copy the thumbnail from the file system into storage...
00953                                 $disposition = $this->getThumbDisposition( $thumbName );
00954                                 $status = $this->repo->quickImport( $tmpThumbPath, $thumbPath, $disposition );
00955                                 if ( $status->isOK() ) {
00956                                         $thumb->setStoragePath( $thumbPath );
00957                                 } else {
00958                                         $thumb = $this->transformErrorOutput( $thumbPath, $thumbUrl, $params, $flags );
00959                                 }
00960                                 // Give extensions a chance to do something with this thumbnail...
00961                                 wfRunHooks( 'FileTransformed', array( $this, $thumb, $tmpThumbPath, $thumbPath ) );
00962                         }
00963 
00964                         // Purge. Useful in the event of Core -> Squid connection failure or squid
00965                         // purge collisions from elsewhere during failure. Don't keep triggering for
00966                         // "thumbs" which have the main image URL though (bug 13776)
00967                         if ( $wgUseSquid ) {
00968                                 if ( !$thumb || $thumb->isError() || $thumb->getUrl() != $this->getURL() ) {
00969                                         SquidUpdate::purge( array( $thumbUrl ) );
00970                                 }
00971                         }
00972                 } while ( false );
00973 
00974                 wfProfileOut( __METHOD__ );
00975                 return is_object( $thumb ) ? $thumb : false;
00976         }
00977 
00982         function getThumbDisposition( $thumbName ) {
00983                 $fileName = $this->name; // file name to suggest
00984                 $thumbExt = FileBackend::extensionFromPath( $thumbName );
00985                 if ( $thumbExt != '' && $thumbExt !== $this->getExtension() ) {
00986                         $fileName .= ".$thumbExt";
00987                 }
00988                 return FileBackend::makeContentDisposition( 'inline', $fileName );
00989         }
00990 
00996         function migrateThumbFile( $thumbName ) {}
00997 
01003         function getHandler() {
01004                 if ( !isset( $this->handler ) ) {
01005                         $this->handler = MediaHandler::getHandler( $this->getMimeType() );
01006                 }
01007                 return $this->handler;
01008         }
01009 
01015         function iconThumb() {
01016                 global $wgStylePath, $wgStyleDirectory;
01017 
01018                 $try = array( 'fileicon-' . $this->getExtension() . '.png', 'fileicon.png' );
01019                 foreach ( $try as $icon ) {
01020                         $path = '/common/images/icons/' . $icon;
01021                         $filepath = $wgStyleDirectory . $path;
01022                         if ( file_exists( $filepath ) ) { // always FS
01023                                 $params = array( 'width' => 120, 'height' => 120 );
01024                                 return new ThumbnailImage( $this, $wgStylePath . $path, false, $params );
01025                         }
01026                 }
01027                 return null;
01028         }
01029 
01034         function getLastError() {
01035                 return $this->lastError;
01036         }
01037 
01044         function getThumbnails() {
01045                 return array();
01046         }
01047 
01055         function purgeCache( $options = array() ) {}
01056 
01062         function purgeDescription() {
01063                 $title = $this->getTitle();
01064                 if ( $title ) {
01065                         $title->invalidateCache();
01066                         $title->purgeSquid();
01067                 }
01068         }
01069 
01074         function purgeEverything() {
01075                 // Delete thumbnails and refresh file metadata cache
01076                 $this->purgeCache();
01077                 $this->purgeDescription();
01078 
01079                 // Purge cache of all pages using this file
01080                 $title = $this->getTitle();
01081                 if ( $title ) {
01082                         $update = new HTMLCacheUpdate( $title, 'imagelinks' );
01083                         $update->doUpdate();
01084                 }
01085         }
01086 
01098         function getHistory( $limit = null, $start = null, $end = null, $inc=true ) {
01099                 return array();
01100         }
01101 
01111         public function nextHistoryLine() {
01112                 return false;
01113         }
01114 
01121         public function resetHistory() {}
01122 
01130         function getHashPath() {
01131                 if ( !isset( $this->hashPath ) ) {
01132                         $this->assertRepoDefined();
01133                         $this->hashPath = $this->repo->getHashPath( $this->getName() );
01134                 }
01135                 return $this->hashPath;
01136         }
01137 
01144         function getRel() {
01145                 return $this->getHashPath() . $this->getName();
01146         }
01147 
01155         function getArchiveRel( $suffix = false ) {
01156                 $path = 'archive/' . $this->getHashPath();
01157                 if ( $suffix === false ) {
01158                         $path = substr( $path, 0, -1 );
01159                 } else {
01160                         $path .= $suffix;
01161                 }
01162                 return $path;
01163         }
01164 
01173         function getThumbRel( $suffix = false ) {
01174                 $path = $this->getRel();
01175                 if ( $suffix !== false ) {
01176                         $path .= '/' . $suffix;
01177                 }
01178                 return $path;
01179         }
01180 
01187         function getUrlRel() {
01188                 return $this->getHashPath() . rawurlencode( $this->getName() );
01189         }
01190 
01200         function getArchiveThumbRel( $archiveName, $suffix = false ) {
01201                 $path = 'archive/' . $this->getHashPath() . $archiveName . "/";
01202                 if ( $suffix === false ) {
01203                         $path = substr( $path, 0, -1 );
01204                 } else {
01205                         $path .= $suffix;
01206                 }
01207                 return $path;
01208         }
01209 
01217         function getArchivePath( $suffix = false ) {
01218                 $this->assertRepoDefined();
01219                 return $this->repo->getZonePath( 'public' ) . '/' . $this->getArchiveRel( $suffix );
01220         }
01221 
01230         function getArchiveThumbPath( $archiveName, $suffix = false ) {
01231                 $this->assertRepoDefined();
01232                 return $this->repo->getZonePath( 'thumb' ) . '/' .
01233                         $this->getArchiveThumbRel( $archiveName, $suffix );
01234         }
01235 
01243         function getThumbPath( $suffix = false ) {
01244                 $this->assertRepoDefined();
01245                 return $this->repo->getZonePath( 'thumb' ) . '/' . $this->getThumbRel( $suffix );
01246         }
01247 
01255         function getArchiveUrl( $suffix = false ) {
01256                 $this->assertRepoDefined();
01257                 $ext = $this->getExtension();
01258                 $path = $this->repo->getZoneUrl( 'public', $ext ) . '/archive/' . $this->getHashPath();
01259                 if ( $suffix === false ) {
01260                         $path = substr( $path, 0, -1 );
01261                 } else {
01262                         $path .= rawurlencode( $suffix );
01263                 }
01264                 return $path;
01265         }
01266 
01275         function getArchiveThumbUrl( $archiveName, $suffix = false ) {
01276                 $this->assertRepoDefined();
01277                 $ext = $this->getExtension();
01278                 $path = $this->repo->getZoneUrl( 'thumb', $ext ) . '/archive/' .
01279                         $this->getHashPath() . rawurlencode( $archiveName ) . "/";
01280                 if ( $suffix === false ) {
01281                         $path = substr( $path, 0, -1 );
01282                 } else {
01283                         $path .= rawurlencode( $suffix );
01284                 }
01285                 return $path;
01286         }
01287 
01295         function getThumbUrl( $suffix = false ) {
01296                 $this->assertRepoDefined();
01297                 $ext = $this->getExtension();
01298                 $path = $this->repo->getZoneUrl( 'thumb', $ext ) . '/' . $this->getUrlRel();
01299                 if ( $suffix !== false ) {
01300                         $path .= '/' . rawurlencode( $suffix );
01301                 }
01302                 return $path;
01303         }
01304 
01312         function getVirtualUrl( $suffix = false ) {
01313                 $this->assertRepoDefined();
01314                 $path = $this->repo->getVirtualUrl() . '/public/' . $this->getUrlRel();
01315                 if ( $suffix !== false ) {
01316                         $path .= '/' . rawurlencode( $suffix );
01317                 }
01318                 return $path;
01319         }
01320 
01328         function getArchiveVirtualUrl( $suffix = false ) {
01329                 $this->assertRepoDefined();
01330                 $path = $this->repo->getVirtualUrl() . '/public/archive/' . $this->getHashPath();
01331                 if ( $suffix === false ) {
01332                         $path = substr( $path, 0, -1 );
01333                 } else {
01334                         $path .= rawurlencode( $suffix );
01335                 }
01336                 return $path;
01337         }
01338 
01346         function getThumbVirtualUrl( $suffix = false ) {
01347                 $this->assertRepoDefined();
01348                 $path = $this->repo->getVirtualUrl() . '/thumb/' . $this->getUrlRel();
01349                 if ( $suffix !== false ) {
01350                         $path .= '/' . rawurlencode( $suffix );
01351                 }
01352                 return $path;
01353         }
01354 
01358         function isHashed() {
01359                 $this->assertRepoDefined();
01360                 return (bool)$this->repo->getHashLevels();
01361         }
01362 
01366         function readOnlyError() {
01367                 throw new MWException( get_class($this) . ': write operations are not supported' );
01368         }
01369 
01381         function recordUpload( $oldver, $desc, $license = '', $copyStatus = '', $source = '', $watch = false ) {
01382                 $this->readOnlyError();
01383         }
01384 
01403         function publish( $srcPath, $flags = 0 ) {
01404                 $this->readOnlyError();
01405         }
01406 
01410         function formatMetadata() {
01411                 if ( !$this->getHandler() ) {
01412                         return false;
01413                 }
01414                 return $this->getHandler()->formatMetadata( $this, $this->getMetadata() );
01415         }
01416 
01422         function isLocal() {
01423                 return $this->repo && $this->repo->isLocal();
01424         }
01425 
01431         function getRepoName() {
01432                 return $this->repo ? $this->repo->getName() : 'unknown';
01433         }
01434 
01440         function getRepo() {
01441                 return $this->repo;
01442         }
01443 
01450         function isOld() {
01451                 return false;
01452         }
01453 
01462         function isDeleted( $field ) {
01463                 return false;
01464         }
01465 
01471         function getVisibility() {
01472                 return 0;
01473         }
01474 
01480         function wasDeleted() {
01481                 $title = $this->getTitle();
01482                 return $title && $title->isDeletedQuick();
01483         }
01484 
01497          function move( $target ) {
01498                 $this->readOnlyError();
01499          }
01500 
01515         function delete( $reason, $suppress = false ) {
01516                 $this->readOnlyError();
01517         }
01518 
01533         function restore( $versions = array(), $unsuppress = false ) {
01534                 $this->readOnlyError();
01535         }
01536 
01544         function isMultipage() {
01545                 return $this->getHandler() && $this->handler->isMultiPage( $this );
01546         }
01547 
01554         function pageCount() {
01555                 if ( !isset( $this->pageCount ) ) {
01556                         if ( $this->getHandler() && $this->handler->isMultiPage( $this ) ) {
01557                                 $this->pageCount = $this->handler->pageCount( $this );
01558                         } else {
01559                                 $this->pageCount = false;
01560                         }
01561                 }
01562                 return $this->pageCount;
01563         }
01564 
01574         static function scaleHeight( $srcWidth, $srcHeight, $dstWidth ) {
01575                 // Exact integer multiply followed by division
01576                 if ( $srcWidth == 0 ) {
01577                         return 0;
01578                 } else {
01579                         return round( $srcHeight * $dstWidth / $srcWidth );
01580                 }
01581         }
01582 
01590         function getImageSize( $fileName ) {
01591                 if ( !$this->getHandler() ) {
01592                         return false;
01593                 }
01594                 return $this->handler->getImageSize( $this, $fileName );
01595         }
01596 
01603         function getDescriptionUrl() {
01604                 if ( $this->repo ) {
01605                         return $this->repo->getDescriptionUrl( $this->getName() );
01606                 } else {
01607                         return false;
01608                 }
01609         }
01610 
01616         function getDescriptionText() {
01617                 global $wgMemc, $wgLang;
01618                 if ( !$this->repo || !$this->repo->fetchDescription ) {
01619                         return false;
01620                 }
01621                 $renderUrl = $this->repo->getDescriptionRenderUrl( $this->getName(), $wgLang->getCode() );
01622                 if ( $renderUrl ) {
01623                         if ( $this->repo->descriptionCacheExpiry > 0 ) {
01624                                 wfDebug("Attempting to get the description from cache...");
01625                                 $key = $this->repo->getLocalCacheKey( 'RemoteFileDescription', 'url', $wgLang->getCode(),
01626                                                                         $this->getName() );
01627                                 $obj = $wgMemc->get($key);
01628                                 if ($obj) {
01629                                         wfDebug("success!\n");
01630                                         return $obj;
01631                                 }
01632                                 wfDebug("miss\n");
01633                         }
01634                         wfDebug( "Fetching shared description from $renderUrl\n" );
01635                         $res = Http::get( $renderUrl );
01636                         if ( $res && $this->repo->descriptionCacheExpiry > 0 ) {
01637                                 $wgMemc->set( $key, $res, $this->repo->descriptionCacheExpiry );
01638                         }
01639                         return $res;
01640                 } else {
01641                         return false;
01642                 }
01643         }
01644 
01657         function getDescription( $audience = self::FOR_PUBLIC, User $user = null ) {
01658                 return null;
01659         }
01660 
01666         function getTimestamp() {
01667                 $this->assertRepoDefined();
01668                 return $this->repo->getFileTimestamp( $this->getPath() );
01669         }
01670 
01676         function getSha1() {
01677                 $this->assertRepoDefined();
01678                 return $this->repo->getFileSha1( $this->getPath() );
01679         }
01680 
01686         function getStorageKey() {
01687                 $hash = $this->getSha1();
01688                 if ( !$hash ) {
01689                         return false;
01690                 }
01691                 $ext = $this->getExtension();
01692                 $dotExt = $ext === '' ? '' : ".$ext";
01693                 return $hash . $dotExt;
01694         }
01695 
01704         function userCan( $field, User $user = null ) {
01705                 return true;
01706         }
01707 
01717         static function getPropsFromPath( $path, $ext = true ) {
01718                 wfDebug( __METHOD__.": Getting file info for $path\n" );
01719                 wfDeprecated( __METHOD__, '1.19' );
01720 
01721                 $fsFile = new FSFile( $path );
01722                 return $fsFile->getProps();
01723         }
01724 
01736         static function sha1Base36( $path ) {
01737                 wfDeprecated( __METHOD__, '1.19' );
01738 
01739                 $fsFile = new FSFile( $path );
01740                 return $fsFile->getSha1Base36();
01741         }
01742 
01746         function getLongDesc() {
01747                 $handler = $this->getHandler();
01748                 if ( $handler ) {
01749                         return $handler->getLongDesc( $this );
01750                 } else {
01751                         return MediaHandler::getGeneralLongDesc( $this );
01752                 }
01753         }
01754 
01758         function getShortDesc() {
01759                 $handler = $this->getHandler();
01760                 if ( $handler ) {
01761                         return $handler->getShortDesc( $this );
01762                 } else {
01763                         return MediaHandler::getGeneralShortDesc( $this );
01764                 }
01765         }
01766 
01770         function getDimensionsString() {
01771                 $handler = $this->getHandler();
01772                 if ( $handler ) {
01773                         return $handler->getDimensionsString( $this );
01774                 } else {
01775                         return '';
01776                 }
01777         }
01778 
01782         function getRedirected() {
01783                 return $this->redirected;
01784         }
01785 
01789         function getRedirectedTitle() {
01790                 if ( $this->redirected ) {
01791                         if ( !$this->redirectTitle ) {
01792                                 $this->redirectTitle = Title::makeTitle( NS_FILE, $this->redirected );
01793                         }
01794                         return $this->redirectTitle;
01795                 }
01796         }
01797 
01802         function redirectedFrom( $from ) {
01803                 $this->redirected = $from;
01804         }
01805 
01809         function isMissing() {
01810                 return false;
01811         }
01812 
01817         public function isCacheable() {
01818                 return true;
01819         }
01820 
01825         protected function assertRepoDefined() {
01826                 if ( !( $this->repo instanceof $this->repoClass ) ) {
01827                         throw new MWException( "A {$this->repoClass} object is not set for this File.\n" );
01828                 }
01829         }
01830 
01835         protected function assertTitleDefined() {
01836                 if ( !( $this->title instanceof Title ) ) {
01837                         throw new MWException( "A Title object is not set for this File.\n" );
01838                 }
01839         }
01840 }