MediaWiki  REL1_24
File.php
Go to the documentation of this file.
00001 <?php
00050 abstract class File {
00051     // Bitfield values akin to the Revision deletion constants
00052     const DELETED_FILE = 1;
00053     const DELETED_COMMENT = 2;
00054     const DELETED_USER = 4;
00055     const DELETED_RESTRICTED = 8;
00056 
00058     const RENDER_NOW = 1;
00063     const RENDER_FORCE = 2;
00064 
00065     const DELETE_SOURCE = 1;
00066 
00067     // Audience options for File::getDescription()
00068     const FOR_PUBLIC = 1;
00069     const FOR_THIS_USER = 2;
00070     const RAW = 3;
00071 
00072     // Options for File::thumbName()
00073     const THUMB_FULL_NAME = 1;
00074 
00095     public $repo;
00096 
00098     protected $title;
00099 
00101     protected $lastError;
00102 
00104     protected $redirected;
00105 
00107     protected $redirectedTitle;
00108 
00110     protected $fsFile;
00111 
00113     protected $handler;
00114 
00116     protected $url;
00117 
00119     protected $extension;
00120 
00122     protected $name;
00123 
00125     protected $path;
00126 
00128     protected $hashPath;
00129 
00133     protected $pageCount;
00134 
00136     protected $transformScript;
00137 
00139     protected $redirectTitle;
00140 
00142     protected $canRender;
00143 
00147     protected $isSafeFile;
00148 
00150     protected $repoClass = 'FileRepo';
00151 
00153     protected $tmpBucketedThumbCache = array();
00154 
00165     function __construct( $title, $repo ) {
00166         if ( $title !== false ) { // subclasses may not use MW titles
00167             $title = self::normalizeTitle( $title, 'exception' );
00168         }
00169         $this->title = $title;
00170         $this->repo = $repo;
00171     }
00172 
00182     static function normalizeTitle( $title, $exception = false ) {
00183         $ret = $title;
00184         if ( $ret instanceof Title ) {
00185             # Normalize NS_MEDIA -> NS_FILE
00186             if ( $ret->getNamespace() == NS_MEDIA ) {
00187                 $ret = Title::makeTitleSafe( NS_FILE, $ret->getDBkey() );
00188             # Sanity check the title namespace
00189             } elseif ( $ret->getNamespace() !== NS_FILE ) {
00190                 $ret = null;
00191             }
00192         } else {
00193             # Convert strings to Title objects
00194             $ret = Title::makeTitleSafe( NS_FILE, (string)$ret );
00195         }
00196         if ( !$ret && $exception !== false ) {
00197             throw new MWException( "`$title` is not a valid file title." );
00198         }
00199 
00200         return $ret;
00201     }
00202 
00203     function __get( $name ) {
00204         $function = array( $this, 'get' . ucfirst( $name ) );
00205         if ( !is_callable( $function ) ) {
00206             return null;
00207         } else {
00208             $this->$name = call_user_func( $function );
00209 
00210             return $this->$name;
00211         }
00212     }
00213 
00221     static function normalizeExtension( $ext ) {
00222         $lower = strtolower( $ext );
00223         $squish = array(
00224             'htm' => 'html',
00225             'jpeg' => 'jpg',
00226             'mpeg' => 'mpg',
00227             'tiff' => 'tif',
00228             'ogv' => 'ogg' );
00229         if ( isset( $squish[$lower] ) ) {
00230             return $squish[$lower];
00231         } elseif ( preg_match( '/^[0-9a-z]+$/', $lower ) ) {
00232             return $lower;
00233         } else {
00234             return '';
00235         }
00236     }
00237 
00246     static function checkExtensionCompatibility( File $old, $new ) {
00247         $oldMime = $old->getMimeType();
00248         $n = strrpos( $new, '.' );
00249         $newExt = self::normalizeExtension( $n ? substr( $new, $n + 1 ) : '' );
00250         $mimeMagic = MimeMagic::singleton();
00251 
00252         return $mimeMagic->isMatchingExtension( $newExt, $oldMime );
00253     }
00254 
00260     function upgradeRow() {
00261     }
00262 
00270     public static function splitMime( $mime ) {
00271         if ( strpos( $mime, '/' ) !== false ) {
00272             return explode( '/', $mime, 2 );
00273         } else {
00274             return array( $mime, 'unknown' );
00275         }
00276     }
00277 
00285     public static function compare( File $a, File $b ) {
00286         return strcmp( $a->getName(), $b->getName() );
00287     }
00288 
00294     public function getName() {
00295         if ( !isset( $this->name ) ) {
00296             $this->assertRepoDefined();
00297             $this->name = $this->repo->getNameFromTitle( $this->title );
00298         }
00299 
00300         return $this->name;
00301     }
00302 
00308     function getExtension() {
00309         if ( !isset( $this->extension ) ) {
00310             $n = strrpos( $this->getName(), '.' );
00311             $this->extension = self::normalizeExtension(
00312                 $n ? substr( $this->getName(), $n + 1 ) : '' );
00313         }
00314 
00315         return $this->extension;
00316     }
00317 
00323     public function getTitle() {
00324         return $this->title;
00325     }
00326 
00332     public function getOriginalTitle() {
00333         if ( $this->redirected ) {
00334             return $this->getRedirectedTitle();
00335         }
00336 
00337         return $this->title;
00338     }
00339 
00345     public function getUrl() {
00346         if ( !isset( $this->url ) ) {
00347             $this->assertRepoDefined();
00348             $ext = $this->getExtension();
00349             $this->url = $this->repo->getZoneUrl( 'public', $ext ) . '/' . $this->getUrlRel();
00350         }
00351 
00352         return $this->url;
00353     }
00354 
00362     public function getFullUrl() {
00363         return wfExpandUrl( $this->getUrl(), PROTO_RELATIVE );
00364     }
00365 
00369     public function getCanonicalUrl() {
00370         return wfExpandUrl( $this->getUrl(), PROTO_CANONICAL );
00371     }
00372 
00376     function getViewURL() {
00377         if ( $this->mustRender() ) {
00378             if ( $this->canRender() ) {
00379                 return $this->createThumb( $this->getWidth() );
00380             } else {
00381                 wfDebug( __METHOD__ . ': supposed to render ' . $this->getName() .
00382                     ' (' . $this->getMimeType() . "), but can't!\n" );
00383 
00384                 return $this->getURL(); #hm... return NULL?
00385             }
00386         } else {
00387             return $this->getURL();
00388         }
00389     }
00390 
00404     public function getPath() {
00405         if ( !isset( $this->path ) ) {
00406             $this->assertRepoDefined();
00407             $this->path = $this->repo->getZonePath( 'public' ) . '/' . $this->getRel();
00408         }
00409 
00410         return $this->path;
00411     }
00412 
00420     public function getLocalRefPath() {
00421         $this->assertRepoDefined();
00422         if ( !isset( $this->fsFile ) ) {
00423             $this->fsFile = $this->repo->getLocalReference( $this->getPath() );
00424             if ( !$this->fsFile ) {
00425                 $this->fsFile = false; // null => false; cache negative hits
00426             }
00427         }
00428 
00429         return ( $this->fsFile )
00430             ? $this->fsFile->getPath()
00431             : false;
00432     }
00433 
00444     public function getWidth( $page = 1 ) {
00445         return false;
00446     }
00447 
00458     public function getHeight( $page = 1 ) {
00459         return false;
00460     }
00461 
00471     public function getThumbnailBucket( $desiredWidth, $page = 1 ) {
00472         global $wgThumbnailBuckets, $wgThumbnailMinimumBucketDistance;
00473 
00474         $imageWidth = $this->getWidth( $page );
00475 
00476         if ( $imageWidth === false ) {
00477             return false;
00478         }
00479 
00480         if ( $desiredWidth > $imageWidth ) {
00481             return false;
00482         }
00483 
00484         if ( !$wgThumbnailBuckets ) {
00485             return false;
00486         }
00487 
00488         $sortedBuckets = $wgThumbnailBuckets;
00489 
00490         sort( $sortedBuckets );
00491 
00492         foreach ( $sortedBuckets as $bucket ) {
00493             if ( $bucket > $imageWidth ) {
00494                 return false;
00495             }
00496 
00497             if ( $bucket - $wgThumbnailMinimumBucketDistance > $desiredWidth ) {
00498                 return $bucket;
00499             }
00500         }
00501 
00502         // Image is bigger than any available bucket
00503         return false;
00504     }
00505 
00513     public function getUser( $type = 'text' ) {
00514         return null;
00515     }
00516 
00522     public function getLength() {
00523         $handler = $this->getHandler();
00524         if ( $handler ) {
00525             return $handler->getLength( $this );
00526         } else {
00527             return 0;
00528         }
00529     }
00530 
00536     public function isVectorized() {
00537         $handler = $this->getHandler();
00538         if ( $handler ) {
00539             return $handler->isVectorized( $this );
00540         } else {
00541             return false;
00542         }
00543     }
00544 
00556     public function getAvailableLanguages() {
00557         $handler = $this->getHandler();
00558         if ( $handler ) {
00559             return $handler->getAvailableLanguages( $this );
00560         } else {
00561             return array();
00562         }
00563     }
00564 
00572     public function getDefaultRenderLanguage() {
00573         $handler = $this->getHandler();
00574         if ( $handler ) {
00575             return $handler->getDefaultRenderLanguage( $this );
00576         } else {
00577             return null;
00578         }
00579     }
00580 
00591     public function canAnimateThumbIfAppropriate() {
00592         $handler = $this->getHandler();
00593         if ( !$handler ) {
00594             // We cannot handle image whatsoever, thus
00595             // one would not expect it to be animated
00596             // so true.
00597             return true;
00598         } else {
00599             if ( $this->allowInlineDisplay()
00600                 && $handler->isAnimatedImage( $this )
00601                 && !$handler->canAnimateThumbnail( $this )
00602             ) {
00603                 // Image is animated, but thumbnail isn't.
00604                 // This is unexpected to the user.
00605                 return false;
00606             } else {
00607                 // Image is not animated, so one would
00608                 // not expect thumb to be
00609                 return true;
00610             }
00611         }
00612     }
00613 
00620     public function getMetadata() {
00621         return false;
00622     }
00623 
00630     public function getCommonMetaArray() {
00631         $handler = $this->getHandler();
00632 
00633         if ( !$handler ) {
00634             return false;
00635         }
00636 
00637         return $handler->getCommonMetaArray( $this );
00638     }
00639 
00648     public function convertMetadataVersion( $metadata, $version ) {
00649         $handler = $this->getHandler();
00650         if ( !is_array( $metadata ) ) {
00651             // Just to make the return type consistent
00652             $metadata = unserialize( $metadata );
00653         }
00654         if ( $handler ) {
00655             return $handler->convertMetadataVersion( $metadata, $version );
00656         } else {
00657             return $metadata;
00658         }
00659     }
00660 
00667     public function getBitDepth() {
00668         return 0;
00669     }
00670 
00677     public function getSize() {
00678         return false;
00679     }
00680 
00688     function getMimeType() {
00689         return 'unknown/unknown';
00690     }
00691 
00699     function getMediaType() {
00700         return MEDIATYPE_UNKNOWN;
00701     }
00702 
00715     function canRender() {
00716         if ( !isset( $this->canRender ) ) {
00717             $this->canRender = $this->getHandler() && $this->handler->canRender( $this ) && $this->exists();
00718         }
00719 
00720         return $this->canRender;
00721     }
00722 
00727     protected function getCanRender() {
00728         return $this->canRender();
00729     }
00730 
00741     function mustRender() {
00742         return $this->getHandler() && $this->handler->mustRender( $this );
00743     }
00744 
00750     function allowInlineDisplay() {
00751         return $this->canRender();
00752     }
00753 
00767     function isSafeFile() {
00768         if ( !isset( $this->isSafeFile ) ) {
00769             $this->isSafeFile = $this->getIsSafeFileUncached();
00770         }
00771 
00772         return $this->isSafeFile;
00773     }
00774 
00780     protected function getIsSafeFile() {
00781         return $this->isSafeFile();
00782     }
00783 
00789     protected function getIsSafeFileUncached() {
00790         global $wgTrustedMediaFormats;
00791 
00792         if ( $this->allowInlineDisplay() ) {
00793             return true;
00794         }
00795         if ( $this->isTrustedFile() ) {
00796             return true;
00797         }
00798 
00799         $type = $this->getMediaType();
00800         $mime = $this->getMimeType();
00801         #wfDebug( "LocalFile::isSafeFile: type= $type, mime= $mime\n" );
00802 
00803         if ( !$type || $type === MEDIATYPE_UNKNOWN ) {
00804             return false; #unknown type, not trusted
00805         }
00806         if ( in_array( $type, $wgTrustedMediaFormats ) ) {
00807             return true;
00808         }
00809 
00810         if ( $mime === "unknown/unknown" ) {
00811             return false; #unknown type, not trusted
00812         }
00813         if ( in_array( $mime, $wgTrustedMediaFormats ) ) {
00814             return true;
00815         }
00816 
00817         return false;
00818     }
00819 
00833     function isTrustedFile() {
00834         #this could be implemented to check a flag in the database,
00835         #look for signatures, etc
00836         return false;
00837     }
00838 
00846     public function exists() {
00847         return $this->getPath() && $this->repo->fileExists( $this->path );
00848     }
00849 
00856     public function isVisible() {
00857         return $this->exists();
00858     }
00859 
00863     function getTransformScript() {
00864         if ( !isset( $this->transformScript ) ) {
00865             $this->transformScript = false;
00866             if ( $this->repo ) {
00867                 $script = $this->repo->getThumbScriptUrl();
00868                 if ( $script ) {
00869                     $this->transformScript = wfAppendQuery( $script, array( 'f' => $this->getName() ) );
00870                 }
00871             }
00872         }
00873 
00874         return $this->transformScript;
00875     }
00876 
00884     function getUnscaledThumb( $handlerParams = array() ) {
00885         $hp =& $handlerParams;
00886         $page = isset( $hp['page'] ) ? $hp['page'] : false;
00887         $width = $this->getWidth( $page );
00888         if ( !$width ) {
00889             return $this->iconThumb();
00890         }
00891         $hp['width'] = $width;
00892         // be sure to ignore any height specification as well (bug 62258)
00893         unset( $hp['height'] );
00894 
00895         return $this->transform( $hp );
00896     }
00897 
00907     public function thumbName( $params, $flags = 0 ) {
00908         $name = ( $this->repo && !( $flags & self::THUMB_FULL_NAME ) )
00909             ? $this->repo->nameForThumb( $this->getName() )
00910             : $this->getName();
00911 
00912         return $this->generateThumbName( $name, $params );
00913     }
00914 
00922     public function generateThumbName( $name, $params ) {
00923         if ( !$this->getHandler() ) {
00924             return null;
00925         }
00926         $extension = $this->getExtension();
00927         list( $thumbExt, ) = $this->getHandler()->getThumbType(
00928             $extension, $this->getMimeType(), $params );
00929         $thumbName = $this->getHandler()->makeParamString( $params ) . '-' . $name;
00930         if ( $thumbExt != $extension ) {
00931             $thumbName .= ".$thumbExt";
00932         }
00933 
00934         return $thumbName;
00935     }
00936 
00954     public function createThumb( $width, $height = -1 ) {
00955         $params = array( 'width' => $width );
00956         if ( $height != -1 ) {
00957             $params['height'] = $height;
00958         }
00959         $thumb = $this->transform( $params );
00960         if ( !$thumb || $thumb->isError() ) {
00961             return '';
00962         }
00963 
00964         return $thumb->getUrl();
00965     }
00966 
00976     protected function transformErrorOutput( $thumbPath, $thumbUrl, $params, $flags ) {
00977         global $wgIgnoreImageErrors;
00978 
00979         $handler = $this->getHandler();
00980         if ( $handler && $wgIgnoreImageErrors && !( $flags & self::RENDER_NOW ) ) {
00981             return $handler->getTransform( $this, $thumbPath, $thumbUrl, $params );
00982         } else {
00983             return new MediaTransformError( 'thumbnail_error',
00984                 $params['width'], 0, wfMessage( 'thumbnail-dest-create' )->text() );
00985         }
00986     }
00987 
00996     function transform( $params, $flags = 0 ) {
00997         global $wgThumbnailEpoch;
00998 
00999         wfProfileIn( __METHOD__ );
01000         do {
01001             if ( !$this->canRender() ) {
01002                 $thumb = $this->iconThumb();
01003                 break; // not a bitmap or renderable image, don't try
01004             }
01005 
01006             // Get the descriptionUrl to embed it as comment into the thumbnail. Bug 19791.
01007             $descriptionUrl = $this->getDescriptionUrl();
01008             if ( $descriptionUrl ) {
01009                 $params['descriptionUrl'] = wfExpandUrl( $descriptionUrl, PROTO_CANONICAL );
01010             }
01011 
01012             $handler = $this->getHandler();
01013             $script = $this->getTransformScript();
01014             if ( $script && !( $flags & self::RENDER_NOW ) ) {
01015                 // Use a script to transform on client request, if possible
01016                 $thumb = $handler->getScriptedTransform( $this, $script, $params );
01017                 if ( $thumb ) {
01018                     break;
01019                 }
01020             }
01021 
01022             $normalisedParams = $params;
01023             $handler->normaliseParams( $this, $normalisedParams );
01024 
01025             $thumbName = $this->thumbName( $normalisedParams );
01026             $thumbUrl = $this->getThumbUrl( $thumbName );
01027             $thumbPath = $this->getThumbPath( $thumbName ); // final thumb path
01028 
01029             if ( $this->repo ) {
01030                 // Defer rendering if a 404 handler is set up...
01031                 if ( $this->repo->canTransformVia404() && !( $flags & self::RENDER_NOW ) ) {
01032                     wfDebug( __METHOD__ . " transformation deferred.\n" );
01033                     // XXX: Pass in the storage path even though we are not rendering anything
01034                     // and the path is supposed to be an FS path. This is due to getScalerType()
01035                     // getting called on the path and clobbering $thumb->getUrl() if it's false.
01036                     $thumb = $handler->getTransform( $this, $thumbPath, $thumbUrl, $params );
01037                     break;
01038                 }
01039                 // Check if an up-to-date thumbnail already exists...
01040                 wfDebug( __METHOD__ . ": Doing stat for $thumbPath\n" );
01041                 if ( !( $flags & self::RENDER_FORCE ) && $this->repo->fileExists( $thumbPath ) ) {
01042                     $timestamp = $this->repo->getFileTimestamp( $thumbPath );
01043                     if ( $timestamp !== false && $timestamp >= $wgThumbnailEpoch ) {
01044                         // XXX: Pass in the storage path even though we are not rendering anything
01045                         // and the path is supposed to be an FS path. This is due to getScalerType()
01046                         // getting called on the path and clobbering $thumb->getUrl() if it's false.
01047                         $thumb = $handler->getTransform( $this, $thumbPath, $thumbUrl, $params );
01048                         $thumb->setStoragePath( $thumbPath );
01049                         break;
01050                     }
01051                 } elseif ( $flags & self::RENDER_FORCE ) {
01052                     wfDebug( __METHOD__ . " forcing rendering per flag File::RENDER_FORCE\n" );
01053                 }
01054 
01055                 // If the backend is ready-only, don't keep generating thumbnails
01056                 // only to return transformation errors, just return the error now.
01057                 if ( $this->repo->getReadOnlyReason() !== false ) {
01058                     $thumb = $this->transformErrorOutput( $thumbPath, $thumbUrl, $params, $flags );
01059                     break;
01060                 }
01061             }
01062 
01063             $tmpFile = $this->makeTransformTmpFile( $thumbPath );
01064 
01065             if ( !$tmpFile ) {
01066                 $thumb = $this->transformErrorOutput( $thumbPath, $thumbUrl, $params, $flags );
01067             } else {
01068                 $thumb = $this->generateAndSaveThumb( $tmpFile, $params, $flags );
01069             }
01070         } while ( false );
01071 
01072         wfProfileOut( __METHOD__ );
01073 
01074         return is_object( $thumb ) ? $thumb : false;
01075     }
01076 
01084     public function generateAndSaveThumb( $tmpFile, $transformParams, $flags ) {
01085         global $wgUseSquid, $wgIgnoreImageErrors;
01086 
01087         $handler = $this->getHandler();
01088 
01089         $normalisedParams = $transformParams;
01090         $handler->normaliseParams( $this, $normalisedParams );
01091 
01092         $thumbName = $this->thumbName( $normalisedParams );
01093         $thumbUrl = $this->getThumbUrl( $thumbName );
01094         $thumbPath = $this->getThumbPath( $thumbName ); // final thumb path
01095 
01096         $tmpThumbPath = $tmpFile->getPath();
01097 
01098         if ( $handler->supportsBucketing() ) {
01099             $this->generateBucketsIfNeeded( $normalisedParams, $flags );
01100         }
01101 
01102         // Actually render the thumbnail...
01103         wfProfileIn( __METHOD__ . '-doTransform' );
01104         $thumb = $handler->doTransform( $this, $tmpThumbPath, $thumbUrl, $transformParams );
01105         wfProfileOut( __METHOD__ . '-doTransform' );
01106         $tmpFile->bind( $thumb ); // keep alive with $thumb
01107 
01108         if ( !$thumb ) { // bad params?
01109             $thumb = false;
01110         } elseif ( $thumb->isError() ) { // transform error
01111             $this->lastError = $thumb->toText();
01112             // Ignore errors if requested
01113             if ( $wgIgnoreImageErrors && !( $flags & self::RENDER_NOW ) ) {
01114                 $thumb = $handler->getTransform( $this, $tmpThumbPath, $thumbUrl, $transformParams );
01115             }
01116         } elseif ( $this->repo && $thumb->hasFile() && !$thumb->fileIsSource() ) {
01117             // Copy the thumbnail from the file system into storage...
01118             $disposition = $this->getThumbDisposition( $thumbName );
01119             $status = $this->repo->quickImport( $tmpThumbPath, $thumbPath, $disposition );
01120             if ( $status->isOK() ) {
01121                 $thumb->setStoragePath( $thumbPath );
01122             } else {
01123                 $thumb = $this->transformErrorOutput( $thumbPath, $thumbUrl, $transformParams, $flags );
01124             }
01125             // Give extensions a chance to do something with this thumbnail...
01126             wfRunHooks( 'FileTransformed', array( $this, $thumb, $tmpThumbPath, $thumbPath ) );
01127         }
01128 
01129         // Purge. Useful in the event of Core -> Squid connection failure or squid
01130         // purge collisions from elsewhere during failure. Don't keep triggering for
01131         // "thumbs" which have the main image URL though (bug 13776)
01132         if ( $wgUseSquid ) {
01133             if ( !$thumb || $thumb->isError() || $thumb->getUrl() != $this->getURL() ) {
01134                 SquidUpdate::purge( array( $thumbUrl ) );
01135             }
01136         }
01137 
01138         return $thumb;
01139     }
01140 
01147     protected function generateBucketsIfNeeded( $params, $flags = 0 ) {
01148         if ( !$this->repo
01149             || !isset( $params['physicalWidth'] )
01150             || !isset( $params['physicalHeight'] )
01151             || !( $bucket = $this->getThumbnailBucket( $params['physicalWidth'] ) )
01152             || $bucket == $params['physicalWidth'] ) {
01153             return false;
01154         }
01155 
01156         $bucketPath = $this->getBucketThumbPath( $bucket );
01157 
01158         if ( $this->repo->fileExists( $bucketPath ) ) {
01159             return false;
01160         }
01161 
01162         $params['physicalWidth'] = $bucket;
01163         $params['width'] = $bucket;
01164 
01165         $params = $this->getHandler()->sanitizeParamsForBucketing( $params );
01166 
01167         $bucketName = $this->getBucketThumbName( $bucket );
01168 
01169         $tmpFile = $this->makeTransformTmpFile( $bucketPath );
01170 
01171         if ( !$tmpFile ) {
01172             return false;
01173         }
01174 
01175         $thumb = $this->generateAndSaveThumb( $tmpFile, $params, $flags );
01176 
01177         if ( !$thumb || $thumb->isError() ) {
01178             return false;
01179         }
01180 
01181         $this->tmpBucketedThumbCache[$bucket] = $tmpFile->getPath();
01182         // For the caching to work, we need to make the tmp file survive as long as
01183         // this object exists
01184         $tmpFile->bind( $this );
01185 
01186         return true;
01187     }
01188 
01194     public function getThumbnailSource( $params ) {
01195         if ( $this->repo
01196             && $this->getHandler()->supportsBucketing()
01197             && isset( $params['physicalWidth'] )
01198             && $bucket = $this->getThumbnailBucket( $params['physicalWidth'] )
01199         ) {
01200             if ( $this->getWidth() != 0 ) {
01201                 $bucketHeight = round( $this->getHeight() * ( $bucket / $this->getWidth() ) );
01202             } else {
01203                 $bucketHeight = 0;
01204             }
01205 
01206             // Try to avoid reading from storage if the file was generated by this script
01207             if ( isset( $this->tmpBucketedThumbCache[$bucket] ) ) {
01208                 $tmpPath = $this->tmpBucketedThumbCache[$bucket];
01209 
01210                 if ( file_exists( $tmpPath ) ) {
01211                     return array(
01212                         'path' => $tmpPath,
01213                         'width' => $bucket,
01214                         'height' => $bucketHeight
01215                     );
01216                 }
01217             }
01218 
01219             $bucketPath = $this->getBucketThumbPath( $bucket );
01220 
01221             if ( $this->repo->fileExists( $bucketPath ) ) {
01222                 $fsFile = $this->repo->getLocalReference( $bucketPath );
01223 
01224                 if ( $fsFile ) {
01225                     return array(
01226                         'path' => $fsFile->getPath(),
01227                         'width' => $bucket,
01228                         'height' => $bucketHeight
01229                     );
01230                 }
01231             }
01232         }
01233 
01234         // Thumbnailing a very large file could result in network saturation if
01235         // everyone does it at once.
01236         if ( $this->getSize() >= 1e7 ) { // 10MB
01237             $that = $this;
01238             $work = new PoolCounterWorkViaCallback( 'GetLocalFileCopy', sha1( $this->getName() ),
01239                 array(
01240                     'doWork' => function() use ( $that ) {
01241                         return $that->getLocalRefPath();
01242                     }
01243                 )
01244             );
01245             $srcPath = $work->execute();
01246         } else {
01247             $srcPath = $this->getLocalRefPath();
01248         }
01249 
01250         // Original file
01251         return array(
01252             'path' => $srcPath,
01253             'width' => $this->getWidth(),
01254             'height' => $this->getHeight()
01255         );
01256     }
01257 
01263     protected function getBucketThumbPath( $bucket ) {
01264         $thumbName = $this->getBucketThumbName( $bucket );
01265         return $this->getThumbPath( $thumbName );
01266     }
01267 
01273     protected function getBucketThumbName( $bucket ) {
01274         return $this->thumbName( array( 'physicalWidth' => $bucket ) );
01275     }
01276 
01282     protected function makeTransformTmpFile( $thumbPath ) {
01283         $thumbExt = FileBackend::extensionFromPath( $thumbPath );
01284         return TempFSFile::factory( 'transform_', $thumbExt );
01285     }
01286 
01292     function getThumbDisposition( $thumbName, $dispositionType = 'inline' ) {
01293         $fileName = $this->name; // file name to suggest
01294         $thumbExt = FileBackend::extensionFromPath( $thumbName );
01295         if ( $thumbExt != '' && $thumbExt !== $this->getExtension() ) {
01296             $fileName .= ".$thumbExt";
01297         }
01298 
01299         return FileBackend::makeContentDisposition( $dispositionType, $fileName );
01300     }
01301 
01308     function migrateThumbFile( $thumbName ) {
01309     }
01310 
01317     function getHandler() {
01318         if ( !isset( $this->handler ) ) {
01319             $this->handler = MediaHandler::getHandler( $this->getMimeType() );
01320         }
01321 
01322         return $this->handler;
01323     }
01324 
01330     function iconThumb() {
01331         global $wgResourceBasePath, $IP;
01332         $assetsPath = "$wgResourceBasePath/resources/assets/file-type-icons/";
01333         $assetsDirectory = "$IP/resources/assets/file-type-icons/";
01334 
01335         $try = array( 'fileicon-' . $this->getExtension() . '.png', 'fileicon.png' );
01336         foreach ( $try as $icon ) {
01337             if ( file_exists( $assetsDirectory . $icon ) ) { // always FS
01338                 $params = array( 'width' => 120, 'height' => 120 );
01339 
01340                 return new ThumbnailImage( $this, $assetsPath . $icon, false, $params );
01341             }
01342         }
01343 
01344         return null;
01345     }
01346 
01352     function getLastError() {
01353         return $this->lastError;
01354     }
01355 
01362     function getThumbnails() {
01363         return array();
01364     }
01365 
01373     function purgeCache( $options = array() ) {
01374     }
01375 
01381     function purgeDescription() {
01382         $title = $this->getTitle();
01383         if ( $title ) {
01384             $title->invalidateCache();
01385             $title->purgeSquid();
01386         }
01387     }
01388 
01393     function purgeEverything() {
01394         // Delete thumbnails and refresh file metadata cache
01395         $this->purgeCache();
01396         $this->purgeDescription();
01397 
01398         // Purge cache of all pages using this file
01399         $title = $this->getTitle();
01400         if ( $title ) {
01401             $update = new HTMLCacheUpdate( $title, 'imagelinks' );
01402             $update->doUpdate();
01403         }
01404     }
01405 
01417     function getHistory( $limit = null, $start = null, $end = null, $inc = true ) {
01418         return array();
01419     }
01420 
01430     public function nextHistoryLine() {
01431         return false;
01432     }
01433 
01440     public function resetHistory() {
01441     }
01442 
01450     function getHashPath() {
01451         if ( !isset( $this->hashPath ) ) {
01452             $this->assertRepoDefined();
01453             $this->hashPath = $this->repo->getHashPath( $this->getName() );
01454         }
01455 
01456         return $this->hashPath;
01457     }
01458 
01465     function getRel() {
01466         return $this->getHashPath() . $this->getName();
01467     }
01468 
01476     function getArchiveRel( $suffix = false ) {
01477         $path = 'archive/' . $this->getHashPath();
01478         if ( $suffix === false ) {
01479             $path = substr( $path, 0, -1 );
01480         } else {
01481             $path .= $suffix;
01482         }
01483 
01484         return $path;
01485     }
01486 
01494     function getThumbRel( $suffix = false ) {
01495         $path = $this->getRel();
01496         if ( $suffix !== false ) {
01497             $path .= '/' . $suffix;
01498         }
01499 
01500         return $path;
01501     }
01502 
01509     function getUrlRel() {
01510         return $this->getHashPath() . rawurlencode( $this->getName() );
01511     }
01512 
01521     function getArchiveThumbRel( $archiveName, $suffix = false ) {
01522         $path = 'archive/' . $this->getHashPath() . $archiveName . "/";
01523         if ( $suffix === false ) {
01524             $path = substr( $path, 0, -1 );
01525         } else {
01526             $path .= $suffix;
01527         }
01528 
01529         return $path;
01530     }
01531 
01538     function getArchivePath( $suffix = false ) {
01539         $this->assertRepoDefined();
01540 
01541         return $this->repo->getZonePath( 'public' ) . '/' . $this->getArchiveRel( $suffix );
01542     }
01543 
01551     function getArchiveThumbPath( $archiveName, $suffix = false ) {
01552         $this->assertRepoDefined();
01553 
01554         return $this->repo->getZonePath( 'thumb' ) . '/' .
01555         $this->getArchiveThumbRel( $archiveName, $suffix );
01556     }
01557 
01564     function getThumbPath( $suffix = false ) {
01565         $this->assertRepoDefined();
01566 
01567         return $this->repo->getZonePath( 'thumb' ) . '/' . $this->getThumbRel( $suffix );
01568     }
01569 
01576     function getTranscodedPath( $suffix = false ) {
01577         $this->assertRepoDefined();
01578 
01579         return $this->repo->getZonePath( 'transcoded' ) . '/' . $this->getThumbRel( $suffix );
01580     }
01581 
01588     function getArchiveUrl( $suffix = false ) {
01589         $this->assertRepoDefined();
01590         $ext = $this->getExtension();
01591         $path = $this->repo->getZoneUrl( 'public', $ext ) . '/archive/' . $this->getHashPath();
01592         if ( $suffix === false ) {
01593             $path = substr( $path, 0, -1 );
01594         } else {
01595             $path .= rawurlencode( $suffix );
01596         }
01597 
01598         return $path;
01599     }
01600 
01608     function getArchiveThumbUrl( $archiveName, $suffix = false ) {
01609         $this->assertRepoDefined();
01610         $ext = $this->getExtension();
01611         $path = $this->repo->getZoneUrl( 'thumb', $ext ) . '/archive/' .
01612             $this->getHashPath() . rawurlencode( $archiveName ) . "/";
01613         if ( $suffix === false ) {
01614             $path = substr( $path, 0, -1 );
01615         } else {
01616             $path .= rawurlencode( $suffix );
01617         }
01618 
01619         return $path;
01620     }
01621 
01629     function getZoneUrl( $zone, $suffix = false ) {
01630         $this->assertRepoDefined();
01631         $ext = $this->getExtension();
01632         $path = $this->repo->getZoneUrl( $zone, $ext ) . '/' . $this->getUrlRel();
01633         if ( $suffix !== false ) {
01634             $path .= '/' . rawurlencode( $suffix );
01635         }
01636 
01637         return $path;
01638     }
01639 
01646     function getThumbUrl( $suffix = false ) {
01647         return $this->getZoneUrl( 'thumb', $suffix );
01648     }
01649 
01656     function getTranscodedUrl( $suffix = false ) {
01657         return $this->getZoneUrl( 'transcoded', $suffix );
01658     }
01659 
01666     function getVirtualUrl( $suffix = false ) {
01667         $this->assertRepoDefined();
01668         $path = $this->repo->getVirtualUrl() . '/public/' . $this->getUrlRel();
01669         if ( $suffix !== false ) {
01670             $path .= '/' . rawurlencode( $suffix );
01671         }
01672 
01673         return $path;
01674     }
01675 
01682     function getArchiveVirtualUrl( $suffix = false ) {
01683         $this->assertRepoDefined();
01684         $path = $this->repo->getVirtualUrl() . '/public/archive/' . $this->getHashPath();
01685         if ( $suffix === false ) {
01686             $path = substr( $path, 0, -1 );
01687         } else {
01688             $path .= rawurlencode( $suffix );
01689         }
01690 
01691         return $path;
01692     }
01693 
01700     function getThumbVirtualUrl( $suffix = false ) {
01701         $this->assertRepoDefined();
01702         $path = $this->repo->getVirtualUrl() . '/thumb/' . $this->getUrlRel();
01703         if ( $suffix !== false ) {
01704             $path .= '/' . rawurlencode( $suffix );
01705         }
01706 
01707         return $path;
01708     }
01709 
01713     function isHashed() {
01714         $this->assertRepoDefined();
01715 
01716         return (bool)$this->repo->getHashLevels();
01717     }
01718 
01722     function readOnlyError() {
01723         throw new MWException( get_class( $this ) . ': write operations are not supported' );
01724     }
01725 
01741     function recordUpload( $oldver, $desc, $license = '', $copyStatus = '', $source = '',
01742         $watch = false, $timestamp = false, User $user = null
01743     ) {
01744         $this->readOnlyError();
01745     }
01746 
01768     function publish( $srcPath, $flags = 0, array $options = array() ) {
01769         $this->readOnlyError();
01770     }
01771 
01775     function formatMetadata() {
01776         if ( !$this->getHandler() ) {
01777             return false;
01778         }
01779 
01780         return $this->getHandler()->formatMetadata( $this, $this->getMetadata() );
01781     }
01782 
01788     function isLocal() {
01789         return $this->repo && $this->repo->isLocal();
01790     }
01791 
01797     function getRepoName() {
01798         return $this->repo ? $this->repo->getName() : 'unknown';
01799     }
01800 
01806     function getRepo() {
01807         return $this->repo;
01808     }
01809 
01816     function isOld() {
01817         return false;
01818     }
01819 
01827     function isDeleted( $field ) {
01828         return false;
01829     }
01830 
01836     function getVisibility() {
01837         return 0;
01838     }
01839 
01845     function wasDeleted() {
01846         $title = $this->getTitle();
01847 
01848         return $title && $title->isDeletedQuick();
01849     }
01850 
01863     function move( $target ) {
01864         $this->readOnlyError();
01865     }
01866 
01882     function delete( $reason, $suppress = false, $user = null ) {
01883         $this->readOnlyError();
01884     }
01885 
01900     function restore( $versions = array(), $unsuppress = false ) {
01901         $this->readOnlyError();
01902     }
01903 
01911     function isMultipage() {
01912         return $this->getHandler() && $this->handler->isMultiPage( $this );
01913     }
01914 
01921     function pageCount() {
01922         if ( !isset( $this->pageCount ) ) {
01923             if ( $this->getHandler() && $this->handler->isMultiPage( $this ) ) {
01924                 $this->pageCount = $this->handler->pageCount( $this );
01925             } else {
01926                 $this->pageCount = false;
01927             }
01928         }
01929 
01930         return $this->pageCount;
01931     }
01932 
01942     static function scaleHeight( $srcWidth, $srcHeight, $dstWidth ) {
01943         // Exact integer multiply followed by division
01944         if ( $srcWidth == 0 ) {
01945             return 0;
01946         } else {
01947             return round( $srcHeight * $dstWidth / $srcWidth );
01948         }
01949     }
01950 
01961     function getImageSize( $filePath ) {
01962         if ( !$this->getHandler() ) {
01963             return false;
01964         }
01965 
01966         return $this->getHandler()->getImageSize( $this, $filePath );
01967     }
01968 
01975     function getDescriptionUrl() {
01976         if ( $this->repo ) {
01977             return $this->repo->getDescriptionUrl( $this->getName() );
01978         } else {
01979             return false;
01980         }
01981     }
01982 
01989     function getDescriptionText( $lang = false ) {
01990         global $wgMemc, $wgLang;
01991         if ( !$this->repo || !$this->repo->fetchDescription ) {
01992             return false;
01993         }
01994         if ( !$lang ) {
01995             $lang = $wgLang;
01996         }
01997         $renderUrl = $this->repo->getDescriptionRenderUrl( $this->getName(), $lang->getCode() );
01998         if ( $renderUrl ) {
01999             if ( $this->repo->descriptionCacheExpiry > 0 ) {
02000                 wfDebug( "Attempting to get the description from cache..." );
02001                 $key = $this->repo->getLocalCacheKey(
02002                     'RemoteFileDescription',
02003                     'url',
02004                     $lang->getCode(),
02005                     $this->getName()
02006                 );
02007                 $obj = $wgMemc->get( $key );
02008                 if ( $obj ) {
02009                     wfDebug( "success!\n" );
02010 
02011                     return $obj;
02012                 }
02013                 wfDebug( "miss\n" );
02014             }
02015             wfDebug( "Fetching shared description from $renderUrl\n" );
02016             $res = Http::get( $renderUrl );
02017             if ( $res && $this->repo->descriptionCacheExpiry > 0 ) {
02018                 $wgMemc->set( $key, $res, $this->repo->descriptionCacheExpiry );
02019             }
02020 
02021             return $res;
02022         } else {
02023             return false;
02024         }
02025     }
02026 
02039     function getDescription( $audience = self::FOR_PUBLIC, User $user = null ) {
02040         return null;
02041     }
02042 
02048     function getTimestamp() {
02049         $this->assertRepoDefined();
02050 
02051         return $this->repo->getFileTimestamp( $this->getPath() );
02052     }
02053 
02059     function getSha1() {
02060         $this->assertRepoDefined();
02061 
02062         return $this->repo->getFileSha1( $this->getPath() );
02063     }
02064 
02070     function getStorageKey() {
02071         $hash = $this->getSha1();
02072         if ( !$hash ) {
02073             return false;
02074         }
02075         $ext = $this->getExtension();
02076         $dotExt = $ext === '' ? '' : ".$ext";
02077 
02078         return $hash . $dotExt;
02079     }
02080 
02089     function userCan( $field, User $user = null ) {
02090         return true;
02091     }
02092 
02096     function getStreamHeaders() {
02097         $handler = $this->getHandler();
02098         if ( $handler ) {
02099             return $handler->getStreamHeaders( $this->getMetadata() );
02100         } else {
02101             return array();
02102         }
02103     }
02104 
02108     function getLongDesc() {
02109         $handler = $this->getHandler();
02110         if ( $handler ) {
02111             return $handler->getLongDesc( $this );
02112         } else {
02113             return MediaHandler::getGeneralLongDesc( $this );
02114         }
02115     }
02116 
02120     function getShortDesc() {
02121         $handler = $this->getHandler();
02122         if ( $handler ) {
02123             return $handler->getShortDesc( $this );
02124         } else {
02125             return MediaHandler::getGeneralShortDesc( $this );
02126         }
02127     }
02128 
02132     function getDimensionsString() {
02133         $handler = $this->getHandler();
02134         if ( $handler ) {
02135             return $handler->getDimensionsString( $this );
02136         } else {
02137             return '';
02138         }
02139     }
02140 
02144     function getRedirected() {
02145         return $this->redirected;
02146     }
02147 
02151     function getRedirectedTitle() {
02152         if ( $this->redirected ) {
02153             if ( !$this->redirectTitle ) {
02154                 $this->redirectTitle = Title::makeTitle( NS_FILE, $this->redirected );
02155             }
02156 
02157             return $this->redirectTitle;
02158         }
02159 
02160         return null;
02161     }
02162 
02167     function redirectedFrom( $from ) {
02168         $this->redirected = $from;
02169     }
02170 
02174     function isMissing() {
02175         return false;
02176     }
02177 
02182     public function isCacheable() {
02183         return true;
02184     }
02185 
02190     protected function assertRepoDefined() {
02191         if ( !( $this->repo instanceof $this->repoClass ) ) {
02192             throw new MWException( "A {$this->repoClass} object is not set for this File.\n" );
02193         }
02194     }
02195 
02200     protected function assertTitleDefined() {
02201         if ( !( $this->title instanceof Title ) ) {
02202             throw new MWException( "A Title object is not set for this File.\n" );
02203         }
02204     }
02205 
02210     public function isExpensiveToThumbnail() {
02211         $handler = $this->getHandler();
02212         return $handler ? $handler->isExpensiveToThumbnail( $this ) : false;
02213     }
02214 }