MediaWiki  REL1_19
File.php
Go to the documentation of this file.
00001 <?php
00035 abstract class File {
00036         const DELETED_FILE = 1;
00037         const DELETED_COMMENT = 2;
00038         const DELETED_USER = 4;
00039         const DELETED_RESTRICTED = 8;
00040 
00042         const RENDER_NOW   = 1;
00047         const RENDER_FORCE = 2;
00048 
00049         const DELETE_SOURCE = 1;
00050 
00073         var $repo;
00074 
00078         var $title;
00079 
00080         var $lastError, $redirected, $redirectedTitle;
00081 
00085         protected $fsFile;
00086 
00090         protected $handler;
00091 
00095         protected $url, $extension, $name, $path, $hashPath, $pageCount, $transformScript;
00096 
00100         protected $canRender, $isSafeFile;
00101 
00105         protected $repoClass = 'FileRepo';
00106 
00117         function __construct( $title, $repo ) {
00118                 if ( $title !== false ) { // subclasses may not use MW titles
00119                         $title = self::normalizeTitle( $title, 'exception' );
00120                 }
00121                 $this->title = $title;
00122                 $this->repo = $repo;
00123         }
00124 
00133         static function normalizeTitle( $title, $exception = false ) {
00134                 $ret = $title;
00135                 if ( $ret instanceof Title ) {
00136                         # Normalize NS_MEDIA -> NS_FILE
00137                         if ( $ret->getNamespace() == NS_MEDIA ) {
00138                                 $ret = Title::makeTitleSafe( NS_FILE, $ret->getDBkey() );
00139                         # Sanity check the title namespace
00140                         } elseif ( $ret->getNamespace() !== NS_FILE ) {
00141                                 $ret = null;
00142                         }
00143                 } else {
00144                         # Convert strings to Title objects
00145                         $ret = Title::makeTitleSafe( NS_FILE, (string)$ret );
00146                 }
00147                 if ( !$ret && $exception !== false ) {
00148                         throw new MWException( "`$title` is not a valid file title." );
00149                 }
00150                 return $ret;
00151         }
00152 
00153         function __get( $name ) {
00154                 $function = array( $this, 'get' . ucfirst( $name ) );
00155                 if ( !is_callable( $function ) ) {
00156                         return null;
00157                 } else {
00158                         $this->$name = call_user_func( $function );
00159                         return $this->$name;
00160                 }
00161         }
00162 
00170         static function normalizeExtension( $ext ) {
00171                 $lower = strtolower( $ext );
00172                 $squish = array(
00173                         'htm' => 'html',
00174                         'jpeg' => 'jpg',
00175                         'mpeg' => 'mpg',
00176                         'tiff' => 'tif',
00177                         'ogv' => 'ogg' );
00178                 if( isset( $squish[$lower] ) ) {
00179                         return $squish[$lower];
00180                 } elseif( preg_match( '/^[0-9a-z]+$/', $lower ) ) {
00181                         return $lower;
00182                 } else {
00183                         return '';
00184                 }
00185         }
00186 
00195         static function checkExtensionCompatibility( File $old, $new ) {
00196                 $oldMime = $old->getMimeType();
00197                 $n = strrpos( $new, '.' );
00198                 $newExt = self::normalizeExtension( $n ? substr( $new, $n + 1 ) : '' );
00199                 $mimeMagic = MimeMagic::singleton();
00200                 return $mimeMagic->isMatchingExtension( $newExt, $oldMime );
00201         }
00202 
00208         function upgradeRow() {}
00209 
00217         public static function splitMime( $mime ) {
00218                 if( strpos( $mime, '/' ) !== false ) {
00219                         return explode( '/', $mime, 2 );
00220                 } else {
00221                         return array( $mime, 'unknown' );
00222                 }
00223         }
00224 
00230         public function getName() {
00231                 if ( !isset( $this->name ) ) {
00232                         $this->assertRepoDefined();
00233                         $this->name = $this->repo->getNameFromTitle( $this->title );
00234                 }
00235                 return $this->name;
00236         }
00237 
00243         function getExtension() {
00244                 if ( !isset( $this->extension ) ) {
00245                         $n = strrpos( $this->getName(), '.' );
00246                         $this->extension = self::normalizeExtension(
00247                                 $n ? substr( $this->getName(), $n + 1 ) : '' );
00248                 }
00249                 return $this->extension;
00250         }
00251 
00257         public function getTitle() {
00258                 return $this->title;
00259         }
00260 
00266         public function getOriginalTitle() {
00267                 if ( $this->redirected ) {
00268                         return $this->getRedirectedTitle();
00269                 }
00270                 return $this->title;
00271         }
00272 
00278         public function getUrl() {
00279                 if ( !isset( $this->url ) ) {
00280                         $this->assertRepoDefined();
00281                         $this->url = $this->repo->getZoneUrl( 'public' ) . '/' . $this->getUrlRel();
00282                 }
00283                 return $this->url;
00284         }
00285 
00293         public function getFullUrl() {
00294                 return wfExpandUrl( $this->getUrl(), PROTO_RELATIVE );
00295         }
00296 
00300         public function getCanonicalUrl() {
00301                 return wfExpandUrl( $this->getUrl(), PROTO_CANONICAL );
00302         }
00303 
00307         function getViewURL() {
00308                 if ( $this->mustRender() ) {
00309                         if ( $this->canRender() ) {
00310                                 return $this->createThumb( $this->getWidth() );
00311                         } else {
00312                                 wfDebug( __METHOD__.': supposed to render ' . $this->getName() .
00313                                         ' (' . $this->getMimeType() . "), but can't!\n" );
00314                                 return $this->getURL(); #hm... return NULL?
00315                         }
00316                 } else {
00317                         return $this->getURL();
00318                 }
00319         }
00320 
00334         public function getPath() {
00335                 if ( !isset( $this->path ) ) {
00336                         $this->assertRepoDefined();
00337                         $this->path = $this->repo->getZonePath( 'public' ) . '/' . $this->getRel();
00338                 }
00339                 return $this->path;
00340         }
00341 
00349         public function getLocalRefPath() {
00350                 $this->assertRepoDefined();
00351                 if ( !isset( $this->fsFile ) ) {
00352                         $this->fsFile = $this->repo->getLocalReference( $this->getPath() );
00353                         if ( !$this->fsFile ) {
00354                                 $this->fsFile = false; // null => false; cache negative hits
00355                         }
00356                 }
00357                 return ( $this->fsFile )
00358                         ? $this->fsFile->getPath()
00359                         : false;
00360         }
00361 
00373         public function getWidth( $page = 1 ) {
00374                 return false;
00375         }
00376 
00388         public function getHeight( $page = 1 ) {
00389                 return false;
00390         }
00391 
00400         public function getUser( $type = 'text' ) {
00401                 return null;
00402         }
00403 
00409         public function getLength() {
00410                 $handler = $this->getHandler();
00411                 if ( $handler ) {
00412                         return $handler->getLength( $this );
00413                 } else {
00414                         return 0;
00415                 }
00416         }
00417 
00423         public function isVectorized() {
00424                 $handler = $this->getHandler();
00425                 if ( $handler ) {
00426                         return $handler->isVectorized( $this );
00427                 } else {
00428                         return false;
00429                 }
00430         }
00431 
00437         public function getMetadata() {
00438                 return false;
00439         }
00440 
00448         public function convertMetadataVersion($metadata, $version) {
00449                 $handler = $this->getHandler();
00450                 if ( !is_array( $metadata ) ) {
00451                         // Just to make the return type consistent
00452                         $metadata = unserialize( $metadata );
00453                 }
00454                 if ( $handler ) {
00455                         return $handler->convertMetadataVersion( $metadata, $version );
00456                 } else {
00457                         return $metadata;
00458                 }
00459         }
00460 
00466         public function getBitDepth() {
00467                 return 0;
00468         }
00469 
00475         public function getSize() {
00476                 return false;
00477         }
00478 
00486         function getMimeType() {
00487                 return 'unknown/unknown';
00488         }
00489 
00496         function getMediaType() {
00497                 return MEDIATYPE_UNKNOWN;
00498         }
00499 
00512         function canRender() {
00513                 if ( !isset( $this->canRender ) ) {
00514                         $this->canRender = $this->getHandler() && $this->handler->canRender( $this );
00515                 }
00516                 return $this->canRender;
00517         }
00518 
00522         protected function getCanRender() {
00523                 return $this->canRender();
00524         }
00525 
00536         function mustRender() {
00537                 return $this->getHandler() && $this->handler->mustRender( $this );
00538         }
00539 
00545         function allowInlineDisplay() {
00546                 return $this->canRender();
00547         }
00548 
00562         function isSafeFile() {
00563                 if ( !isset( $this->isSafeFile ) ) {
00564                         $this->isSafeFile = $this->_getIsSafeFile();
00565                 }
00566                 return $this->isSafeFile;
00567         }
00568 
00574         protected function getIsSafeFile() {
00575                 return $this->isSafeFile();
00576         }
00577 
00583         protected function _getIsSafeFile() {
00584                 global $wgTrustedMediaFormats;
00585 
00586                 if ( $this->allowInlineDisplay() ) {
00587                         return true;
00588                 }
00589                 if ($this->isTrustedFile()) {
00590                         return true;
00591                 }
00592 
00593                 $type = $this->getMediaType();
00594                 $mime = $this->getMimeType();
00595                 #wfDebug("LocalFile::isSafeFile: type= $type, mime= $mime\n");
00596 
00597                 if ( !$type || $type === MEDIATYPE_UNKNOWN ) {
00598                         return false; #unknown type, not trusted
00599                 }
00600                 if ( in_array( $type, $wgTrustedMediaFormats ) ) {
00601                         return true;
00602                 }
00603 
00604                 if ( $mime === "unknown/unknown" ) {
00605                         return false; #unknown type, not trusted
00606                 }
00607                 if ( in_array( $mime, $wgTrustedMediaFormats) ) {
00608                         return true;
00609                 }
00610 
00611                 return false;
00612         }
00613 
00627         function isTrustedFile() {
00628                 #this could be implemented to check a flag in the database,
00629                 #look for signatures, etc
00630                 return false;
00631         }
00632 
00640         public function exists() {
00641                 return $this->getPath() && $this->repo->fileExists( $this->path );
00642         }
00643 
00650         public function isVisible() {
00651                 return $this->exists();
00652         }
00653 
00657         function getTransformScript() {
00658                 if ( !isset( $this->transformScript ) ) {
00659                         $this->transformScript = false;
00660                         if ( $this->repo ) {
00661                                 $script = $this->repo->getThumbScriptUrl();
00662                                 if ( $script ) {
00663                                         $this->transformScript = "$script?f=" . urlencode( $this->getName() );
00664                                 }
00665                         }
00666                 }
00667                 return $this->transformScript;
00668         }
00669 
00677         function getUnscaledThumb( $handlerParams = array() ) {
00678                 $hp =& $handlerParams;
00679                 $page = isset( $hp['page'] ) ? $hp['page'] : false;
00680                 $width = $this->getWidth( $page );
00681                 if ( !$width ) {
00682                         return $this->iconThumb();
00683                 }
00684                 $hp['width'] = $width;
00685                 return $this->transform( $hp );
00686         }
00687 
00696         function thumbName( $params ) {
00697                 return $this->generateThumbName( $this->getName(), $params );
00698         }
00699 
00708         function generateThumbName( $name, $params ) {
00709                 if ( !$this->getHandler() ) {
00710                         return null;
00711                 }
00712                 $extension = $this->getExtension();
00713                 list( $thumbExt, $thumbMime ) = $this->handler->getThumbType(
00714                         $extension, $this->getMimeType(), $params );
00715                 $thumbName = $this->handler->makeParamString( $params ) . '-' . $name;
00716                 if ( $thumbExt != $extension ) {
00717                         $thumbName .= ".$thumbExt";
00718                 }
00719                 return $thumbName;
00720         }
00721 
00739         public function createThumb( $width, $height = -1 ) {
00740                 $params = array( 'width' => $width );
00741                 if ( $height != -1 ) {
00742                         $params['height'] = $height;
00743                 }
00744                 $thumb = $this->transform( $params );
00745                 if ( is_null( $thumb ) || $thumb->isError() ) {
00746                         return '';
00747                 }
00748                 return $thumb->getUrl();
00749         }
00750 
00760         protected function transformErrorOutput( $thumbPath, $thumbUrl, $params, $flags ) {
00761                 global $wgIgnoreImageErrors;
00762 
00763                 if ( $wgIgnoreImageErrors && !( $flags & self::RENDER_NOW ) ) {
00764                         return $this->handler->getTransform( $this, $thumbPath, $thumbUrl, $params );
00765                 } else {
00766                         return new MediaTransformError( 'thumbnail_error',
00767                                 $params['width'], 0, wfMsg( 'thumbnail-dest-create' ) );
00768                 }
00769         }
00770 
00779         function transform( $params, $flags = 0 ) {
00780                 global $wgUseSquid, $wgIgnoreImageErrors, $wgThumbnailEpoch;
00781 
00782                 wfProfileIn( __METHOD__ );
00783                 do {
00784                         if ( !$this->canRender() ) {
00785                                 $thumb = $this->iconThumb();
00786                                 break; // not a bitmap or renderable image, don't try
00787                         }
00788 
00789                         // Get the descriptionUrl to embed it as comment into the thumbnail. Bug 19791.
00790                         $descriptionUrl = $this->getDescriptionUrl();
00791                         if ( $descriptionUrl ) {
00792                                 $params['descriptionUrl'] = wfExpandUrl( $descriptionUrl, PROTO_CANONICAL );
00793                         }
00794 
00795                         $script = $this->getTransformScript();
00796                         if ( $script && !( $flags & self::RENDER_NOW ) ) {
00797                                 // Use a script to transform on client request, if possible
00798                                 $thumb = $this->handler->getScriptedTransform( $this, $script, $params );
00799                                 if ( $thumb ) {
00800                                         break;
00801                                 }
00802                         }
00803 
00804                         $normalisedParams = $params;
00805                         $this->handler->normaliseParams( $this, $normalisedParams );
00806 
00807                         $thumbName = $this->thumbName( $normalisedParams );
00808                         $thumbUrl = $this->getThumbUrl( $thumbName );
00809                         $thumbPath = $this->getThumbPath( $thumbName ); // final thumb path
00810 
00811                         if ( $this->repo ) {
00812                                 // Defer rendering if a 404 handler is set up...
00813                                 if ( $this->repo->canTransformVia404() && !( $flags & self::RENDER_NOW ) ) {
00814                                         wfDebug( __METHOD__ . " transformation deferred." );
00815                                         // XXX: Pass in the storage path even though we are not rendering anything
00816                                         // and the path is supposed to be an FS path. This is due to getScalerType()
00817                                         // getting called on the path and clobbering $thumb->getUrl() if it's false.
00818                                         $thumb = $this->handler->getTransform( $this, $thumbPath, $thumbUrl, $params );
00819                                         break;
00820                                 }
00821                                 // Clean up broken thumbnails as needed
00822                                 $this->migrateThumbFile( $thumbName );
00823                                 // Check if an up-to-date thumbnail already exists...
00824                                 wfDebug( __METHOD__.": Doing stat for $thumbPath\n" );
00825                                 if ( $this->repo->fileExists( $thumbPath ) && !( $flags & self::RENDER_FORCE ) ) {
00826                                         $timestamp = $this->repo->getFileTimestamp( $thumbPath );
00827                                         if ( $timestamp !== false && $timestamp >= $wgThumbnailEpoch ) {
00828                                                 // XXX: Pass in the storage path even though we are not rendering anything
00829                                                 // and the path is supposed to be an FS path. This is due to getScalerType()
00830                                                 // getting called on the path and clobbering $thumb->getUrl() if it's false.
00831                                                 $thumb = $this->handler->getTransform( $this, $thumbPath, $thumbUrl, $params );
00832                                                 $thumb->setStoragePath( $thumbPath );
00833                                                 break;
00834                                         }
00835                                 } elseif ( $flags & self::RENDER_FORCE ) {
00836                                         wfDebug( __METHOD__ . " forcing rendering per flag File::RENDER_FORCE\n" );
00837                                 }
00838                         }
00839 
00840                         // Create a temp FS file with the same extension and the thumbnail
00841                         $thumbExt = FileBackend::extensionFromPath( $thumbPath );
00842                         $tmpFile = TempFSFile::factory( 'transform_', $thumbExt );
00843                         if ( !$tmpFile ) {
00844                                 $thumb = $this->transformErrorOutput( $thumbPath, $thumbUrl, $params, $flags );
00845                                 break;
00846                         }
00847                         $tmpThumbPath = $tmpFile->getPath(); // path of 0-byte temp file
00848 
00849                         // Actually render the thumbnail...
00850                         $thumb = $this->handler->doTransform( $this, $tmpThumbPath, $thumbUrl, $params );
00851                         $tmpFile->bind( $thumb ); // keep alive with $thumb
00852 
00853                         if ( !$thumb ) { // bad params?
00854                                 $thumb = null;
00855                         } elseif ( $thumb->isError() ) { // transform error
00856                                 $this->lastError = $thumb->toText();
00857                                 // Ignore errors if requested
00858                                 if ( $wgIgnoreImageErrors && !( $flags & self::RENDER_NOW ) ) {
00859                                         $thumb = $this->handler->getTransform( $this, $tmpThumbPath, $thumbUrl, $params );
00860                                 }
00861                         } elseif ( $this->repo && $thumb->hasFile() && !$thumb->fileIsSource() ) {
00862                                 $backend = $this->repo->getBackend();
00863                                 // Copy the thumbnail from the file system into storage. This avoids using
00864                                 // FileRepo::store(); getThumbPath() uses a different zone in some subclasses.
00865                                 $backend->prepare( array( 'dir' => dirname( $thumbPath ) ) );
00866                                 $status = $backend->store(
00867                                         array( 'src' => $tmpThumbPath, 'dst' => $thumbPath, 'overwrite' => 1 ),
00868                                         array( 'force' => 1, 'nonLocking' => 1, 'allowStale' => 1 )
00869                                 );
00870                                 if ( $status->isOK() ) {
00871                                         $thumb->setStoragePath( $thumbPath );
00872                                 } else {
00873                                         $thumb = $this->transformErrorOutput( $thumbPath, $thumbUrl, $params, $flags );
00874                                 }
00875                         }
00876 
00877                         // Purge. Useful in the event of Core -> Squid connection failure or squid
00878                         // purge collisions from elsewhere during failure. Don't keep triggering for
00879                         // "thumbs" which have the main image URL though (bug 13776)
00880                         if ( $wgUseSquid ) {
00881                                 if ( !$thumb || $thumb->isError() || $thumb->getUrl() != $this->getURL() ) {
00882                                         SquidUpdate::purge( array( $thumbUrl ) );
00883                                 }
00884                         }
00885                 } while ( false );
00886 
00887                 wfProfileOut( __METHOD__ );
00888                 return is_object( $thumb ) ? $thumb : false;
00889         }
00890 
00896         function migrateThumbFile( $thumbName ) {}
00897 
00903         function getHandler() {
00904                 if ( !isset( $this->handler ) ) {
00905                         $this->handler = MediaHandler::getHandler( $this->getMimeType() );
00906                 }
00907                 return $this->handler;
00908         }
00909 
00915         function iconThumb() {
00916                 global $wgStylePath, $wgStyleDirectory;
00917 
00918                 $try = array( 'fileicon-' . $this->getExtension() . '.png', 'fileicon.png' );
00919                 foreach ( $try as $icon ) {
00920                         $path = '/common/images/icons/' . $icon;
00921                         $filepath = $wgStyleDirectory . $path;
00922                         if ( file_exists( $filepath ) ) { // always FS
00923                                 return new ThumbnailImage( $this, $wgStylePath . $path, 120, 120 );
00924                         }
00925                 }
00926                 return null;
00927         }
00928 
00933         function getLastError() {
00934                 return $this->lastError;
00935         }
00936 
00942         function getThumbnails() {
00943                 return array();
00944         }
00945 
00953         function purgeCache( $options = array() ) {}
00954 
00960         function purgeDescription() {
00961                 $title = $this->getTitle();
00962                 if ( $title ) {
00963                         $title->invalidateCache();
00964                         $title->purgeSquid();
00965                 }
00966         }
00967 
00972         function purgeEverything() {
00973                 // Delete thumbnails and refresh file metadata cache
00974                 $this->purgeCache();
00975                 $this->purgeDescription();
00976 
00977                 // Purge cache of all pages using this file
00978                 $title = $this->getTitle();
00979                 if ( $title ) {
00980                         $update = new HTMLCacheUpdate( $title, 'imagelinks' );
00981                         $update->doUpdate();
00982                 }
00983         }
00984 
00996         function getHistory($limit = null, $start = null, $end = null, $inc=true) {
00997                 return array();
00998         }
00999 
01008         public function nextHistoryLine() {
01009                 return false;
01010         }
01011 
01018         public function resetHistory() {}
01019 
01027         function getHashPath() {
01028                 if ( !isset( $this->hashPath ) ) {
01029                         $this->assertRepoDefined();
01030                         $this->hashPath = $this->repo->getHashPath( $this->getName() );
01031                 }
01032                 return $this->hashPath;
01033         }
01034 
01041         function getRel() {
01042                 return $this->getHashPath() . $this->getName();
01043         }
01044 
01052         function getArchiveRel( $suffix = false ) {
01053                 $path = 'archive/' . $this->getHashPath();
01054                 if ( $suffix === false ) {
01055                         $path = substr( $path, 0, -1 );
01056                 } else {
01057                         $path .= $suffix;
01058                 }
01059                 return $path;
01060         }
01061 
01070         function getThumbRel( $suffix = false ) {
01071                 $path = $this->getRel();
01072                 if ( $suffix !== false ) {
01073                         $path .= '/' . $suffix;
01074                 }
01075                 return $path;
01076         }
01077 
01084         function getUrlRel() {
01085                 return $this->getHashPath() . rawurlencode( $this->getName() );
01086         }
01087 
01097         function getArchiveThumbRel( $archiveName, $suffix = false ) {
01098                 $path = 'archive/' . $this->getHashPath() . $archiveName . "/";
01099                 if ( $suffix === false ) {
01100                         $path = substr( $path, 0, -1 );
01101                 } else {
01102                         $path .= $suffix;
01103                 }
01104                 return $path;
01105         }
01106 
01114         function getArchivePath( $suffix = false ) {
01115                 $this->assertRepoDefined();
01116                 return $this->repo->getZonePath( 'public' ) . '/' . $this->getArchiveRel( $suffix );
01117         }
01118 
01127         function getArchiveThumbPath( $archiveName, $suffix = false ) {
01128                 $this->assertRepoDefined();
01129                 return $this->repo->getZonePath( 'thumb' ) . '/' .
01130                         $this->getArchiveThumbRel( $archiveName, $suffix );
01131         }
01132 
01140         function getThumbPath( $suffix = false ) {
01141                 $this->assertRepoDefined();
01142                 return $this->repo->getZonePath( 'thumb' ) . '/' . $this->getThumbRel( $suffix );
01143         }
01144 
01152         function getArchiveUrl( $suffix = false ) {
01153                 $this->assertRepoDefined();
01154                 $path = $this->repo->getZoneUrl( 'public' ) . '/archive/' . $this->getHashPath();
01155                 if ( $suffix === false ) {
01156                         $path = substr( $path, 0, -1 );
01157                 } else {
01158                         $path .= rawurlencode( $suffix );
01159                 }
01160                 return $path;
01161         }
01162 
01171         function getArchiveThumbUrl( $archiveName, $suffix = false ) {
01172                 $this->assertRepoDefined();
01173                 $path = $this->repo->getZoneUrl( 'thumb' ) . '/archive/' .
01174                         $this->getHashPath() . rawurlencode( $archiveName ) . "/";
01175                 if ( $suffix === false ) {
01176                         $path = substr( $path, 0, -1 );
01177                 } else {
01178                         $path .= rawurlencode( $suffix );
01179                 }
01180                 return $path;
01181         }
01182 
01190         function getThumbUrl( $suffix = false ) {
01191                 $this->assertRepoDefined();
01192                 $path = $this->repo->getZoneUrl( 'thumb' ) . '/' . $this->getUrlRel();
01193                 if ( $suffix !== false ) {
01194                         $path .= '/' . rawurlencode( $suffix );
01195                 }
01196                 return $path;
01197         }
01198 
01206         function getVirtualUrl( $suffix = false ) {
01207                 $this->assertRepoDefined();
01208                 $path = $this->repo->getVirtualUrl() . '/public/' . $this->getUrlRel();
01209                 if ( $suffix !== false ) {
01210                         $path .= '/' . rawurlencode( $suffix );
01211                 }
01212                 return $path;
01213         }
01214 
01222         function getArchiveVirtualUrl( $suffix = false ) {
01223                 $this->assertRepoDefined();
01224                 $path = $this->repo->getVirtualUrl() . '/public/archive/' . $this->getHashPath();
01225                 if ( $suffix === false ) {
01226                         $path = substr( $path, 0, -1 );
01227                 } else {
01228                         $path .= rawurlencode( $suffix );
01229                 }
01230                 return $path;
01231         }
01232 
01240         function getThumbVirtualUrl( $suffix = false ) {
01241                 $this->assertRepoDefined();
01242                 $path = $this->repo->getVirtualUrl() . '/thumb/' . $this->getUrlRel();
01243                 if ( $suffix !== false ) {
01244                         $path .= '/' . rawurlencode( $suffix );
01245                 }
01246                 return $path;
01247         }
01248 
01252         function isHashed() {
01253                 $this->assertRepoDefined();
01254                 return $this->repo->isHashed();
01255         }
01256 
01260         function readOnlyError() {
01261                 throw new MWException( get_class($this) . ': write operations are not supported' );
01262         }
01263 
01275         function recordUpload( $oldver, $desc, $license = '', $copyStatus = '', $source = '', $watch = false ) {
01276                 $this->readOnlyError();
01277         }
01278 
01297         function publish( $srcPath, $flags = 0 ) {
01298                 $this->readOnlyError();
01299         }
01300 
01304         function formatMetadata() {
01305                 if ( !$this->getHandler() ) {
01306                         return false;
01307                 }
01308                 return $this->getHandler()->formatMetadata( $this, $this->getMetadata() );
01309         }
01310 
01316         function isLocal() {
01317                 return $this->repo && $this->repo->isLocal();
01318         }
01319 
01325         function getRepoName() {
01326                 return $this->repo ? $this->repo->getName() : 'unknown';
01327         }
01328 
01334         function getRepo() {
01335                 return $this->repo;
01336         }
01337 
01344         function isOld() {
01345                 return false;
01346         }
01347 
01356         function isDeleted( $field ) {
01357                 return false;
01358         }
01359 
01364         function getVisibility() {
01365                 return 0;
01366         }
01367 
01373         function wasDeleted() {
01374                 $title = $this->getTitle();
01375                 return $title && $title->isDeletedQuick();
01376         }
01377 
01390          function move( $target ) {
01391                 $this->readOnlyError();
01392          }
01393 
01408         function delete( $reason, $suppress = false ) {
01409                 $this->readOnlyError();
01410         }
01411 
01426         function restore( $versions = array(), $unsuppress = false ) {
01427                 $this->readOnlyError();
01428         }
01429 
01437         function isMultipage() {
01438                 return $this->getHandler() && $this->handler->isMultiPage( $this );
01439         }
01440 
01447         function pageCount() {
01448                 if ( !isset( $this->pageCount ) ) {
01449                         if ( $this->getHandler() && $this->handler->isMultiPage( $this ) ) {
01450                                 $this->pageCount = $this->handler->pageCount( $this );
01451                         } else {
01452                                 $this->pageCount = false;
01453                         }
01454                 }
01455                 return $this->pageCount;
01456         }
01457 
01467         static function scaleHeight( $srcWidth, $srcHeight, $dstWidth ) {
01468                 // Exact integer multiply followed by division
01469                 if ( $srcWidth == 0 ) {
01470                         return 0;
01471                 } else {
01472                         return round( $srcHeight * $dstWidth / $srcWidth );
01473                 }
01474         }
01475 
01483         function getImageSize( $fileName ) {
01484                 if ( !$this->getHandler() ) {
01485                         return false;
01486                 }
01487                 return $this->handler->getImageSize( $this, $fileName );
01488         }
01489 
01496         function getDescriptionUrl() {
01497                 if ( $this->repo ) {
01498                         return $this->repo->getDescriptionUrl( $this->getName() );
01499                 } else {
01500                         return false;
01501                 }
01502         }
01503 
01509         function getDescriptionText() {
01510                 global $wgMemc, $wgLang;
01511                 if ( !$this->repo || !$this->repo->fetchDescription ) {
01512                         return false;
01513                 }
01514                 $renderUrl = $this->repo->getDescriptionRenderUrl( $this->getName(), $wgLang->getCode() );
01515                 if ( $renderUrl ) {
01516                         if ( $this->repo->descriptionCacheExpiry > 0 ) {
01517                                 wfDebug("Attempting to get the description from cache...");
01518                                 $key = $this->repo->getLocalCacheKey( 'RemoteFileDescription', 'url', $wgLang->getCode(),
01519                                                                         $this->getName() );
01520                                 $obj = $wgMemc->get($key);
01521                                 if ($obj) {
01522                                         wfDebug("success!\n");
01523                                         return $obj;
01524                                 }
01525                                 wfDebug("miss\n");
01526                         }
01527                         wfDebug( "Fetching shared description from $renderUrl\n" );
01528                         $res = Http::get( $renderUrl );
01529                         if ( $res && $this->repo->descriptionCacheExpiry > 0 ) {
01530                                 $wgMemc->set( $key, $res, $this->repo->descriptionCacheExpiry );
01531                         }
01532                         return $res;
01533                 } else {
01534                         return false;
01535                 }
01536         }
01537 
01544         function getDescription() {
01545                 return null;
01546         }
01547 
01553         function getTimestamp() {
01554                 $this->assertRepoDefined();
01555                 return $this->repo->getFileTimestamp( $this->getPath() );
01556         }
01557 
01563         function getSha1() {
01564                 $this->assertRepoDefined();
01565                 return $this->repo->getFileSha1( $this->getPath() );
01566         }
01567 
01573         function getStorageKey() {
01574                 $hash = $this->getSha1();
01575                 if ( !$hash ) {
01576                         return false;
01577                 }
01578                 $ext = $this->getExtension();
01579                 $dotExt = $ext === '' ? '' : ".$ext";
01580                 return $hash . $dotExt;
01581         }
01582 
01591         function userCan( $field, User $user = null ) {
01592                 return true;
01593         }
01594 
01604         static function getPropsFromPath( $path, $ext = true ) {
01605                 wfDebug( __METHOD__.": Getting file info for $path\n" );
01606                 wfDeprecated( __METHOD__, '1.19' );
01607 
01608                 $fsFile = new FSFile( $path );
01609                 return $fsFile->getProps();
01610         }
01611 
01623         static function sha1Base36( $path ) {
01624                 wfDeprecated( __METHOD__, '1.19' );
01625 
01626                 $fsFile = new FSFile( $path );
01627                 return $fsFile->getSha1Base36();
01628         }
01629 
01633         function getLongDesc() {
01634                 $handler = $this->getHandler();
01635                 if ( $handler ) {
01636                         return $handler->getLongDesc( $this );
01637                 } else {
01638                         return MediaHandler::getGeneralLongDesc( $this );
01639                 }
01640         }
01641 
01645         function getShortDesc() {
01646                 $handler = $this->getHandler();
01647                 if ( $handler ) {
01648                         return $handler->getShortDesc( $this );
01649                 } else {
01650                         return MediaHandler::getGeneralShortDesc( $this );
01651                 }
01652         }
01653 
01657         function getDimensionsString() {
01658                 $handler = $this->getHandler();
01659                 if ( $handler ) {
01660                         return $handler->getDimensionsString( $this );
01661                 } else {
01662                         return '';
01663                 }
01664         }
01665 
01669         function getRedirected() {
01670                 return $this->redirected;
01671         }
01672 
01676         function getRedirectedTitle() {
01677                 if ( $this->redirected ) {
01678                         if ( !$this->redirectTitle ) {
01679                                 $this->redirectTitle = Title::makeTitle( NS_FILE, $this->redirected );
01680                         }
01681                         return $this->redirectTitle;
01682                 }
01683         }
01684 
01689         function redirectedFrom( $from ) {
01690                 $this->redirected = $from;
01691         }
01692 
01696         function isMissing() {
01697                 return false;
01698         }
01699 
01704         protected function assertRepoDefined() {
01705                 if ( !( $this->repo instanceof $this->repoClass ) ) {
01706                         throw new MWException( "A {$this->repoClass} object is not set for this File.\n" );
01707                 }
01708         }
01709 
01714         protected function assertTitleDefined() {
01715                 if ( !( $this->title instanceof Title ) ) {
01716                         throw new MWException( "A Title object is not set for this File.\n" );
01717                 }
01718         }
01719 }