-Security reminder: If you have PHP's register_globals option set, you should
-turn it off. Although MediaWiki will work with it enabled, it exposes your
-server to potential security vulnerabilities.
+Security reminder: If you have PHP's register_globals option set, you must
+turn it off. MediaWiki will no longer work with it enabled.
== MediaWiki 1.24 ==
production.
=== Configuration changes in 1.24 ===
+* MediaWiki will no longer run if register_globals is enabled. It has been
+ deprecated for 5 years now, and was removed in PHP 5.4. For more information
+ about why, see <https://www.mediawiki.org/wiki/register_globals>.
* The server's canonical hostname is available as $wgServerName, which is
exposed in both mw.config and ApiQuerySiteInfo.
* Introduced $wgPagePropsHaveSortkey as a backwards-compatibility switch,
SpecialPageBeforeFormDisplay.
* (bug 65781) Removed block warning on included {{Special:Contributions}}
* Removed Skin::makeGlobalVariablesScript. (deprecated since 1.19)
+* Removed MWNamespace::isMain(). (deprecated since 1.19)
+* Removed Preferences::loadOldSearchNs(). (deprecated since 1.19)
+* Removed OutputPage::getStatusMessage(). (deprecated since 1.18)
+* Removed OutputPage::isUserJsAllowed(). (deprecated since 1.18)
+* Removed Title::updateTitleProtection(). (deprecated since 1.19)
==== Renamed classes ====
* CLDRPluralRuleConverter_Expression to CLDRPluralRuleConverterExpression
300
);
+/**
+ * When defined, is an array of image widths used as buckets for thumbnail generation.
+ * The goal is to save resources by generating thumbnails based on reference buckets instead of
+ * always using the original. This will incur a speed gain but cause a quality loss.
+ *
+ * The buckets generation is chained, with each bucket generated based on the above bucket
+ * when possible. File handlers have to opt into using that feature. For now only BitmapHandler
+ * supports it.
+ */
+$wgThumbnailBuckets = null;
+
+/**
+ * When using thumbnail buckets as defined above, this sets the minimum distance with the bucket
+ * above the requested size. The distance represents how pany extra pixels of width the bucket needs
+ * in order to be used as the reference for a given thumbnail. For example, with the following buckets:
+ *
+ * $wgThumbnailBuckets = array ( 128, 256, 512 );
+ *
+ * and a distance of 50:
+ *
+ * $wgThumbnailMinimumBucketDistance = 50;
+ *
+ * If we want to render a thumbnail of width 220px, the 512px bucket will be used,
+ * because 220 + 50 = 270 and the closest bucket bigger than 270px is 512.
+ */
+$wgThumbnailMinimumBucketDistance = 0;
+
/**
* Default parameters for the "<gallery>" tag
*/
return !self::isTalk( $index );
}
- /**
- * @see self::isSubject
- * @deprecated since 1.19 Please use the more consistently named isSubject
- * @return bool
- */
- public static function isMain( $index ) {
- wfDeprecated( __METHOD__, '1.19' );
- return self::isSubject( $index );
- }
-
/**
* Is the given namespace a talk namespace?
*
);
}
- /**
- * Return whether user JavaScript is allowed for this page
- * @deprecated since 1.18 Load modules with ResourceLoader, and origin and
- * trustworthiness is identified and enforced automagically.
- * @return bool
- */
- public function isUserJsAllowed() {
- wfDeprecated( __METHOD__, '1.18' );
- return $this->getAllowedModules( ResourceLoaderModule::TYPE_SCRIPTS ) >=
- ResourceLoaderModule::ORIGIN_USER_INDIVIDUAL;
- }
-
/**
* Show what level of JavaScript / CSS untrustworthiness is allowed on this page
* @see ResourceLoaderModule::$origin
}
}
- /**
- * Get the message associated with the HTTP response code $code
- *
- * @param int $code Status code
- * @return string|null Message or null if $code is not in the list of messages
- *
- * @deprecated since 1.18 Use HttpStatus::getMessage() instead.
- */
- public static function getStatusMessage( $code ) {
- wfDeprecated( __METHOD__, '1.18' );
- return HttpStatus::getMessage( $code );
- }
-
/**
* Finally, all the text has been munged and accumulated into
* the object, let's actually output it:
return array( $result, 'mailerror' );
}
}
-
- /**
- * @deprecated since 1.19
- * @param User $user
- * @return array
- */
- public static function loadOldSearchNs( $user ) {
- wfDeprecated( __METHOD__, '1.19' );
-
- $searchableNamespaces = SearchEngine::searchableNamespaces();
- // Back compat with old format
- $arr = array();
-
- foreach ( $searchableNamespaces as $ns => $name ) {
- if ( $user->getOption( 'searchNs' . $ns ) ) {
- $arr[] = $ns;
- }
- }
-
- return $arr;
- }
}
/** Some tweaks to allow js prefs to work */
return $this->mTitleProtection;
}
- /**
- * Update the title protection status
- *
- * @deprecated since 1.19; use WikiPage::doUpdateRestrictions() instead.
- * @param string $create_perm Permission required for creation
- * @param string $reason Reason for protection
- * @param string $expiry Expiry timestamp
- * @return bool
- */
- public function updateTitleProtection( $create_perm, $reason, $expiry ) {
- wfDeprecated( __METHOD__, '1.19' );
-
- global $wgUser;
-
- $limit = array( 'create' => $create_perm );
- $expiry = array( 'create' => $expiry );
-
- $page = WikiPage::factory( $this );
- $cascade = false;
- $status = $page->doUpdateRestrictions( $limit, $expiry, $cascade, $reason, $wgUser );
-
- return $status->isOK();
- }
-
/**
* Remove any title protection due to page existing
*/
* @file
*/
-# Protect against register_globals
+# Die if register_globals is enabled (PHP <=5.3)
# This must be done before any globals are set by the code
if ( ini_get( 'register_globals' ) ) {
- if ( isset( $_REQUEST['GLOBALS'] ) || isset( $_FILES['GLOBALS'] ) ) {
- die( '<a href="http://www.hardened-php.net/globals-problem">'
- . '$GLOBALS overwrite vulnerability</a>' );
- }
-
- $verboten = array(
- 'GLOBALS',
- '_SERVER',
- 'HTTP_SERVER_VARS',
- '_GET',
- 'HTTP_GET_VARS',
- '_POST',
- 'HTTP_POST_VARS',
- '_COOKIE',
- 'HTTP_COOKIE_VARS',
- '_FILES',
- 'HTTP_POST_FILES',
- '_ENV',
- 'HTTP_ENV_VARS',
- '_REQUEST',
- '_SESSION',
- 'HTTP_SESSION_VARS'
- );
-
- foreach ( $_REQUEST as $name => $value ) {
- if ( in_array( $name, $verboten ) ) {
- header( "HTTP/1.1 500 Internal Server Error" );
- echo "register_globals security paranoia: trying to overwrite superglobals, aborting.";
- die( -1 );
- }
- unset( $GLOBALS[$name] );
- }
+ die( 'MediaWiki does not support installations where register_globals is enabled. '
+ . 'Please see <a href="https://www.mediawiki.org/wiki/register_globals">mediawiki.org</a> '
+ . 'for help on how to disable it.' );
}
# bug 15461: Make IE8 turn off content sniffing. Everybody else should ignore this
return false;
}
+ if ( $schema === false ) {
+ global $wgDBmwschema;
+ $schema = $wgDBmwschema;
+ }
+
$res = $this->query( "SELECT 1 FROM INFORMATION_SCHEMA.TABLES
WHERE TABLE_TYPE = 'BASE TABLE'
AND TABLE_SCHEMA = '$schema' AND TABLE_NAME = '$table'" );
// Used internally, we want the schema split off from the table name and returned
// as a list with 3 elements (database, schema, table)
$table = explode( '.', $table );
- if ( count( $table ) == 2 ) {
+ while ( count( $table ) < 3 ) {
array_unshift( $table, false );
}
}
/** @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.
*
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
return null;
}
$extension = $this->getExtension();
- list( $thumbExt, ) = $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 MediaTransformOutput|bool False on failure
*/
function transform( $params, $flags = 0 ) {
- global $wgUseSquid, $wgIgnoreImageErrors, $wgThumbnailEpoch;
+ global $wgThumbnailEpoch;
wfProfileIn( __METHOD__ );
do {
} 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 = 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, $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__ );
+ // Original file
+ return array(
+ 'path' => $this->getLocalRefPath(),
+ 'width' => $this->getWidth(),
+ 'height' => $this->getHeight()
+ );
+ }
- return is_object( $thumb ) ? $thumb : false;
+ /**
+ * 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
+ * @returns TempFSFile
+ */
+ protected function makeTransformTmpFile( $thumbPath ) {
+ $thumbExt = FileBackend::extensionFromPath( $thumbPath );
+ return TempFSFile::factory( 'transform_', $thumbExt );
}
/**
return false;
}
- return $this->handler->getImageSize( $this, $filePath );
+ return $this->getHandler()->getImageSize( $this, $filePath );
}
/**
$this->settings[$var] = $GLOBALS[$var];
}
- $compiledDBs = array();
+ $this->compiledDBs = array();
foreach ( self::getDBTypes() as $type ) {
$installer = $this->getDBInstaller( $type );
if ( !$installer->isCompiled() ) {
continue;
}
- $compiledDBs[] = $type;
-
- $defaults = $installer->getGlobalDefaults();
-
- foreach ( $installer->getGlobalNames() as $var ) {
- if ( isset( $defaults[$var] ) ) {
- $this->settings[$var] = $defaults[$var];
- } else {
- $this->settings[$var] = $GLOBALS[$var];
- }
- }
+ $this->compiledDBs[] = $type;
}
- $this->compiledDBs = $compiledDBs;
$this->parserTitle = Title::newFromText( 'Installer' );
$this->parserOptions = new ParserOptions; // language will be wrong :(
/**
* Environment check for register_globals.
+ * Prevent installation if enabled
*/
protected function envCheckRegisterGlobals() {
if ( wfIniGetBool( 'register_globals' ) ) {
- $this->showMessage( 'config-register-globals' );
+ $this->showMessage( 'config-register-globals-error' );
+ return false;
}
+
+ return true;
}
/**
'password' => $password,
'dbname' => false,
'flags' => 0,
+ 'schema' => $this->getVar( 'wgDBmwschema' ),
'tablePrefix' => $this->getVar( 'wgDBprefix' ) ) );
$db->prepareStatements( false );
$db->scrollableCursor( false );
return $status;
}
+ public function getGlobalDefaults() {
+ // The default $wgDBmwschema is null, which breaks Postgres and other DBMSes that require
+ // the use of a schema, so we need to set it here
+ return array(
+ 'wgDBmwschema' => 'mediawiki',
+ );
+ }
+
/**
* Try to see if the login exists
* @param string $user Username to check
* @param string $user User name
* @param string $password Password
* @param string $dbName Database name
+ * @param string $schema Database schema
* @return Status
*/
- protected function openConnectionWithParams( $user, $password, $dbName ) {
+ protected function openConnectionWithParams( $user, $password, $dbName, $schema ) {
$status = Status::newGood();
try {
- $db = new DatabasePostgres(
- $this->getVar( 'wgDBserver' ),
- $user,
- $password,
- $dbName
- );
+ $db = Database::factory( 'postgres', array(
+ 'host' => $this->getVar( 'wgDBserver' ),
+ 'user' => $user,
+ 'password' => $password,
+ 'dbname' => $dbName,
+ 'schema' => $schema ) );
$status->value = $db;
} catch ( DBConnectionError $e ) {
$status->fatal( 'config-connection-error', $e->getMessage() );
return $this->openConnectionWithParams(
$this->getVar( '_InstallUser' ),
$this->getVar( '_InstallPassword' ),
- $this->getVar( 'wgDBname' ) );
+ $this->getVar( 'wgDBname' ),
+ $this->getVar( 'wgDBmwschema' ) );
case 'create-tables':
$status = $this->openPgConnection( 'create-schema' );
if ( $status->isOK() ) {
$status = Status::newGood();
foreach ( $dbs as $db ) {
try {
- $conn = new DatabasePostgres(
- $this->getVar( 'wgDBserver' ),
+ $conn = $this->openConnectionWithParams(
$user,
$password,
- $db );
+ $db,
+ $this->getVar( 'wgDBmwschema' ) );
} catch ( DBConnectionError $error ) {
$conn = false;
$status->fatal( 'config-pg-test-error', $db,
return $status;
}
+ public function getGlobalDefaults() {
+ // The default $wgDBmwschema is null, which breaks Postgres and other DBMSes that require
+ // the use of a schema, so we need to set it here
+ return array(
+ 'wgDBmwschema' => 'mediawiki',
+ );
+ }
+
public function setupPLpgSQL() {
// Connect as the install user, since it owns the database and so is
// the user that needs to run "CREATE LANGAUGE"
"config-no-db": "Could not find a suitable database driver! You need to install a database driver for PHP.\nThe following database types are supported: $1.\n\nIf you compiled PHP yourself, reconfigure it with a database client enabled, for example, using <code>./configure --with-mysqli</code>.\nIf you installed PHP from a Debian or Ubuntu package, then you also need to install, for example, the <code>php5-mysql</code> package.",
"config-outdated-sqlite": "<strong>Warning:</strong> you have SQLite $1, which is lower than minimum required version $2. SQLite will be unavailable.",
"config-no-fts3": "<strong>Warning:</strong> SQLite is compiled without the [//sqlite.org/fts3.html FTS3 module], search features will be unavailable on this backend.",
- "config-register-globals": "<strong>Warning: PHP's <code>[http://php.net/register_globals register_globals]</code> option is enabled.\nDisable it if you can.</strong>\nMediaWiki will work, but your server is exposed to potential security vulnerabilities.",
+ "config-register-globals-error": "<strong>Error: PHP's <code>[http://php.net/register_globals register_globals]</code> option is enabled.\nIt must be disabled to continue with installation.</strong>\nSee [https://www.mediawiki.org/wiki/register_globals https://www.mediawiki.org/wiki/register_globals] for help on how to do so.",
"config-magic-quotes-runtime": "<strong>Fatal: [http://www.php.net/manual/en/ref.info.php#ini.magic-quotes-runtime magic_quotes_runtime] is active!'</strong>\nThis option corrupts data input unpredictably.\nYou cannot install or use MediaWiki unless this option is disabled.",
"config-magic-quotes-sybase": "<strong>Fatal: [http://www.php.net/manual/en/ref.info.php#ini.magic-quotes-sybase magic_quotes_sybase] is active!</strong>\nThis option corrupts data input unpredictably.\nYou cannot install or use MediaWiki unless this option is disabled.",
"config-mbstring": "<strong>Fatal: [http://www.php.net/manual/en/ref.mbstring.php#mbstring.overload mbstring.func_overload] is active!</strong>\nThis option causes errors and may corrupt data unpredictably.\nYou cannot install or use MediaWiki unless this option is disabled.",
"config-no-db": "{{doc-important|Do not translate \"<code>./configure --with-mysqli</code>\" and \"<code>php5-mysql</code>\".}}\nParameters:\n* $1 is comma separated list of database types supported by MediaWiki.",
"config-outdated-sqlite": "Used as warning. Parameters:\n* $1 - the version of SQLite that has been installed\n* $2 - minimum version",
"config-no-fts3": "A \"[[:wikipedia:Front and back ends|backend]]\" is a system or component that ordinary users don't interact with directly and don't need to know about, and that is responsible for a distinct task or service - for example, a storage back-end is a generic system for storing data which other applications can use. Possible alternatives for back-end are \"system\" or \"service\", or (depending on context and language) even leave it untranslated.",
- "config-register-globals": "Status message in the MediaWiki installer environment checks.",
+ "config-register-globals-error": "Error message in the MediaWiki installer environment checks.",
"config-magic-quotes-runtime": "{{Related|Config-fatal}}",
"config-magic-quotes-sybase": "{{Related|Config-fatal}}",
"config-mbstring": "{{Related|Config-fatal}}",
/**
* Get the message associated with HTTP response code $code
*
- * Replace OutputPage::getStatusMessage( $code )
- *
* @param $code Integer: status code
* @return String or null: message or null if $code is not in the list of
* messages
if ( !$this->normaliseParams( $image, $params ) ) {
return new TransformParameterError( $params );
}
+
# Create a parameter array to pass to the scaler
$scalerParams = array(
# The size to which the image will be resized
}
# Transform functions and binaries need a FS source file
- $scalerParams['srcPath'] = $image->getLocalRefPath();
+ $thumbnailSource = $image->getThumbnailSource( $params );
+
+ $scalerParams['srcPath'] = $thumbnailSource['path'];
+ $scalerParams['srcWidth'] = $thumbnailSource['width'];
+ $scalerParams['srcHeight'] = $thumbnailSource['height'];
+
if ( $scalerParams['srcPath'] === false ) { // Failed to get local copy
wfDebugLog( 'thumbnail',
sprintf( 'Thumbnail failed on %s: could not get local copy of "%s"',
}
/**
- * Rerurns whether the file needs to be rendered. Returns true if the
+ * Returns whether the file needs to be rendered. Returns true if the
* file requires rotation and we are able to rotate it.
*
* @param File $file
->numParams( $file->getWidth(), $file->getHeight() )->text();
}
}
+
+ public function sanitizeParamsForBucketing( $params ) {
+ $params = parent::sanitizeParamsForBucketing( $params );
+
+ // We unset the height parameters in order to let normaliseParams recalculate them
+ // Otherwise there might be a height discrepancy
+ if ( isset( $params['height'] ) ) {
+ unset( $params['height'] );
+ }
+
+ if ( isset( $params['physicalHeight'] ) ) {
+ unset( $params['physicalHeight'] );
+ }
+
+ return $params;
+ }
}
return parent::rotate( $file, $params );
}
}
+
+ public function supportsBucketing() {
+ return true;
+ }
+
+ public function sanitizeParamsForBucketing( $params ) {
+ $params = parent::sanitizeParamsForBucketing( $params );
+
+ // Quality needs to be cleared for bucketing. Buckets need to be default quality
+ if ( isset( $params['quality'] ) ) {
+ unset( $params['quality'] );
+ }
+
+ return $params;
+ }
}
public function isExpensiveToThumbnail( $file ) {
return false;
}
+
+ /**
+ * Returns whether or not this handler supports the chained generation of thumbnails according
+ * to buckets
+ * @return boolean
+ * @since 1.24
+ */
+ public function supportsBucketing() {
+ return false;
+ }
+
+ /**
+ * Returns a normalised params array for which parameters have been cleaned up for bucketing
+ * purposes
+ * @param array $params
+ * @return array
+ */
+ public function sanitizeParamsForBucketing( $params ) {
+ return $params;
+ }
}
return $wgLang->commaList( $info );
}
+
+ public function supportsBucketing() {
+ return true;
+ }
}
}
$rows[$subject] .=
- Xml::openElement(
- 'td', array( 'style' => 'white-space: nowrap' )
- ) .
+ Xml::openElement( 'td' ) .
Xml::checkLabel(
$name,
"ns{$namespace}",
true
);
$this->addOption( 'confpath', "Path to write LocalSettings.php to ($IP)", false, true );
+ $this->addOption( 'dbschema', 'The schema for the MediaWiki DB in PostgreSQL/Microsoft SQL Server (mediawiki)', false, true );
/*
- $this->addOption( 'dbschema', 'The schema for the MediaWiki DB in pg (mediawiki)', false, true );
$this->addOption( 'namespace', 'The project namespace (same as the "name" argument)', false, true );
*/
$this->addOption( 'env-checks', "Run environment checks only, don't change anything" );
* Show gallery captions when focused. Copied directly from jquery.mw-jump.js.
* Also Dynamically resize images to justify them.
*/
-( function ( $, mw ) {
+( function ( $ ) {
$( function () {
var isTouchScreen,
gettingFocus,
$imageElm,
imageElm,
$caption,
- hookInfo,
i,
j,
avgZoom,
}
if ( curRow[j].aspect === 0 || !isFinite( curRow[j].aspect ) ) {
- mw.log( 'Skipping item ' + j + ' due to aspect: ' + curRow[j].aspect );
// One of the dimensions are 0. Probably should
// not try to resize.
combinedPadding += curRow[j].width;
// Also on the off chance there is a bug in this
// code, would prevent accidentally expanding to
// be 10 billion pixels wide.
- mw.log( 'mw.page.gallery: Cannot fit row, aspect is ' + preferredHeight / curRowHeight );
if ( i === rows.length - 1 ) {
// If its the last row, and we can't fit it,
// don't make the entire row huge.
}
if ( !isFinite( preferredHeight ) ) {
// This *definitely* should not happen.
- mw.log( 'mw.page.gallery: Trying to resize row ' + i + ' to ' + preferredHeight + '?!' );
// Skip this row.
continue;
}
if ( preferredHeight < 5 ) {
// Well something clearly went wrong...
- mw.log( {
- maxWidth: maxWidth,
- combinedPadding: combinedPadding,
- combinedAspect: combinedAspect,
- wantedWidth: wantedWidth
- } );
- mw.log( 'mw.page.gallery: [BUG!] Fitting row ' + i + ' to too small a size: ' + preferredHeight );
// Skip this row.
continue;
}
if ( newWidth < 60 || !isFinite( newWidth ) ) {
// Making something skinnier than this will mess up captions,
- mw.log( 'mw.page.gallery: Tried to make image ' + newWidth + 'px wide but too narrow.' );
if ( newWidth < 1 || !isFinite( newWidth ) ) {
$innerDiv.height( preferredHeight );
// Don't even try and touch the image size if it could mean
$caption.width( curRow[j].captionWidth + ( newWidth - curRow[j].imgWidth ) );
}
- hookInfo = {
- fullWidth: newWidth + padding,
- imgWidth: newWidth,
- imgHeight: preferredHeight,
- $innerDiv: $innerDiv,
- $imageDiv: $imageDiv,
- $outerDiv: $outerDiv,
- // Whether the hook took action
- resolved: false
- };
-
- /**
- * Gallery resize.
- *
- * If your handler resizes an image, it should also set the resolved
- * property to true. Additionally, because this module only exposes this
- * logic temporarily, you should load your module in position top to
- * ensure it is registered before this runs (FIXME: Don't use mw.hook)
- *
- * See TimedMediaHandler for an example.
- *
- * @event mediawiki_page_gallery_resize
- * @member mw.hook
- * @param {Object} hookInfo
- */
- mw.hook( 'mediawiki.page.gallery.resize' ).fire( hookInfo );
-
- if ( !hookInfo.resolved ) {
- if ( imageElm ) {
- // We don't always have an img, e.g. in the case of an invalid file.
- imageElm.width = newWidth;
- imageElm.height = preferredHeight;
- } else {
- // Not a file box.
- $imageDiv.height( preferredHeight );
- }
+ if ( imageElm ) {
+ // We don't always have an img, e.g. in the case of an invalid file.
+ imageElm.width = newWidth;
+ imageElm.height = preferredHeight;
+ } else {
+ // Not a file box.
+ $imageDiv.height( preferredHeight );
}
}
}
}() );
} );
} );
-}( jQuery, mediaWiki ) );
+}( jQuery ) );
}
fieldset#mw-searchoptions table td {
padding-right: 1em;
+ white-space: nowrap;
}
fieldset#mw-searchoptions div.divider {
clear: both;
--- /dev/null
+<?php
+
+class FileRepoFileTest extends MediaWikiMediaTestCase {
+ /**
+ * @dataProvider getThumbnailBucketProvider
+ * @covers File::getThumbnailBucket
+ */
+ public function testGetThumbnailBucket( $data ) {
+ $this->setMwGlobals( 'wgThumbnailBuckets', $data['buckets'] );
+ $this->setMwGlobals( 'wgThumbnailMinimumBucketDistance', $data['minimumBucketDistance'] );
+
+ $fileMock = $this->getMockBuilder( 'File' )
+ ->setConstructorArgs( array( 'fileMock', false ) )
+ ->setMethods( array( 'getWidth' ) )
+ ->getMockForAbstractClass();
+
+ $fileMock->expects( $this->any() )->method( 'getWidth' )->will(
+ $this->returnValue( $data['width'] ) );
+
+ $this->assertEquals(
+ $data['expectedBucket'],
+ $fileMock->getThumbnailBucket( $data['requestedWidth'] ),
+ $data['message'] );
+ }
+
+ public function getThumbnailBucketProvider() {
+ $defaultBuckets = array( 256, 512, 1024, 2048, 4096 );
+
+ return array(
+ array( array(
+ 'buckets' => $defaultBuckets,
+ 'minimumBucketDistance' => 0,
+ 'width' => 3000,
+ 'requestedWidth' => 120,
+ 'expectedBucket' => 256,
+ 'message' => 'Picking bucket bigger than requested size'
+ ) ),
+ array( array(
+ 'buckets' => $defaultBuckets,
+ 'minimumBucketDistance' => 0,
+ 'width' => 3000,
+ 'requestedWidth' => 300,
+ 'expectedBucket' => 512,
+ 'message' => 'Picking bucket bigger than requested size'
+ ) ),
+ array( array(
+ 'buckets' => $defaultBuckets,
+ 'minimumBucketDistance' => 0,
+ 'width' => 3000,
+ 'requestedWidth' => 1024,
+ 'expectedBucket' => 2048,
+ 'message' => 'Picking bucket bigger than requested size'
+ ) ),
+ array( array(
+ 'buckets' => $defaultBuckets,
+ 'minimumBucketDistance' => 0,
+ 'width' => 3000,
+ 'requestedWidth' => 2048,
+ 'expectedBucket' => false,
+ 'message' => 'Picking no bucket because none is bigger than the requested size'
+ ) ),
+ array( array(
+ 'buckets' => $defaultBuckets,
+ 'minimumBucketDistance' => 0,
+ 'width' => 3000,
+ 'requestedWidth' => 3500,
+ 'expectedBucket' => false,
+ 'message' => 'Picking no bucket because requested size is bigger than original'
+ ) ),
+ array( array(
+ 'buckets' => array( 1024 ),
+ 'minimumBucketDistance' => 0,
+ 'width' => 3000,
+ 'requestedWidth' => 1024,
+ 'expectedBucket' => false,
+ 'message' => 'Picking no bucket because requested size equals biggest bucket'
+ ) ),
+ array( array(
+ 'buckets' => null,
+ 'minimumBucketDistance' => 0,
+ 'width' => 3000,
+ 'requestedWidth' => 1024,
+ 'expectedBucket' => false,
+ 'message' => 'Picking no bucket because no buckets have been specified'
+ ) ),
+ array( array(
+ 'buckets' => array( 256, 512 ),
+ 'minimumBucketDistance' => 10,
+ 'width' => 3000,
+ 'requestedWidth' => 245,
+ 'expectedBucket' => 256,
+ 'message' => 'Requested width is distant enough from next bucket for it to be picked'
+ ) ),
+ array( array(
+ 'buckets' => array( 256, 512 ),
+ 'minimumBucketDistance' => 10,
+ 'width' => 3000,
+ 'requestedWidth' => 246,
+ 'expectedBucket' => 512,
+ 'message' => 'Requested width is too close to next bucket, picking next one'
+ ) ),
+ );
+ }
+
+ /**
+ * @dataProvider getThumbnailSourceProvider
+ * @covers File::getThumbnailSource
+ */
+ public function testGetThumbnailSource( $data ) {
+ $backendMock = $this->getMockBuilder( 'FSFileBackend' )
+ ->setConstructorArgs( array( array( 'name' => 'backendMock', 'wikiId' => wfWikiId() ) ) )
+ ->getMock();
+
+ $repoMock = $this->getMockBuilder( 'FileRepo' )
+ ->setConstructorArgs( array( array( 'name' => 'repoMock', 'backend' => $backendMock ) ) )
+ ->setMethods( array( 'fileExists', 'getLocalReference' ) )
+ ->getMock();
+
+ $fsFile = new FSFile( 'fsFilePath' );
+
+ $repoMock->expects( $this->any() )->method( 'fileExists' )->will(
+ $this->returnValue( true ) );
+
+ $repoMock->expects( $this->any() )->method( 'getLocalReference' )->will(
+ $this->returnValue( $fsFile ) );
+
+ $handlerMock = $this->getMock( 'BitmapHandler', array( 'supportsBucketing' ) );
+ $handlerMock->expects( $this->any() )->method( 'supportsBucketing' )->will(
+ $this->returnValue( $data['supportsBucketing'] ) );
+
+ $fileMock = $this->getMockBuilder( 'File' )
+ ->setConstructorArgs( array( 'fileMock', $repoMock ) )
+ ->setMethods( array( 'getThumbnailBucket', 'getLocalRefPath', 'getHandler' ) )
+ ->getMockForAbstractClass();
+
+ $fileMock->expects( $this->any() )->method( 'getThumbnailBucket' )->will(
+ $this->returnValue( $data['thumbnailBucket'] ) );
+
+ $fileMock->expects( $this->any() )->method( 'getLocalRefPath' )->will(
+ $this->returnValue( 'localRefPath' ) );
+
+ $fileMock->expects( $this->any() )->method( 'getHandler' )->will(
+ $this->returnValue( $handlerMock ) );
+
+ $reflection = new ReflectionClass( $fileMock );
+ $reflection_property = $reflection->getProperty( 'handler' );
+ $reflection_property->setAccessible( true );
+ $reflection_property->setValue( $fileMock, $handlerMock );
+
+ if ( !is_null( $data['tmpBucketedThumbCache'] ) ) {
+ $reflection_property = $reflection->getProperty( 'tmpBucketedThumbCache' );
+ $reflection_property->setAccessible( true );
+ $reflection_property->setValue( $fileMock, $data['tmpBucketedThumbCache'] );
+ }
+
+ $result = $fileMock->getThumbnailSource(
+ array( 'physicalWidth' => $data['physicalWidth'] ) );
+
+ $this->assertEquals( $data['expectedPath'], $result['path'], $data['message'] );
+ }
+
+ public function getThumbnailSourceProvider() {
+ return array(
+ array( array(
+ 'supportsBucketing' => true,
+ 'tmpBucketedThumbCache' => null,
+ 'thumbnailBucket' => 1024,
+ 'physicalWidth' => 2048,
+ 'expectedPath' => 'fsFilePath',
+ 'message' => 'Path downloaded from storage'
+ ) ),
+ array( array(
+ 'supportsBucketing' => true,
+ 'tmpBucketedThumbCache' => array( 1024 => '/tmp/shouldnotexist' + rand() ),
+ 'thumbnailBucket' => 1024,
+ 'physicalWidth' => 2048,
+ 'expectedPath' => 'fsFilePath',
+ 'message' => 'Path downloaded from storage because temp file is missing'
+ ) ),
+ array( array(
+ 'supportsBucketing' => true,
+ 'tmpBucketedThumbCache' => array( 1024 => '/tmp' ),
+ 'thumbnailBucket' => 1024,
+ 'physicalWidth' => 2048,
+ 'expectedPath' => '/tmp',
+ 'message' => 'Temporary path because temp file was found'
+ ) ),
+ array( array(
+ 'supportsBucketing' => false,
+ 'tmpBucketedThumbCache' => null,
+ 'thumbnailBucket' => 1024,
+ 'physicalWidth' => 2048,
+ 'expectedPath' => 'localRefPath',
+ 'message' => 'Original file path because bucketing is unsupported by handler'
+ ) ),
+ array( array(
+ 'supportsBucketing' => true,
+ 'tmpBucketedThumbCache' => null,
+ 'thumbnailBucket' => false,
+ 'physicalWidth' => 2048,
+ 'expectedPath' => 'localRefPath',
+ 'message' => 'Original file path because no width provided'
+ ) ),
+ );
+ }
+
+ /**
+ * @dataProvider generateBucketsIfNeededProvider
+ * @covers File::generateBucketsIfNeeded
+ */
+ public function testGenerateBucketsIfNeeded( $data ) {
+ $this->setMwGlobals( 'wgThumbnailBuckets', $data['buckets'] );
+
+ $backendMock = $this->getMockBuilder( 'FSFileBackend' )
+ ->setConstructorArgs( array( array( 'name' => 'backendMock', 'wikiId' => wfWikiId() ) ) )
+ ->getMock();
+
+ $repoMock = $this->getMockBuilder( 'FileRepo' )
+ ->setConstructorArgs( array( array( 'name' => 'repoMock', 'backend' => $backendMock ) ) )
+ ->setMethods( array( 'fileExists', 'getLocalReference' ) )
+ ->getMock();
+
+ $fileMock = $this->getMockBuilder( 'File' )
+ ->setConstructorArgs( array( 'fileMock', $repoMock ) )
+ ->setMethods( array( 'getWidth', 'getBucketThumbPath', 'makeTransformTmpFile', 'generateAndSaveThumb', 'getHandler' ) )
+ ->getMockForAbstractClass();
+
+ $handlerMock = $this->getMock( 'JpegHandler', array( 'supportsBucketing' ) );
+ $handlerMock->expects( $this->any() )->method( 'supportsBucketing' )->will(
+ $this->returnValue( true ) );
+
+ $fileMock->expects( $this->any() )->method( 'getHandler' )->will(
+ $this->returnValue( $handlerMock ) );
+
+ $reflectionMethod = new ReflectionMethod( 'File', 'generateBucketsIfNeeded' );
+ $reflectionMethod->setAccessible( true );
+
+ $fileMock->expects( $this->any() )
+ ->method( 'getWidth' )
+ ->will( $this->returnValue( $data['width'] ) );
+
+ $fileMock->expects( $data['expectedGetBucketThumbPathCalls'] )
+ ->method( 'getBucketThumbPath' );
+
+ $repoMock->expects( $data['expectedFileExistsCalls'] )
+ ->method( 'fileExists' )
+ ->will( $this->returnValue( $data['fileExistsReturn'] ) );
+
+ $fileMock->expects( $data['expectedMakeTransformTmpFile'] )
+ ->method( 'makeTransformTmpFile' )
+ ->will( $this->returnValue( $data['makeTransformTmpFileReturn'] ) );
+
+ $fileMock->expects( $data['expectedGenerateAndSaveThumb'] )
+ ->method( 'generateAndSaveThumb' )
+ ->will( $this->returnValue( $data['generateAndSaveThumbReturn'] ) );
+
+ $this->assertEquals( $data['expectedResult'],
+ $reflectionMethod->invoke(
+ $fileMock,
+ array(
+ 'physicalWidth' => $data['physicalWidth'],
+ 'physicalHeight' => $data['physicalHeight'] )
+ ),
+ $data['message'] );
+ }
+
+ public function generateBucketsIfNeededProvider() {
+ $defaultBuckets = array( 256, 512, 1024, 2048, 4096 );
+
+ return array(
+ array( array(
+ 'buckets' => $defaultBuckets,
+ 'width' => 256,
+ 'physicalWidth' => 256,
+ 'physicalHeight' => 100,
+ 'expectedGetBucketThumbPathCalls' => $this->never(),
+ 'expectedFileExistsCalls' => $this->never(),
+ 'fileExistsReturn' => null,
+ 'expectedMakeTransformTmpFile' => $this->never(),
+ 'makeTransformTmpFileReturn' => false,
+ 'expectedGenerateAndSaveThumb' => $this->never(),
+ 'generateAndSaveThumbReturn' => false,
+ 'expectedResult' => false,
+ 'message' => 'No bucket found, nothing to generate'
+ ) ),
+ array( array(
+ 'buckets' => $defaultBuckets,
+ 'width' => 5000,
+ 'physicalWidth' => 300,
+ 'physicalHeight' => 200,
+ 'expectedGetBucketThumbPathCalls' => $this->once(),
+ 'expectedFileExistsCalls' => $this->once(),
+ 'fileExistsReturn' => true,
+ 'expectedMakeTransformTmpFile' => $this->never(),
+ 'makeTransformTmpFileReturn' => false,
+ 'expectedGenerateAndSaveThumb' => $this->never(),
+ 'generateAndSaveThumbReturn' => false,
+ 'expectedResult' => false,
+ 'message' => 'File already exists, no reason to generate buckets'
+ ) ),
+ array( array(
+ 'buckets' => $defaultBuckets,
+ 'width' => 5000,
+ 'physicalWidth' => 300,
+ 'physicalHeight' => 200,
+ 'expectedGetBucketThumbPathCalls' => $this->once(),
+ 'expectedFileExistsCalls' => $this->once(),
+ 'fileExistsReturn' => false,
+ 'expectedMakeTransformTmpFile' => $this->once(),
+ 'makeTransformTmpFileReturn' => false,
+ 'expectedGenerateAndSaveThumb' => $this->never(),
+ 'generateAndSaveThumbReturn' => false,
+ 'expectedResult' => false,
+ 'message' => 'Cannot generate temp file for bucket'
+ ) ),
+ array( array(
+ 'buckets' => $defaultBuckets,
+ 'width' => 5000,
+ 'physicalWidth' => 300,
+ 'physicalHeight' => 200,
+ 'expectedGetBucketThumbPathCalls' => $this->once(),
+ 'expectedFileExistsCalls' => $this->once(),
+ 'fileExistsReturn' => false,
+ 'expectedMakeTransformTmpFile' => $this->once(),
+ 'makeTransformTmpFileReturn' => new TempFSFile( '/tmp/foo' ),
+ 'expectedGenerateAndSaveThumb' => $this->once(),
+ 'generateAndSaveThumbReturn' => false,
+ 'expectedResult' => false,
+ 'message' => 'Bucket image could not be generated'
+ ) ),
+ array( array(
+ 'buckets' => $defaultBuckets,
+ 'width' => 5000,
+ 'physicalWidth' => 300,
+ 'physicalHeight' => 200,
+ 'expectedGetBucketThumbPathCalls' => $this->once(),
+ 'expectedFileExistsCalls' => $this->once(),
+ 'fileExistsReturn' => false,
+ 'expectedMakeTransformTmpFile' => $this->once(),
+ 'makeTransformTmpFileReturn' => new TempFSFile( '/tmp/foo' ),
+ 'expectedGenerateAndSaveThumb' => $this->once(),
+ 'generateAndSaveThumbReturn' => new ThumbnailImage( false, 'bar', false, false ),
+ 'expectedResult' => true,
+ 'message' => 'Bucket image could not be generated'
+ ) ),
+ );
+ }
+}