MediaWiki  REL1_21
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->getHandler()->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                         $handler = $this->getHandler();
00877                         $script = $this->getTransformScript();
00878                         if ( $script && !( $flags & self::RENDER_NOW ) ) {
00879                                 // Use a script to transform on client request, if possible
00880                                 $thumb = $handler->getScriptedTransform( $this, $script, $params );
00881                                 if ( $thumb ) {
00882                                         break;
00883                                 }
00884                         }
00885 
00886                         $normalisedParams = $params;
00887                         $handler->normaliseParams( $this, $normalisedParams );
00888 
00889                         $thumbName = $this->thumbName( $normalisedParams );
00890                         $thumbUrl = $this->getThumbUrl( $thumbName );
00891                         $thumbPath = $this->getThumbPath( $thumbName ); // final thumb path
00892 
00893                         if ( $this->repo ) {
00894                                 // Defer rendering if a 404 handler is set up...
00895                                 if ( $this->repo->canTransformVia404() && !( $flags & self::RENDER_NOW ) ) {
00896                                         wfDebug( __METHOD__ . " transformation deferred." );
00897                                         // XXX: Pass in the storage path even though we are not rendering anything
00898                                         // and the path is supposed to be an FS path. This is due to getScalerType()
00899                                         // getting called on the path and clobbering $thumb->getUrl() if it's false.
00900                                         $thumb = $handler->getTransform( $this, $thumbPath, $thumbUrl, $params );
00901                                         break;
00902                                 }
00903                                 // Clean up broken thumbnails as needed
00904                                 $this->migrateThumbFile( $thumbName );
00905                                 // Check if an up-to-date thumbnail already exists...
00906                                 wfDebug( __METHOD__ . ": Doing stat for $thumbPath\n" );
00907                                 if ( !( $flags & self::RENDER_FORCE ) && $this->repo->fileExists( $thumbPath ) ) {
00908                                         $timestamp = $this->repo->getFileTimestamp( $thumbPath );
00909                                         if ( $timestamp !== false && $timestamp >= $wgThumbnailEpoch ) {
00910                                                 // XXX: Pass in the storage path even though we are not rendering anything
00911                                                 // and the path is supposed to be an FS path. This is due to getScalerType()
00912                                                 // getting called on the path and clobbering $thumb->getUrl() if it's false.
00913                                                 $thumb = $handler->getTransform(
00914                                                         $this, $thumbPath, $thumbUrl, $params );
00915                                                 $thumb->setStoragePath( $thumbPath );
00916                                                 break;
00917                                         }
00918                                 } elseif ( $flags & self::RENDER_FORCE ) {
00919                                         wfDebug( __METHOD__ . " forcing rendering per flag File::RENDER_FORCE\n" );
00920                                 }
00921                         }
00922 
00923                         // If the backend is ready-only, don't keep generating thumbnails
00924                         // only to return transformation errors, just return the error now.
00925                         if ( $this->repo->getReadOnlyReason() !== false ) {
00926                                 $thumb = $this->transformErrorOutput( $thumbPath, $thumbUrl, $params, $flags );
00927                                 break;
00928                         }
00929 
00930                         // Create a temp FS file with the same extension and the thumbnail
00931                         $thumbExt = FileBackend::extensionFromPath( $thumbPath );
00932                         $tmpFile = TempFSFile::factory( 'transform_', $thumbExt );
00933                         if ( !$tmpFile ) {
00934                                 $thumb = $this->transformErrorOutput( $thumbPath, $thumbUrl, $params, $flags );
00935                                 break;
00936                         }
00937                         $tmpThumbPath = $tmpFile->getPath(); // path of 0-byte temp file
00938 
00939                         // Actually render the thumbnail...
00940                         wfProfileIn( __METHOD__ . '-doTransform' );
00941                         $thumb = $handler->doTransform( $this, $tmpThumbPath, $thumbUrl, $params );
00942                         wfProfileOut( __METHOD__ . '-doTransform' );
00943                         $tmpFile->bind( $thumb ); // keep alive with $thumb
00944 
00945                         if ( !$thumb ) { // bad params?
00946                                 $thumb = null;
00947                         } elseif ( $thumb->isError() ) { // transform error
00948                                 $this->lastError = $thumb->toText();
00949                                 // Ignore errors if requested
00950                                 if ( $wgIgnoreImageErrors && !( $flags & self::RENDER_NOW ) ) {
00951                                         $thumb = $handler->getTransform( $this, $tmpThumbPath, $thumbUrl, $params );
00952                                 }
00953                         } elseif ( $this->repo && $thumb->hasFile() && !$thumb->fileIsSource() ) {
00954                                 // Copy the thumbnail from the file system into storage...
00955                                 $disposition = $this->getThumbDisposition( $thumbName );
00956                                 $status = $this->repo->quickImport( $tmpThumbPath, $thumbPath, $disposition );
00957                                 if ( $status->isOK() ) {
00958                                         $thumb->setStoragePath( $thumbPath );
00959                                 } else {
00960                                         $thumb = $this->transformErrorOutput( $thumbPath, $thumbUrl, $params, $flags );
00961                                 }
00962                                 // Give extensions a chance to do something with this thumbnail...
00963                                 wfRunHooks( 'FileTransformed', array( $this, $thumb, $tmpThumbPath, $thumbPath ) );
00964                         }
00965 
00966                         // Purge. Useful in the event of Core -> Squid connection failure or squid
00967                         // purge collisions from elsewhere during failure. Don't keep triggering for
00968                         // "thumbs" which have the main image URL though (bug 13776)
00969                         if ( $wgUseSquid ) {
00970                                 if ( !$thumb || $thumb->isError() || $thumb->getUrl() != $this->getURL() ) {
00971                                         SquidUpdate::purge( array( $thumbUrl ) );
00972                                 }
00973                         }
00974                 } while ( false );
00975 
00976                 wfProfileOut( __METHOD__ );
00977                 return is_object( $thumb ) ? $thumb : false;
00978         }
00979 
00984         function getThumbDisposition( $thumbName ) {
00985                 $fileName = $this->name; // file name to suggest
00986                 $thumbExt = FileBackend::extensionFromPath( $thumbName );
00987                 if ( $thumbExt != '' && $thumbExt !== $this->getExtension() ) {
00988                         $fileName .= ".$thumbExt";
00989                 }
00990                 return FileBackend::makeContentDisposition( 'inline', $fileName );
00991         }
00992 
00998         function migrateThumbFile( $thumbName ) {}
00999 
01005         function getHandler() {
01006                 if ( !isset( $this->handler ) ) {
01007                         $this->handler = MediaHandler::getHandler( $this->getMimeType() );
01008                 }
01009                 return $this->handler;
01010         }
01011 
01017         function iconThumb() {
01018                 global $wgStylePath, $wgStyleDirectory;
01019 
01020                 $try = array( 'fileicon-' . $this->getExtension() . '.png', 'fileicon.png' );
01021                 foreach ( $try as $icon ) {
01022                         $path = '/common/images/icons/' . $icon;
01023                         $filepath = $wgStyleDirectory . $path;
01024                         if ( file_exists( $filepath ) ) { // always FS
01025                                 $params = array( 'width' => 120, 'height' => 120 );
01026                                 return new ThumbnailImage( $this, $wgStylePath . $path, false, $params );
01027                         }
01028                 }
01029                 return null;
01030         }
01031 
01036         function getLastError() {
01037                 return $this->lastError;
01038         }
01039 
01046         function getThumbnails() {
01047                 return array();
01048         }
01049 
01057         function purgeCache( $options = array() ) {}
01058 
01064         function purgeDescription() {
01065                 $title = $this->getTitle();
01066                 if ( $title ) {
01067                         $title->invalidateCache();
01068                         $title->purgeSquid();
01069                 }
01070         }
01071 
01076         function purgeEverything() {
01077                 // Delete thumbnails and refresh file metadata cache
01078                 $this->purgeCache();
01079                 $this->purgeDescription();
01080 
01081                 // Purge cache of all pages using this file
01082                 $title = $this->getTitle();
01083                 if ( $title ) {
01084                         $update = new HTMLCacheUpdate( $title, 'imagelinks' );
01085                         $update->doUpdate();
01086                 }
01087         }
01088 
01100         function getHistory( $limit = null, $start = null, $end = null, $inc=true ) {
01101                 return array();
01102         }
01103 
01113         public function nextHistoryLine() {
01114                 return false;
01115         }
01116 
01123         public function resetHistory() {}
01124 
01132         function getHashPath() {
01133                 if ( !isset( $this->hashPath ) ) {
01134                         $this->assertRepoDefined();
01135                         $this->hashPath = $this->repo->getHashPath( $this->getName() );
01136                 }
01137                 return $this->hashPath;
01138         }
01139 
01146         function getRel() {
01147                 return $this->getHashPath() . $this->getName();
01148         }
01149 
01157         function getArchiveRel( $suffix = false ) {
01158                 $path = 'archive/' . $this->getHashPath();
01159                 if ( $suffix === false ) {
01160                         $path = substr( $path, 0, -1 );
01161                 } else {
01162                         $path .= $suffix;
01163                 }
01164                 return $path;
01165         }
01166 
01175         function getThumbRel( $suffix = false ) {
01176                 $path = $this->getRel();
01177                 if ( $suffix !== false ) {
01178                         $path .= '/' . $suffix;
01179                 }
01180                 return $path;
01181         }
01182 
01189         function getUrlRel() {
01190                 return $this->getHashPath() . rawurlencode( $this->getName() );
01191         }
01192 
01202         function getArchiveThumbRel( $archiveName, $suffix = false ) {
01203                 $path = 'archive/' . $this->getHashPath() . $archiveName . "/";
01204                 if ( $suffix === false ) {
01205                         $path = substr( $path, 0, -1 );
01206                 } else {
01207                         $path .= $suffix;
01208                 }
01209                 return $path;
01210         }
01211 
01219         function getArchivePath( $suffix = false ) {
01220                 $this->assertRepoDefined();
01221                 return $this->repo->getZonePath( 'public' ) . '/' . $this->getArchiveRel( $suffix );
01222         }
01223 
01232         function getArchiveThumbPath( $archiveName, $suffix = false ) {
01233                 $this->assertRepoDefined();
01234                 return $this->repo->getZonePath( 'thumb' ) . '/' .
01235                         $this->getArchiveThumbRel( $archiveName, $suffix );
01236         }
01237 
01245         function getThumbPath( $suffix = false ) {
01246                 $this->assertRepoDefined();
01247                 return $this->repo->getZonePath( 'thumb' ) . '/' . $this->getThumbRel( $suffix );
01248         }
01249 
01257         function getTranscodedPath( $suffix = false ) {
01258                 $this->assertRepoDefined();
01259                 return $this->repo->getZonePath( 'transcoded' ) . '/' . $this->getThumbRel( $suffix );
01260         }
01261 
01269         function getArchiveUrl( $suffix = false ) {
01270                 $this->assertRepoDefined();
01271                 $ext = $this->getExtension();
01272                 $path = $this->repo->getZoneUrl( 'public', $ext ) . '/archive/' . $this->getHashPath();
01273                 if ( $suffix === false ) {
01274                         $path = substr( $path, 0, -1 );
01275                 } else {
01276                         $path .= rawurlencode( $suffix );
01277                 }
01278                 return $path;
01279         }
01280 
01289         function getArchiveThumbUrl( $archiveName, $suffix = false ) {
01290                 $this->assertRepoDefined();
01291                 $ext = $this->getExtension();
01292                 $path = $this->repo->getZoneUrl( 'thumb', $ext ) . '/archive/' .
01293                         $this->getHashPath() . rawurlencode( $archiveName ) . "/";
01294                 if ( $suffix === false ) {
01295                         $path = substr( $path, 0, -1 );
01296                 } else {
01297                         $path .= rawurlencode( $suffix );
01298                 }
01299                 return $path;
01300         }
01301 
01310         function getZoneUrl( $zone, $suffix = false ) {
01311                 $this->assertRepoDefined();
01312                 $ext = $this->getExtension();
01313                 $path = $this->repo->getZoneUrl( $zone, $ext ) . '/' . $this->getUrlRel();
01314                 if ( $suffix !== false ) {
01315                         $path .= '/' . rawurlencode( $suffix );
01316                 }
01317                 return $path;
01318         }
01319 
01327         function getThumbUrl( $suffix = false ) {
01328                 return $this->getZoneUrl( 'thumb', $suffix );
01329         }
01330 
01338         function getTranscodedUrl( $suffix = false ) {
01339                 return $this->getZoneUrl( 'transcoded', $suffix );
01340         }
01341 
01349         function getVirtualUrl( $suffix = false ) {
01350                 $this->assertRepoDefined();
01351                 $path = $this->repo->getVirtualUrl() . '/public/' . $this->getUrlRel();
01352                 if ( $suffix !== false ) {
01353                         $path .= '/' . rawurlencode( $suffix );
01354                 }
01355                 return $path;
01356         }
01357 
01365         function getArchiveVirtualUrl( $suffix = false ) {
01366                 $this->assertRepoDefined();
01367                 $path = $this->repo->getVirtualUrl() . '/public/archive/' . $this->getHashPath();
01368                 if ( $suffix === false ) {
01369                         $path = substr( $path, 0, -1 );
01370                 } else {
01371                         $path .= rawurlencode( $suffix );
01372                 }
01373                 return $path;
01374         }
01375 
01383         function getThumbVirtualUrl( $suffix = false ) {
01384                 $this->assertRepoDefined();
01385                 $path = $this->repo->getVirtualUrl() . '/thumb/' . $this->getUrlRel();
01386                 if ( $suffix !== false ) {
01387                         $path .= '/' . rawurlencode( $suffix );
01388                 }
01389                 return $path;
01390         }
01391 
01395         function isHashed() {
01396                 $this->assertRepoDefined();
01397                 return (bool)$this->repo->getHashLevels();
01398         }
01399 
01403         function readOnlyError() {
01404                 throw new MWException( get_class( $this ) . ': write operations are not supported' );
01405         }
01406 
01422         function recordUpload( $oldver, $desc, $license = '', $copyStatus = '', $source = '', $watch = false, $timestamp = false, User $user = null ) {
01423                 $this->readOnlyError();
01424         }
01425 
01448         function publish( $srcPath, $flags = 0, array $options = array() ) {
01449                 $this->readOnlyError();
01450         }
01451 
01455         function formatMetadata() {
01456                 if ( !$this->getHandler() ) {
01457                         return false;
01458                 }
01459                 return $this->getHandler()->formatMetadata( $this, $this->getMetadata() );
01460         }
01461 
01467         function isLocal() {
01468                 return $this->repo && $this->repo->isLocal();
01469         }
01470 
01476         function getRepoName() {
01477                 return $this->repo ? $this->repo->getName() : 'unknown';
01478         }
01479 
01485         function getRepo() {
01486                 return $this->repo;
01487         }
01488 
01495         function isOld() {
01496                 return false;
01497         }
01498 
01507         function isDeleted( $field ) {
01508                 return false;
01509         }
01510 
01516         function getVisibility() {
01517                 return 0;
01518         }
01519 
01525         function wasDeleted() {
01526                 $title = $this->getTitle();
01527                 return $title && $title->isDeletedQuick();
01528         }
01529 
01542         function move( $target ) {
01543                 $this->readOnlyError();
01544         }
01545 
01560         function delete( $reason, $suppress = false ) {
01561                 $this->readOnlyError();
01562         }
01563 
01578         function restore( $versions = array(), $unsuppress = false ) {
01579                 $this->readOnlyError();
01580         }
01581 
01589         function isMultipage() {
01590                 return $this->getHandler() && $this->handler->isMultiPage( $this );
01591         }
01592 
01599         function pageCount() {
01600                 if ( !isset( $this->pageCount ) ) {
01601                         if ( $this->getHandler() && $this->handler->isMultiPage( $this ) ) {
01602                                 $this->pageCount = $this->handler->pageCount( $this );
01603                         } else {
01604                                 $this->pageCount = false;
01605                         }
01606                 }
01607                 return $this->pageCount;
01608         }
01609 
01619         static function scaleHeight( $srcWidth, $srcHeight, $dstWidth ) {
01620                 // Exact integer multiply followed by division
01621                 if ( $srcWidth == 0 ) {
01622                         return 0;
01623                 } else {
01624                         return round( $srcHeight * $dstWidth / $srcWidth );
01625                 }
01626         }
01627 
01635         function getImageSize( $fileName ) {
01636                 if ( !$this->getHandler() ) {
01637                         return false;
01638                 }
01639                 return $this->handler->getImageSize( $this, $fileName );
01640         }
01641 
01648         function getDescriptionUrl() {
01649                 if ( $this->repo ) {
01650                         return $this->repo->getDescriptionUrl( $this->getName() );
01651                 } else {
01652                         return false;
01653                 }
01654         }
01655 
01661         function getDescriptionText() {
01662                 global $wgMemc, $wgLang;
01663                 if ( !$this->repo || !$this->repo->fetchDescription ) {
01664                         return false;
01665                 }
01666                 $renderUrl = $this->repo->getDescriptionRenderUrl( $this->getName(), $wgLang->getCode() );
01667                 if ( $renderUrl ) {
01668                         if ( $this->repo->descriptionCacheExpiry > 0 ) {
01669                                 wfDebug( "Attempting to get the description from cache..." );
01670                                 $key = $this->repo->getLocalCacheKey( 'RemoteFileDescription', 'url', $wgLang->getCode(),
01671                                                                         $this->getName() );
01672                                 $obj = $wgMemc->get( $key );
01673                                 if ( $obj ) {
01674                                         wfDebug( "success!\n" );
01675                                         return $obj;
01676                                 }
01677                                 wfDebug( "miss\n" );
01678                         }
01679                         wfDebug( "Fetching shared description from $renderUrl\n" );
01680                         $res = Http::get( $renderUrl );
01681                         if ( $res && $this->repo->descriptionCacheExpiry > 0 ) {
01682                                 $wgMemc->set( $key, $res, $this->repo->descriptionCacheExpiry );
01683                         }
01684                         return $res;
01685                 } else {
01686                         return false;
01687                 }
01688         }
01689 
01702         function getDescription( $audience = self::FOR_PUBLIC, User $user = null ) {
01703                 return null;
01704         }
01705 
01711         function getTimestamp() {
01712                 $this->assertRepoDefined();
01713                 return $this->repo->getFileTimestamp( $this->getPath() );
01714         }
01715 
01721         function getSha1() {
01722                 $this->assertRepoDefined();
01723                 return $this->repo->getFileSha1( $this->getPath() );
01724         }
01725 
01731         function getStorageKey() {
01732                 $hash = $this->getSha1();
01733                 if ( !$hash ) {
01734                         return false;
01735                 }
01736                 $ext = $this->getExtension();
01737                 $dotExt = $ext === '' ? '' : ".$ext";
01738                 return $hash . $dotExt;
01739         }
01740 
01749         function userCan( $field, User $user = null ) {
01750                 return true;
01751         }
01752 
01763         static function getPropsFromPath( $path, $ext = true ) {
01764                 wfDebug( __METHOD__ . ": Getting file info for $path\n" );
01765                 wfDeprecated( __METHOD__, '1.19' );
01766 
01767                 $fsFile = new FSFile( $path );
01768                 return $fsFile->getProps();
01769         }
01770 
01783         static function sha1Base36( $path ) {
01784                 wfDeprecated( __METHOD__, '1.19' );
01785 
01786                 $fsFile = new FSFile( $path );
01787                 return $fsFile->getSha1Base36();
01788         }
01789 
01793         function getStreamHeaders() {
01794                 $handler = $this->getHandler();
01795                 if ( $handler ) {
01796                         return $handler->getStreamHeaders( $this->getMetadata() );
01797                 } else {
01798                         return array();
01799                 }
01800         }
01801 
01805         function getLongDesc() {
01806                 $handler = $this->getHandler();
01807                 if ( $handler ) {
01808                         return $handler->getLongDesc( $this );
01809                 } else {
01810                         return MediaHandler::getGeneralLongDesc( $this );
01811                 }
01812         }
01813 
01817         function getShortDesc() {
01818                 $handler = $this->getHandler();
01819                 if ( $handler ) {
01820                         return $handler->getShortDesc( $this );
01821                 } else {
01822                         return MediaHandler::getGeneralShortDesc( $this );
01823                 }
01824         }
01825 
01829         function getDimensionsString() {
01830                 $handler = $this->getHandler();
01831                 if ( $handler ) {
01832                         return $handler->getDimensionsString( $this );
01833                 } else {
01834                         return '';
01835                 }
01836         }
01837 
01841         function getRedirected() {
01842                 return $this->redirected;
01843         }
01844 
01848         function getRedirectedTitle() {
01849                 if ( $this->redirected ) {
01850                         if ( !$this->redirectTitle ) {
01851                                 $this->redirectTitle = Title::makeTitle( NS_FILE, $this->redirected );
01852                         }
01853                         return $this->redirectTitle;
01854                 }
01855                 return null;
01856         }
01857 
01862         function redirectedFrom( $from ) {
01863                 $this->redirected = $from;
01864         }
01865 
01869         function isMissing() {
01870                 return false;
01871         }
01872 
01877         public function isCacheable() {
01878                 return true;
01879         }
01880 
01885         protected function assertRepoDefined() {
01886                 if ( !( $this->repo instanceof $this->repoClass ) ) {
01887                         throw new MWException( "A {$this->repoClass} object is not set for this File.\n" );
01888                 }
01889         }
01890 
01895         protected function assertTitleDefined() {
01896                 if ( !( $this->title instanceof Title ) ) {
01897                         throw new MWException( "A Title object is not set for this File.\n" );
01898                 }
01899         }
01900 }