MediaWiki
REL1_24
|
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 }