diff options
author | Luke Shumaker <lukeshu@sbcglobal.net> | 2016-05-01 15:12:12 -0400 |
---|---|---|
committer | Luke Shumaker <lukeshu@sbcglobal.net> | 2016-05-01 15:12:12 -0400 |
commit | c9aa36da061816dee256a979c2ff8d2ee41824d9 (patch) | |
tree | 29f7002b80ee984b488bd047dbbd80b36bf892e9 /includes/filerepo/file/File.php | |
parent | b4274e0e33eafb5e9ead9d949ebf031a9fb8363b (diff) | |
parent | d1ba966140d7a60cd5ae4e8667ceb27c1a138592 (diff) |
Merge branch 'archwiki'
# Conflicts:
# skins/ArchLinux.php
# skins/ArchLinux/archlogo.gif
Diffstat (limited to 'includes/filerepo/file/File.php')
-rw-r--r-- | includes/filerepo/file/File.php | 827 |
1 files changed, 568 insertions, 259 deletions
diff --git a/includes/filerepo/file/File.php b/includes/filerepo/file/File.php index ec5f927b..b574c5e7 100644 --- a/includes/filerepo/file/File.php +++ b/includes/filerepo/file/File.php @@ -91,45 +91,67 @@ abstract class File { * The following member variables are not lazy-initialised */ - /** - * @var FileRepo|bool - */ - var $repo; + /** @var FileRepo|LocalRepo|ForeignAPIRepo|bool */ + public $repo; - /** - * @var Title - */ - var $title; + /** @var Title|string|bool */ + protected $title; - var $lastError, $redirected, $redirectedTitle; + /** @var string Text of last error */ + protected $lastError; - /** - * @var FSFile|bool False if undefined - */ + /** @var string Main part of the title, with underscores (Title::getDBkey) */ + protected $redirected; + + /** @var Title */ + protected $redirectedTitle; + + /** @var FSFile|bool False if undefined */ protected $fsFile; - /** - * @var MediaHandler - */ + /** @var MediaHandler */ protected $handler; - /** - * @var string + /** @var string The URL corresponding to one of the four basic zones */ + protected $url; + + /** @var string File extension */ + protected $extension; + + /** @var string The name of a file from its title object */ + protected $name; + + /** @var string The storage path corresponding to one of the zones */ + protected $path; + + /** @var string Relative path including trailing slash */ + protected $hashPath; + + /** @var string Number of pages of a multipage document, or false for + * documents which aren't multipage documents */ - protected $url, $extension, $name, $path, $hashPath, $pageCount, $transformScript; + protected $pageCount; + /** @var string URL of transformscript (for example thumb.php) */ + protected $transformScript; + + /** @var Title */ protected $redirectTitle; - /** - * @var bool - */ - protected $canRender, $isSafeFile; + /** @var bool Wether the output of transform() for this file is likely to be valid. */ + protected $canRender; - /** - * @var string Required Repository class type + /** @var bool Wether this media file is in a format that is unlikely to + * contain viruses or malicious content */ + protected $isSafeFile; + + /** @var string Required Repository class type */ protected $repoClass = 'FileRepo'; + /** @var array Cache of tmp filepaths pointing to generated bucket thumbnails, keyed by width */ + protected $tmpBucketedThumbCache = array(); + /** * Call this constructor from child classes. * @@ -137,8 +159,8 @@ abstract class File { * may return false or throw exceptions if they are not set. * Most subclasses will want to call assertRepoDefined() here. * - * @param $title Title|string|bool - * @param $repo FileRepo|bool + * @param Title|string|bool $title + * @param FileRepo|bool $repo */ function __construct( $title, $repo ) { if ( $title !== false ) { // subclasses may not use MW titles @@ -152,7 +174,7 @@ abstract class File { * Given a string or Title object return either a * valid Title object with namespace NS_FILE or null * - * @param $title Title|string + * @param Title|string $title * @param string|bool $exception Use 'exception' to throw an error on bad titles * @throws MWException * @return Title|null @@ -174,6 +196,7 @@ abstract class File { if ( !$ret && $exception !== false ) { throw new MWException( "`$title` is not a valid file title." ); } + return $ret; } @@ -183,6 +206,7 @@ abstract class File { return null; } else { $this->$name = call_user_func( $function ); + return $this->$name; } } @@ -214,7 +238,7 @@ abstract class File { /** * Checks if file extensions are compatible * - * @param $old File Old file + * @param File $old Old file * @param string $new New name * * @return bool|null @@ -224,6 +248,7 @@ abstract class File { $n = strrpos( $new, '.' ); $newExt = self::normalizeExtension( $n ? substr( $new, $n + 1 ) : '' ); $mimeMagic = MimeMagic::singleton(); + return $mimeMagic->isMatchingExtension( $newExt, $oldMime ); } @@ -232,7 +257,8 @@ abstract class File { * Called by ImagePage * STUB */ - function upgradeRow() {} + function upgradeRow() { + } /** * Split an internet media type into its two components; if not @@ -252,10 +278,9 @@ abstract class File { /** * Callback for usort() to do file sorts by name * - * @param $a File - * @param $b File - * - * @return Integer: result of name comparison + * @param File $a + * @param File $b + * @return int Result of name comparison */ public static function compare( File $a, File $b ) { return strcmp( $a->getName(), $b->getName() ); @@ -271,6 +296,7 @@ abstract class File { $this->assertRepoDefined(); $this->name = $this->repo->getNameFromTitle( $this->title ); } + return $this->name; } @@ -285,6 +311,7 @@ abstract class File { $this->extension = self::normalizeExtension( $n ? substr( $this->getName(), $n + 1 ) : '' ); } + return $this->extension; } @@ -306,6 +333,7 @@ abstract class File { if ( $this->redirected ) { return $this->getRedirectedTitle(); } + return $this->title; } @@ -320,6 +348,7 @@ abstract class File { $ext = $this->getExtension(); $this->url = $this->repo->getZoneUrl( 'public', $ext ) . '/' . $this->getUrlRel(); } + return $this->url; } @@ -328,7 +357,7 @@ abstract class File { * Upload URL paths _may or may not_ be fully qualified, so * we check. Local paths are assumed to belong on $wgServer. * - * @return String + * @return string */ public function getFullUrl() { return wfExpandUrl( $this->getUrl(), PROTO_RELATIVE ); @@ -351,6 +380,7 @@ abstract class File { } else { wfDebug( __METHOD__ . ': supposed to render ' . $this->getName() . ' (' . $this->getMimeType() . "), but can't!\n" ); + return $this->getURL(); #hm... return NULL? } } else { @@ -376,6 +406,7 @@ abstract class File { $this->assertRepoDefined(); $this->path = $this->repo->getZonePath( 'public' ) . '/' . $this->getRel(); } + return $this->path; } @@ -394,6 +425,7 @@ abstract class File { $this->fsFile = false; // null => false; cache negative hits } } + return ( $this->fsFile ) ? $this->fsFile->getPath() : false; @@ -406,9 +438,8 @@ abstract class File { * STUB * Overridden by LocalFile, UnregisteredLocalFile * - * @param $page int - * - * @return number + * @param int $page + * @return int|bool */ public function getWidth( $page = 1 ) { return false; @@ -421,20 +452,62 @@ abstract class File { * STUB * Overridden by LocalFile, UnregisteredLocalFile * - * @param $page int - * - * @return bool|number False on failure + * @param int $page + * @return bool|int False on failure */ public function getHeight( $page = 1 ) { return false; } /** + * Return the smallest bucket from $wgThumbnailBuckets which is at least + * $wgThumbnailMinimumBucketDistance larger than $desiredWidth. The returned bucket, if any, + * will always be bigger than $desiredWidth. + * + * @param int $desiredWidth + * @param int $page + * @return bool|int + */ + public function getThumbnailBucket( $desiredWidth, $page = 1 ) { + global $wgThumbnailBuckets, $wgThumbnailMinimumBucketDistance; + + $imageWidth = $this->getWidth( $page ); + + if ( $imageWidth === false ) { + return false; + } + + if ( $desiredWidth > $imageWidth ) { + return false; + } + + if ( !$wgThumbnailBuckets ) { + return false; + } + + $sortedBuckets = $wgThumbnailBuckets; + + sort( $sortedBuckets ); + + foreach ( $sortedBuckets as $bucket ) { + if ( $bucket > $imageWidth ) { + return false; + } + + if ( $bucket - $wgThumbnailMinimumBucketDistance > $desiredWidth ) { + return $bucket; + } + } + + // Image is bigger than any available bucket + return false; + } + + /** * Returns ID or name of user who uploaded the file * STUB * * @param string $type 'text' or 'id' - * * @return string|int */ public function getUser( $type = 'text' ) { @@ -444,7 +517,7 @@ abstract class File { /** * Get the duration of a media file in seconds * - * @return number + * @return int */ public function getLength() { $handler = $this->getHandler(); @@ -470,11 +543,47 @@ abstract class File { } /** + * Gives a (possibly empty) list of languages to render + * the file in. + * + * If the file doesn't have translations, or if the file + * format does not support that sort of thing, returns + * an empty array. + * + * @return array + * @since 1.23 + */ + public function getAvailableLanguages() { + $handler = $this->getHandler(); + if ( $handler ) { + return $handler->getAvailableLanguages( $this ); + } else { + return array(); + } + } + + /** + * In files that support multiple language, what is the default language + * to use if none specified. + * + * @return string Lang code, or null if filetype doesn't support multiple languages. + * @since 1.23 + */ + public function getDefaultRenderLanguage() { + $handler = $this->getHandler(); + if ( $handler ) { + return $handler->getDefaultRenderLanguage( $this ); + } else { + return null; + } + } + + /** * Will the thumbnail be animated if one would expect it to be. * * Currently used to add a warning to the image description page * - * @return bool false if the main image is both animated + * @return bool False if the main image is both animated * and the thumbnail is not. In all other cases must return * true. If image is not renderable whatsoever, should * return true. @@ -506,18 +615,35 @@ abstract class File { * Get handler-specific metadata * Overridden by LocalFile, UnregisteredLocalFile * STUB - * @return bool + * @return bool|array */ public function getMetadata() { return false; } /** + * Like getMetadata but returns a handler independent array of common values. + * @see MediaHandler::getCommonMetaArray() + * @return array|bool Array or false if not supported + * @since 1.23 + */ + public function getCommonMetaArray() { + $handler = $this->getHandler(); + + if ( !$handler ) { + return false; + } + + return $handler->getCommonMetaArray( $this ); + } + + /** * get versioned metadata * - * @param $metadata Mixed Array or String of (serialized) metadata - * @param $version integer version number. - * @return Array containing metadata, or what was passed to it on fail (unserializing if not array) + * @param array|string $metadata Array or string of (serialized) metadata + * @param int $version Version number. + * @return array Array containing metadata, or what was passed to it on fail + * (unserializing if not array) */ public function convertMetadataVersion( $metadata, $version ) { $handler = $this->getHandler(); @@ -553,7 +679,7 @@ abstract class File { } /** - * Returns the mime type of the file. + * Returns the MIME type of the file. * Overridden by LocalFile, UnregisteredLocalFile * STUB * @@ -588,8 +714,9 @@ abstract class File { */ function canRender() { if ( !isset( $this->canRender ) ) { - $this->canRender = $this->getHandler() && $this->handler->canRender( $this ); + $this->canRender = $this->getHandler() && $this->handler->canRender( $this ) && $this->exists(); } + return $this->canRender; } @@ -639,8 +766,9 @@ abstract class File { */ function isSafeFile() { if ( !isset( $this->isSafeFile ) ) { - $this->isSafeFile = $this->_getIsSafeFile(); + $this->isSafeFile = $this->getIsSafeFileUncached(); } + return $this->isSafeFile; } @@ -658,7 +786,7 @@ abstract class File { * * @return bool */ - protected function _getIsSafeFile() { + protected function getIsSafeFileUncached() { global $wgTrustedMediaFormats; if ( $this->allowInlineDisplay() ) { @@ -713,7 +841,7 @@ abstract class File { * * Overridden by LocalFile to avoid unnecessary stat calls. * - * @return boolean Whether file exists in the repository. + * @return bool Whether file exists in the repository. */ public function exists() { return $this->getPath() && $this->repo->fileExists( $this->path ); @@ -723,7 +851,7 @@ abstract class File { * Returns true if file exists in the repository and can be included in a page. * It would be unsafe to include private images, making public thumbnails inadvertently * - * @return boolean Whether file exists in the repository and is includable. + * @return bool Whether file exists in the repository and is includable. */ public function isVisible() { return $this->exists(); @@ -742,13 +870,14 @@ abstract class File { } } } + return $this->transformScript; } /** * Get a ThumbnailImage which is the same size as the source * - * @param $handlerParams array + * @param array $handlerParams * * @return string */ @@ -760,6 +889,9 @@ abstract class File { return $this->iconThumb(); } $hp['width'] = $width; + // be sure to ignore any height specification as well (bug 62258) + unset( $hp['height'] ); + return $this->transform( $hp ); } @@ -768,14 +900,15 @@ abstract class File { * Use File::THUMB_FULL_NAME to always get a name like "<params>-<source>". * Otherwise, the format may be "<params>-<source>" or "<params>-thumbnail.<ext>". * - * @param array $params handler-specific parameters - * @param $flags integer Bitfield that supports THUMB_* constants + * @param array $params Handler-specific parameters + * @param int $flags Bitfield that supports THUMB_* constants * @return string */ public function thumbName( $params, $flags = 0 ) { $name = ( $this->repo && !( $flags & self::THUMB_FULL_NAME ) ) ? $this->repo->nameForThumb( $this->getName() ) : $this->getName(); + return $this->generateThumbName( $name, $params ); } @@ -784,7 +917,6 @@ abstract class File { * * @param string $name * @param array $params Parameters which will be passed to MediaHandler::makeParamString - * * @return string */ public function generateThumbName( $name, $params ) { @@ -792,12 +924,13 @@ abstract class File { return null; } $extension = $this->getExtension(); - list( $thumbExt, $thumbMime ) = $this->handler->getThumbType( + list( $thumbExt, ) = $this->getHandler()->getThumbType( $extension, $this->getMimeType(), $params ); - $thumbName = $this->handler->makeParamString( $params ) . '-' . $name; + $thumbName = $this->getHandler()->makeParamString( $params ) . '-' . $name; if ( $thumbExt != $extension ) { $thumbName .= ".$thumbExt"; } + return $thumbName; } @@ -813,8 +946,8 @@ abstract class File { * specified, the generated image will be no bigger than width x height, * and will also have correct aspect ratio. * - * @param $width Integer: maximum width of the generated thumbnail - * @param $height Integer: maximum height of the image (optional) + * @param int $width Maximum width of the generated thumbnail + * @param int $height Maximum height of the image (optional) * * @return string */ @@ -824,9 +957,10 @@ abstract class File { $params['height'] = $height; } $thumb = $this->transform( $params ); - if ( is_null( $thumb ) || $thumb->isError() ) { + if ( !$thumb || $thumb->isError() ) { return ''; } + return $thumb->getUrl(); } @@ -835,8 +969,8 @@ abstract class File { * * @param string $thumbPath Thumbnail storage path * @param string $thumbUrl Thumbnail URL - * @param $params Array - * @param $flags integer + * @param array $params + * @param int $flags * @return MediaTransformOutput */ protected function transformErrorOutput( $thumbPath, $thumbUrl, $params, $flags ) { @@ -854,13 +988,13 @@ abstract class File { /** * Transform a media file * - * @param array $params an associative array of handler-specific parameters. - * Typical keys are width, height and page. - * @param $flags Integer: a bitfield, may contain self::RENDER_NOW to force rendering + * @param array $params An associative array of handler-specific parameters. + * Typical keys are width, height and page. + * @param int $flags A bitfield, may contain self::RENDER_NOW to force rendering * @return MediaTransformOutput|bool False on failure */ function transform( $params, $flags = 0 ) { - global $wgUseSquid, $wgIgnoreImageErrors, $wgThumbnailEpoch; + global $wgThumbnailEpoch; wfProfileIn( __METHOD__ ); do { @@ -895,15 +1029,13 @@ abstract class File { if ( $this->repo ) { // Defer rendering if a 404 handler is set up... if ( $this->repo->canTransformVia404() && !( $flags & self::RENDER_NOW ) ) { - wfDebug( __METHOD__ . " transformation deferred." ); + wfDebug( __METHOD__ . " transformation deferred.\n" ); // XXX: Pass in the storage path even though we are not rendering anything // and the path is supposed to be an FS path. This is due to getScalerType() // getting called on the path and clobbering $thumb->getUrl() if it's false. $thumb = $handler->getTransform( $this, $thumbPath, $thumbUrl, $params ); break; } - // Clean up broken thumbnails as needed - $this->migrateThumbFile( $thumbName ); // Check if an up-to-date thumbnail already exists... wfDebug( __METHOD__ . ": Doing stat for $thumbPath\n" ); if ( !( $flags & self::RENDER_FORCE ) && $this->repo->fileExists( $thumbPath ) ) { @@ -919,94 +1051,274 @@ abstract class File { } elseif ( $flags & self::RENDER_FORCE ) { wfDebug( __METHOD__ . " forcing rendering per flag File::RENDER_FORCE\n" ); } - } - // If the backend is ready-only, don't keep generating thumbnails - // only to return transformation errors, just return the error now. - if ( $this->repo->getReadOnlyReason() !== false ) { - $thumb = $this->transformErrorOutput( $thumbPath, $thumbUrl, $params, $flags ); - break; + // If the backend is ready-only, don't keep generating thumbnails + // only to return transformation errors, just return the error now. + if ( $this->repo->getReadOnlyReason() !== false ) { + $thumb = $this->transformErrorOutput( $thumbPath, $thumbUrl, $params, $flags ); + break; + } } - // Create a temp FS file with the same extension and the thumbnail - $thumbExt = FileBackend::extensionFromPath( $thumbPath ); - $tmpFile = TempFSFile::factory( 'transform_', $thumbExt ); + $tmpFile = $this->makeTransformTmpFile( $thumbPath ); + if ( !$tmpFile ) { $thumb = $this->transformErrorOutput( $thumbPath, $thumbUrl, $params, $flags ); - break; + } else { + $thumb = $this->generateAndSaveThumb( $tmpFile, $params, $flags ); } - $tmpThumbPath = $tmpFile->getPath(); // path of 0-byte temp file - - // Actually render the thumbnail... - wfProfileIn( __METHOD__ . '-doTransform' ); - $thumb = $handler->doTransform( $this, $tmpThumbPath, $thumbUrl, $params ); - wfProfileOut( __METHOD__ . '-doTransform' ); - $tmpFile->bind( $thumb ); // keep alive with $thumb - - if ( !$thumb ) { // bad params? - $thumb = null; - } elseif ( $thumb->isError() ) { // transform error - $this->lastError = $thumb->toText(); - // Ignore errors if requested - if ( $wgIgnoreImageErrors && !( $flags & self::RENDER_NOW ) ) { - $thumb = $handler->getTransform( $this, $tmpThumbPath, $thumbUrl, $params ); - } - } elseif ( $this->repo && $thumb->hasFile() && !$thumb->fileIsSource() ) { - // Copy the thumbnail from the file system into storage... - $disposition = $this->getThumbDisposition( $thumbName ); - $status = $this->repo->quickImport( $tmpThumbPath, $thumbPath, $disposition ); - if ( $status->isOK() ) { - $thumb->setStoragePath( $thumbPath ); - } else { - $thumb = $this->transformErrorOutput( $thumbPath, $thumbUrl, $params, $flags ); + } while ( false ); + + wfProfileOut( __METHOD__ ); + + return is_object( $thumb ) ? $thumb : false; + } + + /** + * Generates a thumbnail according to the given parameters and saves it to storage + * @param TempFSFile $tmpFile Temporary file where the rendered thumbnail will be saved + * @param array $transformParams + * @param int $flags + * @return bool|MediaTransformOutput + */ + public function generateAndSaveThumb( $tmpFile, $transformParams, $flags ) { + global $wgUseSquid, $wgIgnoreImageErrors; + + $handler = $this->getHandler(); + + $normalisedParams = $transformParams; + $handler->normaliseParams( $this, $normalisedParams ); + + $thumbName = $this->thumbName( $normalisedParams ); + $thumbUrl = $this->getThumbUrl( $thumbName ); + $thumbPath = $this->getThumbPath( $thumbName ); // final thumb path + + $tmpThumbPath = $tmpFile->getPath(); + + if ( $handler->supportsBucketing() ) { + $this->generateBucketsIfNeeded( $normalisedParams, $flags ); + } + + // Actually render the thumbnail... + wfProfileIn( __METHOD__ . '-doTransform' ); + $thumb = $handler->doTransform( $this, $tmpThumbPath, $thumbUrl, $transformParams ); + wfProfileOut( __METHOD__ . '-doTransform' ); + $tmpFile->bind( $thumb ); // keep alive with $thumb + + if ( !$thumb ) { // bad params? + $thumb = false; + } elseif ( $thumb->isError() ) { // transform error + $this->lastError = $thumb->toText(); + // Ignore errors if requested + if ( $wgIgnoreImageErrors && !( $flags & self::RENDER_NOW ) ) { + $thumb = $handler->getTransform( $this, $tmpThumbPath, $thumbUrl, $transformParams ); + } + } elseif ( $this->repo && $thumb->hasFile() && !$thumb->fileIsSource() ) { + // Copy the thumbnail from the file system into storage... + $disposition = $this->getThumbDisposition( $thumbName ); + $status = $this->repo->quickImport( $tmpThumbPath, $thumbPath, $disposition ); + if ( $status->isOK() ) { + $thumb->setStoragePath( $thumbPath ); + } else { + $thumb = $this->transformErrorOutput( $thumbPath, $thumbUrl, $transformParams, $flags ); + } + // Give extensions a chance to do something with this thumbnail... + wfRunHooks( 'FileTransformed', array( $this, $thumb, $tmpThumbPath, $thumbPath ) ); + } + + // Purge. Useful in the event of Core -> Squid connection failure or squid + // purge collisions from elsewhere during failure. Don't keep triggering for + // "thumbs" which have the main image URL though (bug 13776) + if ( $wgUseSquid ) { + if ( !$thumb || $thumb->isError() || $thumb->getUrl() != $this->getURL() ) { + SquidUpdate::purge( array( $thumbUrl ) ); + } + } + + return $thumb; + } + + /** + * Generates chained bucketed thumbnails if needed + * @param array $params + * @param int $flags + * @return bool Whether at least one bucket was generated + */ + protected function generateBucketsIfNeeded( $params, $flags = 0 ) { + if ( !$this->repo + || !isset( $params['physicalWidth'] ) + || !isset( $params['physicalHeight'] ) + || !( $bucket = $this->getThumbnailBucket( $params['physicalWidth'] ) ) + || $bucket == $params['physicalWidth'] ) { + return false; + } + + $bucketPath = $this->getBucketThumbPath( $bucket ); + + if ( $this->repo->fileExists( $bucketPath ) ) { + return false; + } + + $params['physicalWidth'] = $bucket; + $params['width'] = $bucket; + + $params = $this->getHandler()->sanitizeParamsForBucketing( $params ); + + $bucketName = $this->getBucketThumbName( $bucket ); + + $tmpFile = $this->makeTransformTmpFile( $bucketPath ); + + if ( !$tmpFile ) { + return false; + } + + $thumb = $this->generateAndSaveThumb( $tmpFile, $params, $flags ); + + if ( !$thumb || $thumb->isError() ) { + return false; + } + + $this->tmpBucketedThumbCache[$bucket] = $tmpFile->getPath(); + // For the caching to work, we need to make the tmp file survive as long as + // this object exists + $tmpFile->bind( $this ); + + return true; + } + + /** + * Returns the most appropriate source image for the thumbnail, given a target thumbnail size + * @param array $params + * @return array Source path and width/height of the source + */ + public function getThumbnailSource( $params ) { + if ( $this->repo + && $this->getHandler()->supportsBucketing() + && isset( $params['physicalWidth'] ) + && $bucket = $this->getThumbnailBucket( $params['physicalWidth'] ) + ) { + if ( $this->getWidth() != 0 ) { + $bucketHeight = round( $this->getHeight() * ( $bucket / $this->getWidth() ) ); + } else { + $bucketHeight = 0; + } + + // Try to avoid reading from storage if the file was generated by this script + if ( isset( $this->tmpBucketedThumbCache[$bucket] ) ) { + $tmpPath = $this->tmpBucketedThumbCache[$bucket]; + + if ( file_exists( $tmpPath ) ) { + return array( + 'path' => $tmpPath, + 'width' => $bucket, + 'height' => $bucketHeight + ); } - // Give extensions a chance to do something with this thumbnail... - wfRunHooks( 'FileTransformed', array( $this, $thumb, $tmpThumbPath, $thumbPath ) ); } - // Purge. Useful in the event of Core -> Squid connection failure or squid - // purge collisions from elsewhere during failure. Don't keep triggering for - // "thumbs" which have the main image URL though (bug 13776) - if ( $wgUseSquid ) { - if ( !$thumb || $thumb->isError() || $thumb->getUrl() != $this->getURL() ) { - SquidUpdate::purge( array( $thumbUrl ) ); + $bucketPath = $this->getBucketThumbPath( $bucket ); + + if ( $this->repo->fileExists( $bucketPath ) ) { + $fsFile = $this->repo->getLocalReference( $bucketPath ); + + if ( $fsFile ) { + return array( + 'path' => $fsFile->getPath(), + 'width' => $bucket, + 'height' => $bucketHeight + ); } } - } while ( false ); + } - wfProfileOut( __METHOD__ ); - return is_object( $thumb ) ? $thumb : false; + // Thumbnailing a very large file could result in network saturation if + // everyone does it at once. + if ( $this->getSize() >= 1e7 ) { // 10MB + $that = $this; + $work = new PoolCounterWorkViaCallback( 'GetLocalFileCopy', sha1( $this->getName() ), + array( + 'doWork' => function() use ( $that ) { + return $that->getLocalRefPath(); + } + ) + ); + $srcPath = $work->execute(); + } else { + $srcPath = $this->getLocalRefPath(); + } + + // Original file + return array( + 'path' => $srcPath, + 'width' => $this->getWidth(), + 'height' => $this->getHeight() + ); + } + + /** + * Returns the repo path of the thumb for a given bucket + * @param int $bucket + * @return string + */ + protected function getBucketThumbPath( $bucket ) { + $thumbName = $this->getBucketThumbName( $bucket ); + return $this->getThumbPath( $thumbName ); + } + + /** + * Returns the name of the thumb for a given bucket + * @param int $bucket + * @return string + */ + protected function getBucketThumbName( $bucket ) { + return $this->thumbName( array( 'physicalWidth' => $bucket ) ); + } + + /** + * Creates a temp FS file with the same extension and the thumbnail + * @param string $thumbPath Thumbnail path + * @return TempFSFile + */ + protected function makeTransformTmpFile( $thumbPath ) { + $thumbExt = FileBackend::extensionFromPath( $thumbPath ); + return TempFSFile::factory( 'transform_', $thumbExt ); } /** * @param string $thumbName Thumbnail name + * @param string $dispositionType Type of disposition (either "attachment" or "inline") * @return string Content-Disposition header value */ - function getThumbDisposition( $thumbName ) { + function getThumbDisposition( $thumbName, $dispositionType = 'inline' ) { $fileName = $this->name; // file name to suggest $thumbExt = FileBackend::extensionFromPath( $thumbName ); if ( $thumbExt != '' && $thumbExt !== $this->getExtension() ) { $fileName .= ".$thumbExt"; } - return FileBackend::makeContentDisposition( 'inline', $fileName ); + + return FileBackend::makeContentDisposition( $dispositionType, $fileName ); } /** * Hook into transform() to allow migration of thumbnail files * STUB * Overridden by LocalFile + * @param string $thumbName */ - function migrateThumbFile( $thumbName ) {} + function migrateThumbFile( $thumbName ) { + } /** * Get a MediaHandler instance for this file * - * @return MediaHandler|boolean Registered MediaHandler for file's mime type or false if none found + * @return MediaHandler|bool Registered MediaHandler for file's MIME type + * or false if none found */ function getHandler() { if ( !isset( $this->handler ) ) { $this->handler = MediaHandler::getHandler( $this->getMimeType() ); } + return $this->handler; } @@ -1016,23 +1328,26 @@ abstract class File { * @return ThumbnailImage */ function iconThumb() { - global $wgStylePath, $wgStyleDirectory; + global $wgResourceBasePath, $IP; + $assetsPath = "$wgResourceBasePath/resources/assets/file-type-icons/"; + $assetsDirectory = "$IP/resources/assets/file-type-icons/"; $try = array( 'fileicon-' . $this->getExtension() . '.png', 'fileicon.png' ); foreach ( $try as $icon ) { - $path = '/common/images/icons/' . $icon; - $filepath = $wgStyleDirectory . $path; - if ( file_exists( $filepath ) ) { // always FS + if ( file_exists( $assetsDirectory . $icon ) ) { // always FS $params = array( 'width' => 120, 'height' => 120 ); - return new ThumbnailImage( $this, $wgStylePath . $path, false, $params ); + + return new ThumbnailImage( $this, $assetsPath . $icon, false, $params ); } } + return null; } /** * Get last thumbnailing error. * Largely obsolete. + * @return string */ function getLastError() { return $this->lastError; @@ -1053,9 +1368,10 @@ abstract class File { * STUB * Overridden by LocalFile * @param array $options Options, which include: - * 'forThumbRefresh' : The purging is only to refresh thumbnails + * 'forThumbRefresh' : The purging is only to refresh thumbnails */ - function purgeCache( $options = array() ) {} + function purgeCache( $options = array() ) { + } /** * Purge the file description page, but don't go after @@ -1091,9 +1407,9 @@ abstract class File { * Return a fragment of the history of file. * * STUB - * @param $limit integer Limit of rows to return - * @param string $start timestamp Only revisions older than $start will be returned - * @param string $end timestamp Only revisions newer than $end will be returned + * @param int $limit Limit of rows to return + * @param string $start Only revisions older than $start will be returned + * @param string $end Only revisions newer than $end will be returned * @param bool $inc Include the endpoints of the time range * * @return array @@ -1121,7 +1437,8 @@ abstract class File { * STUB * Overridden in LocalFile. */ - public function resetHistory() {} + public function resetHistory() { + } /** * Get the filename hash component of the directory including trailing slash, @@ -1135,6 +1452,7 @@ abstract class File { $this->assertRepoDefined(); $this->hashPath = $this->repo->getHashPath( $this->getName() ); } + return $this->hashPath; } @@ -1151,7 +1469,7 @@ abstract class File { /** * Get the path of an archived file relative to the public zone root * - * @param bool|string $suffix if not false, the name of an archived thumbnail file + * @param bool|string $suffix If not false, the name of an archived thumbnail file * * @return string */ @@ -1162,6 +1480,7 @@ abstract class File { } else { $path .= $suffix; } + return $path; } @@ -1169,8 +1488,7 @@ abstract class File { * Get the path, relative to the thumbnail zone root, of the * thumbnail directory or a particular file if $suffix is specified * - * @param bool|string $suffix if not false, the name of a thumbnail file - * + * @param bool|string $suffix If not false, the name of a thumbnail file * @return string */ function getThumbRel( $suffix = false ) { @@ -1178,6 +1496,7 @@ abstract class File { if ( $suffix !== false ) { $path .= '/' . $suffix; } + return $path; } @@ -1195,9 +1514,8 @@ abstract class File { * Get the path, relative to the thumbnail zone root, for an archived file's thumbs directory * or a specific thumb if the $suffix is given. * - * @param string $archiveName the timestamped name of an archived image - * @param bool|string $suffix if not false, the name of a thumbnail file - * + * @param string $archiveName The timestamped name of an archived image + * @param bool|string $suffix If not false, the name of a thumbnail file * @return string */ function getArchiveThumbRel( $archiveName, $suffix = false ) { @@ -1207,64 +1525,64 @@ abstract class File { } else { $path .= $suffix; } + return $path; } /** * Get the path of the archived file. * - * @param bool|string $suffix if not false, the name of an archived file. - * + * @param bool|string $suffix If not false, the name of an archived file. * @return string */ function getArchivePath( $suffix = false ) { $this->assertRepoDefined(); + return $this->repo->getZonePath( 'public' ) . '/' . $this->getArchiveRel( $suffix ); } /** * Get the path of an archived file's thumbs, or a particular thumb if $suffix is specified * - * @param string $archiveName the timestamped name of an archived image - * @param bool|string $suffix if not false, the name of a thumbnail file - * + * @param string $archiveName The timestamped name of an archived image + * @param bool|string $suffix If not false, the name of a thumbnail file * @return string */ function getArchiveThumbPath( $archiveName, $suffix = false ) { $this->assertRepoDefined(); + return $this->repo->getZonePath( 'thumb' ) . '/' . - $this->getArchiveThumbRel( $archiveName, $suffix ); + $this->getArchiveThumbRel( $archiveName, $suffix ); } /** * Get the path of the thumbnail directory, or a particular file if $suffix is specified * - * @param bool|string $suffix if not false, the name of a thumbnail file - * + * @param bool|string $suffix If not false, the name of a thumbnail file * @return string */ function getThumbPath( $suffix = false ) { $this->assertRepoDefined(); + return $this->repo->getZonePath( 'thumb' ) . '/' . $this->getThumbRel( $suffix ); } /** * Get the path of the transcoded directory, or a particular file if $suffix is specified * - * @param bool|string $suffix if not false, the name of a media file - * + * @param bool|string $suffix If not false, the name of a media file * @return string */ function getTranscodedPath( $suffix = false ) { $this->assertRepoDefined(); + return $this->repo->getZonePath( 'transcoded' ) . '/' . $this->getThumbRel( $suffix ); } /** * Get the URL of the archive directory, or a particular file if $suffix is specified * - * @param bool|string $suffix if not false, the name of an archived file - * + * @param bool|string $suffix If not false, the name of an archived file * @return string */ function getArchiveUrl( $suffix = false ) { @@ -1276,15 +1594,15 @@ abstract class File { } else { $path .= rawurlencode( $suffix ); } + return $path; } /** * Get the URL of the archived file's thumbs, or a particular thumb if $suffix is specified * - * @param string $archiveName the timestamped name of an archived image - * @param bool|string $suffix if not false, the name of a thumbnail file - * + * @param string $archiveName The timestamped name of an archived image + * @param bool|string $suffix If not false, the name of a thumbnail file * @return string */ function getArchiveThumbUrl( $archiveName, $suffix = false ) { @@ -1297,16 +1615,16 @@ abstract class File { } else { $path .= rawurlencode( $suffix ); } + return $path; } /** * Get the URL of the zone directory, or a particular file if $suffix is specified * - * @param string $zone name of requested zone - * @param bool|string $suffix if not false, the name of a file in zone - * - * @return string path + * @param string $zone Name of requested zone + * @param bool|string $suffix If not false, the name of a file in zone + * @return string Path */ function getZoneUrl( $zone, $suffix = false ) { $this->assertRepoDefined(); @@ -1315,15 +1633,15 @@ abstract class File { if ( $suffix !== false ) { $path .= '/' . rawurlencode( $suffix ); } + return $path; } /** * Get the URL of the thumbnail directory, or a particular file if $suffix is specified * - * @param bool|string $suffix if not false, the name of a thumbnail file - * - * @return string path + * @param bool|string $suffix If not false, the name of a thumbnail file + * @return string Path */ function getThumbUrl( $suffix = false ) { return $this->getZoneUrl( 'thumb', $suffix ); @@ -1332,9 +1650,8 @@ abstract class File { /** * Get the URL of the transcoded directory, or a particular file if $suffix is specified * - * @param bool|string $suffix if not false, the name of a media file - * - * @return string path + * @param bool|string $suffix If not false, the name of a media file + * @return string Path */ function getTranscodedUrl( $suffix = false ) { return $this->getZoneUrl( 'transcoded', $suffix ); @@ -1343,8 +1660,7 @@ abstract class File { /** * Get the public zone virtual URL for a current version source file * - * @param bool|string $suffix if not false, the name of a thumbnail file - * + * @param bool|string $suffix If not false, the name of a thumbnail file * @return string */ function getVirtualUrl( $suffix = false ) { @@ -1353,14 +1669,14 @@ abstract class File { if ( $suffix !== false ) { $path .= '/' . rawurlencode( $suffix ); } + return $path; } /** * Get the public zone virtual URL for an archived version source file * - * @param bool|string $suffix if not false, the name of a thumbnail file - * + * @param bool|string $suffix If not false, the name of a thumbnail file * @return string */ function getArchiveVirtualUrl( $suffix = false ) { @@ -1371,14 +1687,14 @@ abstract class File { } else { $path .= rawurlencode( $suffix ); } + return $path; } /** * Get the virtual URL for a thumbnail file or directory * - * @param bool|string $suffix if not false, the name of a thumbnail file - * + * @param bool|string $suffix If not false, the name of a thumbnail file * @return string */ function getThumbVirtualUrl( $suffix = false ) { @@ -1387,6 +1703,7 @@ abstract class File { if ( $suffix !== false ) { $path .= '/' . rawurlencode( $suffix ); } + return $path; } @@ -1395,6 +1712,7 @@ abstract class File { */ function isHashed() { $this->assertRepoDefined(); + return (bool)$this->repo->getHashLevels(); } @@ -1409,18 +1727,20 @@ abstract class File { * Record a file upload in the upload log and the image table * STUB * Overridden by LocalFile - * @param $oldver - * @param $desc - * @param $license string - * @param $copyStatus string - * @param $source string - * @param $watch bool - * @param $timestamp string|bool - * @param $user User object or null to use $wgUser + * @param string $oldver + * @param string $desc + * @param string $license + * @param string $copyStatus + * @param string $source + * @param bool $watch + * @param string|bool $timestamp + * @param null|User $user User object or null to use $wgUser * @return bool * @throws MWException */ - function recordUpload( $oldver, $desc, $license = '', $copyStatus = '', $source = '', $watch = false, $timestamp = false, User $user = null ) { + function recordUpload( $oldver, $desc, $license = '', $copyStatus = '', $source = '', + $watch = false, $timestamp = false, User $user = null + ) { $this->readOnlyError(); } @@ -1435,13 +1755,12 @@ abstract class File { * Options to $options include: * - headers : name/value map of HTTP headers to use in response to GET/HEAD requests * - * @param string $srcPath local filesystem path to the source image - * @param $flags Integer: a bitwise combination of: - * File::DELETE_SOURCE Delete the source file, i.e. move - * rather than copy + * @param string $srcPath Local filesystem path to the source image + * @param int $flags A bitwise combination of: + * File::DELETE_SOURCE Delete the source file, i.e. move rather than copy * @param array $options Optional additional parameters - * @return FileRepoStatus object. On success, the value member contains the - * archive name, or an empty string if it was a new file. + * @return FileRepoStatus On success, the value member contains the + * archive name, or an empty string if it was a new file. * * STUB * Overridden by LocalFile @@ -1457,6 +1776,7 @@ abstract class File { if ( !$this->getHandler() ) { return false; } + return $this->getHandler()->formatMetadata( $this, $this->getMetadata() ); } @@ -1481,7 +1801,7 @@ abstract class File { /** * Returns the repository * - * @return FileRepo|bool + * @return FileRepo|LocalRepo|bool */ function getRepo() { return $this->repo; @@ -1501,8 +1821,7 @@ abstract class File { * Is this file a "deleted" file in a private archive? * STUB * - * @param integer $field one of DELETED_* bitfield constants - * + * @param int $field One of DELETED_* bitfield constants * @return bool */ function isDeleted( $field ) { @@ -1525,6 +1844,7 @@ abstract class File { */ function wasDeleted() { $title = $this->getTitle(); + return $title && $title->isDeletedQuick(); } @@ -1537,8 +1857,8 @@ abstract class File { * Cache purging is done; checks for validity * and logging are caller's responsibility * - * @param $target Title New file name - * @return FileRepoStatus object. + * @param Title $target New file name + * @return FileRepoStatus */ function move( $target ) { $this->readOnlyError(); @@ -1552,13 +1872,14 @@ abstract class File { * * Cache purging is done; logging is caller's responsibility. * - * @param $reason String - * @param $suppress Boolean: hide content from sysops? - * @return bool on success, false on some kind of failure + * @param string $reason + * @param bool $suppress Hide content from sysops? + * @param User|null $user + * @return bool Boolean on success, false on some kind of failure * STUB * Overridden by LocalFile */ - function delete( $reason, $suppress = false ) { + function delete( $reason, $suppress = false, $user = null ) { $this->readOnlyError(); } @@ -1568,11 +1889,11 @@ abstract class File { * * May throw database exceptions on error. * - * @param array $versions set of record ids of deleted items to restore, - * or empty to restore all revisions. - * @param bool $unsuppress remove restrictions on content upon restoration? - * @return int|bool the number of file revisions restored if successful, - * or false on failure + * @param array $versions Set of record ids of deleted items to restore, + * or empty to restore all revisions. + * @param bool $unsuppress Remove restrictions on content upon restoration? + * @return int|bool The number of file revisions restored if successful, + * or false on failure * STUB * Overridden by LocalFile */ @@ -1585,7 +1906,7 @@ abstract class File { * e.g. DJVU or PDF. Note that this may be true even if the file in * question only has a single page. * - * @return Bool + * @return bool */ function isMultipage() { return $this->getHandler() && $this->handler->isMultiPage( $this ); @@ -1605,15 +1926,16 @@ abstract class File { $this->pageCount = false; } } + return $this->pageCount; } /** * Calculate the height of a thumbnail using the source and destination width * - * @param $srcWidth - * @param $srcHeight - * @param $dstWidth + * @param int $srcWidth + * @param int $srcHeight + * @param int $dstWidth * * @return int */ @@ -1628,16 +1950,20 @@ abstract class File { /** * Get an image size array like that returned by getImageSize(), or false if it - * can't be determined. + * can't be determined. Loads the image size directly from the file ignoring caches. * - * @param string $fileName The filename - * @return Array + * @note Use getWidth()/getHeight() instead of this method unless you have a + * a good reason. This method skips all caches. + * + * @param string $filePath The path to the file (e.g. From getLocalPathRef() ) + * @return array The width, followed by height, with optionally more things after */ - function getImageSize( $fileName ) { + function getImageSize( $filePath ) { if ( !$this->getHandler() ) { return false; } - return $this->handler->getImageSize( $this, $fileName ); + + return $this->getHandler()->getImageSize( $this, $filePath ); } /** @@ -1657,7 +1983,7 @@ abstract class File { /** * Get the HTML text of the description page, if available * - * @param $lang Language Optional language to fetch description in + * @param bool|Language $lang Optional language to fetch description in * @return string */ function getDescriptionText( $lang = false ) { @@ -1672,11 +1998,16 @@ abstract class File { if ( $renderUrl ) { if ( $this->repo->descriptionCacheExpiry > 0 ) { wfDebug( "Attempting to get the description from cache..." ); - $key = $this->repo->getLocalCacheKey( 'RemoteFileDescription', 'url', $lang->getCode(), - $this->getName() ); + $key = $this->repo->getLocalCacheKey( + 'RemoteFileDescription', + 'url', + $lang->getCode(), + $this->getName() + ); $obj = $wgMemc->get( $key ); if ( $obj ) { wfDebug( "success!\n" ); + return $obj; } wfDebug( "miss\n" ); @@ -1686,6 +2017,7 @@ abstract class File { if ( $res && $this->repo->descriptionCacheExpiry > 0 ) { $wgMemc->set( $key, $res, $this->repo->descriptionCacheExpiry ); } + return $res; } else { return false; @@ -1696,12 +2028,12 @@ abstract class File { * Get description of file revision * STUB * - * @param $audience Integer: one of: - * File::FOR_PUBLIC to be displayed to all users - * File::FOR_THIS_USER to be displayed to the given user - * File::RAW get the description regardless of permissions - * @param $user User object to check for, only if FOR_THIS_USER is passed - * to the $audience parameter + * @param int $audience One of: + * File::FOR_PUBLIC to be displayed to all users + * File::FOR_THIS_USER to be displayed to the given user + * File::RAW get the description regardless of permissions + * @param User $user User object to check for, only if FOR_THIS_USER is + * passed to the $audience parameter * @return string */ function getDescription( $audience = self::FOR_PUBLIC, User $user = null ) { @@ -1715,6 +2047,7 @@ abstract class File { */ function getTimestamp() { $this->assertRepoDefined(); + return $this->repo->getFileTimestamp( $this->getPath() ); } @@ -1725,6 +2058,7 @@ abstract class File { */ function getSha1() { $this->assertRepoDefined(); + return $this->repo->getFileSha1( $this->getPath() ); } @@ -1740,6 +2074,7 @@ abstract class File { } $ext = $this->getExtension(); $dotExt = $ext === '' ? '' : ".$ext"; + return $hash . $dotExt; } @@ -1747,53 +2082,16 @@ abstract class File { * Determine if the current user is allowed to view a particular * field of this file, if it's marked as deleted. * STUB - * @param $field Integer - * @param $user User object to check, or null to use $wgUser - * @return Boolean + * @param int $field + * @param User $user User object to check, or null to use $wgUser + * @return bool */ function userCan( $field, User $user = null ) { return true; } /** - * Get an associative array containing information about a file in the local filesystem. - * - * @param string $path absolute local filesystem path - * @param $ext Mixed: the file extension, or true to extract it from the filename. - * Set it to false to ignore the extension. - * - * @return array - * @deprecated since 1.19 - */ - static function getPropsFromPath( $path, $ext = true ) { - wfDebug( __METHOD__ . ": Getting file info for $path\n" ); - wfDeprecated( __METHOD__, '1.19' ); - - $fsFile = new FSFile( $path ); - return $fsFile->getProps(); - } - - /** - * Get a SHA-1 hash of a file in the local filesystem, in base-36 lower case - * encoding, zero padded to 31 digits. - * - * 160 log 2 / log 36 = 30.95, so the 160-bit hash fills 31 digits in base 36 - * fairly neatly. - * - * @param $path string - * - * @return bool|string False on failure - * @deprecated since 1.19 - */ - static function sha1Base36( $path ) { - wfDeprecated( __METHOD__, '1.19' ); - - $fsFile = new FSFile( $path ); - return $fsFile->getSha1Base36(); - } - - /** - * @return Array HTTP header name/value map to use for HEAD/GET request responses + * @return array HTTP header name/value map to use for HEAD/GET request responses */ function getStreamHeaders() { $handler = $this->getHandler(); @@ -1841,7 +2139,7 @@ abstract class File { } /** - * @return + * @return string */ function getRedirected() { return $this->redirected; @@ -1855,13 +2153,15 @@ abstract class File { if ( !$this->redirectTitle ) { $this->redirectTitle = Title::makeTitle( NS_FILE, $this->redirected ); } + return $this->redirectTitle; } + return null; } /** - * @param $from + * @param string $from * @return void */ function redirectedFrom( $from ) { @@ -1877,7 +2177,7 @@ abstract class File { /** * Check if this file object is small and can be cached - * @return boolean + * @return bool */ public function isCacheable() { return true; @@ -1902,4 +2202,13 @@ abstract class File { throw new MWException( "A Title object is not set for this File.\n" ); } } + + /** + * True if creating thumbnails from the file is large or otherwise resource-intensive. + * @return bool + */ + public function isExpensiveToThumbnail() { + $handler = $this->getHandler(); + return $handler ? $handler->isExpensiveToThumbnail( $this ) : false; + } } |