$params['width'] = $params['w'];
unset( $params['w'] );
}
+ if ( isset( $params['width'] ) && substr( $params['width'], -2 ) == 'px' ) {
+ // strip the px (pixel) suffix, if found
+ $params['width'] = substr( $width, 0, strlen( $width ) - 2 );
+ }
if ( isset( $params['p'] ) ) {
$params['page'] = $params['p'];
}
return;
}
- // Check if the file is hidden
- if ( $img->isDeleted( File::DELETED_FILE ) ) {
- wfThumbError( 404, "The source file '$fileName' does not exist." );
- return;
- }
-
// Check permissions if there are read restrictions
$varyHeader = array();
if ( !in_array( 'read', User::getGroupPermissions( array( '*' ) ), true ) ) {
$varyHeader[] = 'Cookie';
}
+ // Check if the file is hidden
+ if ( $img->isDeleted( File::DELETED_FILE ) ) {
+ wfThumbError( 404, "The source file '$fileName' does not exist." );
+ return;
+ }
+
// Do rendering parameters extraction from thumbnail name.
if ( isset( $params['thumbName'] ) ) {
$params = wfExtractThumbParams( $img, $params );
} elseif ( $user->pingLimiter( 'renderfile' ) ) {
wfThumbError( 500, wfMessage( 'actionthrottledtext' ) );
return;
- } elseif ( wfThumbIsAttemptThrottled( $img, $thumbName, 4 ) ) {
- wfThumbError( 500, wfMessage( 'thumbnail_image-failure-limit', 4 ) );
- return;
}
- // Thumbnail isn't already there, so create the new thumbnail...
- $thumb = null;
- try {
- // Record failures on PHP fatals too
- register_shutdown_function( function() use ( &$thumb, $img, $thumbName ) {
- if ( $thumb === null ) { // transform() gave a fatal
- wfThumbIncrAttemptFailures( $img, $thumbName );
- }
- } );
- $thumb = $img->transform( $params, File::RENDER_NOW );
- } catch ( Exception $ex ) {
- // Tried to select a page on a non-paged file?
- $thumb = false;
- }
+ // Actually generate a new thumbnail
+ list( $thumb, $errorMsg ) = wfGenerateThumbnail( $img, $params, $thumbName, $thumbPath );
// Check for thumbnail generation errors...
- $errorMsg = false;
$msg = wfMessage( 'thumbnail_error' );
if ( !$thumb ) {
- $errorMsg = $msg->rawParams( 'File::transform() returned false' )->escaped();
+ $errorMsg = $errorMsg ?: $msg->rawParams( 'File::transform() returned false' )->escaped();
} elseif ( $thumb->isError() ) {
$errorMsg = $thumb->getHtmlMsg();
} elseif ( !$thumb->hasFile() ) {
}
if ( $errorMsg !== false ) {
- wfThumbIncrAttemptFailures( $img, $thumbName );
wfThumbError( 500, $errorMsg );
} else {
// Stream the file if there were no errors
}
/**
- * Returns true if this thumbnail is one that MediaWiki generates
- * links to on file description pages and possibly parser output.
- *
- * $params is considered non-standard if they involve a non-standard
- * width or any parameter aside from width and page number. The number
- * of possible files with standard parameters is far less than that of all
- * possible combinations; rate-limiting for them can thus be more generious.
+ * Actually try to generate a new thumbnail
*
- * @param File $img
+ * @param File $file
* @param array $params
- * @return bool
+ * @param string $thumbName
+ * @param string $thumbPath
+ * @return array (MediaTransformOutput|bool, string|bool error message HTML)
*/
-function wfThumbIsStandard( File $img, array $params ) {
- global $wgThumbLimits, $wgImageLimits;
- // @TODO: use polymorphism with media handler here
- if ( array_diff( array_keys( $params ), array( 'width', 'page' ) ) ) {
- return false; // extra parameters present
+function wfGenerateThumbnail( File $file, array $params, $thumbName, $thumbPath ) {
+ global $wgMemc, $wgAttemptFailureEpoch;
+
+ $key = wfMemcKey( 'attempt-failures', $wgAttemptFailureEpoch,
+ $file->getRepo()->getName(), md5( $file->getName() ), md5( $thumbName ) );
+
+ // Check if this file keeps failing to render
+ if ( $wgMemc->get( $key ) >= 4 ) {
+ return array( false, wfMessage( 'thumbnail_image-failure-limit', 4 ) );
}
- if ( isset( $params['width'] ) ) {
- $widths = $wgThumbLimits;
- foreach ( $wgImageLimits as $pair ) {
- $widths[] = $pair[0];
+
+ $done = false;
+ // Record failures on PHP fatals in addition to caching exceptions
+ register_shutdown_function( function() use ( &$done, $key ) {
+ if ( !$done ) { // transform() gave a fatal
+ global $wgMemc;
+ // Randomize TTL to reduce stampedes
+ $wgMemc->incrWithInit( $key, 3600 + mt_rand( 0, 300 ) );
}
- if ( !in_array( $params['width'], $widths ) ) {
- return false;
+ } );
+
+ $thumb = false;
+ $errorHtml = false;
+
+ // Thumbnail isn't already there, so create the new thumbnail...
+ try {
+ $work = new PoolCounterWorkViaCallback( 'FileRender', sha1( $file->getName() ),
+ array(
+ 'doWork' => function() use ( $file, $params ) {
+ return $file->transform( $params, File::RENDER_NOW );
+ },
+ 'getCachedWork' => function() use ( $file, $params, $thumbPath ) {
+ // If the worker that finished made this thumbnail then use it.
+ // Otherwise, it probably made a different thumbnail for this file.
+ return $file->getRepo()->fileExists( $thumbPath )
+ ? $file->transform( $params, File::RENDER_NOW )
+ : false; // retry once more in exclusive mode
+ },
+ 'fallback' => function() {
+ return wfMessage( 'generic-pool-error' )->parse();
+ },
+ 'error' => function ( $status ) {
+ return $status->getHTML();
+ }
+ )
+ );
+ $result = $work->execute();
+ if ( $result instanceof MediaTransformOutput ) {
+ $thumb = $result;
+ } elseif ( is_string( $result ) ) { // error
+ $errorHtml = $result;
}
+ } catch ( Exception $e ) {
+ // Tried to select a page on a non-paged file?
}
- return true;
-}
-/**
- * @param File $img
- * @param string $thumbName
- * @param int $limit
- * @return int|bool
- */
-function wfThumbIsAttemptThrottled( File $img, $thumbName, $limit ) {
- global $wgMemc;
+ $done = true; // no PHP fatal occured
+
+ if ( !$thumb || $thumb->isError() ) {
+ // Randomize TTL to reduce stampedes
+ $wgMemc->incrWithInit( $key, 3600 + mt_rand( 0, 300 ) );
+ }
- return ( $wgMemc->get( wfThumbAttemptKey( $img, $thumbName ) ) >= $limit );
+ return array( $thumb, $errorHtml );
}
/**
- * @param File $img
- * @param string $thumbName
+ * Returns true if this thumbnail is one that MediaWiki generates
+ * links to on file description pages and possibly parser output.
+ *
+ * $params is considered non-standard if they involve a non-standard
+ * width or any non-default parameters aside from width and page number.
+ * The number of possible files with standard parameters is far less than
+ * that of all combinations; rate-limiting for them can thus be more generious.
+ *
+ * @param File $file
+ * @param array $params
+ * @return bool
*/
-function wfThumbIncrAttemptFailures( File $img, $thumbName ) {
- global $wgMemc;
+function wfThumbIsStandard( File $file, array $params ) {
+ global $wgThumbLimits, $wgImageLimits;
- $key = wfThumbAttemptKey( $img, $thumbName );
- if ( !$wgMemc->incr( $key, 1 ) ) {
- if ( !$wgMemc->add( $key, 1, 3600 ) ) {
- $wgMemc->incr( $key, 1 );
+ $handler = $file->getHandler();
+ if ( !$handler || !isset( $params['width'] ) ) {
+ return false;
+ }
+
+ $basicParams = array();
+ if ( isset( $params['page'] ) ) {
+ $basicParams['page'] = $params['page'];
+ }
+
+ // Check if the width matches one of $wgThumbLimits
+ if ( in_array( $params['width'], $wgThumbLimits ) ) {
+ $normalParams = $basicParams + array( 'width' => $params['width'] );
+ // Append any default values to the map (e.g. "lossy", "lossless", ...)
+ $handler->normaliseParams( $file, $normalParams );
+ } else {
+ // If not, then check if the width matchs one of $wgImageLimits
+ $match = false;
+ foreach ( $wgImageLimits as $pair ) {
+ $normalParams = $basicParams + array( 'width' => $pair[0], 'height' => $pair[1] );
+ // Decide whether the thumbnail should be scaled on width or height.
+ // Also append any default values to the map (e.g. "lossy", "lossless", ...)
+ $handler->normaliseParams( $file, $normalParams );
+ // Check if this standard thumbnail size maps to the given width
+ if ( $normalParams['width'] == $params['width'] ) {
+ $match = true;
+ break;
+ }
+ }
+ if ( !$match ) {
+ return false; // not standard for description pages
}
}
-}
-/**
- * @param File $img
- * @param string $thumbName
- * @return string
- */
-function wfThumbAttemptKey( File $img, $thumbName ) {
- global $wgAttemptFailureEpoch;
+ // Check that the given values for non-page, non-width, params are just defaults
+ foreach ( $params as $key => $value ) {
+ if ( !isset( $normalParams[$key] ) || $normalParams[$key] != $value ) {
+ return false;
+ }
+ }
- return wfMemcKey( 'attempt-failures', $wgAttemptFailureEpoch,
- $img->getRepo()->getName(), md5( $img->getName() ), md5( $thumbName ) );
+ return true;
}
/**